Skip to content

Commit f49c22a

Browse files
committed
Added support for DynamoDbAutoGeneratedKey annotation
1 parent 5998f4b commit f49c22a

File tree

6 files changed

+65
-58
lines changed

6 files changed

+65
-58
lines changed

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@
4242
* Generates a random UUID (via {@link java.util.UUID#randomUUID()}) for any attribute tagged with
4343
* {@code @DynamoDbAutoGeneratedKey} when that attribute is missing or empty on a write (put/update).
4444
* <p>
45-
* <b>Key Difference from @DynamoDbAutoGeneratedUuid:</b> This extension only generates UUIDs when the
46-
* attribute value is null or empty, preserving existing values. In contrast, {@code @DynamoDbAutoGeneratedUuid}
47-
* always generates new UUIDs regardless of existing values.
45+
* <b>Key Difference from @DynamoDbAutoGeneratedUuid:</b> This extension only generates UUIDs when the
46+
* attribute value is null or empty, preserving existing values. In contrast, {@code @DynamoDbAutoGeneratedUuid} always generates
47+
* new UUIDs regardless of existing values.
4848
* <p>
4949
* <b>Conflict Detection:</b> This extension cannot be used together with {@code @DynamoDbAutoGeneratedUuid} on the same
50-
* attribute. If both annotations are applied to the same field, an {@link IllegalArgumentException} will be thrown
51-
* at runtime to prevent unpredictable behavior based on extension load order.
50+
* attribute. If both annotations are applied to the same field, an {@link IllegalArgumentException} will be thrown at runtime to
51+
* prevent unpredictable behavior based on extension load order.
5252
* <p>
5353
* The annotation may be placed <b>only</b> on key attributes:
5454
* <ul>
@@ -60,7 +60,7 @@
6060
* annotated attributes against the table's known key attributes. If an annotated attribute
6161
* is not a PK/SK or an GSI/LSI, an {@link IllegalArgumentException} is thrown.</p>
6262
*
63-
* <p><b>UpdateBehavior Limitations:</b> {@code @DynamoDbUpdateBehavior} has no effect on primary keys due to
63+
* <p><b>UpdateBehavior Limitations:</b> {@code @DynamoDbUpdateBehavior} has no effect on primary keys due to
6464
* DynamoDB's UpdateItem API requirements. It only affects secondary index keys.</p>
6565
*/
6666
@SdkPublicApi
@@ -92,8 +92,8 @@ public static Builder builder() {
9292
* If this table has attributes tagged for auto-generation, insert a UUID value into the outgoing item for any such attribute
9393
* that is currently missing/empty. Unlike {@code @DynamoDbAutoGeneratedUuid}, this preserves existing values.
9494
* <p>
95-
* Also validates that the annotation is only used on PK/SK/GSI/LSI key attributes and that there are no conflicts
96-
* with @DynamoDbAutoGeneratedUuid.
95+
* Also validates that the annotation is only used on PK/SK/GSI/LSI key attributes and that there are no conflicts with
96+
* @DynamoDbAutoGeneratedUuid.
9797
*/
9898
@Override
9999
public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import static org.assertj.core.api.Assertions.assertThat;
99
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
1010
import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
11+
import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute;
12+
import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
1113
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
1214
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey;
1315
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey;
@@ -44,12 +46,13 @@ public class AutoGeneratedKeyExtensionTest {
4446
.getter(ItemWithKey::getId)
4547
.setter(ItemWithKey::setId)
4648
.addTag(primaryPartitionKey()) // PK
47-
.addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute()))
49+
.addTag(autoGeneratedKeyAttribute()))
4850
.addAttribute(String.class, a -> a.name("keyAttribute")
4951
.getter(ItemWithKey::getKeyAttribute)
5052
.setter(ItemWithKey::setKeyAttribute)
51-
.tags(secondaryPartitionKey("gsi_keys_only"), // GSI
52-
AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute()))
53+
.tags(
54+
secondaryPartitionKey("gsi_keys_only"), // GSI
55+
autoGeneratedKeyAttribute()))
5356
.addAttribute(String.class, a -> a.name("simpleString")
5457
.getter(ItemWithKey::getSimpleString)
5558
.setter(ItemWithKey::setSimpleString))
@@ -69,7 +72,7 @@ public class AutoGeneratedKeyExtensionTest {
6972
.getter(ItemWithKey::getKeyAttribute)
7073
.setter(ItemWithKey::setKeyAttribute)
7174
// No index tags here — autogen on non-key fails at beforeWrite()
72-
.addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute()))
75+
.addTag(autoGeneratedKeyAttribute()))
7376
.addAttribute(String.class, a -> a.name("simpleString")
7477
.getter(ItemWithKey::getSimpleString)
7578
.setter(ItemWithKey::setSimpleString))
@@ -91,8 +94,9 @@ public class AutoGeneratedKeyExtensionTest {
9194
.addAttribute(String.class, a -> a.name("simpleString")
9295
.getter(ItemWithKey::getSimpleString)
9396
.setter(ItemWithKey::setSimpleString)
94-
.tags(secondarySortKey("lsi1"), // LSI
95-
AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute()))
97+
.tags(
98+
secondarySortKey("lsi1"), // LSI
99+
autoGeneratedKeyAttribute()))
96100
.build();
97101

