1import static java.util.stream.Collectors.toList; 2 3import java.util.ArrayList; 4import java.util.Arrays; 5import java.util.Collection; 6import java.util.Collections; 7import java.util.List; 8import java.util.Objects; 9import java.util.function.Function; 10import software.amazon.awssdk.annotations.Generated; 11import software.amazon.awssdk.annotations.SdkInternalApi; 12import software.amazon.awssdk.core.SdkPojo; 13import software.amazon.awssdk.core.SdkResponse; 14import software.amazon.awssdk.core.exception.SdkClientException; 15import software.amazon.awssdk.core.exception.SdkServiceException; 16import software.amazon.awssdk.core.waiters.WaiterAcceptor; 17import software.amazon.awssdk.core.waiters.WaiterState; 18import software.amazon.awssdk.utils.ToString; 19 20/** 21 * Contains classes used at runtime by the code generator classes for waiter acceptors generated from JMESPath 22 * expressions. 23 */ 24@Generated("software.amazon.awssdk:codegen") 25@SdkInternalApi 26public final class WaitersRuntime { 27 /** 28 * The default acceptors that should be matched *last* in the list of acceptors used by the SDK client waiters. 29 */ 30 public static final List<WaiterAcceptor<Object>> DEFAULT_ACCEPTORS = Collections.unmodifiableList(defaultAcceptors()); 31 32 private WaitersRuntime() { 33 } 34 35 private static List<WaiterAcceptor<Object>> defaultAcceptors() { 36 return Collections.singletonList(retryOnUnmatchedResponseWaiter()); 37 } 38 39 private static WaiterAcceptor<Object> retryOnUnmatchedResponseWaiter() { 40 return WaiterAcceptor.retryOnResponseAcceptor(r -> true); 41 } 42 43 /** 44 * An intermediate value for JMESPath expressions, encapsulating the different data types supported by JMESPath and the 45 * operations on that data. 46 */ 47 public static final class Value { 48 /** 49 * A null value. 50 */ 51 private static final Value NULL_VALUE = new Value(null); 52 53 /** 54 * The type associated with this value. 55 */ 56 private final Type type; 57 58 /** 59 * Whether this value is a "projection" value. Projection values are LIST values where certain operations are performed 60 * on each element of the list, instead of on the entire list. 61 */ 62 private final boolean isProjection; 63 64 /** 65 * The value if this is a {@link Type#POJO} (or null otherwise). 66 */ 67 private SdkPojo pojoValue; 68 69 /** 70 * The value if this is an {@link Type#INTEGER} (or null otherwise). 71 */ 72 private Integer integerValue; 73 74 /** 75 * The value if this is an {@link Type#STRING} (or null otherwise). 76 */ 77 private String stringValue; 78 79 /** 80 * The value if this is an {@link Type#LIST} (or null otherwise). 81 */ 82 private List<Object> listValue; 83 84 /** 85 * The value if this is an {@link Type#BOOLEAN} (or null otherwise). 86 */ 87 private Boolean booleanValue; 88 89 /** 90 * Create a LIST value, specifying whether this is a projection. This is private and is usually invoked by 91 * {@link #newProjection(Collection)}. 92 */ 93 private Value(Collection<?> value, boolean projection) { 94 this.type = Type.LIST; 95 this.listValue = new ArrayList<>(value); 96 this.isProjection = projection; 97 } 98 99 /** 100 * Create a non-projection value, where the value type is determined reflectively. 101 */ 102 public Value(Object value) { 103 this.isProjection = false; 104 105 if (value == null) { 106 this.type = Type.NULL; 107 } else if (value instanceof SdkPojo) { 108 this.type = Type.POJO; 109 this.pojoValue = (SdkPojo) value; 110 } else if (value instanceof String) { 111 this.type = Type.STRING; 112 this.stringValue = (String) value; 113 } else if (value instanceof Integer) { 114 this.type = Type.INTEGER; 115 this.integerValue = (Integer) value; 116 } else if (value instanceof Collection) { 117 this.type = Type.LIST; 118 this.listValue = new ArrayList<>(((Collection<?>) value)); 119 } else if (value instanceof Boolean) { 120 this.type = Type.BOOLEAN; 121 this.booleanValue = (Boolean) value; 122 } else { 123 throw new IllegalArgumentException("Unsupported value type: " + value.getClass()); 124 } 125 } 126 127 /** 128 * Create a {@link Type#LIST} with a {@link #isProjection} of true. 129 */ 130 private static Value newProjection(Collection<?> values) { 131 return new Value(values, true); 132 } 133 134 /** 135 * Retrieve the actual value that this represents (this will be the same value passed to the constructor). 136 */ 137 public Object value() { 138 switch (type) { 139 case NULL: return null; 140 case POJO: return pojoValue; 141 case INTEGER: return integerValue; 142 case STRING: return stringValue; 143 case BOOLEAN: return booleanValue; 144 case LIST: return listValue; 145 default: throw new IllegalStateException(); 146 } 147 } 148 149 /** 150 * Retrieve the actual value that this represents, as a list. 151 */ 152 public List<Object> values() { 153 if (type == Type.NULL) { 154 return Collections.emptyList(); 155 } 156 157 if (type == Type.LIST) { 158 return listValue; 159 } 160 161 return Collections.singletonList(value()); 162 } 163 164 /** 165 * Convert this value to a new constant value, discarding the current value. 166 */ 167 public Value constant(Value value) { 168 return value; 169 } 170 171 /** 172 * Convert this value to a new constant value, discarding the current value. 173 */ 174 public Value constant(Object constant) { 175 return new Value(constant); 176 } 177 178 /** 179 * Execute a wildcard expression on this value: https://jmespath.org/specification.html#wildcard-expressions 180 */ 181 public Value wildcard() { 182 if (type == Type.NULL) { 183 return NULL_VALUE; 184 } 185 186 if (type != Type.POJO) { 187 throw new IllegalArgumentException("Cannot flatten a " + type); 188 } 189 190 return Value.newProjection(pojoValue.sdkFields().stream() 191 .map(f -> f.getValueOrDefault(pojoValue)) 192 .filter(Objects::nonNull) 193 .collect(toList())); 194 } 195 196 /** 197 * Execute a flattening expression on this value: https://jmespath.org/specification.html#flatten-operator 198 */ 199 public Value flatten() { 200 if (type == Type.NULL) { 201 return NULL_VALUE; 202 } 203 204 if (type != Type.LIST) { 205 throw new IllegalArgumentException("Cannot flatten a " + type); 206 } 207 208 List<Object> result = new ArrayList<>(); 209 for (Object listEntry : listValue) { 210 Value listValue = new Value(listEntry); 211 if (listValue.type != Type.LIST) { 212 result.add(listEntry); 213 } else { 214 result.addAll(listValue.listValue); 215 } 216 } 217 218 return Value.newProjection(result); 219 } 220 221 /** 222 * Retrieve an identifier from this value: https://jmespath.org/specification.html#identifiers 223 */ 224 public Value field(String fieldName) { 225 if (isProjection) { 226 return project(v -> v.field(fieldName)); 227 } 228 229 if (type == Type.NULL) { 230 return NULL_VALUE; 231 } 232 233 if (type == Type.POJO) { 234 return pojoValue.sdkFields() 235 .stream() 236 .filter(f -> f.memberName().equals(fieldName)) 237 .map(f -> f.getValueOrDefault(pojoValue)) 238 .map(Value::new) 239 .findAny() 240 .orElseThrow(() -> new IllegalArgumentException("No such field: " + fieldName)); 241 } 242 243 throw new IllegalArgumentException("Cannot get a field from a " + type); 244 } 245 246 /** 247 * Filter this value: https://jmespath.org/specification.html#filter-expressions 248 */ 249 public Value filter(Function<Value, Value> predicate) { 250 if (isProjection) { 251 return project(f -> f.filter(predicate)); 252 } 253 254 if (type == Type.NULL) { 255 return NULL_VALUE; 256 } 257 258 if (type != Type.LIST) { 259 throw new IllegalArgumentException("Unsupported type for filter function: " + type); 260 } 261 262 List<Object> results = new ArrayList<>(); 263 listValue.forEach(entry -> { 264 Value entryValue = new Value(entry); 265 Value predicateResult = predicate.apply(entryValue); 266 if (predicateResult.isTrue()) { 267 results.add(entry); 268 } 269 }); 270 return new Value(results); 271 } 272 273 /** 274 * Execute the length function, with this value as the first parameter: https://jmespath.org/specification.html#length 275 */ 276 public Value length() { 277 if (type == Type.NULL) { 278 return NULL_VALUE; 279 } 280 281 if (type == Type.STRING) { 282 return new Value(stringValue.length()); 283 } 284 285 if (type == Type.POJO) { 286 return new Value(pojoValue.sdkFields().size()); 287 } 288 289 if (type == Type.LIST) { 290 return new Value(Math.toIntExact(listValue.size())); 291 } 292 293 throw new IllegalArgumentException("Unsupported type for length function: " + type); 294 } 295 296 /** 297 * Execute the contains function, with this value as the first parameter: https://jmespath.org/specification.html#contains 298 */ 299 public Value contains(Value rhs) { 300 if (type == Type.NULL) { 301 return NULL_VALUE; 302 } 303 304 if (type == Type.STRING) { 305 if (rhs.type != Type.STRING) { 306 // Unclear from the spec whether we can check for a boolean in a string, for example... 307 return new Value(false); 308 } 309 310 return new Value(stringValue.contains(rhs.stringValue)); 311 } 312 313 if (type == Type.LIST) { 314 return new Value(listValue.stream().anyMatch(v -> Objects.equals(v, rhs.value()))); 315 } 316 317 throw new IllegalArgumentException("Unsupported type for contains function: " + type); 318 } 319 320 /** 321 * Compare this value to another value, using the specified comparison operator: 322 * https://jmespath.org/specification.html#comparison-operators 323 */ 324 public Value compare(String comparison, Value rhs) { 325 if (type != rhs.type) { 326 return new Value(false); 327 } 328 329 if (type == Type.INTEGER) { 330 switch (comparison) { 331 case "<": return new Value(integerValue < rhs.integerValue); 332 case "<=": return new Value(integerValue <= rhs.integerValue); 333 case ">": return new Value(integerValue > rhs.integerValue); 334 case ">=": return new Value(integerValue >= rhs.integerValue); 335 case "==": return new Value(Objects.equals(integerValue, rhs.integerValue)); 336 case "!=": return new Value(!Objects.equals(integerValue, rhs.integerValue)); 337 default: throw new IllegalArgumentException("Unsupported comparison: " + comparison); 338 } 339 } 340 341 if (type == Type.NULL || type == Type.STRING || type == Type.BOOLEAN) { 342 switch (comparison) { 343 case "<": 344 case "<=": 345 case ">": 346 case ">=": 347 return NULL_VALUE; // Invalid comparison, spec says to treat as null. 348 case "==": return new Value(Objects.equals(value(), rhs.value())); 349 case "!=": return new Value(!Objects.equals(value(), rhs.value())); 350 default: throw new IllegalArgumentException("Unsupported comparison: " + comparison); 351 } 352 } 353 354 throw new IllegalArgumentException("Unsupported type in comparison: " + type); 355 } 356 357 /** 358 * Perform a multi-select list expression on this value: https://jmespath.org/specification.html#multiselect-list 359 */ 360 @SafeVarargs 361 public final Value multiSelectList(Function<Value, Value>... functions) { 362 if (isProjection) { 363 return project(v -> v.multiSelectList(functions)); 364 } 365 if (type == Type.NULL) { 366 return NULL_VALUE; 367 } 368 369 List<Object> result = new ArrayList<>(); 370 for (Function<Value, Value> function : functions) { 371 result.add(function.apply(this).value()); 372 } 373 return new Value(result); 374 } 375 376 /** 377 * Perform an OR comparison between this value and another one: https://jmespath.org/specification.html#or-expressions 378 */ 379 public Value or(Value rhs) { 380 if (isTrue()) { 381 return this; 382 } else { 383 return rhs.isTrue() ? rhs : NULL_VALUE; 384 } 385 } 386 387 /** 388 * Perform an AND comparison between this value and another one: https://jmespath.org/specification.html#or-expressions 389 */ 390 public Value and(Value rhs) { 391 return isTrue() ? rhs : this; 392 } 393 394 /** 395 * Perform a NOT conversion on this value: https://jmespath.org/specification.html#not-expressions 396 */ 397 public Value not() { 398 return new Value(!isTrue()); 399 } 400 401 /** 402 * Returns true is this value is "true-like" (or false otherwise): https://jmespath.org/specification.html#or-expressions 403 */ 404 private boolean isTrue() { 405 switch (type) { 406 case POJO: 407 return !pojoValue.sdkFields().isEmpty(); 408 case LIST: 409 return !listValue.isEmpty(); 410 case STRING: 411 return !stringValue.isEmpty(); 412 case BOOLEAN: 413 return booleanValue; 414 default: 415 return false; 416 } 417 } 418 419 /** 420 * Project the provided function across all values in this list. Assumes this is a LIST and isProjection is true. 421 */ 422 private Value project(Function<Value, Value> functionToApply) { 423 return new Value(listValue.stream() 424 .map(Value::new) 425 .map(functionToApply) 426 .map(Value::value) 427 .collect(toList()), 428 true); 429 } 430 431 /** 432 * The JMESPath type of this value. 433 */ 434 private enum Type { 435 POJO, 436 LIST, 437 BOOLEAN, 438 STRING, 439 INTEGER, 440 NULL 441 } 442 443 @Override 444 public boolean equals(Object o) { 445 if (this == o) { 446 return true; 447 } 448 if (o == null || getClass() != o.getClass()) { 449 return false; 450 } 451 452 Value value = (Value) o; 453 454 return type == value.type && Objects.equals(value(), value.value()); 455 } 456 457 @Override 458 public int hashCode() { 459 Object value = value(); 460 461 int result = type.hashCode(); 462 result = 31 * result + (value != null ? value.hashCode() : 0); 463 return result; 464 } 465 466 @Override 467 public String toString() { 468 return ToString.builder("Value") 469 .add("type", type) 470 .add("value", value()) 471 .build(); 472 } 473 } 474 475 /** 476 * A {@link WaiterAcceptor} implementation that checks for a specific HTTP response status, regardless of whether it's 477 * reported by a response or an exception. 478 */ 479 public static final class ResponseStatusAcceptor implements WaiterAcceptor<SdkResponse> { 480 private final int statusCode; 481 private final WaiterState waiterState; 482 483 public ResponseStatusAcceptor(int statusCode, WaiterState waiterState) { 484 this.statusCode = statusCode; 485 this.waiterState = waiterState; 486 } 487 488 @Override 489 public WaiterState waiterState() { 490 return waiterState; 491 } 492 493 @Override 494 public boolean matches(SdkResponse response) { 495 return response.sdkHttpResponse() != null && 496 response.sdkHttpResponse().statusCode() == statusCode; 497 } 498 499 @Override 500 public boolean matches(Throwable throwable) { 501 if (throwable instanceof SdkServiceException) { 502 return ((SdkServiceException) throwable).statusCode() == statusCode; 503 } 504 505 return false; 506 } 507 } 508} 509