|
29 | 29 | import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
|
30 | 30 | import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
|
31 | 31 | import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
|
32 |
| -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior; |
33 | 32 | import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
| 33 | +import software.amazon.awssdk.utils.StringUtils; |
34 | 34 | import software.amazon.awssdk.utils.Validate;
|
35 | 35 |
|
36 | 36 |
|
37 | 37 | /**
|
38 |
| - * This extension facilitates the automatic generation of a unique UUID (Universally Unique Identifier) for a specified attribute |
39 |
| - * every time a new record is written to the database. The generated UUID is obtained using the |
40 |
| - * {@link java.util.UUID#randomUUID()} method. |
41 |
| - * <p> |
42 |
| - * This extension is not loaded by default when you instantiate a |
43 |
| - * {@link software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient}. Therefore, you need to specify it in a custom |
44 |
| - * extension when creating the enhanced client. |
45 |
| - * <p> |
46 |
| - * Example to add AutoGeneratedUuidExtension along with default extensions is |
47 |
| - * {@snippet : |
48 |
| - * DynamoDbEnhancedClient.builder().extensions(Stream.concat(ExtensionResolver.defaultExtensions().stream(), |
49 |
| - * Stream.of(AutoGeneratedUuidExtension.create())).collect(Collectors.toList())).build(); |
50 |
| - *} |
51 |
| - * </p> |
52 |
| - * <p> |
53 |
| - * Example to just add AutoGeneratedUuidExtension without default extensions is |
54 |
| - * {@snippet : |
55 |
| - * DynamoDbEnhancedClient.builder().extensions(AutoGeneratedUuidExtension.create()).build(); |
56 |
| - *} |
57 |
| - * </p> |
58 |
| - * <p> |
59 |
| - * To utilize the auto-generated UUID feature, first, create a field in your model that will store the UUID for the attribute. |
60 |
| - * This class field must be of type {@link java.lang.String}, and you need to tag it as the autoGeneratedUuidAttribute. If you are |
61 |
| - * using the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema}, then you should use the |
62 |
| - * {@link software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid} annotation. If you are using |
63 |
| - * the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema}, then you should use the |
64 |
| - * {@link |
65 |
| - * software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags#autoGeneratedUuidAttribute()} |
66 |
| - * static attribute tag. |
67 |
| - * </p> |
68 |
| - * <p> |
69 |
| - * Every time a new record is successfully put into the database, the specified attribute will be automatically populated with a |
70 |
| - * unique UUID generated using {@link java.util.UUID#randomUUID()}. If the UUID needs to be created only for `putItem` and should |
71 |
| - * not be generated for an `updateItem`, then |
72 |
| - * {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior#WRITE_IF_NOT_EXISTS} must be along with |
73 |
| - * {@link DynamoDbUpdateBehavior} |
| 38 | + * This extension facilitates the automatic generation of a unique UUID (Universally Unique Identifier) for attributes tagged with |
| 39 | + * {@code @DynamoDbAutoGeneratedUuid} or {@link AutoGeneratedUuidExtension.AttributeTags#autoGeneratedUuidAttribute()}. The |
| 40 | + * generated UUID is obtained using {@link java.util.UUID#randomUUID()}. |
74 | 41 | *
|
75 |
| - * </p> |
| 42 | + * <p>Usage:</p> |
| 43 | + * <ul> |
| 44 | + * <li>This extension is not loaded by default; register it explicitly when building |
| 45 | + * a {@link software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient}.</li> |
| 46 | + * <li>The annotated attribute must be of type {@link String}.</li> |
| 47 | + * <li>If the attribute is <b>missing or empty</b> in the item being written, a UUID |
| 48 | + * will be generated and set in the outgoing request map.</li> |
| 49 | + * <li>If the attribute already has a non-empty value, it is preserved and not overwritten.</li> |
| 50 | + * </ul> |
| 51 | + * |
| 52 | + * <p><b>Update behavior:</b></p> |
| 53 | + * <ul> |
| 54 | + * <li>With the default {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior#WRITE_ALWAYS}, |
| 55 | + * a missing attribute on an update will cause a new UUID to be generated and written.</li> |
| 56 | + * <li>With {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior#WRITE_IF_NOT_EXISTS}, |
| 57 | + * a missing attribute on an update will cause a UUID to be generated in the outgoing map, |
| 58 | + * but DynamoDB will preserve the existing stored value thanks to the conditional |
| 59 | + * <code>if_not_exists(...)</code> expression the mapper generates.</li> |
| 60 | + * </ul> |
| 61 | + * |
| 62 | + * <p><b>Difference between putItem and updateItem:</b></p> |
| 63 | + * <ul> |
| 64 | + * <li>{@code putItem} always replaces the entire item. If the field is absent in the payload, the extension |
| 65 | + * will generate a new UUID, even when {@code WRITE_IF_NOT_EXISTS} is specified.</li> |
| 66 | + * <li>{@code updateItem} respects {@code WRITE_IF_NOT_EXISTS}: if the attribute already exists in DynamoDB, |
| 67 | + * the previously stored value is preserved and a new UUID will not overwrite it.</li> |
| 68 | + * </ul> |
| 69 | + * |
| 70 | + * <p>Examples:</p> |
| 71 | + * <pre>{@code |
| 72 | + * DynamoDbEnhancedClient client = DynamoDbEnhancedClient.builder() |
| 73 | + * .dynamoDbClient(lowLevelClient) |
| 74 | + * .extensions(AutoGeneratedUuidExtension.create()) |
| 75 | + * .build(); |
| 76 | + * }</pre> |
76 | 77 | */
|
| 78 | + |
77 | 79 | @SdkPublicApi
|
78 | 80 | @ThreadSafe
|
79 | 81 | public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientExtension {
|
@@ -110,15 +112,18 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
|
110 | 112 | }
|
111 | 113 |
|
112 | 114 | Map<String, AttributeValue> itemToTransform = new HashMap<>(context.items());
|
113 |
| - customMetadataObject.forEach(key -> insertUuidInItemToTransform(itemToTransform, key)); |
| 115 | + customMetadataObject.forEach(key -> insertUuidIfMissing(itemToTransform, key)); |
114 | 116 | return WriteModification.builder()
|
115 | 117 | .transformedItem(Collections.unmodifiableMap(itemToTransform))
|
116 | 118 | .build();
|
117 | 119 | }
|
118 | 120 |
|
119 |
| - private void insertUuidInItemToTransform(Map<String, AttributeValue> itemToTransform, |
120 |
| - String key) { |
121 |
| - itemToTransform.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build()); |
| 121 | + private static void insertUuidIfMissing(Map<String, AttributeValue> item, String key) { |
| 122 | + AttributeValue existing = item.get(key); |
| 123 | + boolean missing = existing == null || StringUtils.isEmpty(existing.s()); |
| 124 | + if (missing) { |
| 125 | + item.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build()); |
| 126 | + } |
122 | 127 | }
|
123 | 128 |
|
124 | 129 | public static final class AttributeTags {
|
|
0 commit comments