98102
@Test
@@ -180,7 +184,7 @@ public void nonStringTypeAnnotatedWithAutoGeneratedKey_throwsIllegalArgumentExce
180184
.addAttribute(Integer.class, a -> a.name("intAttribute")
181185
.getter(ItemWithKey::getIntAttribute)
182186
.setter(ItemWithKey::setIntAttribute)
183-
.addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute()))
187+
.addTag(autoGeneratedKeyAttribute()))
184188
.addAttribute(String.class, a -> a.name("simpleString")
185189
.getter(ItemWithKey::getSimpleString)
186190
.setter(ItemWithKey::setSimpleString))
@@ -263,8 +267,8 @@ public void conflictingAnnotations_throwsIllegalArgumentException() {
263267
.setter(ItemWithKey::setId)
264268
.addTag(primaryPartitionKey())
265269
// Both annotations on the same attribute
266-
.addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute())
267-
.addTag(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute()))
270+
.addTag(autoGeneratedKeyAttribute())
271+
.addTag(autoGeneratedUuidAttribute()))
268272
.addAttribute(String.class, a -> a.name("simpleString")
269273
.getter(ItemWithKey::getSimpleString)
270274
.setter(ItemWithKey::setSimpleString))
@@ -304,8 +308,8 @@ public void conflictingAnnotations_onSecondaryKey_throwsIllegalArgumentException
304308
.setter(ItemWithKey::setKeyAttribute)
305309
.addTag(secondaryPartitionKey("gsi1"))
306310
// Both annotations on the same GSI key
307-
.addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute())
308-
.addTag(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute()))
311+
.addTag(autoGeneratedKeyAttribute())
312+
.addTag(autoGeneratedUuidAttribute()))
309313
.addAttribute(String.class, a -> a.name("simpleString")
310314
.getter(ItemWithKey::getSimpleString)
311315
.setter(ItemWithKey::setSimpleString))
@@ -342,8 +346,8 @@ public void conflictDetection_worksRegardlessOfExtensionOrder() {
342346
.getter(ItemWithKey::getId)
343347
.setter(ItemWithKey::setId)
344348
.addTag(primaryPartitionKey())
345-
.addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute())
346-
.addTag(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute()))
349+
.addTag(autoGeneratedKeyAttribute())
350+
.addTag(autoGeneratedUuidAttribute()))
347351
.build();
348352

349353
ItemWithKey item = new ItemWithKey();

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ public void conflictingAnnotations_throwsIllegalArgumentException() {
189189
.tableMetadata(conflictingSchema.tableMetadata())
190190
.operationName(OperationName.PUT_ITEM)
191191
.operationContext(PRIMARY_CONTEXT)
192-
.build()))
192+
.build())
193+
)
193194
.withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
194195
+ "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
195196
}

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package software.amazon.awssdk.enhanced.dynamodb.extensions;
1717

1818
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
19+
import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute;
20+
import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
1921
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
2022

2123
import java.util.Map;
@@ -29,8 +31,8 @@
2931
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
3032

