1 /** 2 * A synchronous client for interacting (generically) with document databases. 3 * 4 * Features: 5 * <ol> 6 * <li>Support for Java-specific types, like {@link Instant} and {@link BigDecimal}.</li> 7 * <li>Support for reading and writing custom objects (eg. Java Beans, POJOs).</li> 8 * </ol> 9 * 10 * All {@link DocumentClient}s should be closed via {@link #close()}. 11 */ 12 @ThreadSafe 13 public interface DocumentClient extends SdkAutoCloseable { 14 /** 15 * Create a {@link DocumentClient} for interating with document databases. 16 * 17 * The provided runtime will be used for handling object persistence. 18 * 19 * Usage Example: 20 * <code> 21 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 22 * client.listRepositories().repositories().forEach(System.out::println); 23 * } 24 * </code> 25 */ create(Class<? extends DocumentClientRuntime> runtimeClass)26 static DocumentClient create(Class<? extends DocumentClientRuntime> runtimeClass); 27 28 /** 29 * Create a {@link DocumentClient.Builder} for configuring and creating {@link DocumentClient}s. 30 * 31 * The provided runtime will be used for handling object persistence. 32 * 33 * Usage Example: 34 * <code> 35 * try (DocumentClient client = DocumentClient.builder(DynamoDbRuntime.class) 36 * .putOption(DynamoDbOption.CLIENT, DynamoDbClient.create()) 37 * .build()) { 38 * client.listRepositories().repositories().forEach(System.out::println); 39 * } 40 * </code> 41 */ builder(Class<? extends DocumentClientRuntime> runtimeClass)42 static DocumentClient.Builder builder(Class<? extends DocumentClientRuntime> runtimeClass); 43 44 /** 45 * Get all {@link DocumentRepository}s that are currently available from this client. 46 * 47 * This should return every repository that will not result in {@code client.repository(...)} throwing a 48 * {@link NoSuchRepositoryException}. 49 * 50 * Usage Example: 51 * <code> 52 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 53 * client.listRepositories().repositories().forEach(System.out::println); 54 * } 55 * </code> 56 */ listRepositories()57 ListRepositoriesResponse listRepositories(); 58 59 /** 60 * Retrieve a {@link DocumentRepository} based on the provided repository name/id. 61 * 62 * The {@link DocumentRepository} is used to directly interact with entities in the remote repository. 63 * See {@link #mappedRepository(Class)} for a way of interacting with the repository using Java objects. 64 * 65 * If the repository does not exist, an exception is thrown. 66 * 67 * Usage Example: 68 * <code> 69 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 70 * DocumentRepository repository = client.repository("my-table"); 71 * assert repository.name().equals("my-table"); 72 * } catch (NoSuchRepositoryException e) { 73 * System.out.println("The requested repository does not exist: " + e.getMessage()); 74 * throw e; 75 * } 76 * </code> 77 */ repository(String repositoryId)78 DocumentRepository repository(String repositoryId) throws NoSuchRepositoryException; 79 80 /** 81 * Retrieve a {@link DocumentRepository} based on the provided repository name/id. 82 * 83 * The {@link DocumentRepository} is used to create, read, update and delete entities in the remote repository. 84 * See {@link #mappedRepository(Class)} for a way of interacting with the repository using Java objects. 85 * 86 * If the repository does not exist, the provided {@link MissingRepositoryBehavior} determines the behavior. 87 * 88 * Usage Example: 89 * <code> 90 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 91 * DocumentRepository repository = client.repository("my-table", MissingRepositoryBehavior.CREATE); 92 * assert repository.name().equals("my-table"); 93 * } 94 * </code> 95 */ repository(String repositoryId, MissingRepositoryBehavior behavior)96 DocumentRepository repository(String repositoryId, MissingRepositoryBehavior behavior) throws NoSuchRepositoryException; 97 98 /** 99 * Retrieve an implementation of a specific {@link MappedRepository} based on the provided Java object class. 100 * 101 * This {@link MappedRepository} implementation is used to create, read, update and delete entities in the remote repository 102 * using Java objects. See {@link #repository(String)} for a way of interacting directly with the entities. 103 * 104 * If the repository does not exist, an exception is thrown. 105 * 106 * Usage Example: 107 * <code> 108 * @MappedRepository("my-table") 109 * public interface MyItemRepository extends MappedRepository<MyItem, String> { 110 * } 111 * 112 * public class MyItem { 113 * @Id 114 * @Column("partition-key") 115 * private UUID partitionKey; 116 * 117 * @Column("creation-time") 118 * private Instant creationTime; 119 * 120 * public String getPartitionKey() { return this.partitionKey; } 121 * public Instant getCreationTime() { return this.creationTime; } 122 * public void setPartitionKey(UUID partitionKey) { this.partitionKey = partitionKey; } 123 * public void setCreationTime(Instant creationTime) { this.creationTime = creationTime; } 124 * } 125 * 126 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 127 * MyItemRepository repository = client.mappedRepository(MyItemRepository.class); 128 * assert repository.name().equals("my-table"); 129 * } catch (NoSuchRepositoryException e) { 130 * System.out.println("The requested repository does not exist: " + e.getMessage()); 131 * throw e; 132 * } 133 * </code> 134 */ mappedRepository(Class<T> repositoryClass)135 <T extends MappedRepository<?, ?>> T mappedRepository(Class<T> repositoryClass); 136 137 /** 138 * Retrieve an implementation of a specific {@link MappedRepository} based on the provided Java object class. 139 * 140 * This {@link MappedRepository} implementation is used to create, read, update and delete entities in the remote repository 141 * using Java objects. See {@link #repository(String)} for a way of interacting directly with the entities. 142 * 143 * If the repository does not exist, the provided {@link MissingRepositoryBehavior} determines the behavior. 144 * 145 * Usage Example: 146 * <code> 147 * @MappedRepository("my-table") 148 * public interface MyItemRepository extends MappedRepository<MyItem, String> { 149 * } 150 * 151 * public class MyItem { 152 * @Id 153 * @Column("partition-key") 154 * private UUID partitionKey; 155 * 156 * @Column("creation-time") 157 * private Instant creationTime; 158 * 159 * public String getPartitionKey() { return this.partitionKey; } 160 * public Instant getCreationTime() { return this.creationTime; } 161 * public void setPartitionKey(UUID partitionKey) { this.partitionKey = partitionKey; } 162 * public void setCreationTime(Instant creationTime) { this.creationTime = creationTime; } 163 * } 164 * 165 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 166 * MyItemRepository repository = client.mappedRepository(MyItemRepository.class, MissingRepositoryBehavior.CREATE); 167 * assert repository.name().equals("my-table"); 168 * } 169 * </code> 170 */ mappedRepository(Class<T> repositoryClass, MissingRepositoryBehavior behavior)171 <T extends MappedRepository<?, ?>> T mappedRepository(Class<T> repositoryClass, MissingRepositoryBehavior behavior); 172 173 /** 174 * A builder for configuring and creating {@link DocumentClient} instances. 175 * 176 * @see #builder(Class) 177 */ 178 @NotThreadSafe 179 interface Builder { 180 /** 181 * Configure the type converters that should be applied globally across all {@link DocumentRepository}s and 182 * {@link MappedRepository}s from the client. This can also be overridden at the entity level. 183 * 184 * The following type conversions are supported by default: 185 * <ul> 186 * <li>{@link Long} / {@link long} -> {@link EntityValueType#LONG}</li> 187 * <li>{@link Integer} / {@link int} -> {@link EntityValueType#INT}</li> 188 * <li>{@link Short} / {@link short} -> {@link EntityValueType#SHORT}</li> 189 * <li>{@link Float} / {@link float} -> {@link EntityValueType#FLOAT}</li> 190 * <li>{@link Double} / {@link double} -> {@link EntityValueType#DOUBLE}</li> 191 * <li>{@link Number} -> {@link EntityValueType#NUMBER}</li> 192 * <li>{@link Temporal} -> {@link EntityValueType#NUMBER}</li> 193 * <li>{@link Char} / {@link char} -> {@link EntityValueType#STRING}</li> 194 * <li>{@link char[]} -> {@link EntityValueType#STRING}</li> 195 * <li>{@link CharSequence} -> {@link EntityValueType#STRING}</li> 196 * <li>{@link UUID} -> {@link EntityValueType#STRING}</li> 197 * <li>{@link byte[]} -> {@link EntityValueType#BYTES}</li> 198 * <li>{@link ByteBuffer} -> {@link EntityValueType#BYTES}</li> 199 * <li>{@link BytesWrapper} -> {@link EntityValueType#BYTES}</li> 200 * <li>{@link InputStream} -> {@link EntityValueType#BYTES}</li> 201 * <li>{@link File} -> {@link EntityValueType#BYTES}</li> 202 * <li>{@link Path} -> {@link EntityValueType#BYTES}</li> 203 * <li>{@link Boolean} -> {@link EntityValueType#BOOLEAN}</li> 204 * <li>{@link Collection} -> {@link EntityValueType#LIST}</li> 205 * <li>{@link Stream} -> {@link EntityValueType#LIST}</li> 206 * <li>{@link Iterable} -> {@link EntityValueType#LIST}</li> 207 * <li>{@link Iterator} -> {@link EntityValueType#LIST}</li> 208 * <li>{@link Enumeration} -> {@link EntityValueType#LIST}</li> 209 * <li>{@link Optional} -> {@link EntityValueType#*}</li> 210 * <li>{@link Map} -> {@link EntityValueType#ENTITY}</li> 211 * <li>{@link Object} -> {@link EntityValueType#ENTITY}</li> 212 * <li>{@link null} -> {@link EntityValueType#NULL}</li> 213 * </ul> 214 * 215 * Usage Example: 216 * <code> 217 * try (DocumentClient client = DocumentClient.builder(DynamoDbRuntime.class) 218 * .addConverter(InstantsAsStringsConverter.create()) 219 * .build()) { 220 * DocumentRepository repository = client.repository("my-table"); 221 * 222 * UUID id = UUID.randomUUID(); 223 * repository.putEntity(Entity.builder() 224 * .putChild("partition-key", id) 225 * .putChild("creation-time", Instant.now()) 226 * .build()); 227 * 228 * Thread.sleep(5_000); // GetEntity is eventually consistent with the Dynamo DB runtime. 229 * 230 * Entity item = repository.getEntity(Entity.builder() 231 * .putChild("partition-key", id) 232 * .build()); 233 * 234 * // Instants are usually converted to a number-type, but it was stored as an ISO-8601 string now because of the 235 * // InstantsAsStringsConverter. 236 * assert item.getChild("creation-time").isString(); 237 * assert item.getChild("creation-time").as(Instant.class).isBetween(Instant.now().minus(1, MINUTE), 238 * Instant.now()); 239 * } 240 * </code> 241 */ converters(Iterable<? extends EntityValueConverter<?>> converters)242 DocumentClient.Builder converters(Iterable<? extends EntityValueConverter<?>> converters); addConverter(EntityValueConverter<?> converter)243 DocumentClient.Builder addConverter(EntityValueConverter<?> converter); clearConverters()244 DocumentClient.Builder clearConverters(); 245 246 /** 247 * Usage Example: 248 * <code> 249 * try (DocumentClient client = DocumentClient.builder(DynamoDbRuntime.class) 250 * .putOption(DynamoDbOption.CLIENT, DynamoDbClient.create()) 251 * .build()) { 252 * client.listRepositories().repositories().forEach(System.out::println); 253 * } 254 * </code> 255 */ options(Map<? extends OptionKey<?>, ?> options)256 DocumentClient.Builder options(Map<? extends OptionKey<?>, ?> options); putOption(OptionKey<T> optionKey, T optionValue)257 <T> DocumentClient.Builder putOption(OptionKey<T> optionKey, T optionValue); removeOption(OptionKey<?> optionKey)258 DocumentClient.Builder removeOption(OptionKey<?> optionKey); clearOptions()259 DocumentClient.Builder clearOptions(); 260 } 261 } 262 263 /** 264 * When calling {@link DocumentClient#repository} or {@link DocumentClient#mappedRepository} and a repository does not exist on 265 * the service side, this is the behavior that the client should take. 266 */ 267 @ThreadSafe 268 public enum MissingRepositoryBehavior { 269 /** 270 * Create the repository, if it's missing. 271 */ 272 CREATE, 273 274 /** 275 * Throw a {@link NoSuchRepositoryException}, if the repository is missing. 276 */ 277 FAIL, 278 279 /** 280 * Do not check whether the repository exists, for performance reasons. Methods that require the repository to exist will 281 * fail. 282 */ 283 DO_NOT_CHECK 284 } 285 286 /** 287 * An interface that repository-specific runtimes can implement to be supported by the document client. 288 */ 289 @ThreadSafe 290 public interface DocumentClientRuntime { 291 } 292 293 /** 294 * The DynamoDB implementation of the {@link DocumentClientRuntime}. 295 */ 296 @ThreadSafe 297 public interface DynamoDbRuntime extends DocumentClientRuntime { 298 } 299 300 /** 301 * The DynamoDB-specific options available for configuring the {@link DynamoDbRuntime} via 302 * {@link DocumentClient.Builder#putOption}. 303 */ 304 @ThreadSafe 305 public class DynamoDbOption { 306 /** 307 * Configure the DynamoDB client that should be used for communicating with DynamoDB. 308 * 309 * This only applies to the {@link DynamoDbRuntime}. 310 * 311 * Usage Example: 312 * <code> 313 * try (DocumentClient client = DocumentClient.builder(DynamoDbRuntime.class) 314 * .putOption(DynamoDbOption.CLIENT, DynamoDbClient.create()) 315 * .build()) { 316 * client.listRepositories().repositories().forEach(System.out::println); 317 * } 318 * </code> 319 */ 320 public static final Option<DynamoDbClient> CLIENT = new Option<>(DynamoDbClient.class); 321 } 322 323 /** 324 * A converter between Java types and repository types. These can be attached to {@link DocumentClient}s and 325 * {@link Entity}s, so that types are automatically converted when writing to and reading from the repository. 326 * 327 * @see DocumentClient.Builder#converters(Iterable) 328 * @see Entity.Builder#addConverter(EntityValueConverter) 329 * 330 * @param T The Java type that is generated by this converter. 331 */ 332 @ThreadSafe 333 public interface EntityValueConverter<T> { 334 /** 335 * The default condition in which this converter is invoked. 336 * 337 * Even if this condition is not satisfied, it can still be invoked directly via 338 * {@link EntityValue#from(Object, EntityValueConverter)}. 339 */ defaultConversionCondition()340 ConversionCondition defaultConversionCondition(); 341 342 /** 343 * Convert the provided Java type into an {@link EntityValue}. 344 */ toEntityValue(T input, ConversionContext context)345 EntityValue toEntityValue(T input, ConversionContext context); 346 347 /** 348 * Convert the provided {@link EntityValue} into a Java type. 349 */ fromEntityValue(EntityValue input, ConversionContext context)350 T fromEntityValue(EntityValue input, ConversionContext context); 351 } 352 353 /** 354 * The condition in which a {@link EntityValueConverter} will be invoked. 355 * 356 * @see EntityValueConverter#defaultConversionCondition() 357 */ 358 @ThreadSafe 359 public interface ConversionCondition { 360 /** 361 * Create a conversion condition that causes an {@link EntityValueConverter} to be invoked if an entity value's 362 * {@link ConversionContext} matches a specific condition. 363 * 364 * This condition has a larger overhead than the {@link #isInstanceOf(Class)} and {@link #never()}, because it must be 365 * invoked for every entity value being converted and its result cannot be cached. For this reason, lower-overhead 366 * conditions like {@link #isInstanceOf(Class)} and {@link #never()} should be favored where performance is important. 367 */ contextSatisfies(Predicate<ConversionContext> contextPredicate)368 static ConversionCondition contextSatisfies(Predicate<ConversionContext> contextPredicate); 369 370 /** 371 * Create a conversion condition that causes an {@link EntityValueConverter} to be invoked if the entity value's 372 * Java type matches the provided class. 373 * 374 * The result of this condition can be cached, and will likely not be invoked for previously-converted types. 375 */ isInstanceOf(Class<?> clazz)376 static ConversionCondition isInstanceOf(Class<?> clazz); 377 378 /** 379 * Create a conversion condition that causes an {@link EntityValueConverter} to never be invoked by default, except 380 * when directly invoked via {@link EntityValue#from(Object, EntityValueConverter)}. 381 * 382 * The result of this condition can be cached, and will likely not be invoked for previously-converted types. 383 */ never()384 static ConversionCondition never(); 385 } 386 387 /** 388 * Additional context that can be used when converting between Java types and {@link EntityValue}s. 389 * 390 * @see EntityValueConverter#fromEntityValue(EntityValue, ConversionContext) 391 * @see EntityValueConverter#toEntityValue(java.lang.Object, ConversionContext) 392 */ 393 @ThreadSafe 394 public interface ConversionContext { 395 /** 396 * The name of the entity value being converted. 397 */ entityValueName()398 String entityValueName(); 399 400 /** 401 * The schema of the entity value being converted. 402 */ entityValueSchema()403 EntityValueSchema entityValueSchema(); 404 405 /** 406 * The entity that contains the entity value being converted. 407 */ parent()408 Entity parent(); 409 410 /** 411 * The schema of the {@link #parent()}. 412 */ parentSchema()413 EntitySchema parentSchema(); 414 } 415 416 /** 417 * The result of invoking {@link DocumentClient#listRepositories()}. 418 */ 419 @ThreadSafe 420 public interface ListRepositoriesResponse { 421 /** 422 * A lazily-populated iterator over all accessible repositories. This may make multiple service calls in the 423 * background when iterating over the full result set. 424 */ repositories()425 SdkIterable<DocumentRepository> repositories(); 426 } 427 428 /** 429 * A client that can be used for creating, reading, updating and deleting entities in a remote repository. 430 * 431 * Created via {@link DocumentClient#repository(String)}. 432 * 433 * Usage Example: 434 * <code> 435 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 436 * DocumentRepository repository = client.repository("my-table"); 437 * 438 * UUID id = UUID.randomUUID(); 439 * repository.putEntity(Entity.builder() 440 * .putChild("partition-key", id) 441 * .putChild("creation-time", Instant.now()) 442 * .build()); 443 * 444 * Thread.sleep(5_000); // GetEntity is eventually consistent with the Dynamo DB runtime. 445 * 446 * Entity item = repository.getEntity(Entity.builder() 447 * .putChild("partition-key", id) 448 * .build()); 449 * 450 * assert item.getChild("creation-time").as(Instant.class).isBetween(Instant.now().minus(1, MINUTE), 451 * Instant.now()); 452 * } catch (NoSuchEntityException e) { 453 * System.out.println("Item could not be found. Maybe we didn't wait long enough for consistency?"); 454 * throw e; 455 * } 456 * </code> 457 */ 458 public interface DocumentRepository { 459 /** 460 * Retrieve the name of this repository. 461 * 462 * Usage Example: 463 * <code> 464 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 465 * DocumentRepository repository = client.repository("my-table"); 466 * assert repository.name().equals("my-table"); 467 * } 468 * </code> 469 */ name()470 String name(); 471 472 /** 473 * Convert this repository to a mapped-repository, so that Java objects can be persisted and retrieved. 474 * 475 * Usage Example: 476 * <code> 477 * @MappedRepository("my-table") 478 * public interface MyItemRepository extends MappedRepository<MyItem, String> { 479 * } 480 * 481 * public class MyItem { 482 * @Id 483 * @Column("partition-key") 484 * private UUID partitionKey; 485 * 486 * @Column("creation-time") 487 * private Instant creationTime; 488 * 489 * public String getPartitionKey() { return this.partitionKey; } 490 * public Instant getCreationTime() { return this.creationTime; } 491 * public void setPartitionKey(UUID partitionKey) { this.partitionKey = partitionKey; } 492 * public void setCreationTime(Instant creationTime) { this.creationTime = creationTime; } 493 * } 494 * 495 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 496 * DocumentRepository unmappedRepository = client.repository("my-table"); 497 * MyItemRepository mappedRepository = unmappedRepository.toMappedRepository(MyItemRepository.class); 498 * assert mappedRepository.name().equals("my-table"); 499 * } 500 * </code> 501 */ toMappedRepository(Class<T> repositoryClass)502 <T extends MappedRepository<?, ?>> T toMappedRepository(Class<T> repositoryClass); 503 504 /** 505 * Create or update an existing entity in the repository. 506 * 507 * Usage Example: 508 * <code> 509 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 510 * DocumentRepository repository = client.repository("my-table"); 511 * 512 * repository.putEntity(Entity.builder() 513 * .putChild("partition-key", UUID.randomUUID()) 514 * .putChild("creation-time", Instant.now()) 515 * .build()); 516 * } 517 * </code> 518 */ putEntity(Entity entity)519 void putEntity(Entity entity); 520 521 /** 522 * Create or update an existing entity in the repository. 523 * 524 * This API allows specifying additional runtime-specific options via {@link PutRequest#putOption}, and retrieving runtime- 525 * specific options via {@link PutResponse#option}. 526 * 527 * Usage Example: 528 * <code> 529 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 530 * DocumentRepository repository = client.repository("my-table"); 531 * 532 * PutResponse response = 533 * repository.putEntity(Entity.builder() 534 * .putChild("partition-key", UUID.randomUUID()) 535 * .putChild("creation-time", Instant.now()) 536 * .build(), 537 * PutRequest.builder() 538 * .putOption(DynamoDbPutRequestOption.REQUEST, 539 * putItemRequest -> putItemRequest.returnConsumedCapacity(TOTAL)) 540 * .build()); 541 * System.out.println(response.option(DynamoDbPutResponseOption.RESPONSE).consumedCapacity()); 542 * } 543 * </code> 544 */ 545 PutResponse putEntity(Entity entity, PutRequest options) 546 547 /** 548 * Retrieve an entity from the repository. The index will be chosen automatically based on the fields provided in the 549 * input entity. 550 * 551 * Usage Example: 552 * <code> 553 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 554 * DocumentRepository repository = client.repository("my-table"); 555 * 556 * UUID id = UUID.randomUUID(); 557 * repository.putEntity(Entity.builder() 558 * .putChild("partition-key", id) 559 * .putChild("creation-time", Instant.now()) 560 * .build()); 561 * 562 * Thread.sleep(5_000); // GetEntity is eventually consistent with the Dynamo DB runtime. 563 * 564 * Entity item = repository.getEntity(Entity.builder() 565 * .putChild("partition-key", id) 566 * .build()); 567 * 568 * assert item.getChild("creation-time").as(Instant.class).isBetween(Instant.now().minus(1, MINUTE), 569 * Instant.now()); 570 * } catch (NoSuchEntityException e) { 571 * System.out.println("Item could not be found. Maybe we didn't wait long enough for consistency?"); 572 * throw e; 573 * } 574 * </code> 575 */ 576 Entity getEntity(Entity keyEntity); 577 578 /** 579 * Retrieve an entity from the repository. The index will be chosen automatically based on the fields provided in the 580 * input entity. 581 * 582 * This API allows specifying additional runtime-specific options via {@link GetRequest#putOption}, and retrieving runtime- 583 * specific options via {@link GetResponse#option}. 584 * 585 * Usage Example: 586 * <code> 587 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 588 * DocumentRepository repository = client.repository("my-table"); 589 * 590 * UUID id = UUID.randomUUID(); 591 * repository.putEntity(Entity.builder() 592 * .putChild("partition-key", id) 593 * .putChild("creation-time", Instant.now()) 594 * .build()); 595 * 596 * GetResponse response = 597 * repository.getEntity(Entity.builder() 598 * .putChild("partition-key", id) 599 * .build(), 600 * GetRequest.builder() 601 * .putOption(DynamoDbGetRequestOption.CONSISTENT, true) 602 * .build()); 603 * 604 * assert response.entity().getChild("creation-time").as(Instant.class).isBetween(Instant.now().minus(1, MINUTE), 605 * Instant.now()); 606 * } 607 * </code> 608 */ getEntity(Entity keyEntity, GetRequest options)609 GetResponse getEntity(Entity keyEntity, GetRequest options); 610 } 611 612 /** 613 * A base class for type-specific repositories for creating, reading, updating and deleting entities in the remote repository 614 * using Java objects. 615 * 616 * Created via {@link DocumentClient#mappedRepository(Class)}. 617 */ 618 public interface MappedRepository<T, ID> extends DocumentRepository { 619 /** 620 * Create or update an existing entity in the repository. 621 * 622 * Usage Example: 623 * <code> 624 * @MappedRepository 625 * public interface MyItemRepository extends MappedRepository<MyItem, String> { 626 * } 627 * 628 * @Repository("my-table") 629 * public class MyItem { 630 * @Id 631 * @Column("partition-key") 632 * private UUID partitionKey; 633 * 634 * @Column("creation-time") 635 * private Instant creationTime; 636 * 637 * public String getPartitionKey() { return this.partitionKey; } 638 * public Instant getCreationTime() { return this.creationTime; } 639 * public void setPartitionKey(UUID partitionKey) { this.partitionKey = partitionKey; } 640 * public void setCreationTime(Instant creationTime) { this.creationTime = creationTime; } 641 * } 642 * 643 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 644 * MyItemRepository repository = client.repository(MyItemRepository.class); 645 * 646 * UUID id = UUID.randomUUID(); 647 * 648 * MyItem itemToCreate = new MyItem(); 649 * itemToCreate.setPartitionKey(id); 650 * itemToCreate.setCreationTime(Instant.now()); 651 * 652 * repository.putObject(itemToCreate); 653 * } 654 * </code> 655 */ putObject(T entity)656 void putObject(T entity); 657 658 /** 659 * Create or update an existing entity in the repository. 660 * 661 * This API allows specifying additional runtime-specific options via {@link PutRequest#putOption}, and retrieving runtime- 662 * specific options via {@link PutObjectResponse#option}. 663 * 664 * Usage Example: 665 * <code> 666 * @MappedRepository 667 * public interface MyItemRepository extends MappedRepository<MyItem, String> { 668 * } 669 * 670 * @Repository("my-table") 671 * public class MyItem { 672 * @Id 673 * @Column("partition-key") 674 * private UUID partitionKey; 675 * 676 * @Column("creation-time") 677 * private Instant creationTime; 678 * 679 * public String getPartitionKey() { return this.partitionKey; } 680 * public Instant getCreationTime() { return this.creationTime; } 681 * public void setPartitionKey(UUID partitionKey) { this.partitionKey = partitionKey; } 682 * public void setCreationTime(Instant creationTime) { this.creationTime = creationTime; } 683 * } 684 * 685 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 686 * MyItemRepository repository = client.repository(MyItemRepository.class); 687 * 688 * UUID id = UUID.randomUUID(); 689 * 690 * MyItem itemToCreate = new MyItem(); 691 * itemToCreate.setPartitionKey(id); 692 * itemToCreate.setCreationTime(Instant.now()); 693 * 694 * PutObjectResponse<MyItem> response = 695 * repository.putObject(itemToCreate, 696 * PutRequest.builder() 697 * .putOption(DynamoDbPutRequestOption.REQUEST, 698 * putItemRequest -> putItemRequest.returnConsumedCapacity(TOTAL)) 699 * .build()); 700 * System.out.println(response.option(DynamoDbPutResponseOption.RESPONSE).consumedCapacity()); 701 * } 702 * </code> 703 */ 704 PutObjectResponse<T> putObject(T entity, PutRequest options) 705 706 /** 707 * Retrieve an object from the repository. The index will be chosen automatically based on the fields provided in the 708 * input object. 709 * 710 * Usage Example: 711 * <code> 712 * @MappedRepository 713 * public interface MyItemRepository extends MappedRepository<MyItem, String> { 714 * } 715 * 716 * @Repository("my-table") 717 * public class MyItem { 718 * @Id 719 * @Column("partition-key") 720 * private UUID partitionKey; 721 * 722 * @Column("creation-time") 723 * private Instant creationTime; 724 * 725 * public String getPartitionKey() { return this.partitionKey; } 726 * public Instant getCreationTime() { return this.creationTime; } 727 * public void setPartitionKey(UUID partitionKey) { this.partitionKey = partitionKey; } 728 * public void setCreationTime(Instant creationTime) { this.creationTime = creationTime; } 729 * } 730 * 731 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 732 * MyItemRepository repository = client.repository(MyItemRepository.class); 733 * 734 * UUID id = UUID.randomUUID(); 735 * 736 * MyItem itemToCreate = new MyItem(); 737 * itemToCreate.setPartitionKey(id); 738 * itemToCreate.setCreationTime(Instant.now()); 739 * 740 * repository.putObject(itemToCreate); 741 * 742 * // Wait a little bit, because getObject is eventually consistent by default. 743 * Thread.sleep(5_000); 744 * 745 * MyItem itemToRetrieve = new MyItem(); 746 * itemToRetrieve.setPartitionKey(id); 747 * 748 * MyItem retrievedItem = repository.getObject(itemToRetrieve); 749 * assert retrievedItem.getCreationTime().isBetween(Instant.now().minus(1, MINUTE), 750 * Instant.now()); 751 * } catch (NoSuchEntityException e) { 752 * System.out.println("Item could not be found. Maybe we didn't wait long enough for consistency?"); 753 * throw e; 754 * } 755 * </code> 756 */ 757 T getObject(ID id); 758 759 /** 760 * Retrieve an entity from the repository. The index will be chosen automatically based on the fields provided in the 761 * input entity. 762 * 763 * This API allows specifying additional runtime-specific options via {@link GetRequest#putOption}, and retrieving runtime- 764 * specific options via {@link GetObjectResponse#option}. 765 * 766 * Usage Example: 767 * <code> 768 * @MappedRepository 769 * public interface MyItemRepository extends MappedRepository<MyItem, String> { 770 * } 771 * 772 * @Repository("my-table") 773 * public class MyItem { 774 * @Id 775 * @Column("partition-key") 776 * private UUID partitionKey; 777 * 778 * @Column("creation-time") 779 * private Instant creationTime; 780 * 781 * public String getPartitionKey() { return this.partitionKey; } 782 * public Instant getCreationTime() { return this.creationTime; } 783 * public void setPartitionKey(UUID partitionKey) { this.partitionKey = partitionKey; } 784 * public void setCreationTime(Instant creationTime) { this.creationTime = creationTime; } 785 * } 786 * 787 * try (DocumentClient client = DocumentClient.create(DynamoDbRuntime.class)) { 788 * MyItemRepository repository = client.repository(MyItemRepository.class); 789 * 790 * UUID id = UUID.randomUUID(); 791 * 792 * MyItem itemToCreate = new MyItem(); 793 * itemToCreate.setPartitionKey(id); 794 * itemToCreate.setCreationTime(Instant.now()); 795 * 796 * repository.putObject(itemToCreate); 797 * 798 * MyItem itemToRetrieve = new MyItem(); 799 * itemToRetrieve.setPartitionKey(id); 800 * 801 * GetObjectResponse<MyItem> response = 802 * repository.getObject(itemToRetrieve, 803 * GetRequest.builder() 804 * .putOption(DynamoDbGetRequestOption.CONSISTENT, true) 805 * .build()); 806 * 807 * assert response.entity().getCreationTime().isBetween(Instant.now().minus(1, MINUTE), 808 * Instant.now()); 809 * } 810 * </code> 811 */ getObject(ID id, GetRequest options)812 GetObjectResponse<T> getObject(ID id, GetRequest options); 813 } 814 815 /** 816 * An entity in a {@link DocumentRepository}. This is similar to a "row" in a traditional relational database. 817 * 818 * In the following repository, { "User ID": 1, "Username": "joe" } is an entity: 819 * 820 * <pre> 821 * Repository: Users 822 * | ------------------ | 823 * | User ID | Username | 824 * | ------------------ | 825 * | 1 | joe | 826 * | 2 | jane | 827 * | ------------------ | 828 * </pre> 829 */ 830 @ThreadSafe 831 public interface Entity { 832 /** 833 * Create a builder for configuring and creating an {@link Entity}. 834 */ builder()835 static Entity.Builder builder(); 836 837 /** 838 * Retrieve all {@link EntityValue}s in this entity. 839 */ children()840 Map<String, EntityValue> children(); 841 842 /** 843 * Retrieve a specific {@link EntityValue} from this entity. 844 */ child(String entityName)845 EntityValue child(String entityName); 846 847 interface Builder { 848 /** 849 * Add a child to this entity. The methods accepting "Object", will be converted using the default 850 * {@link EntityValueConverter}s. 851 */ putChild(String entityName, Object value)852 Entity.Builder putChild(String entityName, Object value); putChild(String entityName, Object value, EntityValueSchema schema)853 Entity.Builder putChild(String entityName, Object value, EntityValueSchema schema); putChild(String entityName, EntityValue value)854 Entity.Builder putChild(String entityName, EntityValue value); putChild(String entityName, EntityValue value, EntityValueSchema schema)855 Entity.Builder putChild(String entityName, EntityValue value, EntityValueSchema schema); removeChild(String entityName)856 Entity.Builder removeChild(String entityName); clearChildren()857 Entity.Builder clearChildren(); 858 859 /** 860 * Add converters that should be used for this entity and its children. These converters are used with a higher 861 * precedence than those configured in the {@link DocumentClient.Builder}. 862 * 863 * See {@link DocumentClient.Builder#addConverter} for example usage. 864 */ converters(Iterable<? extends EntityValueConverter<?>> converters)865 Entity.Builder converters(Iterable<? extends EntityValueConverter<?>> converters); addConverter(EntityValueConverter<?> converter)866 Entity.Builder addConverter(EntityValueConverter<?> converter); clearConverters()867 Entity.Builder clearConverters(); 868 869 /** 870 * Create an {@link Entity} using the current configuration on the builder. 871 */ build()872 Entity build(); 873 } 874 } 875 876 /** 877 * The value within an {@link Entity}. In a traditional relational database, this would be analogous to a cell 878 * in the table. 879 * 880 * In the following table, "joe" and "jane" are both entity values: 881 * <pre> 882 * Table: Users 883 * | ------------------ | 884 * | User ID | Username | 885 * | ------------------ | 886 * | 1 | joe | 887 * | 2 | jane | 888 * | ------------------ | 889 * </pre> 890 */ 891 @ThreadSafe 892 public interface EntityValue { 893 /** 894 * Create an {@link EntityValue} from the provided object. 895 */ from(Object object)896 static EntityValue from(Object object); 897 898 /** 899 * Create an {@link EntityValue} from the provided object, and associate this value with the provided 900 * {@link EntityValueConverter}. This allows it to be immediately converted with {@link #as(Class)}. 901 * 902 * This is equivalent to {@code EntityValue.from(object).convertFromJavaType(converter)}. 903 */ from(Object object, EntityValueConverter<?> converter)904 static EntityValue from(Object object, EntityValueConverter<?> converter); 905 906 /** 907 * Create an {@link EntityValue} that represents the null type. 908 */ nullValue()909 static EntityValue nullValue(); 910 911 /** 912 * Retrieve the {@link EntityValueType} of this value. 913 */ type()914 EntityValueType type(); 915 916 /** 917 * The {@code is*} methods can be used to check the underlying repository type of the entity value. 918 * 919 * If the type isn't known (eg. because it was created via {@link EntityValue#from(Object)}), {@link #isJavaType()} 920 * will return true. Such types will be converted into repository-specific types by the document client before they are 921 * persisted. 922 */ 923 isString()924 boolean isString(); isNumber()925 default boolean isNumber() { return isFloat() || isDouble() || isShort() || isInt() || isLong(); } isFloat()926 boolean isFloat(); isDouble()927 boolean isDouble(); isShort()928 boolean isShort(); isInt()929 boolean isInt(); isLong()930 boolean isLong(); isBytes()931 boolean isBytes(); isBoolean()932 boolean isBoolean(); isNull()933 boolean isNull(); isJavaType()934 boolean isJavaType(); 935 isList()936 boolean isList(); isEntity()937 boolean isEntity(); 938 939 /** 940 * Convert this entity value into the requested Java type. 941 * 942 * This uses the {@link EntityValueConverter} configured on this type via 943 * {@link #from(Object, EntityValueConverter)} or {@link #convertFromJavaType(EntityValueConverter)}. 944 */ as(Class<T> type)945 <T> T as(Class<T> type); 946 947 /** 948 * The {@code as*} methods can be used to retrieve this value without the type-conversion overhead of {@link #as(Class)}. 949 * 950 * An exception will be thrown from these methods if the requested type does not match the actual underlying type. When 951 * the type isn't know, the {@code is*} or {@link #type()} methods can be used to query the underlying type before 952 * invoking these {@code as*} methods. 953 */ 954 asString()955 String asString(); asNumber()956 BigDecimal asNumber(); asFloat()957 float asFloat(); asDouble()958 double asDouble(); asShort()959 short asShort(); asInt()960 int asInt(); asLong()961 long asLong(); asBytes()962 SdkBytes asBytes(); asBoolean()963 Boolean asBoolean(); asJavaType()964 Object asJavaType(); 965 asList()966 List<EntityValue> asList(); asEntity()967 Entity asEntity(); 968 969 /** 970 * Convert this entity value from a {@link EntityValueType#JAVA_TYPE} to a type that can be persisted in DynamoDB. 971 * 972 * This will throw an exception if {@link #isJavaType()} is false. 973 */ convertFromJavaType(EntityValueConverter<?> converter)974 EntityValue convertFromJavaType(EntityValueConverter<?> converter); 975 } 976 977 /** 978 * The underlying repository type of an {@link EntityValue}. 979 */ 980 @ThreadSafe 981 public enum EntityValueType { 982 ENTITY, 983 LIST, 984 STRING, 985 FLOAT, 986 DOUBLE, 987 SHORT, 988 INT, 989 LONG, 990 BYTES, 991 BOOLEAN, 992 NULL, 993 JAVA_TYPE 994 } 995 996 /** 997 * The schema for a specific entity. This describes the entity's structure and which values it contains. 998 * 999 * This is mostly an implementation detail, and can be ignored except by developers interested in creating 1000 * {@link EntityValueConverter}s. 1001 */ 1002 @ThreadSafe 1003 public interface EntitySchema { 1004 /** 1005 * Create a builder for configuring and creating an {@link EntitySchema}. 1006 */ builder()1007 static EntitySchema.Builder builder(); 1008 1009 interface Builder { 1010 /** 1011 * The Java type of the entity that this schema represents. 1012 */ javaType(Class<?> javaType)1013 EntitySchema.Builder javaType(Class<?> javaType); 1014 1015 /** 1016 * The repository type of the entity that this schema represents. 1017 */ entityValueType(EntityValueType entityValueType)1018 EntitySchema.Builder entityValueType(EntityValueType entityValueType); 1019 1020 /** 1021 * The converter that should be used for converting an entity conforming to this schema to/from the 1022 * repository-specific type. 1023 */ converter(EntityValueConverter<?> converter)1024 EntitySchema.Builder converter(EntityValueConverter<?> converter); 1025 1026 /** 1027 * Specify the child schemas that describe each child of this entity. 1028 */ childSchemas(Map<String, EntityValueSchema> childSchemas)1029 EntitySchema.Builder childSchemas(Map<String, EntityValueSchema> childSchemas); putChildSchema(String childName, EntityValueSchema childSchema)1030 EntitySchema.Builder putChildSchema(String childName, EntityValueSchema childSchema); removeChildSchema(String childName)1031 EntitySchema.Builder removeChildSchema(String childName); clearChildSchemas()1032 EntitySchema.Builder clearChildSchemas(); 1033 1034 /** 1035 * Create an {@link EntitySchema} using the current configuration on the builder. 1036 */ build()1037 EntitySchema build(); 1038 } 1039 } 1040 1041 /** 1042 * The schema for a specific entity value. This describes the entity child's structure, including what the Java-specific type 1043 * representation is for this value, etc. 1044 * 1045 * This is mostly an implementation detail, and can be ignored except by developers interested in creating 1046 * {@link EntityValueConverter}. 1047 */ 1048 @ThreadSafe 1049 public interface EntityValueSchema { 1050 /** 1051 * Create a builder for configuring and creating an {@link EntityValueSchema}s. 1052 */ builder()1053 static EntityValueSchema.Builder builder(); 1054 1055 interface Builder { 1056 /** 1057 * Specify the Java-specific type representation for this type. 1058 */ javaType(Class<?> javaType)1059 EntityValueSchema.Builder javaType(Class<?> javaType); 1060 1061 /** 1062 * The repository type of the value that this schema represents. 1063 */ entityValueType(EntityValueType entityValueType)1064 EntityValueSchema.Builder entityValueType(EntityValueType entityValueType); 1065 1066 /** 1067 * The converter that should be used for converting a value conforming to this schema to/from the 1068 * repository-specific type. 1069 */ converter(EntityValueConverter<?> converter)1070 EntityValueSchema.Builder converter(EntityValueConverter<?> converter); 1071 1072 /** 1073 * Create an {@link EntityValueSchema} using the current configuration on the builder. 1074 */ build()1075 EntityValueSchema build(); 1076 } 1077 }