3133
/**
32-
* Tests to verify that conflicting annotations (@DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid)
33-
* are properly detected and throw exceptions regardless of extension load order.
34+
* Tests to verify that conflicting annotations (@DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid) are properly detected
35+
* and throw exceptions regardless of extension load order.
3436
*/
3537
public class ConflictingAnnotationsTest {
3638

@@ -53,8 +55,8 @@ public class ConflictingAnnotationsTest {
5355
.setter(TestItem::setId)
5456
.addTag(primaryPartitionKey())
5557
// Both annotations on the same attribute
56-
.addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute())
57-
.addTag(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute()))
58+
.addTag(autoGeneratedKeyAttribute())
59+
.addTag(autoGeneratedUuidAttribute()))
5860
.build();
5961

6062
@Test
@@ -66,13 +68,13 @@ public void keyExtensionFirst_detectsConflictWithUuidExtension() {
6668

6769
// AutoGeneratedKeyExtension runs first and detects conflict
6870
assertThatExceptionOfType(IllegalArgumentException.class)
69-
.isThrownBy(() ->
70-
keyExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
71-
.items(items)
72-
.tableMetadata(CONFLICTING_SCHEMA.tableMetadata())
73-
.operationName(OperationName.PUT_ITEM)
74-
.operationContext(PRIMARY_CONTEXT)
75-
.build())
71+
.isThrownBy(() -> keyExtension.beforeWrite(
72+
DefaultDynamoDbExtensionContext.builder()
73+
.items(items)
74+
.tableMetadata(CONFLICTING_SCHEMA.tableMetadata())
75+
.operationName(OperationName.PUT_ITEM)
76+
.operationContext(PRIMARY_CONTEXT)
77+
.build())
7678
)
7779
.withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
7880
+ "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
@@ -87,13 +89,13 @@ public void uuidExtensionFirst_detectsConflictWithKeyExtension() {
8789

8890
// AutoGeneratedUuidExtension runs first and detects conflict
8991
assertThatExceptionOfType(IllegalArgumentException.class)
90-
.isThrownBy(() ->
91-
uuidExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
92-
.items(items)
93-
.tableMetadata(CONFLICTING_SCHEMA.tableMetadata())
94-
.operationName(OperationName.PUT_ITEM)
95-
.operationContext(PRIMARY_CONTEXT)
96-
.build())
92+
.isThrownBy(() -> uuidExtension.beforeWrite(
93+
DefaultDynamoDbExtensionContext.builder()
94+
.items(items)
95+
.tableMetadata(CONFLICTING_SCHEMA.tableMetadata())
96+
.operationName(OperationName.PUT_ITEM)
97+
.operationContext(PRIMARY_CONTEXT)
98+
.build())
9799
)
98100
.withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
99101
+ "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
@@ -109,11 +111,11 @@ public void separateAttributes_noConflict() {
109111
.getter(TestItemSeparate::getId)
110112
.setter(TestItemSeparate::setId)
111113
.addTag(primaryPartitionKey())
112-
.addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute()))
114+
.addTag(autoGeneratedKeyAttribute()))
113115
.addAttribute(String.class, a -> a.name("uuidField")
114116
.getter(TestItemSeparate::getUuidField)
115117
.setter(TestItemSeparate::setUuidField)
116-
.addTag(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute()))
118+
.addTag(autoGeneratedUuidAttribute()))
117119
.build();
118120

119121
TestItemSeparate item = new TestItemSeparate();
@@ -122,18 +124,18 @@ public void separateAttributes_noConflict() {
122124

123125
// Both extensions should work without conflict when on different attributes
124126
keyExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
125-
.items(items)
126-
.tableMetadata(separateSchema.tableMetadata())
127-
.operationName(OperationName.PUT_ITEM)
128-
.operationContext(PRIMARY_CONTEXT)
129-
.build());
130-
131-
uuidExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
132127
.items(items)
133128
.tableMetadata(separateSchema.tableMetadata())
134129
.operationName(OperationName.PUT_ITEM)
135130
.operationContext(PRIMARY_CONTEXT)
136131
.build());
132+
133+
uuidExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
134+
.items(items)
135+
.tableMetadata(separateSchema.tableMetadata())
136+
.operationName(OperationName.PUT_ITEM)
137+
.operationContext(PRIMARY_CONTEXT)
138+
.build());
137139
}
138140

139141
public static class TestItem {

0 commit comments

Comments
 (0)