From 7cec4a2e6be008f424dba515154e551f07b84f15 Mon Sep 17 00:00:00 2001
From: Ana Satirbasa
Date: Thu, 6 Nov 2025 09:41:11 +0200
Subject: [PATCH 01/17] Added support for DynamoDbAutoGeneratedKey annotation
---
...-AmazonDynamoDBEnhancedClient-cbcc2bb.json | 6 +
.../extensions/AutoGeneratedKeyExtension.java | 219 +++++++++
.../AutoGeneratedUuidExtension.java | 30 ++
.../annotations/DynamoDbAutoGeneratedKey.java | 77 ++++
.../extensions/AutoGeneratedKeyTag.java | 33 ++
.../enhanced/dynamodb/UuidTestUtils.java | 33 ++
.../AutoGeneratedKeyExtensionTest.java | 434 ++++++++++++++++++
.../AutoGeneratedUuidExtensionTest.java | 39 ++
.../ConflictingAnnotationsTest.java | 173 +++++++
.../AutoGeneratedKeyRecordTest.java | 410 +++++++++++++++++
.../AutoGeneratedUuidRecordTest.java | 60 ++-
11 files changed, 1509 insertions(+), 5 deletions(-)
create mode 100644 .changes/next-release/feature-AmazonDynamoDBEnhancedClient-cbcc2bb.json
create mode 100644 services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
create mode 100644 services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
create mode 100644 services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedKeyTag.java
create mode 100644 services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/UuidTestUtils.java
create mode 100644 services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
create mode 100644 services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
create mode 100644 services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
diff --git a/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-cbcc2bb.json b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-cbcc2bb.json
new file mode 100644
index 000000000000..b8c7700af796
--- /dev/null
+++ b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-cbcc2bb.json
@@ -0,0 +1,6 @@
+{
+ "type": "feature",
+ "category": "Amazon DynamoDB Enhanced Client",
+ "contributor": "",
+ "description": "Added the support for DynamoDbAutoGeneratedKey annotation"
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
new file mode 100644
index 000000000000..0898bb7e57d1
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.extensions;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Consumer;
+import software.amazon.awssdk.annotations.SdkPublicApi;
+import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbExtensionContext;
+import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
+import software.amazon.awssdk.enhanced.dynamodb.IndexMetadata;
+import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.utils.StringUtils;
+import software.amazon.awssdk.utils.Validate;
+
+/**
+ * Generates a random UUID (via {@link java.util.UUID#randomUUID()}) for any attribute tagged with
+ * {@code @DynamoDbAutoGeneratedKey} when that attribute is missing or empty on a write (put/update).
+ *
+ * Key Difference from @DynamoDbAutoGeneratedUuid: This extension only generates UUIDs when the
+ * attribute value is null or empty, preserving existing values. In contrast, {@code @DynamoDbAutoGeneratedUuid} always generates
+ * new UUIDs regardless of existing values.
+ *
+ * Conflict Detection: This extension cannot be used together with {@code @DynamoDbAutoGeneratedUuid} on the same
+ * attribute. If both annotations are applied to the same field, an {@link IllegalArgumentException} will be thrown at runtime to
+ * prevent unpredictable behavior based on extension load order.
+ *
+ * The annotation may be placed only on key attributes:
+ *
+ *
Primary partition key (PK) or primary sort key (SK)
+ *
Partition key or sort key of any secondary index (GSI or LSI)
+ *
+ *
+ *
Validation: The extension enforces this at runtime during {@link #beforeWrite} by comparing the
+ * annotated attributes against the table's known key attributes. If an annotated attribute
+ * is not a PK/SK or an GSI/LSI, an {@link IllegalArgumentException} is thrown.
+ *
+ *
UpdateBehavior Limitations: {@code @DynamoDbUpdateBehavior} has no effect on primary keys due to
+ * DynamoDB's UpdateItem API requirements. It only affects secondary index keys.
+ */
+@SdkPublicApi
+@ThreadSafe
+public final class AutoGeneratedKeyExtension implements DynamoDbEnhancedClientExtension {
+
+ /**
+ * Custom metadata key under which we store the set of annotated attribute names.
+ */
+ private static final String CUSTOM_METADATA_KEY =
+ "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
+
+ /**
+ * Metadata key used by AutoGeneratedUuidExtension to detect conflicts.
+ */
+ private static final String UUID_EXTENSION_METADATA_KEY =
+ "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
+
+ private static final AutoGeneratedKeyAttribute AUTO_GENERATED_KEY_ATTRIBUTE = new AutoGeneratedKeyAttribute();
+
+ private AutoGeneratedKeyExtension() {
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * If this table has attributes tagged for auto-generation, insert a UUID value into the outgoing item for any such attribute
+ * that is currently missing/empty. Unlike {@code @DynamoDbAutoGeneratedUuid}, this preserves existing values.
+ *
+ * Also validates that the annotation is only used on PK/SK/GSI/LSI key attributes and that there are no conflicts with
+ *
+ * @DynamoDbAutoGeneratedUuid.
+ */
+ @Override
+ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
+ Collection taggedAttributes = context.tableMetadata()
+ .customMetadataObject(CUSTOM_METADATA_KEY, Collection.class)
+ .orElse(null);
+
+ if (taggedAttributes == null || taggedAttributes.isEmpty()) {
+ return WriteModification.builder().build();
+ }
+
+ // Check for conflicts with @DynamoDbAutoGeneratedUuid
+ Collection uuidTaggedAttributes = context.tableMetadata()
+ .customMetadataObject(UUID_EXTENSION_METADATA_KEY, Collection.class)
+ .orElse(Collections.emptyList());
+
+ taggedAttributes.stream()
+ .filter(uuidTaggedAttributes::contains)
+ .findFirst()
+ .ifPresent(attribute -> {
+ throw new IllegalArgumentException(
+ "Attribute '" + attribute + "' cannot have both @DynamoDbAutoGeneratedKey and "
+ + "@DynamoDbAutoGeneratedUuid annotations. These annotations have conflicting behaviors "
+ + "and cannot be used together on the same attribute.");
+ });
+
+ TableMetadata meta = context.tableMetadata();
+ Set allowedKeys = new HashSet<>();
+
+ // ensure every @DynamoDbAutoGeneratedKey attribute is a PK/SK or GSI/LSI. If not, throw IllegalArgumentException
+ allowedKeys.add(meta.primaryPartitionKey());
+ meta.primarySortKey().ifPresent(allowedKeys::add);
+
+ for (IndexMetadata idx : meta.indices()) {
+ String indexName = idx.name();
+ allowedKeys.add(meta.indexPartitionKey(indexName));
+ meta.indexSortKey(indexName).ifPresent(allowedKeys::add);
+ }
+
+ taggedAttributes.stream()
+ .filter(attr -> !allowedKeys.contains(attr))
+ .findFirst()
+ .ifPresent(attr -> {
+ throw new IllegalArgumentException(
+ "@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
+ + "primary partition key, primary sort key, or GSI/LSI partition/sort keys."
+ + "Invalid placement on attribute: " + attr);
+ });
+
+ // Generate UUIDs for missing/empty annotated attributes
+ Map itemToTransform = new HashMap<>(context.items());
+ taggedAttributes.forEach(attr -> insertUuidIfMissing(itemToTransform, attr));
+
+ return WriteModification.builder()
+ .transformedItem(Collections.unmodifiableMap(itemToTransform))
+ .build();
+ }
+
+ private void insertUuidIfMissing(Map itemToTransform, String key) {
+ AttributeValue existing = itemToTransform.get(key);
+ if (Objects.isNull(existing) || StringUtils.isBlank(existing.s())) {
+ itemToTransform.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
+ }
+ }
+
+ /**
+ * Static helpers used by the {@code @BeanTableSchemaAttributeTag}-based annotation tag.
+ */
+ public static final class AttributeTags {
+ private AttributeTags() {
+ }
+
+ /**
+ * @return a {@link StaticAttributeTag} that marks the attribute for auto-generated key behavior.
+ */
+ public static StaticAttributeTag autoGeneratedKeyAttribute() {
+ return AUTO_GENERATED_KEY_ATTRIBUTE;
+ }
+ }
+
+ /**
+ * Stateless builder.
+ */
+ public static final class Builder {
+ private Builder() {
+ }
+
+ public AutoGeneratedKeyExtension build() {
+ return new AutoGeneratedKeyExtension();
+ }
+ }
+
+ /**
+ * Validates Java type and records the tagged attribute into table metadata so {@link #beforeWrite} can find it at runtime.
+ */
+ private static final class AutoGeneratedKeyAttribute implements StaticAttributeTag {
+
+ @Override
+ public void validateType(String attributeName,
+ EnhancedType type,
+ AttributeValueType attributeValueType) {
+
+ Validate.notNull(type, "type is null");
+ Validate.notNull(type.rawClass(), "rawClass is null");
+ Validate.notNull(attributeValueType, "attributeValueType is null");
+
+ if (!type.rawClass().equals(String.class)) {
+ throw new IllegalArgumentException(String.format(
+ "Attribute '%s' of Class type %s is not a suitable Java Class type to be used as a Auto Generated "
+ + "Key attribute. Only String Class type is supported.", attributeName, type.rawClass()));
+ }
+ }
+
+ @Override
+ public Consumer modifyMetadata(String attributeName,
+ AttributeValueType attributeValueType) {
+ // Record the names of the attributes annotated with @DynamoDbAutoGeneratedKey for later lookup in beforeWrite()
+ return metadata -> metadata.addCustomMetadataObject(
+ CUSTOM_METADATA_KEY, Collections.singleton(attributeName));
+ }
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
index d92db8c60bbd..5a4712a177d3 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
@@ -39,6 +39,14 @@
* every time a new record is written to the database. The generated UUID is obtained using the
* {@link java.util.UUID#randomUUID()} method.
*
+ * Key Difference from @DynamoDbAutoGeneratedKey: This extension always generates new UUIDs on every write,
+ * regardless of existing values. In contrast, {@code @DynamoDbAutoGeneratedKey} only generates UUIDs when the
+ * attribute value is null or empty, preserving existing values.
+ *
+ * Conflict Detection: This extension cannot be used together with {@code @DynamoDbAutoGeneratedKey} on the same
+ * attribute. If both annotations are applied to the same field, an {@link IllegalArgumentException} will be thrown
+ * at runtime to prevent unpredictable behavior.
+ *
* This extension is not loaded by default when you instantiate a
* {@link software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient}. Therefore, you need to specify it in a custom
* extension when creating the enhanced client.
@@ -79,6 +87,13 @@
public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientExtension {
private static final String CUSTOM_METADATA_KEY =
"software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
+
+ /**
+ * Metadata key used by AutoGeneratedKeyExtension to detect conflicts.
+ */
+ private static final String KEY_EXTENSION_METADATA_KEY =
+ "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
+
private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ATTRIBUTE = new AutoGeneratedUuidAttribute();
private AutoGeneratedUuidExtension() {
@@ -109,6 +124,21 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
return WriteModification.builder().build();
}
+ // Check for conflicts with @DynamoDbAutoGeneratedKey
+ Collection keyTaggedAttributes = context.tableMetadata()
+ .customMetadataObject(KEY_EXTENSION_METADATA_KEY, Collection.class)
+ .orElse(Collections.emptyList());
+
+ customMetadataObject.stream()
+ .filter(keyTaggedAttributes::contains)
+ .findFirst()
+ .ifPresent(attribute -> {
+ throw new IllegalArgumentException(
+ "Attribute '" + attribute + "' cannot have both @DynamoDbAutoGeneratedKey and "
+ + "@DynamoDbAutoGeneratedUuid annotations. These annotations have conflicting behaviors "
+ + "and cannot be used together on the same attribute.");
+ });
+
Map itemToTransform = new HashMap<>(context.items());
customMetadataObject.forEach(key -> insertUuidInItemToTransform(itemToTransform, key));
return WriteModification.builder()
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
new file mode 100644
index 000000000000..3fe249b5eb0f
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.extensions.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import software.amazon.awssdk.annotations.SdkPublicApi;
+import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.AutoGeneratedKeyTag;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.BeanTableSchemaAttributeTag;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
+
+/**
+ * Annotation that marks a key attribute to be automatically populated with a random UUID if no value is provided during a write
+ * operation (put or update). This annotation is intended to work specifically with key attributes.
+ *
+ *
This annotation is designed for use with the V2 {@link software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema}.
+ * It is registered via {@link BeanTableSchemaAttributeTag} and its behavior is implemented by
+ * {@link software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension}.
+ *
+ *
Where this annotation can be applied
+ * This annotation is only valid on attributes that serve as keys:
+ *
+ *
The table's primary partition key or sort key
+ *
The partition key or sort key of a secondary index (GSI or LSI)
+ *
+ * If applied to any other attribute, the {@code AutoGeneratedKeyExtension} will throw an
+ * {@link IllegalArgumentException} at runtime.
+ *
+ *
How values are generated
+ *
+ *
On writes where the annotated attribute is null or empty, a new UUID value is generated
+ * using {@link java.util.UUID#randomUUID()}.
+ *
If a value is already set on the attribute, that value is preserved and not replaced.
+ *
This behavior differs from {@code @DynamoDbAutoGeneratedUuid}, which always generates new UUIDs regardless of existing
+ * values.
+ *
+ *
+ *
Behavior with UpdateBehavior
+ *
Primary Keys: {@link DynamoDbUpdateBehavior} has no effect on primary partition keys
+ * or primary sort keys. Primary keys are required for UpdateItem operations in DynamoDB and cannot be conditionally
+ * updated. UUIDs will be generated whenever the primary key attribute is missing or empty, regardless of any
+ * {@code UpdateBehavior} setting.
+ *
+ *
Secondary Index Keys: For GSI/LSI keys, {@link DynamoDbUpdateBehavior} can be used:
+ *
+ *
{@link UpdateBehavior#WRITE_ALWAYS} (default) – Generate a new UUID whenever the attribute is missing during write.
+ *
{@link UpdateBehavior#WRITE_IF_NOT_EXISTS} – Generate a UUID only on the first write, preserving the value on
+ * subsequent updates.
+ *
+ *
+ *
Type restriction
+ * This annotation is only valid on attributes of type {@link String}.
+ */
+@SdkPublicApi
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target( {ElementType.METHOD, ElementType.FIELD})
+@BeanTableSchemaAttributeTag(AutoGeneratedKeyTag.class)
+public @interface DynamoDbAutoGeneratedKey {
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedKeyTag.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedKeyTag.java
new file mode 100644
index 000000000000..815e15bccd7a
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedKeyTag.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.internal.extensions;
+
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
+
+@SdkInternalApi
+public final class AutoGeneratedKeyTag {
+
+ private AutoGeneratedKeyTag() {
+ }
+
+ public static StaticAttributeTag attributeTagFor(DynamoDbAutoGeneratedKey annotation) {
+ return AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute();
+ }
+
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/UuidTestUtils.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/UuidTestUtils.java
new file mode 100644
index 000000000000..6b4314bc5668
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/UuidTestUtils.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb;
+
+import java.util.UUID;
+
+public final class UuidTestUtils {
+
+ private UuidTestUtils() {
+ }
+
+ public static boolean isValidUuid(String uuid) {
+ try {
+ UUID.fromString(uuid);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
new file mode 100644
index 000000000000..ee053f1a16fe
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.extensions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
+import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute;
+import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.enhanced.dynamodb.OperationContext;
+import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
+import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
+import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext;
+import software.amazon.awssdk.enhanced.dynamodb.internal.operations.OperationName;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+
+public class AutoGeneratedKeyExtensionTest {
+
+ private static final String RECORD_ID = "id123";
+ private static final String TABLE_NAME = "table-name";
+
+ private static final OperationContext PRIMARY_CONTEXT =
+ DefaultOperationContext.create(TABLE_NAME, TableMetadata.primaryIndexName());
+
+ private final AutoGeneratedKeyExtension extension = AutoGeneratedKeyExtension.builder().build();
+
+ /**
+ * Schema that places @DynamoDbAutoGeneratedKey on GSI key ("keyAttribute") so the validation passes.
+ */
+ private static final StaticTableSchema ITEM_WITH_KEY_SCHEMA =
+ StaticTableSchema.builder(ItemWithKey.class)
+ .newItemSupplier(ItemWithKey::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithKey::getId)
+ .setter(ItemWithKey::setId)
+ .addTag(primaryPartitionKey()) // PK
+ .addTag(autoGeneratedKeyAttribute()))
+ .addAttribute(String.class, a -> a.name("keyAttribute")
+ .getter(ItemWithKey::getKeyAttribute)
+ .setter(ItemWithKey::setKeyAttribute)
+ .tags(
+ secondaryPartitionKey("gsi_keys_only"), // GSI
+ autoGeneratedKeyAttribute()))
+ .addAttribute(String.class, a -> a.name("simpleString")
+ .getter(ItemWithKey::getSimpleString)
+ .setter(ItemWithKey::setSimpleString))
+ .build();
+
+ /**
+ * Schema that places @DynamoDbAutoGeneratedKey on a NON-KEY attribute to trigger the exception.
+ */
+ private static final StaticTableSchema INVALID_NONKEY_AUTOGEN_SCHEMA =
+ StaticTableSchema.builder(ItemWithKey.class)
+ .newItemSupplier(ItemWithKey::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithKey::getId)
+ .setter(ItemWithKey::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(String.class, a -> a.name("keyAttribute")
+ .getter(ItemWithKey::getKeyAttribute)
+ .setter(ItemWithKey::setKeyAttribute)
+ // No index tags here — autogen on non-key fails at beforeWrite()
+ .addTag(autoGeneratedKeyAttribute()))
+ .addAttribute(String.class, a -> a.name("simpleString")
+ .getter(ItemWithKey::getSimpleString)
+ .setter(ItemWithKey::setSimpleString))
+ .build();
+
+ /**
+ * Schema that places @DynamoDbAutoGeneratedKey on LSI key ("simpleString") so the validation passes.
+ */
+ private static final StaticTableSchema LSI_SK_AUTOGEN_SCHEMA =
+ StaticTableSchema.builder(ItemWithKey.class)
+ .newItemSupplier(ItemWithKey::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithKey::getId)
+ .setter(ItemWithKey::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(String.class, a -> a.name("keyAttribute")
+ .getter(ItemWithKey::getKeyAttribute)
+ .setter(ItemWithKey::setKeyAttribute))
+ .addAttribute(String.class, a -> a.name("simpleString")
+ .getter(ItemWithKey::getSimpleString)
+ .setter(ItemWithKey::setSimpleString)
+ .tags(
+ secondarySortKey("lsi1"), // LSI
+ autoGeneratedKeyAttribute()))
+ .build();
+
+ @Test
+ public void updateItem_withExistingKey_preservesValueAndDoesNotGenerateNew() {
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID);
+ String preset = UUID.randomUUID().toString();
+ item.setKeyAttribute(preset);
+
+ Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
+ assertThat(items).hasSize(2);
+
+ WriteModification result =
+ extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ Map transformed = result.transformedItem();
+ assertThat(transformed).isNotNull().hasSize(2);
+ assertThat(transformed).containsEntry("id", AttributeValue.fromS(RECORD_ID));
+
+ // Ensures the attribute remains a valid UUID without altering the preset value
+ assertThat(isValidUuid(transformed.get("keyAttribute").s())).isTrue();
+ assertThat(result.updateExpression()).isNull();
+ }
+
+ @Test
+ public void updateItem_withoutKey_generatesNewUuid() {
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID);
+
+ Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
+ assertThat(items).hasSize(1);
+
+ WriteModification result =
+ extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ Map transformed = result.transformedItem();
+ assertThat(transformed).isNotNull().hasSize(2);
+ assertThat(transformed).containsEntry("id", AttributeValue.fromS(RECORD_ID));
+ assertThat(isValidUuid(transformed.get("keyAttribute").s())).isTrue();
+ assertThat(result.updateExpression()).isNull();
+ }
+
+ @Test
+ public void updateItem_withMissingKeyAttribute_insertsGeneratedUuid() {
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID);
+
+ Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
+ assertThat(items).hasSize(1);
+
+ WriteModification result =
+ extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ assertThat(result.transformedItem()).isNotNull();
+ assertThat(result.updateExpression()).isNull();
+ assertThat(result.transformedItem()).hasSize(2);
+ assertThat(isValidUuid(result.transformedItem().get("keyAttribute").s())).isTrue();
+ }
+
+ @Test
+ public void nonStringTypeAnnotatedWithAutoGeneratedKey_throwsIllegalArgumentException() {
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() ->
+ StaticTableSchema.builder(ItemWithKey.class)
+ .newItemSupplier(ItemWithKey::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithKey::getId)
+ .setter(ItemWithKey::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(Integer.class, a -> a.name("intAttribute")
+ .getter(ItemWithKey::getIntAttribute)
+ .setter(ItemWithKey::setIntAttribute)
+ .addTag(autoGeneratedKeyAttribute()))
+ .addAttribute(String.class, a -> a.name("simpleString")
+ .getter(ItemWithKey::getSimpleString)
+ .setter(ItemWithKey::setSimpleString))
+ .build()
+ )
+ .withMessage("Attribute 'intAttribute' of Class type class java.lang.Integer is not a suitable Java Class type "
+ + "to be used as a Auto Generated Key attribute. Only String Class type is supported.");
+ }
+
+ @Test
+ public void autoGeneratedKey_onSecondaryPartitionKey_generatesUuid() {
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID); // keyAttribute (GSI PK) is missing → should be generated
+
+ Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
+
+ WriteModification result =
+ extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ Map transformed = result.transformedItem();
+ assertThat(transformed).isNotNull();
+ assertThat(isValidUuid(transformed.get("keyAttribute").s())).isTrue(); // generated for GSI PK
+ }
+
+ @Test
+ public void autoGeneratedKey_onSecondarySortKey_generatesUuid() {
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID); // simpleString (GSI/LSI) is missing → should be generated
+
+ Map items = LSI_SK_AUTOGEN_SCHEMA.itemToMap(item, true);
+
+ WriteModification result =
+ extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(LSI_SK_AUTOGEN_SCHEMA.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ Map transformed = result.transformedItem();
+ assertThat(transformed).isNotNull();
+ assertThat(isValidUuid(transformed.get("simpleString").s())).isTrue(); // generated for index SK
+ }
+
+ @Test
+ public void autoGeneratedKey_onNonKey_throwsIllegalArgumentException() {
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID); // keyAttribute is annotated but NOT a key in this schema → should fail at beforeWrite()
+
+ Map items = INVALID_NONKEY_AUTOGEN_SCHEMA.itemToMap(item, true);
+
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() ->
+ extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(INVALID_NONKEY_AUTOGEN_SCHEMA.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build())
+ )
+ .withMessageContaining("@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
+ + "primary partition key, primary sort key, or GSI/LSI partition/sort keys.")
+ .withMessageContaining("keyAttribute");
+ }
+
+ @Test
+ public void conflictingAnnotations_throwsIllegalArgumentException() {
+ // Create a schema with both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid on the same attribute
+ StaticTableSchema conflictingSchema =
+ StaticTableSchema
+ .builder(ItemWithKey.class)
+ .newItemSupplier(ItemWithKey::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithKey::getId)
+ .setter(ItemWithKey::setId)
+ .addTag(primaryPartitionKey())
+ // Both annotations on the same attribute
+ .addTag(autoGeneratedKeyAttribute())
+ .addTag(autoGeneratedUuidAttribute()))
+ .addAttribute(String.class, a -> a.name("simpleString")
+ .getter(ItemWithKey::getSimpleString)
+ .setter(ItemWithKey::setSimpleString))
+ .build();
+
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID);
+
+ Map items = conflictingSchema.itemToMap(item, true);
+
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() ->
+ extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(conflictingSchema.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build())
+ )
+ .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ }
+
+ @Test
+ public void conflictingAnnotations_onSecondaryKey_throwsIllegalArgumentException() {
+ // Create a schema with both annotations on a GSI key
+ StaticTableSchema conflictingGsiSchema =
+ StaticTableSchema
+ .builder(ItemWithKey.class)
+ .newItemSupplier(ItemWithKey::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithKey::getId)
+ .setter(ItemWithKey::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(String.class, a -> a.name("keyAttribute")
+ .getter(ItemWithKey::getKeyAttribute)
+ .setter(ItemWithKey::setKeyAttribute)
+ .addTag(secondaryPartitionKey("gsi1"))
+ // Both annotations on the same GSI key
+ .addTag(autoGeneratedKeyAttribute())
+ .addTag(autoGeneratedUuidAttribute()))
+ .addAttribute(String.class, a -> a.name("simpleString")
+ .getter(ItemWithKey::getSimpleString)
+ .setter(ItemWithKey::setSimpleString))
+ .build();
+
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID);
+
+ Map items = conflictingGsiSchema.itemToMap(item, true);
+
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() ->
+ extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(conflictingGsiSchema.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build()))
+ .withMessage("Attribute 'keyAttribute' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
+ + "annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ }
+
+ @Test
+ public void conflictDetection_worksRegardlessOfExtensionOrder() {
+ // Verify that AutoGeneratedKeyExtension detects conflicts even when
+ // AutoGeneratedUuidExtension metadata is already present
+ StaticTableSchema conflictingSchema =
+ StaticTableSchema
+ .builder(ItemWithKey.class)
+ .newItemSupplier(ItemWithKey::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithKey::getId)
+ .setter(ItemWithKey::setId)
+ .addTag(primaryPartitionKey())
+ .addTag(autoGeneratedKeyAttribute())
+ .addTag(autoGeneratedUuidAttribute()))
+ .build();
+
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID);
+
+ Map items = conflictingSchema.itemToMap(item, true);
+
+ // Test that the conflict is detected regardless of which extension runs first
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() ->
+ extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(conflictingSchema.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build()))
+ .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
+ + "annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ }
+
+ private static class ItemWithKey {
+
+ private String id;
+ private String keyAttribute;
+ private String simpleString;
+ private Integer intAttribute;
+
+ ItemWithKey() {
+ }
+
+ public Integer getIntAttribute() {
+ return intAttribute;
+ }
+
+ public void setIntAttribute(Integer intAttribute) {
+ this.intAttribute = intAttribute;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getKeyAttribute() {
+ return keyAttribute;
+ }
+
+ public void setKeyAttribute(String keyAttribute) {
+ this.keyAttribute = keyAttribute;
+ }
+
+ public String getSimpleString() {
+ return simpleString;
+ }
+
+ public void setSimpleString(String simpleString) {
+ this.simpleString = simpleString;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ItemWithKey)) {
+ return false;
+ }
+ ItemWithKey that = (ItemWithKey) o;
+ return Objects.equals(id, that.id)
+ && Objects.equals(keyAttribute, that.keyAttribute)
+ && Objects.equals(simpleString, that.simpleString);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, keyAttribute, simpleString);
+ }
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
index cc69f503d50f..cd92fa5fb063 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
@@ -156,6 +156,45 @@ void IllegalArgumentException_for_AutogeneratedUuid_withNonStringType() {
+ " to be used as a Auto Generated Uuid attribute. Only String Class type is supported.");
}
+ @Test
+ public void conflictingAnnotations_throwsIllegalArgumentException() {
+ // Create a schema with both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid on the same attribute
+ StaticTableSchema conflictingSchema =
+ StaticTableSchema.builder(ItemWithUuid.class)
+ .newItemSupplier(ItemWithUuid::new)
+ .addAttribute(String.class,
+ a -> a.name("id")
+ .getter(ItemWithUuid::getId)
+ .setter(ItemWithUuid::setId)
+ .addTag(primaryPartitionKey())
+ // Both annotations on the same attribute
+ .addTag(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute())
+ .addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute()))
+ .addAttribute(String.class,
+ a -> a.name("simpleString")
+ .getter(ItemWithUuid::getSimpleString)
+ .setter(ItemWithUuid::setSimpleString))
+ .build();
+
+ ItemWithUuid item = new ItemWithUuid();
+ item.setId(RECORD_ID);
+
+ Map items = conflictingSchema.itemToMap(item, true);
+
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() ->
+ atomicCounterExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(conflictingSchema.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build())
+ )
+ .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ }
+
public static boolean isValidUuid(String uuid) {
return UUID_PATTERN.matcher(uuid).matches();
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
new file mode 100644
index 000000000000..c2ed794ee310
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.extensions;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute;
+import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
+
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.enhanced.dynamodb.OperationContext;
+import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
+import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
+import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext;
+import software.amazon.awssdk.enhanced.dynamodb.internal.operations.OperationName;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+
+/**
+ * Tests to verify that conflicting annotations (@DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid) are properly detected
+ * and throw exceptions regardless of extension load order.
+ */
+public class ConflictingAnnotationsTest {
+
+ private static final String RECORD_ID = "test-id";
+ private static final String TABLE_NAME = "test-table";
+ private static final OperationContext PRIMARY_CONTEXT =
+ DefaultOperationContext.create(TABLE_NAME, TableMetadata.primaryIndexName());
+
+ private final AutoGeneratedKeyExtension keyExtension = AutoGeneratedKeyExtension.builder().build();
+ private final AutoGeneratedUuidExtension uuidExtension = AutoGeneratedUuidExtension.create();
+
+ /**
+ * Schema with both annotations on the same attribute to test conflict detection.
+ */
+ private static final StaticTableSchema CONFLICTING_SCHEMA =
+ StaticTableSchema.builder(TestItem.class)
+ .newItemSupplier(TestItem::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(TestItem::getId)
+ .setter(TestItem::setId)
+ .addTag(primaryPartitionKey())
+ // Both annotations on the same attribute
+ .addTag(autoGeneratedKeyAttribute())
+ .addTag(autoGeneratedUuidAttribute()))
+ .build();
+
+ @Test
+ public void keyExtensionFirst_detectsConflictWithUuidExtension() {
+ TestItem item = new TestItem();
+ item.setId(RECORD_ID);
+
+ Map items = CONFLICTING_SCHEMA.itemToMap(item, true);
+
+ // AutoGeneratedKeyExtension runs first and detects conflict
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> keyExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(CONFLICTING_SCHEMA.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build())
+ )
+ .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ }
+
+ @Test
+ public void uuidExtensionFirst_detectsConflictWithKeyExtension() {
+ TestItem item = new TestItem();
+ item.setId(RECORD_ID);
+
+ Map items = CONFLICTING_SCHEMA.itemToMap(item, true);
+
+ // AutoGeneratedUuidExtension runs first and detects conflict
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> uuidExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(CONFLICTING_SCHEMA.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build())
+ )
+ .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ }
+
+ @Test
+ public void separateAttributes_noConflict() {
+ // Schema with annotations on different attributes - should work fine
+ StaticTableSchema separateSchema =
+ StaticTableSchema.builder(TestItemSeparate.class)
+ .newItemSupplier(TestItemSeparate::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(TestItemSeparate::getId)
+ .setter(TestItemSeparate::setId)
+ .addTag(primaryPartitionKey())
+ .addTag(autoGeneratedKeyAttribute()))
+ .addAttribute(String.class, a -> a.name("uuidField")
+ .getter(TestItemSeparate::getUuidField)
+ .setter(TestItemSeparate::setUuidField)
+ .addTag(autoGeneratedUuidAttribute()))
+ .build();
+
+ TestItemSeparate item = new TestItemSeparate();
+
+ Map items = separateSchema.itemToMap(item, true);
+
+ // Both extensions should work without conflict when on different attributes
+ keyExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(separateSchema.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ uuidExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(separateSchema.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+ }
+
+ public static class TestItem {
+ private String id;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+ }
+
+ public static class TestItemSeparate {
+ private String id;
+ private String uuidField;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getUuidField() {
+ return uuidField;
+ }
+
+ public void setUuidField(String uuidField) {
+ this.uuidField = uuidField;
+ }
+ }
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
new file mode 100644
index 000000000000..7416fd92e5d2
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.functionaltests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.AutoGeneratedUuidRecordTest.assertValidUuid;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.updateBehavior;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.assertj.core.api.Assertions;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
+import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
+
+/**
+ * Tests for @DynamoDbAutoGeneratedKey annotation functionality.
+ *
+ * Tests cover: - Basic UUID generation on all 4 key types (primary PK/SK, GSI PK/SK) - UpdateBehavior control (WRITE_ALWAYS vs
+ * WRITE_IF_NOT_EXISTS) for secondary index keys - Primary key limitations (UpdateBehavior has no effect) - Error handling for
+ * invalid usage - Integration with other extensions (VersionedRecord)
+ */
+@RunWith(Parameterized.class)
+public class AutoGeneratedKeyRecordTest extends LocalDynamoDbSyncTestBase {
+
+ private final DynamoDbTable mappedTable;
+
+ public AutoGeneratedKeyRecordTest(String testName, TableSchema schema) {
+ this.mappedTable = DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(AutoGeneratedKeyExtension.builder().build())
+ .build()
+ .table(getConcreteTableName("AutoGenKey-table"), schema);
+ }
+
+ @Parameters(name = "{index}: {0}")
+ public static Collection
*
*
Secondary Index Keys: For GSI/LSI keys, {@link DynamoDbUpdateBehavior} can be used:
*
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
index 7416fd92e5d2..30ceeaa952bf 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
@@ -16,6 +16,8 @@
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
import org.assertj.core.api.Assertions;
import org.junit.After;
import org.junit.Before;
@@ -40,6 +42,8 @@
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
+import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
/**
* Tests for @DynamoDbAutoGeneratedKey annotation functionality.
@@ -255,6 +259,119 @@ public void conflictingAnnotations_throwsException() {
}
+ // ========== Transactional Write Operations Tests ==========
+ @Test
+ public void transactWrite_generatesKeysForMultipleOperations() {
+ TestRecord putRecord = new TestRecord();
+ putRecord.setPayload("transact-put");
+ TestRecord updateRecord = new TestRecord();
+ updateRecord.setPayload("transact-update");
+
+ DynamoDbEnhancedClient client = createClient();
+ client.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, putRecord)
+ .addUpdateItem(mappedTable, updateRecord)
+ .build());
+
+ List allRecords = mappedTable.scan().items().stream().collect(Collectors.toList());
+ assertThat(allRecords).hasSize(2);
+ allRecords.forEach(this::assertAllKeysGenerated);
+ }
+
+ @Test
+ public void transactWrite_preservesExistingKeysAndGeneratesMissing() {
+ TestRecord record = new TestRecord();
+ record.setId("existing-id");
+ record.setGsiPk("existing-gsi-pk");
+ record.setPayload("transact-preserve");
+
+ DynamoDbEnhancedClient client = createClient();
+ client.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+
+ TestRecord retrieved = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertThat(retrieved.getId()).isEqualTo("existing-id");
+ assertThat(retrieved.getGsiPk()).isEqualTo("existing-gsi-pk");
+ assertValidUuid(retrieved.getSortKey());
+ assertValidUuid(retrieved.getGsiSk());
+ }
+
+
+ // ========== Batch Write Operations Tests ==========
+ @Test
+ public void batchWrite_generatesAllMissingKeys() {
+ TestRecord record1 = new TestRecord();
+ record1.setPayload("batch1");
+ TestRecord record2 = new TestRecord();
+ record2.setPayload("batch2");
+
+ DynamoDbEnhancedClient client = createClient();
+ client.batchWriteItem(r -> r.addWriteBatch(
+ WriteBatch.builder(TestRecord.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record1)
+ .addPutItem(record2)
+ .build()));
+
+ List allRecords = mappedTable.scan().items().stream().collect(Collectors.toList());
+ assertThat(allRecords).hasSize(2);
+ allRecords.forEach(this::assertAllKeysGenerated);
+ }
+
+ @Test
+ public void batchWrite_preservesExistingKeysAndGeneratesMissing() {
+ TestRecord record = new TestRecord();
+ record.setId("existing-id");
+ record.setSortKey("existing-sort");
+ record.setPayload("batch-preserve");
+
+ DynamoDbEnhancedClient client = createClient();
+ client.batchWriteItem(r -> r.addWriteBatch(
+ WriteBatch.builder(TestRecord.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record)
+ .build()));
+
+ TestRecord retrieved = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertThat(retrieved.getId()).isEqualTo("existing-id");
+ assertThat(retrieved.getSortKey()).isEqualTo("existing-sort");
+ assertValidUuid(retrieved.getGsiPk());
+ assertValidUuid(retrieved.getGsiSk());
+ }
+
+ @Test
+ public void batchWrite_mixedPutDeleteOperations() {
+ TestRecord existingRecord = new TestRecord();
+ existingRecord.setId("to-delete");
+ existingRecord.setSortKey("to-delete-sort");
+ mappedTable.putItem(existingRecord);
+
+ TestRecord newRecord = new TestRecord();
+ newRecord.setPayload("batch-mixed");
+
+ DynamoDbEnhancedClient client = createClient();
+ client.batchWriteItem(r -> r.addWriteBatch(
+ WriteBatch.builder(TestRecord.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(newRecord)
+ .addDeleteItem(b -> b.key(k -> k.partitionValue("to-delete").sortValue("to-delete-sort")))
+ .build()));
+
+ List allRecords = mappedTable.scan().items().stream().collect(Collectors.toList());
+ assertThat(allRecords).hasSize(1);
+
+ TestRecord remaining = allRecords.get(0);
+ assertAllKeysGenerated(remaining);
+ assertThat(remaining.getPayload()).isEqualTo("batch-mixed");
+ }
+
private DynamoDbEnhancedClient createClient() {
return DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
@@ -266,6 +383,13 @@ private void deleteTableByName(String tableName) {
getDynamoDbClient().deleteTable(b -> b.tableName(tableName));
}
+ private void assertAllKeysGenerated(TestRecord record) {
+ assertValidUuid(record.getId());
+ assertValidUuid(record.getSortKey());
+ assertValidUuid(record.getGsiPk());
+ assertValidUuid(record.getGsiSk());
+ }
+
@DynamoDbBean
public static class TestRecord {
private String id;
From 031ef4476fa389bfcb5719b24a58381e975c5148 Mon Sep 17 00:00:00 2001
From: Ana Satirbasa
Date: Sun, 16 Nov 2025 20:34:32 +0200
Subject: [PATCH 03/17] Added support for DynamoDbAutoGeneratedKey annotation
---
.../extensions/annotations/DynamoDbAutoGeneratedKey.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
index 7a4c7ce90df5..a7021bb9f989 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
@@ -71,7 +71,7 @@
@SdkPublicApi
@Documented
@Retention(RetentionPolicy.RUNTIME)
-@Target( {ElementType.METHOD, ElementType.FIELD})
+@Target({ElementType.METHOD, ElementType.FIELD})
@BeanTableSchemaAttributeTag(AutoGeneratedKeyTag.class)
public @interface DynamoDbAutoGeneratedKey {
}
\ No newline at end of file
From 8c4826dcfc99a670d0795dff649939f30694fac4 Mon Sep 17 00:00:00 2001
From: Ana Satirbasa
Date: Thu, 15 Jan 2026 10:57:10 +0200
Subject: [PATCH 04/17] Increased unit and integration test coverage to 100%
---
.../AutoGeneratedKeyIntegrationTest.java | 359 ++++++++++++++++++
.../AutoGeneratedUuidIntegrationTest.java | 245 ++++++++++++
.../enhanced/dynamodb/model/BeanRecord.java | 79 ++++
.../AutogeneratedKeyConflictingRecord.java | 37 ++
.../AutogeneratedKeyInvalidTypeRecord.java | 35 ++
...togeneratedKeyOnNonKeyAttributeRecord.java | 44 +++
.../AutogeneratedKeyRecord.java | 75 ++++
.../AutogeneratedKeyVersionedRecord.java | 55 +++
.../AutogeneratedUuidInvalidTypeRecord.java | 35 ++
.../AutogeneratedUuidRecord.java | 47 +++
.../extensions/AutoGeneratedKeyExtension.java | 2 +-
.../AutoGeneratedKeyExtensionTest.java | 26 ++
.../AutoGeneratedUuidExtensionTest.java | 26 ++
.../VersionedRecordExtensionTest.java | 106 +++++-
14 files changed, 1169 insertions(+), 2 deletions(-)
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedKeyIntegrationTest.java
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedUuidIntegrationTest.java
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/BeanRecord.java
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyConflictingRecord.java
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyInvalidTypeRecord.java
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyOnNonKeyAttributeRecord.java
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyRecord.java
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyVersionedRecord.java
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidInvalidTypeRecord.java
create mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidRecord.java
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedKeyIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedKeyIntegrationTest.java
new file mode 100644
index 000000000000..56834bc840c5
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedKeyIntegrationTest.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension;
+import software.amazon.awssdk.enhanced.dynamodb.model.BeanRecord;
+import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
+import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
+import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyConflictingRecord;
+import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyInvalidTypeRecord;
+import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyOnNonKeyAttributeRecord;
+import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyRecord;
+import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyVersionedRecord;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+
+public class AutoGeneratedKeyIntegrationTest extends DynamoDbEnhancedIntegrationTestBase {
+
+ private static final String TABLE_NAME = createTestTableName();
+
+ private static DynamoDbClient dynamoDbClient;
+ private static DynamoDbEnhancedClient enhancedClient;
+ private static DynamoDbTable mappedTable;
+
+ @BeforeClass
+ public static void beforeClass() {
+ dynamoDbClient = createDynamoDbClient();
+ enhancedClient = DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(dynamoDbClient)
+ .extensions(AutoGeneratedKeyExtension.builder().build())
+ .build();
+ mappedTable = enhancedClient.table(TABLE_NAME, TableSchema.fromClass(AutogeneratedKeyRecord.class));
+ mappedTable.createTable();
+ dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME));
+ }
+
+ @After
+ public void tearDown() {
+ mappedTable.scan()
+ .items()
+ .forEach(record -> mappedTable.deleteItem(record));
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ try {
+ dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME));
+ } finally {
+ dynamoDbClient.close();
+ }
+ }
+
+ @Test
+ public void putItem_whenKeysNotPopulated_generatesNewUuids() {
+ AutogeneratedKeyRecord record = new AutogeneratedKeyRecord();
+
+ mappedTable.putItem(record);
+ AutogeneratedKeyRecord result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ isValidUuid(result.getGsiPk());
+ isValidUuid(result.getGsiSk());
+ }
+
+ @Test
+ public void putItem_whenKeysAlreadyPopulated_preservesExistingUuids() {
+ AutogeneratedKeyRecord record = new AutogeneratedKeyRecord();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+ record.setGsiPk("existing-gsiPk");
+ record.setGsiSk("existing-gsiSk");
+
+ mappedTable.putItem(record);
+ AutogeneratedKeyRecord result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertThat(result.getId()).isEqualTo("existing-id");
+ assertThat(result.getSortKey()).isEqualTo("existing-sk");
+ assertThat(result.getGsiPk()).isEqualTo("existing-gsiPk");
+ assertThat(result.getGsiSk()).isEqualTo("existing-gsiSk");
+ }
+
+ @Test
+ public void updateItem_respectsUpdateBehavior() {
+ // put initial item
+ AutogeneratedKeyRecord record = new AutogeneratedKeyRecord();
+ mappedTable.putItem(record);
+ AutogeneratedKeyRecord afterPut = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+ String originalPk = afterPut.getId();
+ String originalSk = afterPut.getSortKey();
+ String originalGsiPk = afterPut.getGsiPk();
+ String originalGsiSk = afterPut.getGsiSk();
+
+
+ // update item
+ AutogeneratedKeyRecord update = new AutogeneratedKeyRecord();
+ update.setId(afterPut.getId());
+ update.setSortKey(afterPut.getSortKey());
+
+ mappedTable.updateItem(update);
+ AutogeneratedKeyRecord afterUpdate =
+ mappedTable.getItem(r -> r.key(k -> k.partitionValue(afterPut.getId()).sortValue(afterPut.getSortKey())));
+
+ // id and sortKey preserve original values as DynamoDbUpdateBehavior has no effect on primary partition keys or sort keys
+ assertThat(afterUpdate.getId()).isEqualTo(originalPk);
+ assertThat(afterUpdate.getSortKey()).isEqualTo(originalSk);
+
+ // gsiPk has WRITE_ALWAYS: regenerates UUID on every update
+ isValidUuid(afterUpdate.getGsiPk());
+ assertThat(afterUpdate.getGsiPk()).isNotEqualTo(originalGsiPk);
+
+ // gsiSk has WRITE_IF_NOT_EXISTS: preserves original UUID, only writes if null
+ assertThat(afterUpdate.getGsiSk()).isEqualTo(originalGsiSk);
+ }
+
+ @Test
+ public void batchWrite_whenKeysNotPopulated_generatesNewUuids() {
+ AutogeneratedKeyRecord record1 = new AutogeneratedKeyRecord();
+ AutogeneratedKeyRecord record2 = new AutogeneratedKeyRecord();
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(
+ WriteBatch.builder(AutogeneratedKeyRecord.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record1)
+ .addPutItem(record2)
+ .build()));
+ List results = mappedTable.scan().items().stream().collect(Collectors.toList());
+
+ assertThat(results.size()).isEqualTo(2);
+ isValidUuid(results.get(0).getId());
+ isValidUuid(results.get(1).getId());
+ isValidUuid(results.get(0).getSortKey());
+ isValidUuid(results.get(1).getSortKey());
+ }
+
+ @Test
+ public void batchWrite_whenKeysAlreadyPopulated_preservesExistingUuids() {
+ AutogeneratedKeyRecord record1 = new AutogeneratedKeyRecord();
+ record1.setId("existing-id-1");
+ record1.setSortKey("existing-sk-1");
+ record1.setGsiPk("existing-gsiPk-1");
+ record1.setGsiSk("existing-gsiSk-1");
+
+ AutogeneratedKeyRecord record2 = new AutogeneratedKeyRecord();
+ record2.setId("existing-id-2");
+ record2.setSortKey("existing-sk-2");
+ record2.setGsiPk("existing-gsiPk-2");
+ record2.setGsiSk("existing-gsiSk-2");
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(
+ WriteBatch.builder(AutogeneratedKeyRecord.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record1)
+ .addPutItem(record2)
+ .build()));
+
+ AutogeneratedKeyRecord savedRecord1 =
+ mappedTable.getItem(r -> r.key(k -> k.partitionValue("existing-id-1").sortValue("existing-sk-1")));
+ assertThat(savedRecord1.getId()).isEqualTo("existing-id-1");
+ assertThat(savedRecord1.getSortKey()).isEqualTo("existing-sk-1");
+ assertThat(savedRecord1.getGsiPk()).isEqualTo("existing-gsiPk-1");
+ assertThat(savedRecord1.getGsiSk()).isEqualTo("existing-gsiSk-1");
+
+ AutogeneratedKeyRecord savedRecord2 =
+ mappedTable.getItem(r -> r.key(k -> k.partitionValue("existing-id-2").sortValue("existing-sk-2")));
+ assertThat(savedRecord2.getId()).isEqualTo("existing-id-2");
+ assertThat(savedRecord2.getSortKey()).isEqualTo("existing-sk-2");
+ assertThat(savedRecord2.getGsiPk()).isEqualTo("existing-gsiPk-2");
+ assertThat(savedRecord2.getGsiSk()).isEqualTo("existing-gsiSk-2");
+ }
+
+ @Test
+ public void transactWrite_whenKeysNotPopulated_generatesNewUuids() {
+ AutogeneratedKeyRecord record = new AutogeneratedKeyRecord();
+
+ enhancedClient.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+
+ AutogeneratedKeyRecord result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ isValidUuid(result.getGsiPk());
+ isValidUuid(result.getGsiSk());
+ }
+
+ @Test
+ public void transactWrite_whenKeysAlreadyPopulated_preservesExistingUuids() {
+ AutogeneratedKeyRecord record = new AutogeneratedKeyRecord();
+ record.setId("existing-id-1");
+ record.setSortKey("existing-sk-1");
+ record.setGsiPk("existing-gsiPk-1");
+ record.setGsiSk("existing-gsiSk-1");
+
+ enhancedClient.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+
+ AutogeneratedKeyRecord result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertThat(result.getId()).isEqualTo("existing-id-1");
+ assertThat(result.getSortKey()).isEqualTo("existing-sk-1");
+ assertThat(result.getGsiPk()).isEqualTo("existing-gsiPk-1");
+ assertThat(result.getGsiSk()).isEqualTo("existing-gsiSk-1");
+ }
+
+ @Test
+ public void putItem_onVersionedRecord_worksWithAutoGeneratedKey() {
+ String tableName = createTestTableName();
+
+ DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(dynamoDbClient)
+ .extensions(AutoGeneratedKeyExtension.builder().build(),
+ VersionedRecordExtension.builder().build())
+ .build();
+ DynamoDbTable mappedTable =
+ enhancedClient.table(tableName, TableSchema.fromClass(AutogeneratedKeyVersionedRecord.class));
+
+ try {
+ mappedTable.createTable();
+ dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+
+ AutogeneratedKeyVersionedRecord record = new AutogeneratedKeyVersionedRecord();
+ record.setData("data-v1");
+ mappedTable.putItem(record);
+
+ AutogeneratedKeyVersionedRecord retrieved =
+ mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+ isValidUuid(retrieved.getId());
+ assertThat(retrieved.getData()).isEqualTo("data-v1");
+ assertThat(retrieved.getVersion()).isEqualTo(1L);
+
+ retrieved.setData("data-v2");
+ AutogeneratedKeyVersionedRecord updated = mappedTable.updateItem(retrieved);
+ isValidUuid(updated.getId());
+ assertThat(updated.getData()).isEqualTo("data-v2");
+ assertThat(updated.getVersion()).isEqualTo(2L);
+
+ } finally {
+ try {
+ mappedTable.deleteTable();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void putItem_whenAnnotationInConflictWithAutogeneratedUuidAnnotation_throwsException() {
+ String tableName = createTestTableName();
+ DynamoDbTable mappedTable =
+ enhancedClient.table(tableName, TableSchema.fromClass(AutogeneratedKeyConflictingRecord.class));
+
+ try {
+ mappedTable.createTable();
+ dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+
+ assertThatThrownBy(() -> mappedTable.putItem(new AutogeneratedKeyConflictingRecord()))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid");
+ } finally {
+ try {
+ mappedTable.deleteTable();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void putItem_whenAnnotationUsedOnNonKeyAttribute_throwsException() {
+ String tableName = createTestTableName();
+ DynamoDbTable mappedTable =
+ enhancedClient.table(tableName, TableSchema.fromClass(AutogeneratedKeyOnNonKeyAttributeRecord.class));
+
+ try {
+ mappedTable.createTable();
+ dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+
+ assertThatThrownBy(() -> mappedTable.putItem(new AutogeneratedKeyOnNonKeyAttributeRecord()))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("@DynamoDbAutoGeneratedKey can only be applied to key attributes");
+ } finally {
+ try {
+ mappedTable.deleteTable();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void putItem_whenNoAutogeneratedKeyAnnotationIsPresent_doesNotRegenerateUuids() {
+ String tableName = createTestTableName();
+ DynamoDbTable mappedTable = enhancedClient.table(tableName, TableSchema.fromClass(BeanRecord.class));
+
+ try {
+ mappedTable.createTable();
+ dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+
+ BeanRecord record = new BeanRecord();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+ record.setGsiPk("existing-gsiPk");
+ record.setGsiSk("existing-gsiSk");
+ record.setData("test");
+
+ mappedTable.putItem(record);
+ BeanRecord retrieved = mappedTable.getItem(
+ r -> r.key(k -> k.partitionValue("existing-id").sortValue("existing-sk")));
+ assertThat(retrieved.getId()).isEqualTo("existing-id");
+ assertThat(retrieved.getSortKey()).isEqualTo("existing-sk");
+ assertThat(retrieved.getGsiPk()).isEqualTo("existing-gsiPk");
+ assertThat(retrieved.getGsiSk()).isEqualTo("existing-gsiSk");
+ assertThat(retrieved.getData()).isEqualTo("test");
+ } finally {
+ try {
+ mappedTable.deleteTable();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void createBean_givenAutogeneratedKeyAnnotationAppliedOnNonStringAttributeType_throwsException() {
+ assertThatThrownBy(() -> TableSchema.fromBean(AutogeneratedKeyInvalidTypeRecord.class))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("not a suitable Java Class type to be used as a Auto Generated Key attribute")
+ .hasMessageContaining("Only String Class type is supported");
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedUuidIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedUuidIntegrationTest.java
new file mode 100644
index 000000000000..2636da9d3b74
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedUuidIntegrationTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension;
+import software.amazon.awssdk.enhanced.dynamodb.model.BeanRecord;
+import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
+import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
+import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyConflictingRecord;
+import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedUuidInvalidTypeRecord;
+import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedUuidRecord;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+
+public class AutoGeneratedUuidIntegrationTest extends DynamoDbEnhancedIntegrationTestBase {
+
+ private static final String TABLE_NAME = createTestTableName();
+
+ private static DynamoDbClient dynamoDbClient;
+ private static DynamoDbEnhancedClient enhancedClient;
+ private static DynamoDbTable mappedTable;
+
+ @BeforeClass
+ public static void beforeClass() {
+ dynamoDbClient = createDynamoDbClient();
+ enhancedClient = DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(dynamoDbClient)
+ .extensions(AutoGeneratedUuidExtension.create())
+ .build();
+ mappedTable = enhancedClient.table(TABLE_NAME, TableSchema.fromClass(AutogeneratedUuidRecord.class));
+ mappedTable.createTable();
+ dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME));
+ }
+
+ @After
+ public void tearDown() {
+ mappedTable.scan()
+ .items()
+ .forEach(record -> mappedTable.deleteItem(record));
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ try {
+ dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME));
+ } finally {
+ dynamoDbClient.close();
+ }
+ }
+
+ @Test
+ public void putItem_whenKeysNotPopulated_generatesNewUuids() {
+ AutogeneratedUuidRecord record = new AutogeneratedUuidRecord();
+
+ mappedTable.putItem(record);
+ AutogeneratedUuidRecord result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ }
+
+ @Test
+ public void putItem_whenKeysAlreadyPopulated_replacesExistingUuids() {
+ AutogeneratedUuidRecord record = new AutogeneratedUuidRecord();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+
+ mappedTable.putItem(record);
+ AutogeneratedUuidRecord result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ assertThat(result.getId()).isNotEqualTo("existing-id");
+ assertThat(result.getSortKey()).isNotEqualTo("existing-sk");
+ }
+
+ @Test
+ public void batchWrite_whenKeysNotPopulated_generatesNewUuids() {
+ AutogeneratedUuidRecord record1 = new AutogeneratedUuidRecord();
+ AutogeneratedUuidRecord record2 = new AutogeneratedUuidRecord();
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(
+ WriteBatch.builder(AutogeneratedUuidRecord.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record1)
+ .addPutItem(record2)
+ .build()));
+ List results = mappedTable.scan().items().stream().collect(Collectors.toList());
+
+ assertThat(results.size()).isEqualTo(2);
+ isValidUuid(results.get(0).getId());
+ isValidUuid(results.get(1).getId());
+ isValidUuid(results.get(0).getSortKey());
+ isValidUuid(results.get(1).getSortKey());
+ }
+
+ @Test
+ public void batchWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
+ AutogeneratedUuidRecord record1 = new AutogeneratedUuidRecord();
+ record1.setId("existing-id-1");
+ record1.setSortKey("existing-sk-1");
+
+ AutogeneratedUuidRecord record2 = new AutogeneratedUuidRecord();
+ record2.setId("existing-id-2");
+ record2.setSortKey("existing-sk-2");
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(
+ WriteBatch.builder(AutogeneratedUuidRecord.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record1)
+ .addPutItem(record2)
+ .build()));
+
+ List results = mappedTable.scan().items().stream().collect(Collectors.toList());
+
+ assertThat(results.size()).isEqualTo(2);
+ isValidUuid(results.get(0).getId());
+ isValidUuid(results.get(1).getId());
+ isValidUuid(results.get(0).getSortKey());
+ isValidUuid(results.get(1).getSortKey());
+
+ assertThat(results.get(0).getId()).isNotEqualTo("existing-id-1");
+ assertThat(results.get(1).getId()).isNotEqualTo("existing-id-2");
+ assertThat(results.get(0).getSortKey()).isNotEqualTo("existing-sk-1");
+ assertThat(results.get(1).getSortKey()).isNotEqualTo("existing-sk-2");
+ }
+
+ @Test
+ public void transactWrite_whenKeysNotPopulated_generatesNewUuids() {
+ AutogeneratedUuidRecord record = new AutogeneratedUuidRecord();
+
+ enhancedClient.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+ AutogeneratedUuidRecord result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ }
+
+ @Test
+ public void transactWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
+ AutogeneratedUuidRecord record = new AutogeneratedUuidRecord();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+
+ enhancedClient.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+ AutogeneratedUuidRecord result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ assertThat(result.getId()).isNotEqualTo("existing-id");
+ assertThat(result.getSortKey()).isNotEqualTo("existing-sk");
+ }
+
+ @Test
+ public void putItem_whenAnnotationInConflictWithAutogeneratedKeyAnnotation_throwsException() {
+ String tableName = createTestTableName();
+ DynamoDbTable mappedTable =
+ enhancedClient.table(tableName, TableSchema.fromClass(AutogeneratedKeyConflictingRecord.class));
+
+ try {
+ mappedTable.createTable();
+ dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+
+ assertThatThrownBy(() -> mappedTable.putItem(new AutogeneratedKeyConflictingRecord()))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid");
+ } finally {
+ try {
+ mappedTable.deleteTable();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void putItem_whenNoAutogeneratedUuidAnnotationIsPresent_doesNotRegenerateUuids() {
+ String tableName = createTestTableName();
+ DynamoDbTable mappedTable = enhancedClient.table(tableName, TableSchema.fromClass(BeanRecord.class));
+
+ try {
+ mappedTable.createTable();
+ dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+
+ BeanRecord record = new BeanRecord();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+ record.setGsiPk("existing-gsiPk");
+ record.setGsiSk("existing-gsiSk");
+ record.setData("data");
+
+ mappedTable.putItem(record);
+ BeanRecord retrieved = mappedTable.getItem(
+ r -> r.key(k -> k.partitionValue("existing-id").sortValue("existing-sk")));
+ assertThat(retrieved.getId()).isEqualTo("existing-id");
+ assertThat(retrieved.getSortKey()).isEqualTo("existing-sk");
+ assertThat(retrieved.getGsiPk()).isEqualTo("existing-gsiPk");
+ assertThat(retrieved.getGsiSk()).isEqualTo("existing-gsiSk");
+ assertThat(retrieved.getData()).isEqualTo("data");
+ } finally {
+ try {
+ mappedTable.deleteTable();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void createBean_givenAutogeneratedUuidAnnotationAppliedOnNonStringAttributeType_throwsException() {
+ assertThatThrownBy(() -> TableSchema.fromBean(AutogeneratedUuidInvalidTypeRecord.class))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("not a suitable Java Class type to be used as a Auto Generated Uuid attribute")
+ .hasMessageContaining("Only String Class type is supported");
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/BeanRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/BeanRecord.java
new file mode 100644
index 000000000000..d493e9f19c06
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/BeanRecord.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.model;
+
+import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
+
+@DynamoDbBean
+public class BeanRecord {
+ private String id;
+ private String sortKey;
+ private String gsiPk;
+ private String gsiSk;
+ private String data;
+
+ @DynamoDbPartitionKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ public String getSortKey() {
+ return sortKey;
+ }
+
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ @DynamoDbSecondaryPartitionKey(indexNames = "gsi1")
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
+ public String getGsiPk() {
+ return gsiPk;
+ }
+
+ public void setGsiPk(String gsiPk) {
+ this.gsiPk = gsiPk;
+ }
+
+ @DynamoDbSecondarySortKey(indexNames = "gsi1")
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
+ public String getGsiSk() {
+ return gsiSk;
+ }
+
+ public void setGsiSk(String gsiSk) {
+ this.gsiSk = gsiSk;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyConflictingRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyConflictingRecord.java
new file mode 100644
index 000000000000..2ccf52378c9c
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyConflictingRecord.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
+
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+
+@DynamoDbBean
+public class AutogeneratedKeyConflictingRecord {
+ private String id;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedKey
+ @DynamoDbAutoGeneratedUuid
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyInvalidTypeRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyInvalidTypeRecord.java
new file mode 100644
index 000000000000..6a6579230590
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyInvalidTypeRecord.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
+
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+
+@DynamoDbBean
+public class AutogeneratedKeyInvalidTypeRecord {
+ private Integer id;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedKey
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyOnNonKeyAttributeRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyOnNonKeyAttributeRecord.java
new file mode 100644
index 000000000000..1dfe7c1db5d1
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyOnNonKeyAttributeRecord.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
+
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+
+@DynamoDbBean
+public class AutogeneratedKeyOnNonKeyAttributeRecord {
+ private String id;
+ private String nonKeyAttribute;
+
+ @DynamoDbPartitionKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbAutoGeneratedKey
+ public String getNonKeyAttribute() {
+ return nonKeyAttribute;
+ }
+
+ public void setNonKeyAttribute(String nonKeyAttribute) {
+ this.nonKeyAttribute = nonKeyAttribute;
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyRecord.java
new file mode 100644
index 000000000000..e04c660127d3
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyRecord.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
+
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
+
+@DynamoDbBean
+public class AutogeneratedKeyRecord {
+ private String id;
+ private String sortKey;
+ private String gsiPk;
+ private String gsiSk;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAutoGeneratedKey
+ public String getSortKey() {
+ return sortKey;
+ }
+
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ @DynamoDbSecondaryPartitionKey(indexNames = "gsi1")
+ @DynamoDbAutoGeneratedKey
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
+ public String getGsiPk() {
+ return gsiPk;
+ }
+
+ public void setGsiPk(String gsiPk) {
+ this.gsiPk = gsiPk;
+ }
+
+ @DynamoDbSecondarySortKey(indexNames = "gsi1")
+ @DynamoDbAutoGeneratedKey
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
+ public String getGsiSk() {
+ return gsiSk;
+ }
+
+ public void setGsiSk(String gsiSk) {
+ this.gsiSk = gsiSk;
+ }
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyVersionedRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyVersionedRecord.java
new file mode 100644
index 000000000000..f4f1d8a9c8ad
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyVersionedRecord.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
+
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+
+@DynamoDbBean
+public class AutogeneratedKeyVersionedRecord {
+ private String id;
+ private Long version;
+ private String data;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbVersionAttribute
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidInvalidTypeRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidInvalidTypeRecord.java
new file mode 100644
index 000000000000..e437ab01ecf2
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidInvalidTypeRecord.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
+
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+
+@DynamoDbBean
+public class AutogeneratedUuidInvalidTypeRecord {
+ private Integer id;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidRecord.java
new file mode 100644
index 000000000000..b7b0d2d1822a
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidRecord.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
+
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+
+@DynamoDbBean
+public class AutogeneratedUuidRecord {
+ private String id;
+ private String sortKey;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAutoGeneratedUuid
+ public String getSortKey() {
+ return sortKey;
+ }
+
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
index 0898bb7e57d1..6d32376ef072 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
@@ -102,7 +102,7 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class)
.orElse(null);
- if (taggedAttributes == null || taggedAttributes.isEmpty()) {
+ if (taggedAttributes == null) {
return WriteModification.builder().build();
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
index ee053f1a16fe..9d5f9277d294 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
@@ -370,6 +370,32 @@ public void conflictDetection_worksRegardlessOfExtensionOrder() {
+ "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
}
+ @Test
+ public void beforeWrite_noTaggedAttributes_returnsEmptyModification() {
+ StaticTableSchema schemaWithoutTags =
+ StaticTableSchema.builder(ItemWithKey.class)
+ .newItemSupplier(ItemWithKey::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithKey::getId)
+ .setter(ItemWithKey::setId)
+ .addTag(primaryPartitionKey()))
+ .build();
+
+ ItemWithKey item = new ItemWithKey();
+ item.setId(RECORD_ID);
+ Map items = schemaWithoutTags.itemToMap(item, true);
+
+ WriteModification result = extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(schemaWithoutTags.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ assertThat(result).isEqualTo(WriteModification.builder().build());
+ }
+
private static class ItemWithKey {
private String id;
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
index cd92fa5fb063..00b5b632558e 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
@@ -195,6 +195,32 @@ public void conflictingAnnotations_throwsIllegalArgumentException() {
+ "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
}
+ @Test
+ public void beforeWrite_noCustomMetadata_returnsEmptyModification() {
+ StaticTableSchema schemaWithoutMetadata =
+ StaticTableSchema.builder(ItemWithUuid.class)
+ .newItemSupplier(ItemWithUuid::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithUuid::getId)
+ .setter(ItemWithUuid::setId)
+ .addTag(primaryPartitionKey()))
+ .build();
+
+ ItemWithUuid item = new ItemWithUuid();
+ item.setId(RECORD_ID);
+ Map items = schemaWithoutMetadata.itemToMap(item, true);
+
+ WriteModification result = atomicCounterExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(schemaWithoutMetadata.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ assertThat(result).isEqualTo(WriteModification.builder().build());
+ }
+
public static boolean isValidUuid(String uuid) {
return UUID_PATTERN.matcher(uuid).matches();
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java
index 3f30fdc8ecdf..81622bd9b495 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java
@@ -19,8 +19,11 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension.AttributeTags.versionAttribute;
import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItem.createUniqueFakeItem;
import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithSort.createUniqueFakeItemWithSort;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
import java.util.HashMap;
import java.util.Map;
@@ -41,6 +44,7 @@
import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeVersionedStaticImmutableItem;
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
@@ -273,7 +277,22 @@ public void customStartingValueAndIncrement_shouldThrow(Long startAt, Long incre
public static Stream customFailingStartAtAndIncrementValues() {
return Stream.of(
Arguments.of(-2L, 1L),
- Arguments.of(3L, 0L));
+ Arguments.of(3L, 0L),
+ Arguments.of(-1L, 0L));
+ }
+
+ @Test
+ public void builder_startAtValueIsLessThanMinusOne_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> VersionedRecordExtension.builder().startAt(-2L).build(),
+ "startAt must be -1 or greater");
+ }
+
+ @Test
+ public void builder_incrementByValueIsLessThanOne_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> VersionedRecordExtension.builder().incrementBy(0L).build(),
+ "incrementBy must be greater than 0.");
}
@Test
@@ -681,4 +700,89 @@ public static Stream customIncrementForExistingVersionValues() {
Arguments.of(3L, null, 10L, "11"),
Arguments.of(null, 3L, 4L, "7"));
}
+
+ @Test
+ public void versionAttribute_withInvalidStartAt_throwsIllegalArgumentException() {
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() ->
+ StaticTableSchema.builder(TestItem.class)
+ .newItemSupplier(TestItem::new)
+ .addAttribute(String.class,
+ a -> a.name("id")
+ .getter(TestItem::getId)
+ .setter(TestItem::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(Long.class,
+ a -> a.name("version")
+ .getter(TestItem::getVersion)
+ .setter(TestItem::setVersion)
+ .addTag(versionAttribute(-2L, 1L)))
+ .build()
+ )
+ .withMessage("startAt must be -1 or greater.");
+ }
+
+ @Test
+ public void versionAttribute_withInvalidIncrementBy_throwsIllegalArgumentException() {
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() ->
+ StaticTableSchema.builder(TestItem.class)
+ .newItemSupplier(TestItem::new)
+ .addAttribute(String.class,
+ a -> a.name("id")
+ .getter(TestItem::getId)
+ .setter(TestItem::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(Long.class,
+ a -> a.name("version")
+ .getter(TestItem::getVersion)
+ .setter(TestItem::setVersion)
+ .addTag(versionAttribute(0L, 0L)))
+ .build()
+ )
+ .withMessage("incrementBy must be greater than 0.");
+ }
+
+ @Test
+ public void versionAttribute_withNonNumericType_throwsIllegalArgumentException() {
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() ->
+ StaticTableSchema.builder(TestItem.class)
+ .newItemSupplier(TestItem::new)
+ .addAttribute(String.class,
+ a -> a.name("id")
+ .getter(TestItem::getId)
+ .setter(TestItem::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(String.class,
+ a -> a.name("version")
+ .getter(TestItem::getId)
+ .setter(TestItem::setId)
+ .addTag(versionAttribute()))
+ .build()
+ )
+ .withMessageContaining(
+ "is not a suitable type to be used as a version attribute. Only type 'N' is supported.");
+ }
+
+ private static class TestItem {
+ private String id;
+ private Long version;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+ }
}
From 79ba90f6119246ce644ea138fbb08ccf634cef11 Mon Sep 17 00:00:00 2001
From: Ana Satirbasa
Date: Thu, 12 Feb 2026 09:38:25 +0200
Subject: [PATCH 05/17] Increased code coverage
---
...-AmazonDynamoDBEnhancedClient-cbcc2bb.json | 2 +-
.../AutoGeneratedUuidIntegrationTest.java | 245 --------------
.../enhanced/dynamodb/model/BeanRecord.java | 79 -----
.../AutogeneratedKeyConflictingRecord.java | 37 --
.../AutogeneratedKeyInvalidTypeRecord.java | 35 --
...togeneratedKeyOnNonKeyAttributeRecord.java | 44 ---
.../AutogeneratedKeyRecord.java | 75 ----
.../AutogeneratedKeyVersionedRecord.java | 55 ---
.../AutogeneratedUuidInvalidTypeRecord.java | 35 --
.../AutogeneratedUuidRecord.java | 47 ---
.../AutoGeneratedUuidExtension.java | 10 +-
.../annotations/DynamoDbAutoGeneratedKey.java | 3 +-
.../AutoGeneratedKeyExtensionTest.java | 23 +-
.../AutoGeneratedUuidExtensionTest.java | 67 ++--
.../AutoGeneratedKeyRecordTest.java | 4 +-
.../AutoGeneratedKeyExtensionTest.java} | 312 +++++++++++++----
.../AutoGeneratedUuidExtensionTest.java | 320 ++++++++++++++++++
17 files changed, 616 insertions(+), 777 deletions(-)
delete mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedUuidIntegrationTest.java
delete mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/BeanRecord.java
delete mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyConflictingRecord.java
delete mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyInvalidTypeRecord.java
delete mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyOnNonKeyAttributeRecord.java
delete mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyRecord.java
delete mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyVersionedRecord.java
delete mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidInvalidTypeRecord.java
delete mode 100644 services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidRecord.java
rename services-custom/dynamodb-enhanced/src/{it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedKeyIntegrationTest.java => test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java} (61%)
create mode 100644 services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
diff --git a/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-cbcc2bb.json b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-cbcc2bb.json
index b8c7700af796..81c91da75550 100644
--- a/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-cbcc2bb.json
+++ b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-cbcc2bb.json
@@ -2,5 +2,5 @@
"type": "feature",
"category": "Amazon DynamoDB Enhanced Client",
"contributor": "",
- "description": "Added the support for DynamoDbAutoGeneratedKey annotation"
+ "description": "Added support for DynamoDbAutoGeneratedKey annotation"
}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedUuidIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedUuidIntegrationTest.java
deleted file mode 100644
index 2636da9d3b74..000000000000
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedUuidIntegrationTest.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
-
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension;
-import software.amazon.awssdk.enhanced.dynamodb.model.BeanRecord;
-import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
-import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
-import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyConflictingRecord;
-import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedUuidInvalidTypeRecord;
-import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedUuidRecord;
-import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
-
-public class AutoGeneratedUuidIntegrationTest extends DynamoDbEnhancedIntegrationTestBase {
-
- private static final String TABLE_NAME = createTestTableName();
-
- private static DynamoDbClient dynamoDbClient;
- private static DynamoDbEnhancedClient enhancedClient;
- private static DynamoDbTable mappedTable;
-
- @BeforeClass
- public static void beforeClass() {
- dynamoDbClient = createDynamoDbClient();
- enhancedClient = DynamoDbEnhancedClient.builder()
- .dynamoDbClient(dynamoDbClient)
- .extensions(AutoGeneratedUuidExtension.create())
- .build();
- mappedTable = enhancedClient.table(TABLE_NAME, TableSchema.fromClass(AutogeneratedUuidRecord.class));
- mappedTable.createTable();
- dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME));
- }
-
- @After
- public void tearDown() {
- mappedTable.scan()
- .items()
- .forEach(record -> mappedTable.deleteItem(record));
- }
-
- @AfterClass
- public static void afterClass() {
- try {
- dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME));
- } finally {
- dynamoDbClient.close();
- }
- }
-
- @Test
- public void putItem_whenKeysNotPopulated_generatesNewUuids() {
- AutogeneratedUuidRecord record = new AutogeneratedUuidRecord();
-
- mappedTable.putItem(record);
- AutogeneratedUuidRecord result = mappedTable.scan().items().stream().findFirst()
- .orElseThrow(() -> new AssertionError("No record found"));
- isValidUuid(result.getId());
- isValidUuid(result.getSortKey());
- }
-
- @Test
- public void putItem_whenKeysAlreadyPopulated_replacesExistingUuids() {
- AutogeneratedUuidRecord record = new AutogeneratedUuidRecord();
- record.setId("existing-id");
- record.setSortKey("existing-sk");
-
- mappedTable.putItem(record);
- AutogeneratedUuidRecord result = mappedTable.scan().items().stream().findFirst()
- .orElseThrow(() -> new AssertionError("No record found"));
-
- isValidUuid(result.getId());
- isValidUuid(result.getSortKey());
- assertThat(result.getId()).isNotEqualTo("existing-id");
- assertThat(result.getSortKey()).isNotEqualTo("existing-sk");
- }
-
- @Test
- public void batchWrite_whenKeysNotPopulated_generatesNewUuids() {
- AutogeneratedUuidRecord record1 = new AutogeneratedUuidRecord();
- AutogeneratedUuidRecord record2 = new AutogeneratedUuidRecord();
-
- enhancedClient.batchWriteItem(req -> req.addWriteBatch(
- WriteBatch.builder(AutogeneratedUuidRecord.class)
- .mappedTableResource(mappedTable)
- .addPutItem(record1)
- .addPutItem(record2)
- .build()));
- List results = mappedTable.scan().items().stream().collect(Collectors.toList());
-
- assertThat(results.size()).isEqualTo(2);
- isValidUuid(results.get(0).getId());
- isValidUuid(results.get(1).getId());
- isValidUuid(results.get(0).getSortKey());
- isValidUuid(results.get(1).getSortKey());
- }
-
- @Test
- public void batchWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
- AutogeneratedUuidRecord record1 = new AutogeneratedUuidRecord();
- record1.setId("existing-id-1");
- record1.setSortKey("existing-sk-1");
-
- AutogeneratedUuidRecord record2 = new AutogeneratedUuidRecord();
- record2.setId("existing-id-2");
- record2.setSortKey("existing-sk-2");
-
- enhancedClient.batchWriteItem(req -> req.addWriteBatch(
- WriteBatch.builder(AutogeneratedUuidRecord.class)
- .mappedTableResource(mappedTable)
- .addPutItem(record1)
- .addPutItem(record2)
- .build()));
-
- List results = mappedTable.scan().items().stream().collect(Collectors.toList());
-
- assertThat(results.size()).isEqualTo(2);
- isValidUuid(results.get(0).getId());
- isValidUuid(results.get(1).getId());
- isValidUuid(results.get(0).getSortKey());
- isValidUuid(results.get(1).getSortKey());
-
- assertThat(results.get(0).getId()).isNotEqualTo("existing-id-1");
- assertThat(results.get(1).getId()).isNotEqualTo("existing-id-2");
- assertThat(results.get(0).getSortKey()).isNotEqualTo("existing-sk-1");
- assertThat(results.get(1).getSortKey()).isNotEqualTo("existing-sk-2");
- }
-
- @Test
- public void transactWrite_whenKeysNotPopulated_generatesNewUuids() {
- AutogeneratedUuidRecord record = new AutogeneratedUuidRecord();
-
- enhancedClient.transactWriteItems(
- TransactWriteItemsEnhancedRequest.builder()
- .addPutItem(mappedTable, record)
- .build());
- AutogeneratedUuidRecord result = mappedTable.scan().items().stream().findFirst()
- .orElseThrow(() -> new AssertionError("No record found"));
-
- isValidUuid(result.getId());
- isValidUuid(result.getSortKey());
- }
-
- @Test
- public void transactWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
- AutogeneratedUuidRecord record = new AutogeneratedUuidRecord();
- record.setId("existing-id");
- record.setSortKey("existing-sk");
-
- enhancedClient.transactWriteItems(
- TransactWriteItemsEnhancedRequest.builder()
- .addPutItem(mappedTable, record)
- .build());
- AutogeneratedUuidRecord result = mappedTable.scan().items().stream().findFirst()
- .orElseThrow(() -> new AssertionError("No record found"));
-
- isValidUuid(result.getId());
- isValidUuid(result.getSortKey());
- assertThat(result.getId()).isNotEqualTo("existing-id");
- assertThat(result.getSortKey()).isNotEqualTo("existing-sk");
- }
-
- @Test
- public void putItem_whenAnnotationInConflictWithAutogeneratedKeyAnnotation_throwsException() {
- String tableName = createTestTableName();
- DynamoDbTable mappedTable =
- enhancedClient.table(tableName, TableSchema.fromClass(AutogeneratedKeyConflictingRecord.class));
-
- try {
- mappedTable.createTable();
- dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
-
- assertThatThrownBy(() -> mappedTable.putItem(new AutogeneratedKeyConflictingRecord()))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid");
- } finally {
- try {
- mappedTable.deleteTable();
- } catch (Exception ignored) {
- }
- }
- }
-
- @Test
- public void putItem_whenNoAutogeneratedUuidAnnotationIsPresent_doesNotRegenerateUuids() {
- String tableName = createTestTableName();
- DynamoDbTable mappedTable = enhancedClient.table(tableName, TableSchema.fromClass(BeanRecord.class));
-
- try {
- mappedTable.createTable();
- dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
-
- BeanRecord record = new BeanRecord();
- record.setId("existing-id");
- record.setSortKey("existing-sk");
- record.setGsiPk("existing-gsiPk");
- record.setGsiSk("existing-gsiSk");
- record.setData("data");
-
- mappedTable.putItem(record);
- BeanRecord retrieved = mappedTable.getItem(
- r -> r.key(k -> k.partitionValue("existing-id").sortValue("existing-sk")));
- assertThat(retrieved.getId()).isEqualTo("existing-id");
- assertThat(retrieved.getSortKey()).isEqualTo("existing-sk");
- assertThat(retrieved.getGsiPk()).isEqualTo("existing-gsiPk");
- assertThat(retrieved.getGsiSk()).isEqualTo("existing-gsiSk");
- assertThat(retrieved.getData()).isEqualTo("data");
- } finally {
- try {
- mappedTable.deleteTable();
- } catch (Exception ignored) {
- }
- }
- }
-
- @Test
- public void createBean_givenAutogeneratedUuidAnnotationAppliedOnNonStringAttributeType_throwsException() {
- assertThatThrownBy(() -> TableSchema.fromBean(AutogeneratedUuidInvalidTypeRecord.class))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("not a suitable Java Class type to be used as a Auto Generated Uuid attribute")
- .hasMessageContaining("Only String Class type is supported");
- }
-}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/BeanRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/BeanRecord.java
deleted file mode 100644
index d493e9f19c06..000000000000
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/BeanRecord.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.model;
-
-import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
-
-@DynamoDbBean
-public class BeanRecord {
- private String id;
- private String sortKey;
- private String gsiPk;
- private String gsiSk;
- private String data;
-
- @DynamoDbPartitionKey
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- @DynamoDbSortKey
- public String getSortKey() {
- return sortKey;
- }
-
- public void setSortKey(String sortKey) {
- this.sortKey = sortKey;
- }
-
- @DynamoDbSecondaryPartitionKey(indexNames = "gsi1")
- @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
- public String getGsiPk() {
- return gsiPk;
- }
-
- public void setGsiPk(String gsiPk) {
- this.gsiPk = gsiPk;
- }
-
- @DynamoDbSecondarySortKey(indexNames = "gsi1")
- @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
- public String getGsiSk() {
- return gsiSk;
- }
-
- public void setGsiSk(String gsiSk) {
- this.gsiSk = gsiSk;
- }
-
- public String getData() {
- return data;
- }
-
- public void setData(String data) {
- this.data = data;
- }
-}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyConflictingRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyConflictingRecord.java
deleted file mode 100644
index 2ccf52378c9c..000000000000
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyConflictingRecord.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
-
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-
-@DynamoDbBean
-public class AutogeneratedKeyConflictingRecord {
- private String id;
-
- @DynamoDbPartitionKey
- @DynamoDbAutoGeneratedKey
- @DynamoDbAutoGeneratedUuid
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyInvalidTypeRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyInvalidTypeRecord.java
deleted file mode 100644
index 6a6579230590..000000000000
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyInvalidTypeRecord.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
-
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-
-@DynamoDbBean
-public class AutogeneratedKeyInvalidTypeRecord {
- private Integer id;
-
- @DynamoDbPartitionKey
- @DynamoDbAutoGeneratedKey
- public Integer getId() {
- return id;
- }
-
- public void setId(Integer id) {
- this.id = id;
- }
-}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyOnNonKeyAttributeRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyOnNonKeyAttributeRecord.java
deleted file mode 100644
index 1dfe7c1db5d1..000000000000
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyOnNonKeyAttributeRecord.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
-
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-
-@DynamoDbBean
-public class AutogeneratedKeyOnNonKeyAttributeRecord {
- private String id;
- private String nonKeyAttribute;
-
- @DynamoDbPartitionKey
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- @DynamoDbAutoGeneratedKey
- public String getNonKeyAttribute() {
- return nonKeyAttribute;
- }
-
- public void setNonKeyAttribute(String nonKeyAttribute) {
- this.nonKeyAttribute = nonKeyAttribute;
- }
-}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyRecord.java
deleted file mode 100644
index e04c660127d3..000000000000
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyRecord.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
-
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
-
-@DynamoDbBean
-public class AutogeneratedKeyRecord {
- private String id;
- private String sortKey;
- private String gsiPk;
- private String gsiSk;
-
- @DynamoDbPartitionKey
- @DynamoDbAutoGeneratedKey
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- @DynamoDbSortKey
- @DynamoDbAutoGeneratedKey
- public String getSortKey() {
- return sortKey;
- }
-
- public void setSortKey(String sortKey) {
- this.sortKey = sortKey;
- }
-
- @DynamoDbSecondaryPartitionKey(indexNames = "gsi1")
- @DynamoDbAutoGeneratedKey
- @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
- public String getGsiPk() {
- return gsiPk;
- }
-
- public void setGsiPk(String gsiPk) {
- this.gsiPk = gsiPk;
- }
-
- @DynamoDbSecondarySortKey(indexNames = "gsi1")
- @DynamoDbAutoGeneratedKey
- @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
- public String getGsiSk() {
- return gsiSk;
- }
-
- public void setGsiSk(String gsiSk) {
- this.gsiSk = gsiSk;
- }
-}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyVersionedRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyVersionedRecord.java
deleted file mode 100644
index f4f1d8a9c8ad..000000000000
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedKeyVersionedRecord.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
-
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-
-@DynamoDbBean
-public class AutogeneratedKeyVersionedRecord {
- private String id;
- private Long version;
- private String data;
-
- @DynamoDbPartitionKey
- @DynamoDbAutoGeneratedKey
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- @DynamoDbVersionAttribute
- public Long getVersion() {
- return version;
- }
-
- public void setVersion(Long version) {
- this.version = version;
- }
-
- public String getData() {
- return data;
- }
-
- public void setData(String data) {
- this.data = data;
- }
-}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidInvalidTypeRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidInvalidTypeRecord.java
deleted file mode 100644
index e437ab01ecf2..000000000000
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidInvalidTypeRecord.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
-
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-
-@DynamoDbBean
-public class AutogeneratedUuidInvalidTypeRecord {
- private Integer id;
-
- @DynamoDbPartitionKey
- @DynamoDbAutoGeneratedUuid
- public Integer getId() {
- return id;
- }
-
- public void setId(Integer id) {
- this.id = id;
- }
-}
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidRecord.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidRecord.java
deleted file mode 100644
index b7b0d2d1822a..000000000000
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/autogeneratedkeys/AutogeneratedUuidRecord.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys;
-
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
-
-@DynamoDbBean
-public class AutogeneratedUuidRecord {
- private String id;
- private String sortKey;
-
- @DynamoDbPartitionKey
- @DynamoDbAutoGeneratedUuid
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- @DynamoDbSortKey
- @DynamoDbAutoGeneratedUuid
- public String getSortKey() {
- return sortKey;
- }
-
- public void setSortKey(String sortKey) {
- this.sortKey = sortKey;
- }
-}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
index 5a4712a177d3..884e12c20f89 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
@@ -40,12 +40,12 @@
* {@link java.util.UUID#randomUUID()} method.
*
* Key Difference from @DynamoDbAutoGeneratedKey: This extension always generates new UUIDs on every write,
- * regardless of existing values. In contrast, {@code @DynamoDbAutoGeneratedKey} only generates UUIDs when the
- * attribute value is null or empty, preserving existing values.
+ * regardless of existing values. In contrast, {@code @DynamoDbAutoGeneratedKey} only generates UUIDs when the attribute value is
+ * null or empty, preserving existing values.
*
* Conflict Detection: This extension cannot be used together with {@code @DynamoDbAutoGeneratedKey} on the same
- * attribute. If both annotations are applied to the same field, an {@link IllegalArgumentException} will be thrown
- * at runtime to prevent unpredictable behavior.
+ * attribute. If both annotations are applied to the same field, an {@link IllegalArgumentException} will be thrown at runtime to
+ * prevent unpredictable behavior.
*
* This extension is not loaded by default when you instantiate a
* {@link software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient}. Therefore, you need to specify it in a custom
@@ -93,7 +93,7 @@ public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientE
*/
private static final String KEY_EXTENSION_METADATA_KEY =
"software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
-
+
private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ATTRIBUTE = new AutoGeneratedUuidAttribute();
private AutoGeneratedUuidExtension() {
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
index a7021bb9f989..6fbb90d5a0c8 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
@@ -63,7 +63,8 @@
*
{@link UpdateBehavior#WRITE_ALWAYS} (default) – Generate a new UUID whenever the attribute is missing during write.
*
{@link UpdateBehavior#WRITE_IF_NOT_EXISTS} – Generate a UUID only on the first write, preserving the value on
* subsequent updates.
- *
+ *
+ *
*
*
Type restriction
* This annotation is only valid on attributes of type {@link String}.
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
index 9d5f9277d294..c19b5f793e3b 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
@@ -28,7 +28,7 @@
public class AutoGeneratedKeyExtensionTest {
- private static final String RECORD_ID = "id123";
+ private static final String RECORD_ID = "1";
private static final String TABLE_NAME = "table-name";
private static final OperationContext PRIMARY_CONTEXT =
@@ -45,13 +45,13 @@ public class AutoGeneratedKeyExtensionTest {
.addAttribute(String.class, a -> a.name("id")
.getter(ItemWithKey::getId)
.setter(ItemWithKey::setId)
- .addTag(primaryPartitionKey()) // PK
+ .addTag(primaryPartitionKey())
.addTag(autoGeneratedKeyAttribute()))
.addAttribute(String.class, a -> a.name("keyAttribute")
.getter(ItemWithKey::getKeyAttribute)
.setter(ItemWithKey::setKeyAttribute)
.tags(
- secondaryPartitionKey("gsi_keys_only"), // GSI
+ secondaryPartitionKey("gsi_keys_only"),
autoGeneratedKeyAttribute()))
.addAttribute(String.class, a -> a.name("simpleString")
.getter(ItemWithKey::getSimpleString)
@@ -71,7 +71,7 @@ public class AutoGeneratedKeyExtensionTest {
.addAttribute(String.class, a -> a.name("keyAttribute")
.getter(ItemWithKey::getKeyAttribute)
.setter(ItemWithKey::setKeyAttribute)
- // No index tags here — autogen on non-key fails at beforeWrite()
+ // index tags not defined — autogen on non-key fails at beforeWrite()
.addTag(autoGeneratedKeyAttribute()))
.addAttribute(String.class, a -> a.name("simpleString")
.getter(ItemWithKey::getSimpleString)
@@ -95,7 +95,7 @@ public class AutoGeneratedKeyExtensionTest {
.getter(ItemWithKey::getSimpleString)
.setter(ItemWithKey::setSimpleString)
.tags(
- secondarySortKey("lsi1"), // LSI
+ secondarySortKey("lsi1"),
autoGeneratedKeyAttribute()))
.build();
@@ -121,7 +121,6 @@ public void updateItem_withExistingKey_preservesValueAndDoesNotGenerateNew() {
assertThat(transformed).isNotNull().hasSize(2);
assertThat(transformed).containsEntry("id", AttributeValue.fromS(RECORD_ID));
- // Ensures the attribute remains a valid UUID without altering the preset value
assertThat(isValidUuid(transformed.get("keyAttribute").s())).isTrue();
assertThat(result.updateExpression()).isNull();
}
@@ -197,7 +196,7 @@ public void nonStringTypeAnnotatedWithAutoGeneratedKey_throwsIllegalArgumentExce
@Test
public void autoGeneratedKey_onSecondaryPartitionKey_generatesUuid() {
ItemWithKey item = new ItemWithKey();
- item.setId(RECORD_ID); // keyAttribute (GSI PK) is missing → should be generated
+ item.setId(RECORD_ID);
Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
@@ -211,13 +210,13 @@ public void autoGeneratedKey_onSecondaryPartitionKey_generatesUuid() {
Map transformed = result.transformedItem();
assertThat(transformed).isNotNull();
- assertThat(isValidUuid(transformed.get("keyAttribute").s())).isTrue(); // generated for GSI PK
+ assertThat(isValidUuid(transformed.get("keyAttribute").s())).isTrue();
}
@Test
public void autoGeneratedKey_onSecondarySortKey_generatesUuid() {
ItemWithKey item = new ItemWithKey();
- item.setId(RECORD_ID); // simpleString (GSI/LSI) is missing → should be generated
+ item.setId(RECORD_ID);
Map items = LSI_SK_AUTOGEN_SCHEMA.itemToMap(item, true);
@@ -231,13 +230,13 @@ public void autoGeneratedKey_onSecondarySortKey_generatesUuid() {
Map transformed = result.transformedItem();
assertThat(transformed).isNotNull();
- assertThat(isValidUuid(transformed.get("simpleString").s())).isTrue(); // generated for index SK
+ assertThat(isValidUuid(transformed.get("simpleString").s())).isTrue();
}
@Test
public void autoGeneratedKey_onNonKey_throwsIllegalArgumentException() {
ItemWithKey item = new ItemWithKey();
- item.setId(RECORD_ID); // keyAttribute is annotated but NOT a key in this schema → should fail at beforeWrite()
+ item.setId(RECORD_ID);
Map items = INVALID_NONKEY_AUTOGEN_SCHEMA.itemToMap(item, true);
@@ -336,8 +335,6 @@ public void conflictingAnnotations_onSecondaryKey_throwsIllegalArgumentException
@Test
public void conflictDetection_worksRegardlessOfExtensionOrder() {
- // Verify that AutoGeneratedKeyExtension detects conflicts even when
- // AutoGeneratedUuidExtension metadata is already present
StaticTableSchema conflictingSchema =
StaticTableSchema
.builder(ItemWithKey.class)
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
index 00b5b632558e..7bdb7e4cf3fb 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
@@ -65,6 +65,34 @@ public class AutoGeneratedUuidExtensionTest {
.setter(ItemWithUuid::setSimpleString))
.build();
+ @Test
+ public void beforeWrite_withNullCustomMetadataObject_returnsNoWriteModifications() {
+ StaticTableSchema schemaWithoutUuidAttribute =
+ StaticTableSchema.builder(ItemWithUuid.class)
+ .newItemSupplier(ItemWithUuid::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithUuid::getId)
+ .setter(ItemWithUuid::setId)
+ .addTag(primaryPartitionKey()))
+ .build();
+
+ ItemWithUuid item = new ItemWithUuid();
+ item.setId(RECORD_ID);
+ Map items = schemaWithoutUuidAttribute.itemToMap(item, true);
+
+ WriteModification result = atomicCounterExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(schemaWithoutUuidAttribute.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT).build());
+
+ assertThat(result).isNotNull();
+ assertThat(result.transformedItem()).isNull();
+ assertThat(result.updateExpression()).isNull();
+ assertThat(result.additionalConditionalExpression()).isNull();
+ }
+
@Test
public void beforeWrite_updateItemOperation_hasUuidInItem_doesNotCreateUpdateExpressionAndFilters() {
ItemWithUuid SimpleItem = new ItemWithUuid();
@@ -156,45 +184,6 @@ void IllegalArgumentException_for_AutogeneratedUuid_withNonStringType() {
+ " to be used as a Auto Generated Uuid attribute. Only String Class type is supported.");
}
- @Test
- public void conflictingAnnotations_throwsIllegalArgumentException() {
- // Create a schema with both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid on the same attribute
- StaticTableSchema conflictingSchema =
- StaticTableSchema.builder(ItemWithUuid.class)
- .newItemSupplier(ItemWithUuid::new)
- .addAttribute(String.class,
- a -> a.name("id")
- .getter(ItemWithUuid::getId)
- .setter(ItemWithUuid::setId)
- .addTag(primaryPartitionKey())
- // Both annotations on the same attribute
- .addTag(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute())
- .addTag(AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute()))
- .addAttribute(String.class,
- a -> a.name("simpleString")
- .getter(ItemWithUuid::getSimpleString)
- .setter(ItemWithUuid::setSimpleString))
- .build();
-
- ItemWithUuid item = new ItemWithUuid();
- item.setId(RECORD_ID);
-
- Map items = conflictingSchema.itemToMap(item, true);
-
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() ->
- atomicCounterExtension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(conflictingSchema.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build())
- )
- .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
- }
-
@Test
public void beforeWrite_noCustomMetadata_returnsEmptyModification() {
StaticTableSchema schemaWithoutMetadata =
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
index 30ceeaa952bf..506789cadd0c 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
@@ -339,7 +339,7 @@ public void batchWrite_preservesExistingKeysAndGeneratesMissing() {
TestRecord retrieved = mappedTable.scan().items().stream().findFirst()
.orElseThrow(() -> new AssertionError("No record found"));
-
+
assertThat(retrieved.getId()).isEqualTo("existing-id");
assertThat(retrieved.getSortKey()).isEqualTo("existing-sort");
assertValidUuid(retrieved.getGsiPk());
@@ -366,7 +366,7 @@ public void batchWrite_mixedPutDeleteOperations() {
List allRecords = mappedTable.scan().items().stream().collect(Collectors.toList());
assertThat(allRecords).hasSize(1);
-
+
TestRecord remaining = allRecords.get(0);
assertAllKeysGenerated(remaining);
assertThat(remaining.getPayload()).isEqualTo("batch-mixed");
diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedKeyIntegrationTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
similarity index 61%
rename from services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedKeyIntegrationTest.java
rename to services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
index 56834bc840c5..52f80818216e 100644
--- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AutoGeneratedKeyIntegrationTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
@@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/
-package software.amazon.awssdk.enhanced.dynamodb;
+package software.amazon.awssdk.enhanced.dynamodb.functionaltests.extensions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -21,56 +21,54 @@
import java.util.List;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.junit.Before;
import org.junit.Test;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
+import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension;
-import software.amazon.awssdk.enhanced.dynamodb.model.BeanRecord;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute;
+import software.amazon.awssdk.enhanced.dynamodb.functionaltests.LocalDynamoDbSyncTestBase;
+import software.amazon.awssdk.enhanced.dynamodb.internal.client.ExtensionResolver;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
-import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyConflictingRecord;
-import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyInvalidTypeRecord;
-import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyOnNonKeyAttributeRecord;
-import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyRecord;
-import software.amazon.awssdk.enhanced.dynamodb.model.autogeneratedkeys.AutogeneratedKeyVersionedRecord;
-import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
-
-public class AutoGeneratedKeyIntegrationTest extends DynamoDbEnhancedIntegrationTestBase {
-
- private static final String TABLE_NAME = createTestTableName();
-
- private static DynamoDbClient dynamoDbClient;
- private static DynamoDbEnhancedClient enhancedClient;
- private static DynamoDbTable mappedTable;
-
- @BeforeClass
- public static void beforeClass() {
- dynamoDbClient = createDynamoDbClient();
- enhancedClient = DynamoDbEnhancedClient.builder()
- .dynamoDbClient(dynamoDbClient)
- .extensions(AutoGeneratedKeyExtension.builder().build())
- .build();
- mappedTable = enhancedClient.table(TABLE_NAME, TableSchema.fromClass(AutogeneratedKeyRecord.class));
- mappedTable.createTable();
- dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME));
- }
- @After
- public void tearDown() {
- mappedTable.scan()
- .items()
- .forEach(record -> mappedTable.deleteItem(record));
+public class AutoGeneratedKeyExtensionTest extends LocalDynamoDbSyncTestBase {
+
+ private static final TableSchema TABLE_SCHEMA =
+ TableSchema.fromClass(AutogeneratedKeyRecord.class);
+
+ private final DynamoDbEnhancedClient enhancedClient =
+ DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(Stream.concat(
+ ExtensionResolver.defaultExtensions().stream(),
+ Stream.of(AutoGeneratedKeyExtension.builder().build()))
+ .collect(Collectors.toList()))
+ .build();
+
+ private final DynamoDbTable mappedTable =
+ enhancedClient.table(getConcreteTableName("autogenerated-key-table"), TABLE_SCHEMA);
+
+ @Before
+ public void createTable() {
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
}
- @AfterClass
- public static void afterClass() {
- try {
- dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME));
- } finally {
- dynamoDbClient.close();
- }
+ @After
+ public void deleteTable() {
+ getDynamoDbClient().deleteTable(r -> r.tableName(getConcreteTableName("autogenerated-key-table")));
}
@Test
@@ -236,27 +234,20 @@ public void transactWrite_whenKeysAlreadyPopulated_preservesExistingUuids() {
@Test
public void putItem_onVersionedRecord_worksWithAutoGeneratedKey() {
- String tableName = createTestTableName();
-
- DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
- .dynamoDbClient(dynamoDbClient)
- .extensions(AutoGeneratedKeyExtension.builder().build(),
- VersionedRecordExtension.builder().build())
- .build();
+ String tableName = "versioned-record-autogenerated-key-table";
DynamoDbTable mappedTable =
enhancedClient.table(tableName, TableSchema.fromClass(AutogeneratedKeyVersionedRecord.class));
try {
- mappedTable.createTable();
- dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
AutogeneratedKeyVersionedRecord record = new AutogeneratedKeyVersionedRecord();
+ record.setId("id");
record.setData("data-v1");
mappedTable.putItem(record);
- AutogeneratedKeyVersionedRecord retrieved =
- mappedTable.scan().items().stream().findFirst()
- .orElseThrow(() -> new AssertionError("No record found"));
+ AutogeneratedKeyVersionedRecord retrieved = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
isValidUuid(retrieved.getId());
assertThat(retrieved.getData()).isEqualTo("data-v1");
assertThat(retrieved.getVersion()).isEqualTo(1L);
@@ -277,13 +268,12 @@ public void putItem_onVersionedRecord_worksWithAutoGeneratedKey() {
@Test
public void putItem_whenAnnotationInConflictWithAutogeneratedUuidAnnotation_throwsException() {
- String tableName = createTestTableName();
+ String tableName = "conflicting-annotations-record-autogenerated-key-table";
DynamoDbTable mappedTable =
enhancedClient.table(tableName, TableSchema.fromClass(AutogeneratedKeyConflictingRecord.class));
try {
- mappedTable.createTable();
- dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
assertThatThrownBy(() -> mappedTable.putItem(new AutogeneratedKeyConflictingRecord()))
.isInstanceOf(IllegalArgumentException.class)
@@ -298,13 +288,12 @@ public void putItem_whenAnnotationInConflictWithAutogeneratedUuidAnnotation_thro
@Test
public void putItem_whenAnnotationUsedOnNonKeyAttribute_throwsException() {
- String tableName = createTestTableName();
+ String tableName = "annotation-on-non-key-attribute-record-autogenerated-key-table";
DynamoDbTable mappedTable =
enhancedClient.table(tableName, TableSchema.fromClass(AutogeneratedKeyOnNonKeyAttributeRecord.class));
try {
- mappedTable.createTable();
- dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
assertThatThrownBy(() -> mappedTable.putItem(new AutogeneratedKeyOnNonKeyAttributeRecord()))
.isInstanceOf(IllegalArgumentException.class)
@@ -319,12 +308,11 @@ public void putItem_whenAnnotationUsedOnNonKeyAttribute_throwsException() {
@Test
public void putItem_whenNoAutogeneratedKeyAnnotationIsPresent_doesNotRegenerateUuids() {
- String tableName = createTestTableName();
+ String tableName = "no-annotation-record-autogenerated-key-table";
DynamoDbTable mappedTable = enhancedClient.table(tableName, TableSchema.fromClass(BeanRecord.class));
try {
- mappedTable.createTable();
- dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(tableName));
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
BeanRecord record = new BeanRecord();
record.setId("existing-id");
@@ -356,4 +344,200 @@ public void createBean_givenAutogeneratedKeyAnnotationAppliedOnNonStringAttribut
.hasMessageContaining("not a suitable Java Class type to be used as a Auto Generated Key attribute")
.hasMessageContaining("Only String Class type is supported");
}
+
+ @DynamoDbBean
+ public static class BeanRecord {
+ private String id;
+ private String sortKey;
+ private String gsiPk;
+ private String gsiSk;
+ private String data;
+
+ @DynamoDbPartitionKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ public String getSortKey() {
+ return sortKey;
+ }
+
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ @DynamoDbSecondaryPartitionKey(indexNames = "gsi1")
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
+ public String getGsiPk() {
+ return gsiPk;
+ }
+
+ public void setGsiPk(String gsiPk) {
+ this.gsiPk = gsiPk;
+ }
+
+ @DynamoDbSecondarySortKey(indexNames = "gsi1")
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
+ public String getGsiSk() {
+ return gsiSk;
+ }
+
+ public void setGsiSk(String gsiSk) {
+ this.gsiSk = gsiSk;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+ }
+
+ @DynamoDbBean
+ public static class AutogeneratedKeyRecord {
+ private String id;
+ private String sortKey;
+ private String gsiPk;
+ private String gsiSk;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAutoGeneratedKey
+ public String getSortKey() {
+ return sortKey;
+ }
+
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ @DynamoDbSecondaryPartitionKey(indexNames = "gsi1")
+ @DynamoDbAutoGeneratedKey
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
+ public String getGsiPk() {
+ return gsiPk;
+ }
+
+ public void setGsiPk(String gsiPk) {
+ this.gsiPk = gsiPk;
+ }
+
+ @DynamoDbSecondarySortKey(indexNames = "gsi1")
+ @DynamoDbAutoGeneratedKey
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
+ public String getGsiSk() {
+ return gsiSk;
+ }
+
+ public void setGsiSk(String gsiSk) {
+ this.gsiSk = gsiSk;
+ }
+ }
+
+ @DynamoDbBean
+ public static class AutogeneratedKeyVersionedRecord {
+ private String id;
+ private Long version;
+ private String data;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbVersionAttribute
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+ }
+
+ @DynamoDbBean
+ public static class AutogeneratedKeyInvalidTypeRecord {
+ private Integer id;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedKey
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+ }
+
+ @DynamoDbBean
+ public static class AutogeneratedKeyOnNonKeyAttributeRecord {
+ private String id;
+ private String nonKeyAttribute;
+
+ @DynamoDbPartitionKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbAutoGeneratedKey
+ public String getNonKeyAttribute() {
+ return nonKeyAttribute;
+ }
+
+ public void setNonKeyAttribute(String nonKeyAttribute) {
+ this.nonKeyAttribute = nonKeyAttribute;
+ }
+ }
+
+ @DynamoDbBean
+ public static class AutogeneratedKeyConflictingRecord {
+ private String id;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedKey
+ @DynamoDbAutoGeneratedUuid
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+ }
}
+
+
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
new file mode 100644
index 000000000000..03588232bd2b
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.functionaltests.extensions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
+import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
+import software.amazon.awssdk.enhanced.dynamodb.functionaltests.LocalDynamoDbSyncTestBase;
+import software.amazon.awssdk.enhanced.dynamodb.internal.client.ExtensionResolver;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
+import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
+
+public class AutoGeneratedUuidExtensionTest extends LocalDynamoDbSyncTestBase {
+
+ private static final TableSchema TABLE_SCHEMA =
+ TableSchema.fromClass(RecordWithAutogeneratedUuid.class);
+
+ private final DynamoDbEnhancedClient enhancedClient =
+ DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(Stream.concat(
+ ExtensionResolver.defaultExtensions().stream(),
+ Stream.of(AutoGeneratedUuidExtension.create()))
+ .collect(Collectors.toList()))
+ .build();
+
+ private final DynamoDbTable mappedTable =
+ enhancedClient.table(getConcreteTableName("autogenerated-uuid-table"), TABLE_SCHEMA);
+
+ @Before
+ public void createTable() {
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+ }
+
+ @After
+ public void deleteTable() {
+ getDynamoDbClient().deleteTable(r -> r.tableName(getConcreteTableName("autogenerated-uuid-table")));
+ }
+
+ @Test
+ public void putItem_whenKeysNotPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
+ record.setId("existing-id");
+
+ mappedTable.putItem(record);
+ RecordWithAutogeneratedUuid result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ }
+
+ @Test
+ public void putItem_whenKeysAlreadyPopulated_replacesExistingUuids() {
+ RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+
+ mappedTable.putItem(record);
+ RecordWithAutogeneratedUuid result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ assertThat(result.getId()).isNotEqualTo("existing-id");
+ assertThat(result.getSortKey()).isNotEqualTo("existing-sk");
+ }
+
+ @Test
+ public void batchWrite_whenKeysNotPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record1 = new RecordWithAutogeneratedUuid();
+ RecordWithAutogeneratedUuid record2 = new RecordWithAutogeneratedUuid();
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(
+ WriteBatch.builder(RecordWithAutogeneratedUuid.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record1)
+ .addPutItem(record2)
+ .build()));
+ List results = mappedTable.scan().items().stream().collect(Collectors.toList());
+
+ assertThat(results.size()).isEqualTo(2);
+ isValidUuid(results.get(0).getId());
+ isValidUuid(results.get(1).getId());
+ isValidUuid(results.get(0).getSortKey());
+ isValidUuid(results.get(1).getSortKey());
+ }
+
+ @Test
+ public void batchWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record1 = new RecordWithAutogeneratedUuid();
+ record1.setId("existing-id-1");
+ record1.setSortKey("existing-sk-1");
+
+ RecordWithAutogeneratedUuid record2 = new RecordWithAutogeneratedUuid();
+ record2.setId("existing-id-2");
+ record2.setSortKey("existing-sk-2");
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(
+ WriteBatch.builder(RecordWithAutogeneratedUuid.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record1)
+ .addPutItem(record2)
+ .build()));
+
+ List results = mappedTable.scan().items().stream().collect(Collectors.toList());
+
+ assertThat(results.size()).isEqualTo(2);
+ isValidUuid(results.get(0).getId());
+ isValidUuid(results.get(1).getId());
+ isValidUuid(results.get(0).getSortKey());
+ isValidUuid(results.get(1).getSortKey());
+
+ assertThat(results.get(0).getId()).isNotEqualTo("existing-id-1");
+ assertThat(results.get(1).getId()).isNotEqualTo("existing-id-2");
+ assertThat(results.get(0).getSortKey()).isNotEqualTo("existing-sk-1");
+ assertThat(results.get(1).getSortKey()).isNotEqualTo("existing-sk-2");
+ }
+
+ @Test
+ public void transactWrite_whenKeysNotPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
+
+ enhancedClient.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+ RecordWithAutogeneratedUuid result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ }
+
+ @Test
+ public void transactWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+
+ enhancedClient.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+ RecordWithAutogeneratedUuid result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ isValidUuid(result.getId());
+ isValidUuid(result.getSortKey());
+ assertThat(result.getId()).isNotEqualTo("existing-id");
+ assertThat(result.getSortKey()).isNotEqualTo("existing-sk");
+ }
+
+ @Test
+ public void putItem_whenNoAutogeneratedUuidAnnotationIsPresent_doesNotRegenerateUuids() {
+ String tableName = "no-autogenerated-uuid-table";
+ DynamoDbTable mappedTable =
+ enhancedClient.table(tableName, TableSchema.fromClass(RecordWithoutAutogeneratedUuid.class));
+
+ try {
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+
+ RecordWithoutAutogeneratedUuid record = new RecordWithoutAutogeneratedUuid();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+ record.setGsiPk("existing-gsiPk");
+ record.setGsiSk("existing-gsiSk");
+ record.setData("data");
+
+ mappedTable.putItem(record);
+ RecordWithoutAutogeneratedUuid retrieved = mappedTable.getItem(
+ r -> r.key(k -> k.partitionValue("existing-id").sortValue("existing-sk")));
+ assertThat(retrieved.getId()).isEqualTo("existing-id");
+ assertThat(retrieved.getSortKey()).isEqualTo("existing-sk");
+ assertThat(retrieved.getGsiPk()).isEqualTo("existing-gsiPk");
+ assertThat(retrieved.getGsiSk()).isEqualTo("existing-gsiSk");
+ assertThat(retrieved.getData()).isEqualTo("data");
+ } finally {
+ try {
+ mappedTable.deleteTable();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void createBean_givenAutogeneratedUuidAnnotationAppliedOnNonStringAttributeType_throwsException() {
+ assertThatThrownBy(() -> TableSchema.fromBean(AutogeneratedUuidInvalidTypeRecord.class))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("not a suitable Java Class type to be used as a Auto Generated Uuid attribute")
+ .hasMessageContaining("Only String Class type is supported");
+ }
+
+ @DynamoDbBean
+ public static class RecordWithAutogeneratedUuid {
+ private String id;
+ private String sortKey;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAutoGeneratedUuid
+ public String getSortKey() {
+ return sortKey;
+ }
+
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
+ }
+
+ @DynamoDbBean
+ public static class RecordWithoutAutogeneratedUuid {
+ private String id;
+ private String sortKey;
+ private String gsiPk;
+ private String gsiSk;
+ private String data;
+
+ @DynamoDbPartitionKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ public String getSortKey() {
+ return sortKey;
+ }
+
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ @DynamoDbSecondaryPartitionKey(indexNames = "gsi1")
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
+ public String getGsiPk() {
+ return gsiPk;
+ }
+
+ public void setGsiPk(String gsiPk) {
+ this.gsiPk = gsiPk;
+ }
+
+ @DynamoDbSecondarySortKey(indexNames = "gsi1")
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
+ public String getGsiSk() {
+ return gsiSk;
+ }
+
+ public void setGsiSk(String gsiSk) {
+ this.gsiSk = gsiSk;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+ }
+
+ @DynamoDbBean
+ public static class AutogeneratedUuidInvalidTypeRecord {
+ private Integer id;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+ }
+}
\ No newline at end of file
From 8ae8405339417007fe460f48262875cf1cbb57d9 Mon Sep 17 00:00:00 2001
From: Ana Satirbasa
Date: Thu, 12 Feb 2026 15:02:24 +0200
Subject: [PATCH 06/17] Tests refactoring
---
.../extensions/AutoGeneratedKeyExtension.java | 15 +-
.../AutoGeneratedKeyExtensionTest.java | 168 +++++++++---------
.../AutoGeneratedUuidExtensionTest.java | 4 +-
.../ConflictingAnnotationsTest.java | 92 +++++-----
.../VersionedRecordExtensionTest.java | 120 +------------
.../AutoGeneratedKeyRecordTest.java | 20 ++-
.../AutoGeneratedUuidRecordTest.java | 8 +-
.../AutoGeneratedKeyExtensionTest.java | 39 ++--
.../AutoGeneratedUuidExtensionTest.java | 12 +-
9 files changed, 184 insertions(+), 294 deletions(-)
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
index 6d32376ef072..9db27b1ff6d8 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
@@ -121,18 +121,17 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
+ "and cannot be used together on the same attribute.");
});
- TableMetadata meta = context.tableMetadata();
+ TableMetadata metadata = context.tableMetadata();
Set allowedKeys = new HashSet<>();
// ensure every @DynamoDbAutoGeneratedKey attribute is a PK/SK or GSI/LSI. If not, throw IllegalArgumentException
- allowedKeys.add(meta.primaryPartitionKey());
- meta.primarySortKey().ifPresent(allowedKeys::add);
+ allowedKeys.add(metadata.primaryPartitionKey());
+ metadata.primarySortKey().ifPresent(allowedKeys::add);
- for (IndexMetadata idx : meta.indices()) {
- String indexName = idx.name();
- allowedKeys.add(meta.indexPartitionKey(indexName));
- meta.indexSortKey(indexName).ifPresent(allowedKeys::add);
- }
+ metadata.indices().stream().map(IndexMetadata::name).forEach(indexName -> {
+ allowedKeys.add(metadata.indexPartitionKey(indexName));
+ metadata.indexSortKey(indexName).ifPresent(allowedKeys::add);
+ });
taggedAttributes.stream()
.filter(attr -> !allowedKeys.contains(attr))
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
index c19b5f793e3b..5b57765872c6 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
@@ -37,71 +37,71 @@ public class AutoGeneratedKeyExtensionTest {
private final AutoGeneratedKeyExtension extension = AutoGeneratedKeyExtension.builder().build();
/**
- * Schema that places @DynamoDbAutoGeneratedKey on GSI key ("keyAttribute") so the validation passes.
+ * Schema that places @DynamoDbAutoGeneratedKey on GSI key ("keyAttribute") -> the validation passes.
*/
- private static final StaticTableSchema ITEM_WITH_KEY_SCHEMA =
- StaticTableSchema.builder(ItemWithKey.class)
- .newItemSupplier(ItemWithKey::new)
+ private static final StaticTableSchema ITEM_WITH_KEY_SCHEMA =
+ StaticTableSchema.builder(AutogeneratedKeyItem.class)
+ .newItemSupplier(AutogeneratedKeyItem::new)
.addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithKey::getId)
- .setter(ItemWithKey::setId)
+ .getter(AutogeneratedKeyItem::getId)
+ .setter(AutogeneratedKeyItem::setId)
.addTag(primaryPartitionKey())
.addTag(autoGeneratedKeyAttribute()))
.addAttribute(String.class, a -> a.name("keyAttribute")
- .getter(ItemWithKey::getKeyAttribute)
- .setter(ItemWithKey::setKeyAttribute)
+ .getter(AutogeneratedKeyItem::getKeyAttribute)
+ .setter(AutogeneratedKeyItem::setKeyAttribute)
.tags(
secondaryPartitionKey("gsi_keys_only"),
autoGeneratedKeyAttribute()))
.addAttribute(String.class, a -> a.name("simpleString")
- .getter(ItemWithKey::getSimpleString)
- .setter(ItemWithKey::setSimpleString))
+ .getter(AutogeneratedKeyItem::getSimpleString)
+ .setter(AutogeneratedKeyItem::setSimpleString))
.build();
/**
- * Schema that places @DynamoDbAutoGeneratedKey on a NON-KEY attribute to trigger the exception.
+ * Schema that places @DynamoDbAutoGeneratedKey on a NON-KEY attribute -> triggers the exception.
*/
- private static final StaticTableSchema INVALID_NONKEY_AUTOGEN_SCHEMA =
- StaticTableSchema.builder(ItemWithKey.class)
- .newItemSupplier(ItemWithKey::new)
+ private static final StaticTableSchema INVALID_NONKEY_AUTOGEN_SCHEMA =
+ StaticTableSchema.builder(AutogeneratedKeyItem.class)
+ .newItemSupplier(AutogeneratedKeyItem::new)
.addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithKey::getId)
- .setter(ItemWithKey::setId)
+ .getter(AutogeneratedKeyItem::getId)
+ .setter(AutogeneratedKeyItem::setId)
.addTag(primaryPartitionKey()))
.addAttribute(String.class, a -> a.name("keyAttribute")
- .getter(ItemWithKey::getKeyAttribute)
- .setter(ItemWithKey::setKeyAttribute)
+ .getter(AutogeneratedKeyItem::getKeyAttribute)
+ .setter(AutogeneratedKeyItem::setKeyAttribute)
// index tags not defined — autogen on non-key fails at beforeWrite()
.addTag(autoGeneratedKeyAttribute()))
.addAttribute(String.class, a -> a.name("simpleString")
- .getter(ItemWithKey::getSimpleString)
- .setter(ItemWithKey::setSimpleString))
+ .getter(AutogeneratedKeyItem::getSimpleString)
+ .setter(AutogeneratedKeyItem::setSimpleString))
.build();
/**
- * Schema that places @DynamoDbAutoGeneratedKey on LSI key ("simpleString") so the validation passes.
+ * Schema that places @DynamoDbAutoGeneratedKey on LSI key ("simpleString") -> the validation passes.
*/
- private static final StaticTableSchema LSI_SK_AUTOGEN_SCHEMA =
- StaticTableSchema.builder(ItemWithKey.class)
- .newItemSupplier(ItemWithKey::new)
+ private static final StaticTableSchema LSI_SK_AUTOGEN_SCHEMA =
+ StaticTableSchema.builder(AutogeneratedKeyItem.class)
+ .newItemSupplier(AutogeneratedKeyItem::new)
.addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithKey::getId)
- .setter(ItemWithKey::setId)
+ .getter(AutogeneratedKeyItem::getId)
+ .setter(AutogeneratedKeyItem::setId)
.addTag(primaryPartitionKey()))
.addAttribute(String.class, a -> a.name("keyAttribute")
- .getter(ItemWithKey::getKeyAttribute)
- .setter(ItemWithKey::setKeyAttribute))
+ .getter(AutogeneratedKeyItem::getKeyAttribute)
+ .setter(AutogeneratedKeyItem::setKeyAttribute))
.addAttribute(String.class, a -> a.name("simpleString")
- .getter(ItemWithKey::getSimpleString)
- .setter(ItemWithKey::setSimpleString)
+ .getter(AutogeneratedKeyItem::getSimpleString)
+ .setter(AutogeneratedKeyItem::setSimpleString)
.tags(
secondarySortKey("lsi1"),
autoGeneratedKeyAttribute()))
.build();
@Test
- public void updateItem_withExistingKey_preservesValueAndDoesNotGenerateNew() {
- ItemWithKey item = new ItemWithKey();
+ public void updateItem_withExistingKey_preservesValueAndDoesNotGenerateNewOne() {
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
String preset = UUID.randomUUID().toString();
item.setKeyAttribute(preset);
@@ -126,8 +126,8 @@ public void updateItem_withExistingKey_preservesValueAndDoesNotGenerateNew() {
}
@Test
- public void updateItem_withoutKey_generatesNewUuid() {
- ItemWithKey item = new ItemWithKey();
+ public void updateItem_withoutExistingKey_generatesNewUuid() {
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
@@ -150,7 +150,7 @@ public void updateItem_withoutKey_generatesNewUuid() {
@Test
public void updateItem_withMissingKeyAttribute_insertsGeneratedUuid() {
- ItemWithKey item = new ItemWithKey();
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
@@ -171,22 +171,22 @@ public void updateItem_withMissingKeyAttribute_insertsGeneratedUuid() {
}
@Test
- public void nonStringTypeAnnotatedWithAutoGeneratedKey_throwsIllegalArgumentException() {
+ public void nonStringAttributeAnnotatedWithAutoGeneratedKey_throwsIllegalArgumentException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() ->
- StaticTableSchema.builder(ItemWithKey.class)
- .newItemSupplier(ItemWithKey::new)
+ StaticTableSchema.builder(AutogeneratedKeyItem.class)
+ .newItemSupplier(AutogeneratedKeyItem::new)
.addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithKey::getId)
- .setter(ItemWithKey::setId)
+ .getter(AutogeneratedKeyItem::getId)
+ .setter(AutogeneratedKeyItem::setId)
.addTag(primaryPartitionKey()))
.addAttribute(Integer.class, a -> a.name("intAttribute")
- .getter(ItemWithKey::getIntAttribute)
- .setter(ItemWithKey::setIntAttribute)
+ .getter(AutogeneratedKeyItem::getIntAttribute)
+ .setter(AutogeneratedKeyItem::setIntAttribute)
.addTag(autoGeneratedKeyAttribute()))
.addAttribute(String.class, a -> a.name("simpleString")
- .getter(ItemWithKey::getSimpleString)
- .setter(ItemWithKey::setSimpleString))
+ .getter(AutogeneratedKeyItem::getSimpleString)
+ .setter(AutogeneratedKeyItem::setSimpleString))
.build()
)
.withMessage("Attribute 'intAttribute' of Class type class java.lang.Integer is not a suitable Java Class type "
@@ -195,7 +195,7 @@ public void nonStringTypeAnnotatedWithAutoGeneratedKey_throwsIllegalArgumentExce
@Test
public void autoGeneratedKey_onSecondaryPartitionKey_generatesUuid() {
- ItemWithKey item = new ItemWithKey();
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
@@ -215,7 +215,7 @@ public void autoGeneratedKey_onSecondaryPartitionKey_generatesUuid() {
@Test
public void autoGeneratedKey_onSecondarySortKey_generatesUuid() {
- ItemWithKey item = new ItemWithKey();
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
Map items = LSI_SK_AUTOGEN_SCHEMA.itemToMap(item, true);
@@ -234,8 +234,8 @@ public void autoGeneratedKey_onSecondarySortKey_generatesUuid() {
}
@Test
- public void autoGeneratedKey_onNonKey_throwsIllegalArgumentException() {
- ItemWithKey item = new ItemWithKey();
+ public void autoGeneratedKey_onNonKeyAttribute_throwsIllegalArgumentException() {
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
Map items = INVALID_NONKEY_AUTOGEN_SCHEMA.itemToMap(item, true);
@@ -255,25 +255,25 @@ public void autoGeneratedKey_onNonKey_throwsIllegalArgumentException() {
}
@Test
- public void conflictingAnnotations_throwsIllegalArgumentException() {
+ public void conflictingAnnotations_onSameAttribute_throwsIllegalArgumentException() {
// Create a schema with both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid on the same attribute
- StaticTableSchema conflictingSchema =
+ StaticTableSchema conflictingSchema =
StaticTableSchema
- .builder(ItemWithKey.class)
- .newItemSupplier(ItemWithKey::new)
+ .builder(AutogeneratedKeyItem.class)
+ .newItemSupplier(AutogeneratedKeyItem::new)
.addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithKey::getId)
- .setter(ItemWithKey::setId)
+ .getter(AutogeneratedKeyItem::getId)
+ .setter(AutogeneratedKeyItem::setId)
.addTag(primaryPartitionKey())
// Both annotations on the same attribute
.addTag(autoGeneratedKeyAttribute())
.addTag(autoGeneratedUuidAttribute()))
.addAttribute(String.class, a -> a.name("simpleString")
- .getter(ItemWithKey::getSimpleString)
- .setter(ItemWithKey::setSimpleString))
+ .getter(AutogeneratedKeyItem::getSimpleString)
+ .setter(AutogeneratedKeyItem::setSimpleString))
.build();
- ItemWithKey item = new ItemWithKey();
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
Map items = conflictingSchema.itemToMap(item, true);
@@ -294,27 +294,27 @@ public void conflictingAnnotations_throwsIllegalArgumentException() {
@Test
public void conflictingAnnotations_onSecondaryKey_throwsIllegalArgumentException() {
// Create a schema with both annotations on a GSI key
- StaticTableSchema conflictingGsiSchema =
+ StaticTableSchema conflictingGsiSchema =
StaticTableSchema
- .builder(ItemWithKey.class)
- .newItemSupplier(ItemWithKey::new)
+ .builder(AutogeneratedKeyItem.class)
+ .newItemSupplier(AutogeneratedKeyItem::new)
.addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithKey::getId)
- .setter(ItemWithKey::setId)
+ .getter(AutogeneratedKeyItem::getId)
+ .setter(AutogeneratedKeyItem::setId)
.addTag(primaryPartitionKey()))
.addAttribute(String.class, a -> a.name("keyAttribute")
- .getter(ItemWithKey::getKeyAttribute)
- .setter(ItemWithKey::setKeyAttribute)
+ .getter(AutogeneratedKeyItem::getKeyAttribute)
+ .setter(AutogeneratedKeyItem::setKeyAttribute)
.addTag(secondaryPartitionKey("gsi1"))
// Both annotations on the same GSI key
.addTag(autoGeneratedKeyAttribute())
.addTag(autoGeneratedUuidAttribute()))
.addAttribute(String.class, a -> a.name("simpleString")
- .getter(ItemWithKey::getSimpleString)
- .setter(ItemWithKey::setSimpleString))
+ .getter(AutogeneratedKeyItem::getSimpleString)
+ .setter(AutogeneratedKeyItem::setSimpleString))
.build();
- ItemWithKey item = new ItemWithKey();
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
Map items = conflictingGsiSchema.itemToMap(item, true);
@@ -335,19 +335,19 @@ public void conflictingAnnotations_onSecondaryKey_throwsIllegalArgumentException
@Test
public void conflictDetection_worksRegardlessOfExtensionOrder() {
- StaticTableSchema conflictingSchema =
+ StaticTableSchema conflictingSchema =
StaticTableSchema
- .builder(ItemWithKey.class)
- .newItemSupplier(ItemWithKey::new)
+ .builder(AutogeneratedKeyItem.class)
+ .newItemSupplier(AutogeneratedKeyItem::new)
.addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithKey::getId)
- .setter(ItemWithKey::setId)
+ .getter(AutogeneratedKeyItem::getId)
+ .setter(AutogeneratedKeyItem::setId)
.addTag(primaryPartitionKey())
.addTag(autoGeneratedKeyAttribute())
.addTag(autoGeneratedUuidAttribute()))
.build();
- ItemWithKey item = new ItemWithKey();
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
Map items = conflictingSchema.itemToMap(item, true);
@@ -368,17 +368,17 @@ public void conflictDetection_worksRegardlessOfExtensionOrder() {
}
@Test
- public void beforeWrite_noTaggedAttributes_returnsEmptyModification() {
- StaticTableSchema schemaWithoutTags =
- StaticTableSchema.builder(ItemWithKey.class)
- .newItemSupplier(ItemWithKey::new)
+ public void beforeWrite_noAttributesTaggedWithAutogeneratedKey_returnsEmptyModification() {
+ StaticTableSchema schemaWithoutTags =
+ StaticTableSchema.builder(AutogeneratedKeyItem.class)
+ .newItemSupplier(AutogeneratedKeyItem::new)
.addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithKey::getId)
- .setter(ItemWithKey::setId)
+ .getter(AutogeneratedKeyItem::getId)
+ .setter(AutogeneratedKeyItem::setId)
.addTag(primaryPartitionKey()))
.build();
- ItemWithKey item = new ItemWithKey();
+ AutogeneratedKeyItem item = new AutogeneratedKeyItem();
item.setId(RECORD_ID);
Map items = schemaWithoutTags.itemToMap(item, true);
@@ -393,14 +393,14 @@ public void beforeWrite_noTaggedAttributes_returnsEmptyModification() {
assertThat(result).isEqualTo(WriteModification.builder().build());
}
- private static class ItemWithKey {
+ private static class AutogeneratedKeyItem {
private String id;
private String keyAttribute;
private String simpleString;
private Integer intAttribute;
- ItemWithKey() {
+ AutogeneratedKeyItem() {
}
public Integer getIntAttribute() {
@@ -440,10 +440,10 @@ public boolean equals(Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof ItemWithKey)) {
+ if (!(o instanceof AutogeneratedKeyItem)) {
return false;
}
- ItemWithKey that = (ItemWithKey) o;
+ AutogeneratedKeyItem that = (AutogeneratedKeyItem) o;
return Objects.equals(id, that.id)
&& Objects.equals(keyAttribute, that.keyAttribute)
&& Objects.equals(simpleString, that.simpleString);
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
index 7bdb7e4cf3fb..d506fced8d6f 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
@@ -66,7 +66,7 @@ public class AutoGeneratedUuidExtensionTest {
.build();
@Test
- public void beforeWrite_withNullCustomMetadataObject_returnsNoWriteModifications() {
+ public void beforeWrite_withNoMetadata_returnsNoWriteModifications() {
StaticTableSchema schemaWithoutUuidAttribute =
StaticTableSchema.builder(ItemWithUuid.class)
.newItemSupplier(ItemWithUuid::new)
@@ -185,7 +185,7 @@ void IllegalArgumentException_for_AutogeneratedUuid_withNonStringType() {
}
@Test
- public void beforeWrite_noCustomMetadata_returnsEmptyModification() {
+ public void beforeWrite_withoutMetadata_returnsEmptyModification() {
StaticTableSchema schemaWithoutMetadata =
StaticTableSchema.builder(ItemWithUuid.class)
.newItemSupplier(ItemWithUuid::new)
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
index c2ed794ee310..7b7d01c7afce 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
@@ -36,8 +36,8 @@
*/
public class ConflictingAnnotationsTest {
- private static final String RECORD_ID = "test-id";
- private static final String TABLE_NAME = "test-table";
+ private static final String RECORD_ID = "1";
+ private static final String TABLE_NAME = "table-name";
private static final OperationContext PRIMARY_CONTEXT =
DefaultOperationContext.create(TABLE_NAME, TableMetadata.primaryIndexName());
@@ -47,12 +47,12 @@ public class ConflictingAnnotationsTest {
/**
* Schema with both annotations on the same attribute to test conflict detection.
*/
- private static final StaticTableSchema CONFLICTING_SCHEMA =
- StaticTableSchema.builder(TestItem.class)
- .newItemSupplier(TestItem::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(TestItem::getId)
- .setter(TestItem::setId)
+ private static final StaticTableSchema CONFLICTING_SCHEMA =
+ StaticTableSchema.builder(RecordWithAutogenerated.class)
+ .newItemSupplier(RecordWithAutogenerated::new)
+ .addAttribute(String.class, a -> a.name("autogeneratedKeyField")
+ .getter(RecordWithAutogenerated::getAutogeneratedKeyField)
+ .setter(RecordWithAutogenerated::setAutogeneratedKeyField)
.addTag(primaryPartitionKey())
// Both annotations on the same attribute
.addTag(autoGeneratedKeyAttribute())
@@ -60,9 +60,9 @@ public class ConflictingAnnotationsTest {
.build();
@Test
- public void keyExtensionFirst_detectsConflictWithUuidExtension() {
- TestItem item = new TestItem();
- item.setId(RECORD_ID);
+ public void autogeneratedKeyExtensionFirst_detectsConflictWithUuidExtension() {
+ RecordWithAutogenerated item = new RecordWithAutogenerated();
+ item.setAutogeneratedKeyField(RECORD_ID);
Map items = CONFLICTING_SCHEMA.itemToMap(item, true);
@@ -76,14 +76,15 @@ public void keyExtensionFirst_detectsConflictWithUuidExtension() {
.operationContext(PRIMARY_CONTEXT)
.build())
)
- .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ .withMessage("Attribute 'autogeneratedKeyField' cannot have both "
+ + "@DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
}
@Test
- public void uuidExtensionFirst_detectsConflictWithKeyExtension() {
- TestItem item = new TestItem();
- item.setId(RECORD_ID);
+ public void autogeneratedUuidExtensionFirst_detectsConflictWithKeyExtension() {
+ RecordWithAutogenerated item = new RecordWithAutogenerated();
+ item.setAutogeneratedKeyField(RECORD_ID);
Map items = CONFLICTING_SCHEMA.itemToMap(item, true);
@@ -97,28 +98,29 @@ public void uuidExtensionFirst_detectsConflictWithKeyExtension() {
.operationContext(PRIMARY_CONTEXT)
.build())
)
- .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ .withMessage("Attribute 'autogeneratedKeyField' cannot have both "
+ + "@DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
}
@Test
- public void separateAttributes_noConflict() {
- // Schema with annotations on different attributes - should work fine
- StaticTableSchema separateSchema =
- StaticTableSchema.builder(TestItemSeparate.class)
- .newItemSupplier(TestItemSeparate::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(TestItemSeparate::getId)
- .setter(TestItemSeparate::setId)
+ public void autogeneratedAnnotations_onSeparateAttributes_doesNotDetectConflict() {
+ // Schema with annotations on different attributes - does not detect conflict
+ StaticTableSchema separateSchema =
+ StaticTableSchema.builder(RecordWithAutogenerated.class)
+ .newItemSupplier(RecordWithAutogenerated::new)
+ .addAttribute(String.class, a -> a.name("autogeneratedKeyField")
+ .getter(RecordWithAutogenerated::getAutogeneratedKeyField)
+ .setter(RecordWithAutogenerated::setAutogeneratedKeyField)
.addTag(primaryPartitionKey())
.addTag(autoGeneratedKeyAttribute()))
- .addAttribute(String.class, a -> a.name("uuidField")
- .getter(TestItemSeparate::getUuidField)
- .setter(TestItemSeparate::setUuidField)
+ .addAttribute(String.class, a -> a.name("autogeneratedUuidField")
+ .getter(RecordWithAutogenerated::getAutogeneratedUuidField)
+ .setter(RecordWithAutogenerated::setAutogeneratedUuidField)
.addTag(autoGeneratedUuidAttribute()))
.build();
- TestItemSeparate item = new TestItemSeparate();
+ RecordWithAutogenerated item = new RecordWithAutogenerated();
Map items = separateSchema.itemToMap(item, true);
@@ -138,36 +140,24 @@ public void separateAttributes_noConflict() {
.build());
}
- public static class TestItem {
- private String id;
+ public static class RecordWithAutogenerated {
+ private String autogeneratedKeyField;
+ private String autogeneratedUuidField;
- public String getId() {
- return id;
+ public String getAutogeneratedKeyField() {
+ return autogeneratedKeyField;
}
- public void setId(String id) {
- this.id = id;
- }
- }
-
- public static class TestItemSeparate {
- private String id;
- private String uuidField;
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
+ public void setAutogeneratedKeyField(String autogeneratedKeyField) {
+ this.autogeneratedKeyField = autogeneratedKeyField;
}
- public String getUuidField() {
- return uuidField;
+ public String getAutogeneratedUuidField() {
+ return autogeneratedUuidField;
}
- public void setUuidField(String uuidField) {
- this.uuidField = uuidField;
+ public void setAutogeneratedUuidField(String autogeneratedUuidField) {
+ this.autogeneratedUuidField = autogeneratedUuidField;
}
}
}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java
index 81622bd9b495..e84378201b20 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java
@@ -19,11 +19,8 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension.AttributeTags.versionAttribute;
import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItem.createUniqueFakeItem;
import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithSort.createUniqueFakeItemWithSort;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
import java.util.HashMap;
import java.util.Map;
@@ -44,7 +41,6 @@
import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeVersionedStaticImmutableItem;
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
@@ -269,30 +265,15 @@ public static Stream customStartAtAndIncrementValues() {
@MethodSource("customFailingStartAtAndIncrementValues")
public void customStartingValueAndIncrement_shouldThrow(Long startAt, Long incrementBy) {
assertThrows(IllegalArgumentException.class, () -> VersionedRecordExtension.builder()
- .startAt(startAt)
- .incrementBy(incrementBy)
- .build());
+ .startAt(startAt)
+ .incrementBy(incrementBy)
+ .build());
}
public static Stream customFailingStartAtAndIncrementValues() {
return Stream.of(
Arguments.of(-2L, 1L),
- Arguments.of(3L, 0L),
- Arguments.of(-1L, 0L));
- }
-
- @Test
- public void builder_startAtValueIsLessThanMinusOne_throwsIllegalArgumentException() {
- assertThrows(IllegalArgumentException.class,
- () -> VersionedRecordExtension.builder().startAt(-2L).build(),
- "startAt must be -1 or greater");
- }
-
- @Test
- public void builder_incrementByValueIsLessThanOne_throwsIllegalArgumentException() {
- assertThrows(IllegalArgumentException.class,
- () -> VersionedRecordExtension.builder().incrementBy(0L).build(),
- "incrementBy must be greater than 0.");
+ Arguments.of(3L, 0L));
}
@Test
@@ -394,9 +375,9 @@ public void customStartingValueAndIncrementWithAnnotation_worksAsExpected() {
@Test
public void customAnnotationValuesAndBuilderValues_annotationShouldTakePrecedence() {
VersionedRecordExtension recordExtension = VersionedRecordExtension.builder()
- .startAt(5L)
- .incrementBy(2L)
- .build();
+ .startAt(5L)
+ .incrementBy(2L)
+ .build();
FakeVersionedThroughAnnotationItem item = new FakeVersionedThroughAnnotationItem();
item.setId(UUID.randomUUID().toString());
@@ -535,7 +516,7 @@ public void customIncrementForExistingVersion_worksAsExpected(Long startAt, Long
@ParameterizedTest
@MethodSource("customIncrementForExistingVersionValues")
public void customIncrementForExistingVersion_withImmutableSchema_worksAsExpected(Long startAt, Long incrementBy,
- Long existingVersion, String expectedNextVersion) {
+ Long existingVersion, String expectedNextVersion) {
VersionedRecordExtension.Builder recordExtensionBuilder = VersionedRecordExtension.builder();
if (startAt != null) {
recordExtensionBuilder.startAt(startAt);
@@ -700,89 +681,4 @@ public static Stream customIncrementForExistingVersionValues() {
Arguments.of(3L, null, 10L, "11"),
Arguments.of(null, 3L, 4L, "7"));
}
-
- @Test
- public void versionAttribute_withInvalidStartAt_throwsIllegalArgumentException() {
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() ->
- StaticTableSchema.builder(TestItem.class)
- .newItemSupplier(TestItem::new)
- .addAttribute(String.class,
- a -> a.name("id")
- .getter(TestItem::getId)
- .setter(TestItem::setId)
- .addTag(primaryPartitionKey()))
- .addAttribute(Long.class,
- a -> a.name("version")
- .getter(TestItem::getVersion)
- .setter(TestItem::setVersion)
- .addTag(versionAttribute(-2L, 1L)))
- .build()
- )
- .withMessage("startAt must be -1 or greater.");
- }
-
- @Test
- public void versionAttribute_withInvalidIncrementBy_throwsIllegalArgumentException() {
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() ->
- StaticTableSchema.builder(TestItem.class)
- .newItemSupplier(TestItem::new)
- .addAttribute(String.class,
- a -> a.name("id")
- .getter(TestItem::getId)
- .setter(TestItem::setId)
- .addTag(primaryPartitionKey()))
- .addAttribute(Long.class,
- a -> a.name("version")
- .getter(TestItem::getVersion)
- .setter(TestItem::setVersion)
- .addTag(versionAttribute(0L, 0L)))
- .build()
- )
- .withMessage("incrementBy must be greater than 0.");
- }
-
- @Test
- public void versionAttribute_withNonNumericType_throwsIllegalArgumentException() {
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() ->
- StaticTableSchema.builder(TestItem.class)
- .newItemSupplier(TestItem::new)
- .addAttribute(String.class,
- a -> a.name("id")
- .getter(TestItem::getId)
- .setter(TestItem::setId)
- .addTag(primaryPartitionKey()))
- .addAttribute(String.class,
- a -> a.name("version")
- .getter(TestItem::getId)
- .setter(TestItem::setId)
- .addTag(versionAttribute()))
- .build()
- )
- .withMessageContaining(
- "is not a suitable type to be used as a version attribute. Only type 'N' is supported.");
- }
-
- private static class TestItem {
- private String id;
- private Long version;
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public Long getVersion() {
- return version;
- }
-
- public void setVersion(Long version) {
- this.version = version;
- }
- }
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
index 506789cadd0c..ff811115937b 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
@@ -48,9 +48,12 @@
/**
* Tests for @DynamoDbAutoGeneratedKey annotation functionality.
*
- * Tests cover: - Basic UUID generation on all 4 key types (primary PK/SK, GSI PK/SK) - UpdateBehavior control (WRITE_ALWAYS vs
- * WRITE_IF_NOT_EXISTS) for secondary index keys - Primary key limitations (UpdateBehavior has no effect) - Error handling for
- * invalid usage - Integration with other extensions (VersionedRecord)
+ * Tests cover:
+ * - Basic UUID generation on all 4 key types (primary PK/SK, GSI PK/SK)
+ * - UpdateBehavior control (WRITE_ALWAYS vs WRITE_IF_NOT_EXISTS) for secondary index keys
+ * - Primary key limitations (UpdateBehavior has no effect)
+ * - Error handling for invalid usage
+ * - Integration with other extensions (VersionedRecord)
*/
@RunWith(Parameterized.class)
public class AutoGeneratedKeyRecordTest extends LocalDynamoDbSyncTestBase {
@@ -116,7 +119,6 @@ public void deleteTable() {
@Test
public void putItem_generatesUuidsForAllFourKeyTypes() {
TestRecord record = new TestRecord();
- // Don't set any keys - they should all be auto-generated
mappedTable.putItem(record);
TestRecord retrieved = mappedTable.scan().items().stream().findFirst()
@@ -128,7 +130,7 @@ public void putItem_generatesUuidsForAllFourKeyTypes() {
assertValidUuid(retrieved.getGsiPk()); // GSI partition key
assertValidUuid(retrieved.getGsiSk()); // GSI sort key
- // Verify they're all different
+ // Verify they're all unique keys
assertThat(retrieved.getId()).isNotEqualTo(retrieved.getSortKey());
assertThat(retrieved.getGsiPk()).isNotEqualTo(retrieved.getGsiSk());
assertThat(retrieved.getId()).isNotEqualTo(retrieved.getGsiPk());
@@ -136,7 +138,7 @@ public void putItem_generatesUuidsForAllFourKeyTypes() {
@Test
public void updateItem_respectsUpdateBehaviorForSecondaryIndexKeys() {
- // Put initial record
+ // Put record
TestRecord record = new TestRecord();
mappedTable.putItem(record);
@@ -145,7 +147,7 @@ public void updateItem_respectsUpdateBehaviorForSecondaryIndexKeys() {
String id = afterPut.getId();
String sortKey = afterPut.getSortKey();
String originalGsiPk = afterPut.getGsiPk(); // WRITE_ALWAYS (default) → should change
- String originalGsiSk = afterPut.getGsiSk(); // WRITE_IF_NOT_EXISTS → should preserve
+ String originalGsiSk = afterPut.getGsiSk(); // WRITE_IF_NOT_EXISTS → should preserve
// Update record
TestRecord updateRecord = new TestRecord();
@@ -161,7 +163,7 @@ public void updateItem_respectsUpdateBehaviorForSecondaryIndexKeys() {
}
@Test
- public void nonKeyAttribute_throwsException() {
+ public void putItem_withAutogeneratedKeySetOnNonKeyAttribute_throwsException() {
String tableName = getConcreteTableName("invalid-usage-test");
DynamoDbEnhancedClient client = createClient();
@@ -231,7 +233,7 @@ public void versionedRecord_worksWithAutoGeneratedKeys() {
}
@Test
- public void conflictingAnnotations_throwsException() {
+ public void autogeneratedConflictingAnnotations_throwsException() {
String tableName = getConcreteTableName("conflicting-annotations-test");
DynamoDbEnhancedClient client = DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
index 3bdc7a5eb218..d513df433c35 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
@@ -312,8 +312,8 @@ public void updateItemConditionTestFailure() {
}
@Test
- public void conflictingAnnotations_throwsException() {
- String tableName = getConcreteTableName("conflicting-annotations-test");
+ public void putItem_onRecordWithAutogeneratedConflictingAnnotations_throwsException() {
+ String tableName = getConcreteTableName("conflicting-annotations-record-table");
DynamoDbEnhancedClient client =
DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
@@ -326,7 +326,7 @@ public void conflictingAnnotations_throwsException() {
table.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
ConflictingAnnotationsRecord record = new ConflictingAnnotationsRecord();
- record.setPayload("test");
+ record.setPayload("payload");
Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> table.putItem(record))
@@ -508,8 +508,8 @@ public static class ConflictingAnnotationsRecord {
// Both annotations on the same field - should cause an exception
@DynamoDbPartitionKey
- @DynamoDbAutoGeneratedUuid
@DynamoDbAutoGeneratedKey
+ @DynamoDbAutoGeneratedUuid
public String getId() {
return id;
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
index 52f80818216e..6e24fe2d96e0 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
@@ -78,6 +78,7 @@ public void putItem_whenKeysNotPopulated_generatesNewUuids() {
mappedTable.putItem(record);
AutogeneratedKeyRecord result = mappedTable.scan().items().stream().findFirst()
.orElseThrow(() -> new AssertionError("No record found"));
+
isValidUuid(result.getId());
isValidUuid(result.getSortKey());
isValidUuid(result.getGsiPk());
@@ -137,15 +138,15 @@ public void updateItem_respectsUpdateBehavior() {
}
@Test
- public void batchWrite_whenKeysNotPopulated_generatesNewUuids() {
- AutogeneratedKeyRecord record1 = new AutogeneratedKeyRecord();
- AutogeneratedKeyRecord record2 = new AutogeneratedKeyRecord();
+ public void batchWrite_whenKeysNotAlreadyPopulated_generatesNewUuids() {
+ AutogeneratedKeyRecord firstRecord = new AutogeneratedKeyRecord();
+ AutogeneratedKeyRecord secondRecord = new AutogeneratedKeyRecord();
enhancedClient.batchWriteItem(req -> req.addWriteBatch(
WriteBatch.builder(AutogeneratedKeyRecord.class)
.mappedTableResource(mappedTable)
- .addPutItem(record1)
- .addPutItem(record2)
+ .addPutItem(firstRecord)
+ .addPutItem(secondRecord)
.build()));
List results = mappedTable.scan().items().stream().collect(Collectors.toList());
@@ -158,23 +159,23 @@ public void batchWrite_whenKeysNotPopulated_generatesNewUuids() {
@Test
public void batchWrite_whenKeysAlreadyPopulated_preservesExistingUuids() {
- AutogeneratedKeyRecord record1 = new AutogeneratedKeyRecord();
- record1.setId("existing-id-1");
- record1.setSortKey("existing-sk-1");
- record1.setGsiPk("existing-gsiPk-1");
- record1.setGsiSk("existing-gsiSk-1");
-
- AutogeneratedKeyRecord record2 = new AutogeneratedKeyRecord();
- record2.setId("existing-id-2");
- record2.setSortKey("existing-sk-2");
- record2.setGsiPk("existing-gsiPk-2");
- record2.setGsiSk("existing-gsiSk-2");
+ AutogeneratedKeyRecord firstRecord = new AutogeneratedKeyRecord();
+ firstRecord.setId("existing-id-1");
+ firstRecord.setSortKey("existing-sk-1");
+ firstRecord.setGsiPk("existing-gsiPk-1");
+ firstRecord.setGsiSk("existing-gsiSk-1");
+
+ AutogeneratedKeyRecord secondRecord = new AutogeneratedKeyRecord();
+ secondRecord.setId("existing-id-2");
+ secondRecord.setSortKey("existing-sk-2");
+ secondRecord.setGsiPk("existing-gsiPk-2");
+ secondRecord.setGsiSk("existing-gsiSk-2");
enhancedClient.batchWriteItem(req -> req.addWriteBatch(
WriteBatch.builder(AutogeneratedKeyRecord.class)
.mappedTableResource(mappedTable)
- .addPutItem(record1)
- .addPutItem(record2)
+ .addPutItem(firstRecord)
+ .addPutItem(secondRecord)
.build()));
AutogeneratedKeyRecord savedRecord1 =
@@ -193,7 +194,7 @@ public void batchWrite_whenKeysAlreadyPopulated_preservesExistingUuids() {
}
@Test
- public void transactWrite_whenKeysNotPopulated_generatesNewUuids() {
+ public void transactWrite_whenKeysNotAlreadyPopulated_generatesNewUuids() {
AutogeneratedKeyRecord record = new AutogeneratedKeyRecord();
enhancedClient.transactWriteItems(
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
index 03588232bd2b..c9a29f0e4563 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
@@ -70,13 +70,14 @@ public void deleteTable() {
}
@Test
- public void putItem_whenKeysNotPopulated_generatesNewUuids() {
+ public void putItem_whenKeysNotAlreadyPopulated_generatesNewUuids() {
RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
record.setId("existing-id");
mappedTable.putItem(record);
RecordWithAutogeneratedUuid result = mappedTable.scan().items().stream().findFirst()
.orElseThrow(() -> new AssertionError("No record found"));
+
isValidUuid(result.getId());
isValidUuid(result.getSortKey());
}
@@ -98,7 +99,7 @@ public void putItem_whenKeysAlreadyPopulated_replacesExistingUuids() {
}
@Test
- public void batchWrite_whenKeysNotPopulated_generatesNewUuids() {
+ public void batchWrite_whenKeysNotAlreadyPopulated_generatesNewUuids() {
RecordWithAutogeneratedUuid record1 = new RecordWithAutogeneratedUuid();
RecordWithAutogeneratedUuid record2 = new RecordWithAutogeneratedUuid();
@@ -149,7 +150,7 @@ public void batchWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
}
@Test
- public void transactWrite_whenKeysNotPopulated_generatesNewUuids() {
+ public void transactWrite_whenKeysNotAlreadyPopulated_generatesNewUuids() {
RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
enhancedClient.transactWriteItems(
@@ -183,7 +184,7 @@ public void transactWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
}
@Test
- public void putItem_whenNoAutogeneratedUuidAnnotationIsPresent_doesNotRegenerateUuids() {
+ public void putItem_whenAutogeneratedUuidAnnotationIsNotPresent_doesNotRegenerateUuids() {
String tableName = "no-autogenerated-uuid-table";
DynamoDbTable mappedTable =
enhancedClient.table(tableName, TableSchema.fromClass(RecordWithoutAutogeneratedUuid.class));
@@ -201,6 +202,7 @@ public void putItem_whenNoAutogeneratedUuidAnnotationIsPresent_doesNotRegenerate
mappedTable.putItem(record);
RecordWithoutAutogeneratedUuid retrieved = mappedTable.getItem(
r -> r.key(k -> k.partitionValue("existing-id").sortValue("existing-sk")));
+
assertThat(retrieved.getId()).isEqualTo("existing-id");
assertThat(retrieved.getSortKey()).isEqualTo("existing-sk");
assertThat(retrieved.getGsiPk()).isEqualTo("existing-gsiPk");
@@ -215,7 +217,7 @@ public void putItem_whenNoAutogeneratedUuidAnnotationIsPresent_doesNotRegenerate
}
@Test
- public void createBean_givenAutogeneratedUuidAnnotationAppliedOnNonStringAttributeType_throwsException() {
+ public void createBean_whenAutogeneratedUuidAnnotationIsAppliedOnNonStringAttribute_throwsException() {
assertThatThrownBy(() -> TableSchema.fromBean(AutogeneratedUuidInvalidTypeRecord.class))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("not a suitable Java Class type to be used as a Auto Generated Uuid attribute")
From 08012f9da054851633a7aa8500969c843016ae82 Mon Sep 17 00:00:00 2001
From: Ana Satirbasa
Date: Thu, 12 Feb 2026 15:40:53 +0200
Subject: [PATCH 07/17] Tests refactoring
---
.../extensions/AutoGeneratedKeyExtension.java | 2 +-
.../AutoGeneratedKeyExtensionTest.java | 150 +++++++++---------
.../AutoGeneratedUuidExtensionTest.java | 5 +-
.../ConflictingAnnotationsTest.java | 6 +-
.../AutoGeneratedKeyRecordTest.java | 21 ++-
.../AutoGeneratedUuidRecordTest.java | 11 +-
.../AutoGeneratedKeyExtensionTest.java | 14 +-
.../AutoGeneratedUuidExtensionTest.java | 5 +-
8 files changed, 111 insertions(+), 103 deletions(-)
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
index 9db27b1ff6d8..d09e7bc47174 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
@@ -139,7 +139,7 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
.ifPresent(attr -> {
throw new IllegalArgumentException(
"@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
- + "primary partition key, primary sort key, or GSI/LSI partition/sort keys."
+ + "primary partition key, primary sort key, or GSI/LSI partition/sort keys. "
+ "Invalid placement on attribute: " + attr);
});
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
index 5b57765872c6..dd059c9a271c 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
@@ -109,13 +109,13 @@ public void updateItem_withExistingKey_preservesValueAndDoesNotGenerateNewOne()
Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
assertThat(items).hasSize(2);
- WriteModification result =
- extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
+ WriteModification result = extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
Map transformed = result.transformedItem();
assertThat(transformed).isNotNull().hasSize(2);
@@ -133,13 +133,13 @@ public void updateItem_withoutExistingKey_generatesNewUuid() {
Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
assertThat(items).hasSize(1);
- WriteModification result =
- extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
+ WriteModification result = extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
Map transformed = result.transformedItem();
assertThat(transformed).isNotNull().hasSize(2);
@@ -156,13 +156,13 @@ public void updateItem_withMissingKeyAttribute_insertsGeneratedUuid() {
Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
assertThat(items).hasSize(1);
- WriteModification result =
- extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
+ WriteModification result = extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
assertThat(result.transformedItem()).isNotNull();
assertThat(result.updateExpression()).isNull();
@@ -189,8 +189,9 @@ public void nonStringAttributeAnnotatedWithAutoGeneratedKey_throwsIllegalArgumen
.setter(AutogeneratedKeyItem::setSimpleString))
.build()
)
- .withMessage("Attribute 'intAttribute' of Class type class java.lang.Integer is not a suitable Java Class type "
- + "to be used as a Auto Generated Key attribute. Only String Class type is supported.");
+ .withMessage(
+ "Attribute 'intAttribute' of Class type class java.lang.Integer is not a suitable Java Class type "
+ + "to be used as a Auto Generated Key attribute. Only String Class type is supported.");
}
@Test
@@ -200,13 +201,13 @@ public void autoGeneratedKey_onSecondaryPartitionKey_generatesUuid() {
Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
- WriteModification result =
- extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
+ WriteModification result = extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
Map transformed = result.transformedItem();
assertThat(transformed).isNotNull();
@@ -220,13 +221,13 @@ public void autoGeneratedKey_onSecondarySortKey_generatesUuid() {
Map items = LSI_SK_AUTOGEN_SCHEMA.itemToMap(item, true);
- WriteModification result =
- extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(LSI_SK_AUTOGEN_SCHEMA.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
+ WriteModification result = extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(LSI_SK_AUTOGEN_SCHEMA.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
Map transformed = result.transformedItem();
assertThat(transformed).isNotNull();
@@ -241,13 +242,13 @@ public void autoGeneratedKey_onNonKeyAttribute_throwsIllegalArgumentException()
Map items = INVALID_NONKEY_AUTOGEN_SCHEMA.itemToMap(item, true);
assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() ->
- extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(INVALID_NONKEY_AUTOGEN_SCHEMA.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build())
+ .isThrownBy(() -> extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(INVALID_NONKEY_AUTOGEN_SCHEMA.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build())
)
.withMessageContaining("@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
+ "primary partition key, primary sort key, or GSI/LSI partition/sort keys.")
@@ -279,16 +280,17 @@ public void conflictingAnnotations_onSameAttribute_throwsIllegalArgumentExceptio
Map items = conflictingSchema.itemToMap(item, true);
assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() ->
- extension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(conflictingSchema.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build())
+ .isThrownBy(() -> extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(conflictingSchema.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build())
)
- .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ .withMessage(
+ "Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
}
@Test
@@ -320,17 +322,16 @@ public void conflictingAnnotations_onSecondaryKey_throwsIllegalArgumentException
Map items = conflictingGsiSchema.itemToMap(item, true);
assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() ->
- extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(conflictingGsiSchema.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build()))
- .withMessage("Attribute 'keyAttribute' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
- + "annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ .isThrownBy(() -> extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(conflictingGsiSchema.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build()))
+ .withMessage(
+ "Attribute 'keyAttribute' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
}
@Test
@@ -354,17 +355,16 @@ public void conflictDetection_worksRegardlessOfExtensionOrder() {
// Test that the conflict is detected regardless of which extension runs first
assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() ->
- extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(conflictingSchema.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build()))
- .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
- + "annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ .isThrownBy(() -> extension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(conflictingSchema.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build()))
+ .withMessage(
+ "Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
}
@Test
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
index d506fced8d6f..33da228c5472 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
@@ -180,8 +180,9 @@ void IllegalArgumentException_for_AutogeneratedUuid_withNonStringType() {
.setter(ItemWithUuid::setSimpleString))
.build())
- .withMessage("Attribute 'intAttribute' of Class type class java.lang.Integer is not a suitable Java Class type"
- + " to be used as a Auto Generated Uuid attribute. Only String Class type is supported.");
+ .withMessage(
+ "Attribute 'intAttribute' of Class type class java.lang.Integer is not a suitable Java Class type"
+ + " to be used as a Auto Generated Uuid attribute. Only String Class type is supported.");
}
@Test
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
index 7b7d01c7afce..456650ec706f 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
@@ -76,9 +76,9 @@ public void autogeneratedKeyExtensionFirst_detectsConflictWithUuidExtension() {
.operationContext(PRIMARY_CONTEXT)
.build())
)
- .withMessage("Attribute 'autogeneratedKeyField' cannot have both "
- + "@DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ .withMessage(
+ "Attribute 'autogeneratedKeyField' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
+ + "annotations. These annotations have conflicting behaviors and cannot be used together on the same attribute.");
}
@Test
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
index ff811115937b..f2b3283b0650 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
@@ -48,12 +48,9 @@
/**
* Tests for @DynamoDbAutoGeneratedKey annotation functionality.
*
- * Tests cover:
- * - Basic UUID generation on all 4 key types (primary PK/SK, GSI PK/SK)
- * - UpdateBehavior control (WRITE_ALWAYS vs WRITE_IF_NOT_EXISTS) for secondary index keys
- * - Primary key limitations (UpdateBehavior has no effect)
- * - Error handling for invalid usage
- * - Integration with other extensions (VersionedRecord)
+ * Tests cover: - Basic UUID generation on all 4 key types (primary PK/SK, GSI PK/SK) - UpdateBehavior control (WRITE_ALWAYS vs
+ * WRITE_IF_NOT_EXISTS) for secondary index keys - Primary key limitations (UpdateBehavior has no effect) - Error handling for
+ * invalid usage - Integration with other extensions (VersionedRecord)
*/
@RunWith(Parameterized.class)
public class AutoGeneratedKeyRecordTest extends LocalDynamoDbSyncTestBase {
@@ -189,8 +186,10 @@ public void putItem_withAutogeneratedKeySetOnNonKeyAttribute_throwsException() {
Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> table.putItem(record))
- .withMessageContaining("@DynamoDbAutoGeneratedKey can only be applied to key attributes")
- .withMessageContaining("notAKey");
+ .withMessage(
+ "@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
+ + "primary partition key, primary sort key, or GSI/LSI partition/sort keys."
+ + "Invalid placement on attribute: notAKey");
} finally {
deleteTableByName(tableName);
}
@@ -252,9 +251,9 @@ public void autogeneratedConflictingAnnotations_throwsException() {
Assertions
.assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> table.putItem(record))
- .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
- + "annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ .withMessage(
+ "Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
} finally {
deleteTableByName(tableName);
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
index d513df433c35..161cf7b30fd5 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
@@ -16,6 +16,7 @@
package software.amazon.awssdk.enhanced.dynamodb.functionaltests;
import static java.util.stream.Collectors.toList;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue;
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
@@ -328,11 +329,11 @@ public void putItem_onRecordWithAutogeneratedConflictingAnnotations_throwsExcept
ConflictingAnnotationsRecord record = new ConflictingAnnotationsRecord();
record.setPayload("payload");
- Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> table.putItem(record))
- .withMessage("Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
- + "annotations. These annotations have conflicting behaviors and cannot be used together "
- + "on the same attribute.");
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> table.putItem(record))
+ .withMessage(
+ "Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
} finally {
getDynamoDbClient().deleteTable(DeleteTableRequest.builder().tableName(tableName).build());
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
index 6e24fe2d96e0..2e1ca89a0830 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
@@ -278,7 +278,9 @@ public void putItem_whenAnnotationInConflictWithAutogeneratedUuidAnnotation_thro
assertThatThrownBy(() -> mappedTable.putItem(new AutogeneratedKeyConflictingRecord()))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid");
+ .hasMessage(
+ "Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
} finally {
try {
mappedTable.deleteTable();
@@ -298,7 +300,10 @@ public void putItem_whenAnnotationUsedOnNonKeyAttribute_throwsException() {
assertThatThrownBy(() -> mappedTable.putItem(new AutogeneratedKeyOnNonKeyAttributeRecord()))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("@DynamoDbAutoGeneratedKey can only be applied to key attributes");
+ .hasMessage(
+ "@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
+ + "primary partition key, primary sort key, or GSI/LSI partition/sort keys. "
+ + "Invalid placement on attribute: nonKeyAttribute");
} finally {
try {
mappedTable.deleteTable();
@@ -342,8 +347,9 @@ public void putItem_whenNoAutogeneratedKeyAnnotationIsPresent_doesNotRegenerateU
public void createBean_givenAutogeneratedKeyAnnotationAppliedOnNonStringAttributeType_throwsException() {
assertThatThrownBy(() -> TableSchema.fromBean(AutogeneratedKeyInvalidTypeRecord.class))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("not a suitable Java Class type to be used as a Auto Generated Key attribute")
- .hasMessageContaining("Only String Class type is supported");
+ .hasMessage(
+ "Attribute 'id' of Class type class java.lang.Integer is not a suitable Java Class type to be used "
+ + "as a Auto Generated Key attribute. Only String Class type is supported.");
}
@DynamoDbBean
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
index c9a29f0e4563..974d638ed8d9 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
@@ -220,8 +220,9 @@ public void putItem_whenAutogeneratedUuidAnnotationIsNotPresent_doesNotRegenerat
public void createBean_whenAutogeneratedUuidAnnotationIsAppliedOnNonStringAttribute_throwsException() {
assertThatThrownBy(() -> TableSchema.fromBean(AutogeneratedUuidInvalidTypeRecord.class))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("not a suitable Java Class type to be used as a Auto Generated Uuid attribute")
- .hasMessageContaining("Only String Class type is supported");
+ .hasMessage(
+ "Attribute 'id' of Class type class java.lang.Integer is not a suitable Java Class type to be used "
+ + "as a Auto Generated Uuid attribute. Only String Class type is supported.");
}
@DynamoDbBean
From 67cd161eb9cb71f3c5c654761c7927350dbe6a0f Mon Sep 17 00:00:00 2001
From: Ana Satirbasa
Date: Fri, 13 Feb 2026 10:32:03 +0200
Subject: [PATCH 08/17] Addressed PR feedback and refactored tests
---
.../extensions/AutoGeneratedKeyExtension.java | 172 +++++++++++-------
.../AutoGeneratedUuidExtension.java | 51 ++++--
.../extensions/ExtensionsValidationUtils.java | 74 ++++++++
.../AutoGeneratedKeyExtensionTest.java | 8 +-
...ogeneratedConflictingAnnotationsTest.java} | 4 +-
.../ExtensionsValidationUtilsTest.java | 92 ++++++++++
.../AutoGeneratedKeyRecordTest.java | 16 +-
.../AutoGeneratedUuidRecordTest.java | 2 +-
.../AutoGeneratedKeyExtensionTest.java | 2 +-
9 files changed, 321 insertions(+), 100 deletions(-)
create mode 100644 services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java
rename services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/{ConflictingAnnotationsTest.java => AutogeneratedConflictingAnnotationsTest.java} (99%)
create mode 100644 services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtilsTest.java
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
index d09e7bc47174..43a1943b521a 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
@@ -15,6 +15,8 @@
package software.amazon.awssdk.enhanced.dynamodb.extensions;
+import static java.util.Collections.newSetFromMap;
+
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -23,6 +25,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
@@ -39,65 +42,86 @@
import software.amazon.awssdk.utils.Validate;
/**
- * Generates a random UUID (via {@link java.util.UUID#randomUUID()}) for any attribute tagged with
- * {@code @DynamoDbAutoGeneratedKey} when that attribute is missing or empty on a write (put/update).
- *
- * Key Difference from @DynamoDbAutoGeneratedUuid: This extension only generates UUIDs when the
- * attribute value is null or empty, preserving existing values. In contrast, {@code @DynamoDbAutoGeneratedUuid} always generates
- * new UUIDs regardless of existing values.
- *
- * Conflict Detection: This extension cannot be used together with {@code @DynamoDbAutoGeneratedUuid} on the same
- * attribute. If both annotations are applied to the same field, an {@link IllegalArgumentException} will be thrown at runtime to
- * prevent unpredictable behavior based on extension load order.
- *
- * The annotation may be placed only on key attributes:
+ * Generates a random UUID (via {@link java.util.UUID#randomUUID()}) for attributes tagged with {@code @DynamoDbAutoGeneratedKey}
+ * when the attribute value is missing or empty during write operations (put, update, batch write, or transact write).
+ *
+ *
Difference from {@code @DynamoDbAutoGeneratedUuid}:
+ * This extension generates a UUID only when the attribute is null or empty, preserving existing values. In contrast,
+ * {@code @DynamoDbAutoGeneratedUuid} always generates a new UUID regardless of the current value.
+ *
+ *
Conflict Detection:
+ * {@code @DynamoDbAutoGeneratedKey} and {@code @DynamoDbAutoGeneratedUuid} cannot be applied to the same attribute. If both
+ * annotations are present, an {@link IllegalArgumentException} is thrown during schema validation.
+ *
+ *
Supported Attributes:
+ * The annotation may only be applied to key attributes:
*
- *
Primary partition key (PK) or primary sort key (SK)
- *
Partition key or sort key of any secondary index (GSI or LSI)
+ *
Primary partition key (PK)
+ *
Primary sort key (SK)
+ *
Partition or sort keys of secondary indexes (GSI or LSI)
*
*
- *
Validation: The extension enforces this at runtime during {@link #beforeWrite} by comparing the
- * annotated attributes against the table's known key attributes. If an annotated attribute
- * is not a PK/SK or an GSI/LSI, an {@link IllegalArgumentException} is thrown.
+ *
Validation Behavior:
+ * Annotation conflict detection and key-placement validation are performed once per {@link TableMetadata}
+ * instance and cached to avoid repeated validation on subsequent writes.
*
- *
UpdateBehavior Limitations: {@code @DynamoDbUpdateBehavior} has no effect on primary keys due to
- * DynamoDB's UpdateItem API requirements. It only affects secondary index keys.
+ *
UpdateBehavior Limitation:
+ * {@code @DynamoDbUpdateBehavior} does not apply to primary keys due to DynamoDB API constraints.
+ * It only affects secondary index keys.
*/
@SdkPublicApi
@ThreadSafe
public final class AutoGeneratedKeyExtension implements DynamoDbEnhancedClientExtension {
/**
- * Custom metadata key under which we store the set of annotated attribute names.
+ * Metadata keys used to record attributes annotated with {@code @DynamoDbAutoGeneratedKey} and
+ * {@code @DynamoDbAutoGeneratedUuid}. These are used during schema validation to detect annotation conflicts.
*/
private static final String CUSTOM_METADATA_KEY =
"software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
+ private static final String UUID_EXTENSION_METADATA_KEY =
+ "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
+
+ private static final StaticAttributeTag AUTO_GENERATED_KEY_ATTRIBUTE = new AutoGeneratedKeyAttribute();
+ private static final String AUTOGENERATED_KEY_ANNOTATION = "@DynamoDbAutoGeneratedKey";
+ private static final String AUTOGENERATED_UUID_ANNOTATION = "@DynamoDbAutoGeneratedUuid";
/**
- * Metadata key used by AutoGeneratedUuidExtension to detect conflicts.
+ * Stores the TableMetadata instances that have already been validated by this extension. Uses a ConcurrentHashMap to ensure
+ * thread-safe access during concurrent write operations.
*/
- private static final String UUID_EXTENSION_METADATA_KEY =
- "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
+ private final Set validatedSchemas = newSetFromMap(new ConcurrentHashMap<>());
- private static final AutoGeneratedKeyAttribute AUTO_GENERATED_KEY_ATTRIBUTE = new AutoGeneratedKeyAttribute();
+ /**
+ * Caches the set of valid key attribute names per TableMetadata instance. Computed once per schema.
+ */
+ private final Map> allowedKeysCache = new ConcurrentHashMap<>();
private AutoGeneratedKeyExtension() {
}
+ /**
+ * @return an Instance of {@link AutoGeneratedKeyExtension}
+ */
+ public static AutoGeneratedKeyExtension create() {
+ return new AutoGeneratedKeyExtension();
+ }
+
public static Builder builder() {
return new Builder();
}
/**
- * If this table has attributes tagged for auto-generation, insert a UUID value into the outgoing item for any such attribute
- * that is currently missing/empty. Unlike {@code @DynamoDbAutoGeneratedUuid}, this preserves existing values.
- *
- * Also validates that the annotation is only used on PK/SK/GSI/LSI key attributes and that there are no conflicts with
+ * Inserts a UUID for attributes tagged with {@code @DynamoDbAutoGeneratedKey} when the attribute is null or empty, preserving
+ * existing values.
*
- * @DynamoDbAutoGeneratedUuid.
+ *
Schema-level validation (annotation conflict detection and key-placement checks)
+ * is executed once per schema instance and cached.
*/
@Override
public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
+
+
Collection taggedAttributes = context.tableMetadata()
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class)
.orElse(null);
@@ -106,44 +130,12 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
return WriteModification.builder().build();
}
- // Check for conflicts with @DynamoDbAutoGeneratedUuid
- Collection uuidTaggedAttributes = context.tableMetadata()
- .customMetadataObject(UUID_EXTENSION_METADATA_KEY, Collection.class)
- .orElse(Collections.emptyList());
-
- taggedAttributes.stream()
- .filter(uuidTaggedAttributes::contains)
- .findFirst()
- .ifPresent(attribute -> {
- throw new IllegalArgumentException(
- "Attribute '" + attribute + "' cannot have both @DynamoDbAutoGeneratedKey and "
- + "@DynamoDbAutoGeneratedUuid annotations. These annotations have conflicting behaviors "
- + "and cannot be used together on the same attribute.");
- });
-
- TableMetadata metadata = context.tableMetadata();
- Set allowedKeys = new HashSet<>();
-
- // ensure every @DynamoDbAutoGeneratedKey attribute is a PK/SK or GSI/LSI. If not, throw IllegalArgumentException
- allowedKeys.add(metadata.primaryPartitionKey());
- metadata.primarySortKey().ifPresent(allowedKeys::add);
-
- metadata.indices().stream().map(IndexMetadata::name).forEach(indexName -> {
- allowedKeys.add(metadata.indexPartitionKey(indexName));
- metadata.indexSortKey(indexName).ifPresent(allowedKeys::add);
- });
+ TableMetadata tableMetadata = context.tableMetadata();
+ if (validatedSchemas.add(tableMetadata)) {
+ validateNoAutoGeneratedAnnotationConflict(tableMetadata);
+ validateAutoGeneratedKeyPlacement(tableMetadata, taggedAttributes);
+ }
- taggedAttributes.stream()
- .filter(attr -> !allowedKeys.contains(attr))
- .findFirst()
- .ifPresent(attr -> {
- throw new IllegalArgumentException(
- "@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
- + "primary partition key, primary sort key, or GSI/LSI partition/sort keys. "
- + "Invalid placement on attribute: " + attr);
- });
-
- // Generate UUIDs for missing/empty annotated attributes
Map itemToTransform = new HashMap<>(context.items());
taggedAttributes.forEach(attr -> insertUuidIfMissing(itemToTransform, attr));
@@ -152,6 +144,52 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
.build();
}
+ /**
+ * Validates (once per TableMetadata instance) that @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid are not applied
+ * to the same attribute.
+ */
+ private static void validateNoAutoGeneratedAnnotationConflict(TableMetadata tableMetadata) {
+ ExtensionsValidationUtils.validateNoAnnotationConflict(
+ tableMetadata,
+ CUSTOM_METADATA_KEY,
+ UUID_EXTENSION_METADATA_KEY,
+ AUTOGENERATED_KEY_ANNOTATION,
+ AUTOGENERATED_UUID_ANNOTATION
+ );
+ }
+
+ /**
+ * Validates that all attributes tagged with @DynamoDbAutoGeneratedKey are either primary keys or secondary index keys.
+ **/
+ private void validateAutoGeneratedKeyPlacement(TableMetadata tableMetadata,
+ Collection taggedAttributeNames) {
+
+ Set allowedKeyAttributes = allowedKeysCache.computeIfAbsent(tableMetadata, metadata -> {
+ Set keyAttributes = new HashSet<>();
+ keyAttributes.add(metadata.primaryPartitionKey());
+ metadata.primarySortKey().ifPresent(keyAttributes::add);
+
+ metadata.indices().stream()
+ .map(IndexMetadata::name)
+ .forEach(indexName -> {
+ keyAttributes.add(metadata.indexPartitionKey(indexName));
+ metadata.indexSortKey(indexName).ifPresent(keyAttributes::add);
+ });
+
+ return keyAttributes;
+ });
+
+ taggedAttributeNames.stream()
+ .filter(attrName -> !allowedKeyAttributes.contains(attrName))
+ .findFirst()
+ .ifPresent(invalidAttribute -> {
+ throw new IllegalArgumentException(
+ "@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
+ + "primary partition key, primary sort key, or GSI/LSI partition/sort keys. "
+ + "Invalid placement on attribute: " + invalidAttribute);
+ });
+ }
+
private void insertUuidIfMissing(Map itemToTransform, String key) {
AttributeValue existing = itemToTransform.get(key);
if (Objects.isNull(existing) || StringUtils.isBlank(existing.s())) {
@@ -210,7 +248,7 @@ public void validateType(String attributeName,
@Override
public Consumer modifyMetadata(String attributeName,
AttributeValueType attributeValueType) {
- // Record the names of the attributes annotated with @DynamoDbAutoGeneratedKey for later lookup in beforeWrite()
+ // Records attribute names annotated with @DynamoDbAutoGeneratedKey for lookup during write processing.
return metadata -> metadata.addCustomMetadataObject(
CUSTOM_METADATA_KEY, Collections.singleton(attributeName));
}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
index 884e12c20f89..e3f7b8922d07 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
@@ -15,11 +15,15 @@
package software.amazon.awssdk.enhanced.dynamodb.extensions;
+import static java.util.Collections.newSetFromMap;
+
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
@@ -27,6 +31,7 @@
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbExtensionContext;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
+import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
@@ -85,17 +90,24 @@
@SdkPublicApi
@ThreadSafe
public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientExtension {
- private static final String CUSTOM_METADATA_KEY =
- "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
/**
- * Metadata key used by AutoGeneratedKeyExtension to detect conflicts.
+ * Custom metadata keys under which AutoGeneratedUuidExtension/AutoGeneratedKeyExtension record attributes annotated with
+ * {@code @DynamoDbAutoGeneratedUuid}/{@code @DynamoDbAutoGeneratedKey}. Used to detect conflicts during schema validation.
*/
- private static final String KEY_EXTENSION_METADATA_KEY =
+ private static final String CUSTOM_METADATA_KEY =
+ "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
+ private static final String AUTOGENERATED_KEY_EXTENSION_METADATA_KEY =
"software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ATTRIBUTE = new AutoGeneratedUuidAttribute();
+ /**
+ * Stores the TableMetadata instances that have already been validated by this extension. Uses a ConcurrentHashMap to ensure
+ * thread-safe access during concurrent write operations.
+ */
+ private final Set validatedSchemas = newSetFromMap(new ConcurrentHashMap<>());
+
private AutoGeneratedUuidExtension() {
}
@@ -124,20 +136,8 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
return WriteModification.builder().build();
}
- // Check for conflicts with @DynamoDbAutoGeneratedKey
- Collection keyTaggedAttributes = context.tableMetadata()
- .customMetadataObject(KEY_EXTENSION_METADATA_KEY, Collection.class)
- .orElse(Collections.emptyList());
-
- customMetadataObject.stream()
- .filter(keyTaggedAttributes::contains)
- .findFirst()
- .ifPresent(attribute -> {
- throw new IllegalArgumentException(
- "Attribute '" + attribute + "' cannot have both @DynamoDbAutoGeneratedKey and "
- + "@DynamoDbAutoGeneratedUuid annotations. These annotations have conflicting behaviors "
- + "and cannot be used together on the same attribute.");
- });
+ TableMetadata metadata = context.tableMetadata();
+ validateNoAnnotationConflict(metadata);
Map itemToTransform = new HashMap<>(context.items());
customMetadataObject.forEach(key -> insertUuidInItemToTransform(itemToTransform, key));
@@ -146,6 +146,21 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
.build();
}
+ /**
+ * Validates (once per TableMetadata instance) that @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid are not applied
+ * to the same attribute.
+ */
+ private void validateNoAnnotationConflict(TableMetadata tableMetadata) {
+ if (validatedSchemas.add(tableMetadata)) {
+ ExtensionsValidationUtils.validateNoAnnotationConflict(
+ tableMetadata,
+ AUTOGENERATED_KEY_EXTENSION_METADATA_KEY,
+ CUSTOM_METADATA_KEY,
+ "@DynamoDbAutoGeneratedKey",
+ "@DynamoDbAutoGeneratedUuid");
+ }
+ }
+
private void insertUuidInItemToTransform(Map itemToTransform,
String key) {
itemToTransform.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java
new file mode 100644
index 000000000000..1b7a81e2ed6a
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.extensions;
+
+import java.util.Collection;
+import java.util.Collections;
+import software.amazon.awssdk.annotations.SdkProtectedApi;
+import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
+
+/**
+ * Provides shared schema validation utilities for DynamoDB enhanced client extensions.
+ */
+@SdkProtectedApi
+public final class ExtensionsValidationUtils {
+
+ private ExtensionsValidationUtils() {
+ }
+
+ /**
+ * Validates that there are no attributes that have both annotations. These annotations have conflicting behaviors and cannot
+ * be used together on the same attribute. If an attribute is found with both annotations, an IllegalArgumentException is
+ * thrown with a message indicating the attribute and the conflicting annotations.
+ *
+ * @param tableMetadata The metadata of the table to validate.
+ * @param firstMetadataKey The metadata key for the first annotation to check for.
+ * @param secondMetadataKey The metadata key for the second annotation to check for.
+ * @param firstAnnotationName The name of the first annotation to use in the error message if a conflict is found.
+ * @param secondAnnotationName The name of the second annotation to use in the error message if a conflict is found.
+ */
+ public static void validateNoAnnotationConflict(TableMetadata tableMetadata,
+ String firstMetadataKey,
+ String secondMetadataKey,
+ String firstAnnotationName,
+ String secondAnnotationName) {
+
+ Collection> attributesWithFirstAnnotation =
+ tableMetadata.customMetadataObject(firstMetadataKey, Collection.class).orElse(Collections.emptyList());
+
+ if (attributesWithFirstAnnotation.isEmpty()) {
+ return;
+ }
+
+ Collection> attributesWithSecondAnnotation =
+ tableMetadata.customMetadataObject(secondMetadataKey, Collection.class).orElse(Collections.emptyList());
+
+ if (attributesWithSecondAnnotation.isEmpty()) {
+ return;
+ }
+
+ attributesWithFirstAnnotation
+ .stream()
+ .filter(attributesWithSecondAnnotation::contains)
+ .findFirst()
+ .ifPresent(attribute -> {
+ throw new IllegalArgumentException(
+ "Attribute '" + attribute + "' cannot have both " + firstAnnotationName
+ + " and " + secondAnnotationName + " annotations. "
+ + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ });
+ }
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
index dd059c9a271c..0720ffa99d8c 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
@@ -34,7 +34,7 @@ public class AutoGeneratedKeyExtensionTest {
private static final OperationContext PRIMARY_CONTEXT =
DefaultOperationContext.create(TABLE_NAME, TableMetadata.primaryIndexName());
- private final AutoGeneratedKeyExtension extension = AutoGeneratedKeyExtension.builder().build();
+ private final AutoGeneratedKeyExtension extension = AutoGeneratedKeyExtension.create();
/**
* Schema that places @DynamoDbAutoGeneratedKey on GSI key ("keyAttribute") -> the validation passes.
@@ -250,9 +250,9 @@ public void autoGeneratedKey_onNonKeyAttribute_throwsIllegalArgumentException()
.operationContext(PRIMARY_CONTEXT)
.build())
)
- .withMessageContaining("@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
- + "primary partition key, primary sort key, or GSI/LSI partition/sort keys.")
- .withMessageContaining("keyAttribute");
+ .withMessage("@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
+ + "primary partition key, primary sort key, or GSI/LSI partition/sort keys. "
+ + "Invalid placement on attribute: keyAttribute");
}
@Test
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutogeneratedConflictingAnnotationsTest.java
similarity index 99%
rename from services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
rename to services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutogeneratedConflictingAnnotationsTest.java
index 456650ec706f..933bc9f64f86 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ConflictingAnnotationsTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutogeneratedConflictingAnnotationsTest.java
@@ -34,14 +34,14 @@
* Tests to verify that conflicting annotations (@DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid) are properly detected
* and throw exceptions regardless of extension load order.
*/
-public class ConflictingAnnotationsTest {
+public class AutogeneratedConflictingAnnotationsTest {
private static final String RECORD_ID = "1";
private static final String TABLE_NAME = "table-name";
private static final OperationContext PRIMARY_CONTEXT =
DefaultOperationContext.create(TABLE_NAME, TableMetadata.primaryIndexName());
- private final AutoGeneratedKeyExtension keyExtension = AutoGeneratedKeyExtension.builder().build();
+ private final AutoGeneratedKeyExtension keyExtension = AutoGeneratedKeyExtension.create();
private final AutoGeneratedUuidExtension uuidExtension = AutoGeneratedUuidExtension.create();
/**
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtilsTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtilsTest.java
new file mode 100644
index 000000000000..bc78b5b45f06
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtilsTest.java
@@ -0,0 +1,92 @@
+package software.amazon.awssdk.enhanced.dynamodb.extensions;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.when;
+import static software.amazon.awssdk.enhanced.dynamodb.extensions.ExtensionsValidationUtils.validateNoAnnotationConflict;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ExtensionsValidationUtilsTest {
+
+ @Mock
+ private TableMetadata metadata;
+
+ private static final String AUTOGENERATED_KEY_METADATA_KEY =
+ "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
+ private static final String AUTOGENERATED_UUID_METADATA_KEY =
+ "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
+
+ private static final String AUTOGENERATED_KEY_ANNOTATION = "@DynamoDbAutoGeneratedKey";
+ private static final String AUTOGENERATED_UUID_ANNOTATION = "@DynamoDbAutoGeneratedUuid";
+
+ @Test
+ public void validateNoAnnotationConflict_whenAnnotationsOverlap_throwsException() {
+ when(metadata.customMetadataObject(AUTOGENERATED_KEY_METADATA_KEY, Collection.class))
+ .thenReturn(Optional.of(Collections.singleton("sharedAttribute")));
+ when(metadata.customMetadataObject(AUTOGENERATED_UUID_METADATA_KEY, Collection.class))
+ .thenReturn(Optional.of(Arrays.asList("sharedAttribute", "otherUuidAttribute")));
+
+ assertThatThrownBy(() -> validateNoAnnotationConflict(
+ metadata,
+ AUTOGENERATED_KEY_METADATA_KEY,
+ AUTOGENERATED_UUID_METADATA_KEY,
+ AUTOGENERATED_KEY_ANNOTATION,
+ AUTOGENERATED_UUID_ANNOTATION
+ ))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(
+ "Attribute 'sharedAttribute' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
+ + "annotations. These annotations have conflicting behaviors and cannot be used together on the same attribute.");
+ }
+
+ @Test
+ public void validateNoAnnotationConflict_whenNoAnnotatedFields_doesNotThrowException() {
+ assertThatCode(() -> validateNoAnnotationConflict(
+ metadata,
+ AUTOGENERATED_KEY_METADATA_KEY,
+ AUTOGENERATED_UUID_METADATA_KEY,
+ AUTOGENERATED_KEY_ANNOTATION,
+ AUTOGENERATED_UUID_ANNOTATION
+ )).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void validateNoAnnotationConflict_whenAnnotationsDontOverlap_doesNotThrowException() {
+ when(metadata.customMetadataObject(AUTOGENERATED_KEY_METADATA_KEY, Collection.class))
+ .thenReturn(Optional.of(Collections.singleton("keyAttribute")));
+ when(metadata.customMetadataObject(AUTOGENERATED_UUID_METADATA_KEY, Collection.class))
+ .thenReturn(Optional.of(Collections.singleton("uuidAttribute")));
+
+ assertThatCode(() -> validateNoAnnotationConflict(
+ metadata,
+ AUTOGENERATED_KEY_METADATA_KEY,
+ AUTOGENERATED_UUID_METADATA_KEY,
+ AUTOGENERATED_KEY_ANNOTATION,
+ AUTOGENERATED_UUID_ANNOTATION
+ )).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void validateNoAnnotationConflict_whenSecondAnnotationIsNotUsed_doesNotThrow() {
+ when(metadata.customMetadataObject(AUTOGENERATED_KEY_METADATA_KEY, Collection.class))
+ .thenReturn(Optional.empty());
+
+ assertThatCode(() -> validateNoAnnotationConflict(
+ metadata,
+ AUTOGENERATED_KEY_METADATA_KEY,
+ AUTOGENERATED_UUID_METADATA_KEY,
+ AUTOGENERATED_KEY_ANNOTATION,
+ AUTOGENERATED_UUID_ANNOTATION
+ )).doesNotThrowAnyException();
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
index f2b3283b0650..2902465297f2 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
@@ -60,7 +60,7 @@ public class AutoGeneratedKeyRecordTest extends LocalDynamoDbSyncTestBase {
public AutoGeneratedKeyRecordTest(String testName, TableSchema schema) {
this.mappedTable = DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
- .extensions(AutoGeneratedKeyExtension.builder().build())
+ .extensions(AutoGeneratedKeyExtension.create())
.build()
.table(getConcreteTableName("AutoGenKey-table"), schema);
}
@@ -188,7 +188,7 @@ public void putItem_withAutogeneratedKeySetOnNonKeyAttribute_throwsException() {
.isThrownBy(() -> table.putItem(record))
.withMessage(
"@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
- + "primary partition key, primary sort key, or GSI/LSI partition/sort keys."
+ + "primary partition key, primary sort key, or GSI/LSI partition/sort keys. "
+ "Invalid placement on attribute: notAKey");
} finally {
deleteTableByName(tableName);
@@ -201,8 +201,9 @@ public void versionedRecord_worksWithAutoGeneratedKeys() {
String tableName = getConcreteTableName("versioned-test");
DynamoDbEnhancedClient client = DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
- .extensions(AutoGeneratedKeyExtension.builder().build(),
- VersionedRecordExtension.builder().build())
+ .extensions(
+ AutoGeneratedKeyExtension.builder().build(),
+ VersionedRecordExtension.builder().build())
.build();
DynamoDbTable table = client.table(tableName, TableSchema.fromBean(VersionedRecord.class));
@@ -236,8 +237,9 @@ public void autogeneratedConflictingAnnotations_throwsException() {
String tableName = getConcreteTableName("conflicting-annotations-test");
DynamoDbEnhancedClient client = DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
- .extensions(AutoGeneratedKeyExtension.builder().build(),
- AutoGeneratedUuidExtension.create())
+ .extensions(
+ AutoGeneratedKeyExtension.create(),
+ AutoGeneratedUuidExtension.create())
.build();
try {
@@ -376,7 +378,7 @@ public void batchWrite_mixedPutDeleteOperations() {
private DynamoDbEnhancedClient createClient() {
return DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
- .extensions(AutoGeneratedKeyExtension.builder().build())
+ .extensions(AutoGeneratedKeyExtension.create())
.build();
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
index 161cf7b30fd5..71f79bf7851e 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
@@ -318,7 +318,7 @@ public void putItem_onRecordWithAutogeneratedConflictingAnnotations_throwsExcept
DynamoDbEnhancedClient client =
DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
- .extensions(AutoGeneratedUuidExtension.create(), AutoGeneratedKeyExtension.builder().build())
+ .extensions(AutoGeneratedUuidExtension.create(), AutoGeneratedKeyExtension.create())
.build();
try {
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
index 2e1ca89a0830..de98bb27c519 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedKeyExtensionTest.java
@@ -54,7 +54,7 @@ public class AutoGeneratedKeyExtensionTest extends LocalDynamoDbSyncTestBase {
.dynamoDbClient(getDynamoDbClient())
.extensions(Stream.concat(
ExtensionResolver.defaultExtensions().stream(),
- Stream.of(AutoGeneratedKeyExtension.builder().build()))
+ Stream.of(AutoGeneratedKeyExtension.create()))
.collect(Collectors.toList()))
.build();
From a4bfb1e6134f0246447b64da5e249cbb6ad00af4 Mon Sep 17 00:00:00 2001
From: Ana Satirbasa
Date: Fri, 13 Feb 2026 12:11:20 +0200
Subject: [PATCH 09/17] Addressed PR feedback
---
.../AutoGeneratedUuidExtension.java | 6 ++-
.../extensions/ExtensionsValidationUtils.java | 24 +++++-----
.../annotations/DynamoDbAutoGeneratedKey.java | 47 ++++---------------
.../DynamoDbAutoGeneratedUuid.java | 18 +++++--
4 files changed, 41 insertions(+), 54 deletions(-)
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
index e3f7b8922d07..9c8e55d82a10 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
@@ -101,6 +101,8 @@ public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientE
"software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ATTRIBUTE = new AutoGeneratedUuidAttribute();
+ private static final String AUTOGENERATED_KEY_ANNOTATION = "@DynamoDbAutoGeneratedKey";
+ private static final String AUTOGENERATED_UUID_ANNOTATION = "@DynamoDbAutoGeneratedUuid";
/**
* Stores the TableMetadata instances that have already been validated by this extension. Uses a ConcurrentHashMap to ensure
@@ -156,8 +158,8 @@ private void validateNoAnnotationConflict(TableMetadata tableMetadata) {
tableMetadata,
AUTOGENERATED_KEY_EXTENSION_METADATA_KEY,
CUSTOM_METADATA_KEY,
- "@DynamoDbAutoGeneratedKey",
- "@DynamoDbAutoGeneratedUuid");
+ AUTOGENERATED_KEY_ANNOTATION,
+ AUTOGENERATED_UUID_ANNOTATION);
}
}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java
index 1b7a81e2ed6a..4a04286e2ece 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java
@@ -35,34 +35,34 @@ private ExtensionsValidationUtils() {
* thrown with a message indicating the attribute and the conflicting annotations.
*
* @param tableMetadata The metadata of the table to validate.
- * @param firstMetadataKey The metadata key for the first annotation to check for.
- * @param secondMetadataKey The metadata key for the second annotation to check for.
+ * @param firstAnnotationMetadataKey The metadata key for the first annotation to check for.
+ * @param secondAnnotationMetadataKey The metadata key for the second annotation to check for.
* @param firstAnnotationName The name of the first annotation to use in the error message if a conflict is found.
* @param secondAnnotationName The name of the second annotation to use in the error message if a conflict is found.
*/
public static void validateNoAnnotationConflict(TableMetadata tableMetadata,
- String firstMetadataKey,
- String secondMetadataKey,
+ String firstAnnotationMetadataKey,
+ String secondAnnotationMetadataKey,
String firstAnnotationName,
String secondAnnotationName) {
- Collection> attributesWithFirstAnnotation =
- tableMetadata.customMetadataObject(firstMetadataKey, Collection.class).orElse(Collections.emptyList());
+ Collection> attributesHavingFirstAnnotation =
+ tableMetadata.customMetadataObject(firstAnnotationMetadataKey, Collection.class).orElse(Collections.emptyList());
- if (attributesWithFirstAnnotation.isEmpty()) {
+ if (attributesHavingFirstAnnotation.isEmpty()) {
return;
}
- Collection> attributesWithSecondAnnotation =
- tableMetadata.customMetadataObject(secondMetadataKey, Collection.class).orElse(Collections.emptyList());
+ Collection> attributesHavingSecondAnnotation =
+ tableMetadata.customMetadataObject(secondAnnotationMetadataKey, Collection.class).orElse(Collections.emptyList());
- if (attributesWithSecondAnnotation.isEmpty()) {
+ if (attributesHavingSecondAnnotation.isEmpty()) {
return;
}
- attributesWithFirstAnnotation
+ attributesHavingFirstAnnotation
.stream()
- .filter(attributesWithSecondAnnotation::contains)
+ .filter(attributesHavingSecondAnnotation::contains)
.findFirst()
.ifPresent(attribute -> {
throw new IllegalArgumentException(
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
index 6fbb90d5a0c8..cdad8d30616b 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
@@ -27,47 +27,20 @@
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
/**
- * Annotation that marks a key attribute to be automatically populated with a random UUID if no value is provided during a write
- * operation (put or update). This annotation is intended to work specifically with key attributes.
+ * Marks a key attribute to be automatically populated with a UUID when the value is null or empty during a write operation.
*
- *
This annotation is designed for use with the V2 {@link software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema}.
- * It is registered via {@link BeanTableSchemaAttributeTag} and its behavior is implemented by
- * {@link software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension}.
+ *
Usage: May only be applied to key attributes: primary partition key, primary sort key, or secondary index
+ * (GSI/LSI) keys. If used on a non-key attribute, {@code AutoGeneratedKeyExtension} throws an {@link IllegalArgumentException}
+ * during schema validation.
*
- *
Where this annotation can be applied
- * This annotation is only valid on attributes that serve as keys:
- *
- *
The table's primary partition key or sort key
- *
The partition key or sort key of a secondary index (GSI or LSI)
- *
- * If applied to any other attribute, the {@code AutoGeneratedKeyExtension} will throw an
- * {@link IllegalArgumentException} at runtime.
+ *
Semantics: Generates a UUID using {@link java.util.UUID#randomUUID()}
+ * only when the attribute is absent. Existing values are preserved.
*
- *
How values are generated
- *
- *
On writes where the annotated attribute is null or empty, a new UUID value is generated
- * using {@link java.util.UUID#randomUUID()}.
- *
If a value is already set on the attribute, that value is preserved and not replaced.
- *
This behavior differs from {@code @DynamoDbAutoGeneratedUuid}, which always generates new UUIDs regardless of existing
- * values.
- *
+ *
Difference from {@code @DynamoDbAutoGeneratedUuid}:
+ * This annotation is intended for key attributes and generates a value only when missing. {@code @DynamoDbAutoGeneratedUuid} can
+ * be applied to any attribute and always generates a new UUID on every write.
*
- *
Behavior with UpdateBehavior
- *
Primary Keys: {@link DynamoDbUpdateBehavior} has no effect on primary partition keys
- * or primary sort keys. Primary keys are immutable in DynamoDB and cannot use conditional update behaviors like
- * {@link UpdateBehavior#WRITE_IF_NOT_EXISTS}. UUIDs will be generated whenever the primary key attribute is missing
- * or empty, regardless of any {@code UpdateBehavior} setting.
- *
- *
Secondary Index Keys: For GSI/LSI keys, {@link DynamoDbUpdateBehavior} can be used:
- *
- *
{@link UpdateBehavior#WRITE_ALWAYS} (default) – Generate a new UUID whenever the attribute is missing during write.
- *
{@link UpdateBehavior#WRITE_IF_NOT_EXISTS} – Generate a UUID only on the first write, preserving the value on
- * subsequent updates.
- *
- *
- *
- *
Type restriction
- * This annotation is only valid on attributes of type {@link String}.
+ *
Valid only for {@link String} attributes.
*/
@SdkPublicApi
@Documented
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java
index 6df85903c20a..5381cd77227b 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java
@@ -19,14 +19,26 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.util.UUID;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.AutoGeneratedUuidTag;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.BeanTableSchemaAttributeTag;
/**
- * Denotes this attribute as recording the auto generated UUID string for the record. Every time a record with this
- * attribute is written to the database it will update the attribute with a {@link UUID#randomUUID} string.
+ * Marks an attribute to be automatically populated with a new UUID on every write operation.
+ *
+ *
Intended Usage:
+ * This annotation is generic and may be applied to any {@link String} attribute, not only key attributes.
+ *
+ *
Generation Semantics:
+ * On every write (put, update, batch write, or transaction write), the attribute is replaced with a new value generated using
+ * {@link java.util.UUID#randomUUID()}, regardless of any existing value.
+ *
+ *
Difference from {@code @DynamoDbAutoGeneratedKey}:
+ * {@code @DynamoDbAutoGeneratedKey} is intended for key attributes and generates UUID only when the value is absent.
+ * {@code @DynamoDbAutoGeneratedUuid} annotation always regenerates the UUID on each write.
Difference from {@code @DynamoDbAutoGeneratedUuid}:
- * This extension generates a UUID only when the attribute is null or empty, preserving existing values. In contrast,
- * {@code @DynamoDbAutoGeneratedUuid} always generates a new UUID regardless of the current value.
- *
- *
Conflict Detection:
- * {@code @DynamoDbAutoGeneratedKey} and {@code @DynamoDbAutoGeneratedUuid} cannot be applied to the same attribute. If both
- * annotations are present, an {@link IllegalArgumentException} is thrown during schema validation.
- *
- *
Supported Attributes:
- * The annotation may only be applied to key attributes:
- *
- *
Primary partition key (PK)
- *
Primary sort key (SK)
- *
Partition or sort keys of secondary indexes (GSI or LSI)
- *
- *
- *
Validation Behavior:
- * Annotation conflict detection and key-placement validation are performed once per {@link TableMetadata}
- * instance and cached to avoid repeated validation on subsequent writes.
- *
- *
UpdateBehavior Limitation:
- * {@code @DynamoDbUpdateBehavior} does not apply to primary keys due to DynamoDB API constraints.
- * It only affects secondary index keys.
- */
-@SdkPublicApi
-@ThreadSafe
-public final class AutoGeneratedKeyExtension implements DynamoDbEnhancedClientExtension {
-
- /**
- * Metadata keys used to record attributes annotated with {@code @DynamoDbAutoGeneratedKey} and
- * {@code @DynamoDbAutoGeneratedUuid}. These are used during schema validation to detect annotation conflicts.
- */
- private static final String CUSTOM_METADATA_KEY =
- "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
- private static final String UUID_EXTENSION_METADATA_KEY =
- "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
-
- private static final StaticAttributeTag AUTO_GENERATED_KEY_ATTRIBUTE = new AutoGeneratedKeyAttribute();
- private static final String AUTOGENERATED_KEY_ANNOTATION = "@DynamoDbAutoGeneratedKey";
- private static final String AUTOGENERATED_UUID_ANNOTATION = "@DynamoDbAutoGeneratedUuid";
-
- /**
- * Stores the TableMetadata instances that have already been validated by this extension. Uses a ConcurrentHashMap to ensure
- * thread-safe access during concurrent write operations.
- */
- private final Set validatedSchemas = newSetFromMap(new ConcurrentHashMap<>());
-
- /**
- * Caches the set of valid key attribute names per TableMetadata instance. Computed once per schema.
- */
- private final Map> allowedKeysCache = new ConcurrentHashMap<>();
-
- private AutoGeneratedKeyExtension() {
- }
-
- /**
- * @return an Instance of {@link AutoGeneratedKeyExtension}
- */
- public static AutoGeneratedKeyExtension create() {
- return new AutoGeneratedKeyExtension();
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- /**
- * Inserts a UUID for attributes tagged with {@code @DynamoDbAutoGeneratedKey} when the attribute is null or empty, preserving
- * existing values.
- *
- *
Schema-level validation (annotation conflict detection and key-placement checks)
- * is executed once per schema instance and cached.
- */
- @Override
- public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
-
-
- Collection taggedAttributes = context.tableMetadata()
- .customMetadataObject(CUSTOM_METADATA_KEY, Collection.class)
- .orElse(null);
-
- if (taggedAttributes == null) {
- return WriteModification.builder().build();
- }
-
- TableMetadata tableMetadata = context.tableMetadata();
- if (validatedSchemas.add(tableMetadata)) {
- validateNoAutoGeneratedAnnotationConflict(tableMetadata);
- validateAutoGeneratedKeyPlacement(tableMetadata, taggedAttributes);
- }
-
- Map itemToTransform = new HashMap<>(context.items());
- taggedAttributes.forEach(attr -> insertUuidIfMissing(itemToTransform, attr));
-
- return WriteModification.builder()
- .transformedItem(Collections.unmodifiableMap(itemToTransform))
- .build();
- }
-
- /**
- * Validates (once per TableMetadata instance) that @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid are not applied
- * to the same attribute.
- */
- private static void validateNoAutoGeneratedAnnotationConflict(TableMetadata tableMetadata) {
- ExtensionsValidationUtils.validateNoAnnotationConflict(
- tableMetadata,
- CUSTOM_METADATA_KEY,
- UUID_EXTENSION_METADATA_KEY,
- AUTOGENERATED_KEY_ANNOTATION,
- AUTOGENERATED_UUID_ANNOTATION
- );
- }
-
- /**
- * Validates that all attributes tagged with @DynamoDbAutoGeneratedKey are either primary keys or secondary index keys.
- **/
- private void validateAutoGeneratedKeyPlacement(TableMetadata tableMetadata,
- Collection taggedAttributeNames) {
-
- Set allowedKeyAttributes = allowedKeysCache.computeIfAbsent(tableMetadata, metadata -> {
-
- // Add primary keys
- Set keyAttributes = new HashSet<>(metadata.primaryKeys());
-
- // Add all secondary index keys
- metadata.indices().stream().map(IndexMetadata::name).forEach(indexName -> {
- keyAttributes.addAll(metadata.indexPartitionKeys(indexName));
- keyAttributes.addAll(metadata.indexSortKeys(indexName));
- });
-
- return keyAttributes;
- });
-
- taggedAttributeNames.stream()
- .filter(attrName -> !allowedKeyAttributes.contains(attrName))
- .findFirst()
- .ifPresent(invalidAttribute -> {
- throw new IllegalArgumentException(
- "@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
- + "primary partition key, primary sort key, or GSI/LSI partition/sort keys. "
- + "Invalid placement on attribute: " + invalidAttribute);
- });
- }
-
- private void insertUuidIfMissing(Map itemToTransform, String key) {
- AttributeValue existing = itemToTransform.get(key);
- if (Objects.isNull(existing) || StringUtils.isBlank(existing.s())) {
- itemToTransform.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
- }
- }
-
- /**
- * Static helpers used by the {@code @BeanTableSchemaAttributeTag}-based annotation tag.
- */
- public static final class AttributeTags {
- private AttributeTags() {
- }
-
- /**
- * @return a {@link StaticAttributeTag} that marks the attribute for auto-generated key behavior.
- */
- public static StaticAttributeTag autoGeneratedKeyAttribute() {
- return AUTO_GENERATED_KEY_ATTRIBUTE;
- }
- }
-
- /**
- * Stateless builder.
- */
- public static final class Builder {
- private Builder() {
- }
-
- public AutoGeneratedKeyExtension build() {
- return new AutoGeneratedKeyExtension();
- }
- }
-
- /**
- * Validates Java type and records the tagged attribute into table metadata so {@link #beforeWrite} can find it at runtime.
- */
- private static final class AutoGeneratedKeyAttribute implements StaticAttributeTag {
-
- @Override
- public void validateType(String attributeName,
- EnhancedType type,
- AttributeValueType attributeValueType) {
-
- Validate.notNull(type, "type is null");
- Validate.notNull(type.rawClass(), "rawClass is null");
- Validate.notNull(attributeValueType, "attributeValueType is null");
-
- if (!type.rawClass().equals(String.class)) {
- throw new IllegalArgumentException(String.format(
- "Attribute '%s' of Class type %s is not a suitable Java Class type to be used as a Auto Generated "
- + "Key attribute. Only String Class type is supported.", attributeName, type.rawClass()));
- }
- }
-
- @Override
- public Consumer modifyMetadata(String attributeName,
- AttributeValueType attributeValueType) {
- return metadata -> metadata.addCustomMetadataObject(CUSTOM_METADATA_KEY, Collections.singleton(attributeName))
- .markAttributeAsKey(attributeName, attributeValueType);
- }
- }
-}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
index 9c8e55d82a10..7e6b9baddfe8 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
@@ -15,15 +15,10 @@
package software.amazon.awssdk.enhanced.dynamodb.extensions;
-import static java.util.Collections.newSetFromMap;
-
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
@@ -31,26 +26,17 @@
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbExtensionContext;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
-import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGenerateStrategy;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.utils.Validate;
/**
- * This extension facilitates the automatic generation of a unique UUID (Universally Unique Identifier) for a specified attribute
- * every time a new record is written to the database. The generated UUID is obtained using the
- * {@link java.util.UUID#randomUUID()} method.
- *
- * Key Difference from @DynamoDbAutoGeneratedKey: This extension always generates new UUIDs on every write,
- * regardless of existing values. In contrast, {@code @DynamoDbAutoGeneratedKey} only generates UUIDs when the attribute value is
- * null or empty, preserving existing values.
- *
- * Conflict Detection: This extension cannot be used together with {@code @DynamoDbAutoGeneratedKey} on the same
- * attribute. If both annotations are applied to the same field, an {@link IllegalArgumentException} will be thrown at runtime to
- * prevent unpredictable behavior.
+ * This extension facilitates the automatic generation of a UUID value for tagged string attributes.
+ * UUID values are generated using {@link java.util.UUID#randomUUID()}.
*
* This extension is not loaded by default when you instantiate a
* {@link software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient}. Therefore, you need to specify it in a custom
@@ -69,46 +55,36 @@
*}
*
*
- * To utilize the auto-generated UUID feature, first, create a field in your model that will store the UUID for the attribute.
- * This class field must be of type {@link java.lang.String}, and you need to tag it as the autoGeneratedUuidAttribute. If you are
+ * To use auto-generated UUID values, create a field in your model that stores the UUID attribute.
+ * This field must be of type {@link java.lang.String}, and you need to tag it as auto-generated UUID. If you are
* using the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema}, then you should use the
- * {@link software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid} annotation. If you are using
+ * {@link DynamoDbAutoGeneratedUuid} annotation. If you are using
* the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema}, then you should use the
* {@link
* software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags#autoGeneratedUuidAttribute()}
* static attribute tag.
*
*
- * Every time a new record is successfully put into the database, the specified attribute will be automatically populated with a
- * unique UUID generated using {@link java.util.UUID#randomUUID()}. If the UUID needs to be created only for `putItem` and should
- * not be generated for an `updateItem`, then
- * {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior#WRITE_IF_NOT_EXISTS} must be along with
- * {@link DynamoDbUpdateBehavior}
- *
+ * Generation behavior is controlled by {@link DynamoDbAutoGenerateStrategy}:
+ *
+ *
{@code ALWAYS}: generate a new UUID on every write handled by this extension.
+ *
{@code CREATE}: generate a UUID only when the current value is missing from the write item map.
+ * Missing means the value is absent, {@code NULL}, or an empty string.
+ *
+ *
+ * {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior} is a separate concern used while building update
+ * expressions for {@code updateItem}. It does not change this extension's strategy decision.
*
*/
@SdkPublicApi
@ThreadSafe
public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientExtension {
-
- /**
- * Custom metadata keys under which AutoGeneratedUuidExtension/AutoGeneratedKeyExtension record attributes annotated with
- * {@code @DynamoDbAutoGeneratedUuid}/{@code @DynamoDbAutoGeneratedKey}. Used to detect conflicts during schema validation.
- */
private static final String CUSTOM_METADATA_KEY =
"software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
- private static final String AUTOGENERATED_KEY_EXTENSION_METADATA_KEY =
- "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
-
- private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ATTRIBUTE = new AutoGeneratedUuidAttribute();
- private static final String AUTOGENERATED_KEY_ANNOTATION = "@DynamoDbAutoGeneratedKey";
- private static final String AUTOGENERATED_UUID_ANNOTATION = "@DynamoDbAutoGeneratedUuid";
-
- /**
- * Stores the TableMetadata instances that have already been validated by this extension. Uses a ConcurrentHashMap to ensure
- * thread-safe access during concurrent write operations.
- */
- private final Set validatedSchemas = newSetFromMap(new ConcurrentHashMap<>());
+ private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ALWAYS_ATTRIBUTE =
+ new AutoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy.ALWAYS);
+ private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_CREATE_ATTRIBUTE =
+ new AutoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy.CREATE);
private AutoGeneratedUuidExtension() {
}
@@ -130,42 +106,33 @@ public static AutoGeneratedUuidExtension create() {
public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
- Collection customMetadataObject = context.tableMetadata()
- .customMetadataObject(CUSTOM_METADATA_KEY, Collection.class)
- .orElse(null);
+ Map customMetadataObject =
+ context.tableMetadata()
+ .customMetadataObject(CUSTOM_METADATA_KEY, Map.class)
+ .orElse(null);
if (customMetadataObject == null) {
return WriteModification.builder().build();
}
- TableMetadata metadata = context.tableMetadata();
- validateNoAnnotationConflict(metadata);
-
Map itemToTransform = new HashMap<>(context.items());
- customMetadataObject.forEach(key -> insertUuidInItemToTransform(itemToTransform, key));
+ customMetadataObject.forEach((key, strategy) -> insertUuidInItemToTransform(itemToTransform, key, strategy));
return WriteModification.builder()
.transformedItem(Collections.unmodifiableMap(itemToTransform))
.build();
}
- /**
- * Validates (once per TableMetadata instance) that @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid are not applied
- * to the same attribute.
- */
- private void validateNoAnnotationConflict(TableMetadata tableMetadata) {
- if (validatedSchemas.add(tableMetadata)) {
- ExtensionsValidationUtils.validateNoAnnotationConflict(
- tableMetadata,
- AUTOGENERATED_KEY_EXTENSION_METADATA_KEY,
- CUSTOM_METADATA_KEY,
- AUTOGENERATED_KEY_ANNOTATION,
- AUTOGENERATED_UUID_ANNOTATION);
+ private void insertUuidInItemToTransform(Map itemToTransform,
+ String key,
+ DynamoDbAutoGenerateStrategy strategy) {
+ if (strategy == DynamoDbAutoGenerateStrategy.CREATE && !isMissingValue(itemToTransform.get(key))) {
+ return;
}
+ itemToTransform.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
}
- private void insertUuidInItemToTransform(Map itemToTransform,
- String key) {
- itemToTransform.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
+ private boolean isMissingValue(AttributeValue currentValue) {
+ return currentValue == null || Boolean.TRUE.equals(currentValue.nul()) || "".equals(currentValue.s());
}
public static final class AttributeTags {
@@ -174,16 +141,34 @@ private AttributeTags() {
}
/**
- * Tags which indicate that the given attribute is supported wih Auto Generated UUID Record Extension.
+ * Tags which indicate that the given attribute is supported with Auto Generated UUID Record Extension.
*
* @return Tag name for AutoGenerated UUID Records
*/
public static StaticAttributeTag autoGeneratedUuidAttribute() {
- return AUTO_GENERATED_UUID_ATTRIBUTE;
+ return AUTO_GENERATED_UUID_ALWAYS_ATTRIBUTE;
+ }
+
+ /**
+ * Tags which indicate that the given attribute is supported with Auto Generated UUID Record Extension and
+ * applies the provided strategy.
+ *
+ * @return Tag name for AutoGenerated UUID Records
+ */
+ public static StaticAttributeTag autoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy strategy) {
+ Validate.notNull(strategy, "strategy is null");
+ return strategy == DynamoDbAutoGenerateStrategy.CREATE
+ ? AUTO_GENERATED_UUID_CREATE_ATTRIBUTE
+ : AUTO_GENERATED_UUID_ALWAYS_ATTRIBUTE;
}
}
private static class AutoGeneratedUuidAttribute implements StaticAttributeTag {
+ private final DynamoDbAutoGenerateStrategy strategy;
+
+ private AutoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy strategy) {
+ this.strategy = strategy;
+ }
@Override
public void validateType(String attributeName, EnhancedType type,
@@ -203,7 +188,8 @@ public void validateType(String attributeName, EnhancedType type,
@Override
public Consumer modifyMetadata(String attributeName,
AttributeValueType attributeValueType) {
- return metadata -> metadata.addCustomMetadataObject(CUSTOM_METADATA_KEY, Collections.singleton(attributeName))
+ return metadata -> metadata.addCustomMetadataObject(
+ CUSTOM_METADATA_KEY, Collections.singletonMap(attributeName, strategy))
.markAttributeAsKey(attributeName, attributeValueType);
}
}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java
deleted file mode 100644
index 4a04286e2ece..000000000000
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtils.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.extensions;
-
-import java.util.Collection;
-import java.util.Collections;
-import software.amazon.awssdk.annotations.SdkProtectedApi;
-import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
-
-/**
- * Provides shared schema validation utilities for DynamoDB enhanced client extensions.
- */
-@SdkProtectedApi
-public final class ExtensionsValidationUtils {
-
- private ExtensionsValidationUtils() {
- }
-
- /**
- * Validates that there are no attributes that have both annotations. These annotations have conflicting behaviors and cannot
- * be used together on the same attribute. If an attribute is found with both annotations, an IllegalArgumentException is
- * thrown with a message indicating the attribute and the conflicting annotations.
- *
- * @param tableMetadata The metadata of the table to validate.
- * @param firstAnnotationMetadataKey The metadata key for the first annotation to check for.
- * @param secondAnnotationMetadataKey The metadata key for the second annotation to check for.
- * @param firstAnnotationName The name of the first annotation to use in the error message if a conflict is found.
- * @param secondAnnotationName The name of the second annotation to use in the error message if a conflict is found.
- */
- public static void validateNoAnnotationConflict(TableMetadata tableMetadata,
- String firstAnnotationMetadataKey,
- String secondAnnotationMetadataKey,
- String firstAnnotationName,
- String secondAnnotationName) {
-
- Collection> attributesHavingFirstAnnotation =
- tableMetadata.customMetadataObject(firstAnnotationMetadataKey, Collection.class).orElse(Collections.emptyList());
-
- if (attributesHavingFirstAnnotation.isEmpty()) {
- return;
- }
-
- Collection> attributesHavingSecondAnnotation =
- tableMetadata.customMetadataObject(secondAnnotationMetadataKey, Collection.class).orElse(Collections.emptyList());
-
- if (attributesHavingSecondAnnotation.isEmpty()) {
- return;
- }
-
- attributesHavingFirstAnnotation
- .stream()
- .filter(attributesHavingSecondAnnotation::contains)
- .findFirst()
- .ifPresent(attribute -> {
- throw new IllegalArgumentException(
- "Attribute '" + attribute + "' cannot have both " + firstAnnotationName
- + " and " + secondAnnotationName + " annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
- });
- }
-}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGenerateStrategy.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGenerateStrategy.java
new file mode 100644
index 000000000000..d69aa51ac74a
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGenerateStrategy.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.extensions.annotations;
+
+import software.amazon.awssdk.annotations.SdkPublicApi;
+
+/**
+ * Strategy used by {@link DynamoDbAutoGeneratedUuid} to decide when UUID values are generated.
+ */
+@SdkPublicApi
+public enum DynamoDbAutoGenerateStrategy {
+ /**
+ * Generate a new UUID on every write operation.
+ */
+ ALWAYS,
+
+ /**
+ * Generate a UUID only when the value is missing.
+ * Missing means the value is absent, DynamoDB {@code NULL}, or an empty string.
+ */
+ CREATE
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
deleted file mode 100644
index 302a72df76c4..000000000000
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedKey.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.extensions.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import software.amazon.awssdk.annotations.SdkPublicApi;
-import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.AutoGeneratedKeyTag;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.BeanTableSchemaAttributeTag;
-
-/**
- * Marks a key attribute to be automatically populated with a UUID when the value is null or empty during a write operation.
- *
- *
Usage: May only be applied to key attributes: primary partition key, primary sort key, or secondary index
- * (GSI/LSI) keys. If used on a non-key attribute, {@code AutoGeneratedKeyExtension} throws an {@link IllegalArgumentException}
- * during schema validation.
- *
- *
Semantics: Generates a UUID using {@link java.util.UUID#randomUUID()}
- * only when the attribute is absent. Existing values are preserved.
- *
- *
Difference from {@code @DynamoDbAutoGeneratedUuid}:
- * This annotation is intended for key attributes and generates a value only when missing. {@code @DynamoDbAutoGeneratedUuid} can
- * be applied to any attribute and always generates a new UUID on every write.
- *
- *
Valid only for {@link String} attributes.
- */
-@SdkPublicApi
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.FIELD})
-@BeanTableSchemaAttributeTag(AutoGeneratedKeyTag.class)
-public @interface DynamoDbAutoGeneratedKey {
-}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java
index 5381cd77227b..c03c960a8f39 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java
@@ -24,25 +24,22 @@
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.BeanTableSchemaAttributeTag;
/**
- * Marks an attribute to be automatically populated with a new UUID on every write operation.
- *
- *
Intended Usage:
- * This annotation is generic and may be applied to any {@link String} attribute, not only key attributes.
- *
- *
Generation Semantics:
- * On every write (put, update, batch write, or transaction write), the attribute is replaced with a new value generated using
- * {@link java.util.UUID#randomUUID()}, regardless of any existing value.
- *
- *
Difference from {@code @DynamoDbAutoGeneratedKey}:
- * {@code @DynamoDbAutoGeneratedKey} is intended for key attributes and generates UUID only when the value is absent.
- * {@code @DynamoDbAutoGeneratedUuid} annotation always regenerates the UUID on each write.
- *
- *
Type Restriction:
- * This annotation may only be applied to attributes of type {@link String}.
+ * Denotes this attribute as recording the auto generated UUID string for the record.
+ *
+ * The {@link #strategy()} controls whether UUID is generated on every write or only when missing.
+ * The default is {@link DynamoDbAutoGenerateStrategy#ALWAYS} for backward compatibility with existing
+ * {@code @DynamoDbAutoGeneratedUuid} usage.
+ * Use {@link DynamoDbAutoGenerateStrategy#CREATE} when you want to generate only if the value is missing.
*/
@SdkPublicApi
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@BeanTableSchemaAttributeTag(AutoGeneratedUuidTag.class)
public @interface DynamoDbAutoGeneratedUuid {
-}
\ No newline at end of file
+ /**
+ * Defines when a new UUID should be generated.
+ *
+ * Defaults to {@link DynamoDbAutoGenerateStrategy#ALWAYS} to preserve backward compatibility.
+ */
+ DynamoDbAutoGenerateStrategy strategy() default DynamoDbAutoGenerateStrategy.ALWAYS;
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedKeyTag.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedKeyTag.java
deleted file mode 100644
index 815e15bccd7a..000000000000
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedKeyTag.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.internal.extensions;
-
-import software.amazon.awssdk.annotations.SdkInternalApi;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
-
-@SdkInternalApi
-public final class AutoGeneratedKeyTag {
-
- private AutoGeneratedKeyTag() {
- }
-
- public static StaticAttributeTag attributeTagFor(DynamoDbAutoGeneratedKey annotation) {
- return AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute();
- }
-
-}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedUuidTag.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedUuidTag.java
index 17872e71954b..499a1db737b8 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedUuidTag.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedUuidTag.java
@@ -27,7 +27,7 @@ private AutoGeneratedUuidTag() {
}
public static StaticAttributeTag attributeTagFor(DynamoDbAutoGeneratedUuid annotation) {
- return AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute();
+ return AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(annotation.strategy());
}
-}
\ No newline at end of file
+}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
deleted file mode 100644
index 0720ffa99d8c..000000000000
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtensionTest.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * Licensed under the Apache License, Version 2.0 (the "License").
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.extensions;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
-import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute;
-import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey;
-
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-import org.junit.jupiter.api.Test;
-import software.amazon.awssdk.enhanced.dynamodb.OperationContext;
-import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
-import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
-import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext;
-import software.amazon.awssdk.enhanced.dynamodb.internal.operations.OperationName;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
-import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
-
-public class AutoGeneratedKeyExtensionTest {
-
- private static final String RECORD_ID = "1";
- private static final String TABLE_NAME = "table-name";
-
- private static final OperationContext PRIMARY_CONTEXT =
- DefaultOperationContext.create(TABLE_NAME, TableMetadata.primaryIndexName());
-
- private final AutoGeneratedKeyExtension extension = AutoGeneratedKeyExtension.create();
-
- /**
- * Schema that places @DynamoDbAutoGeneratedKey on GSI key ("keyAttribute") -> the validation passes.
- */
- private static final StaticTableSchema ITEM_WITH_KEY_SCHEMA =
- StaticTableSchema.builder(AutogeneratedKeyItem.class)
- .newItemSupplier(AutogeneratedKeyItem::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(AutogeneratedKeyItem::getId)
- .setter(AutogeneratedKeyItem::setId)
- .addTag(primaryPartitionKey())
- .addTag(autoGeneratedKeyAttribute()))
- .addAttribute(String.class, a -> a.name("keyAttribute")
- .getter(AutogeneratedKeyItem::getKeyAttribute)
- .setter(AutogeneratedKeyItem::setKeyAttribute)
- .tags(
- secondaryPartitionKey("gsi_keys_only"),
- autoGeneratedKeyAttribute()))
- .addAttribute(String.class, a -> a.name("simpleString")
- .getter(AutogeneratedKeyItem::getSimpleString)
- .setter(AutogeneratedKeyItem::setSimpleString))
- .build();
-
- /**
- * Schema that places @DynamoDbAutoGeneratedKey on a NON-KEY attribute -> triggers the exception.
- */
- private static final StaticTableSchema INVALID_NONKEY_AUTOGEN_SCHEMA =
- StaticTableSchema.builder(AutogeneratedKeyItem.class)
- .newItemSupplier(AutogeneratedKeyItem::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(AutogeneratedKeyItem::getId)
- .setter(AutogeneratedKeyItem::setId)
- .addTag(primaryPartitionKey()))
- .addAttribute(String.class, a -> a.name("keyAttribute")
- .getter(AutogeneratedKeyItem::getKeyAttribute)
- .setter(AutogeneratedKeyItem::setKeyAttribute)
- // index tags not defined — autogen on non-key fails at beforeWrite()
- .addTag(autoGeneratedKeyAttribute()))
- .addAttribute(String.class, a -> a.name("simpleString")
- .getter(AutogeneratedKeyItem::getSimpleString)
- .setter(AutogeneratedKeyItem::setSimpleString))
- .build();
-
- /**
- * Schema that places @DynamoDbAutoGeneratedKey on LSI key ("simpleString") -> the validation passes.
- */
- private static final StaticTableSchema LSI_SK_AUTOGEN_SCHEMA =
- StaticTableSchema.builder(AutogeneratedKeyItem.class)
- .newItemSupplier(AutogeneratedKeyItem::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(AutogeneratedKeyItem::getId)
- .setter(AutogeneratedKeyItem::setId)
- .addTag(primaryPartitionKey()))
- .addAttribute(String.class, a -> a.name("keyAttribute")
- .getter(AutogeneratedKeyItem::getKeyAttribute)
- .setter(AutogeneratedKeyItem::setKeyAttribute))
- .addAttribute(String.class, a -> a.name("simpleString")
- .getter(AutogeneratedKeyItem::getSimpleString)
- .setter(AutogeneratedKeyItem::setSimpleString)
- .tags(
- secondarySortKey("lsi1"),
- autoGeneratedKeyAttribute()))
- .build();
-
- @Test
- public void updateItem_withExistingKey_preservesValueAndDoesNotGenerateNewOne() {
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
- String preset = UUID.randomUUID().toString();
- item.setKeyAttribute(preset);
-
- Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
- assertThat(items).hasSize(2);
-
- WriteModification result = extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
-
- Map transformed = result.transformedItem();
- assertThat(transformed).isNotNull().hasSize(2);
- assertThat(transformed).containsEntry("id", AttributeValue.fromS(RECORD_ID));
-
- assertThat(isValidUuid(transformed.get("keyAttribute").s())).isTrue();
- assertThat(result.updateExpression()).isNull();
- }
-
- @Test
- public void updateItem_withoutExistingKey_generatesNewUuid() {
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
-
- Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
- assertThat(items).hasSize(1);
-
- WriteModification result = extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
-
- Map transformed = result.transformedItem();
- assertThat(transformed).isNotNull().hasSize(2);
- assertThat(transformed).containsEntry("id", AttributeValue.fromS(RECORD_ID));
- assertThat(isValidUuid(transformed.get("keyAttribute").s())).isTrue();
- assertThat(result.updateExpression()).isNull();
- }
-
- @Test
- public void updateItem_withMissingKeyAttribute_insertsGeneratedUuid() {
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
-
- Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
- assertThat(items).hasSize(1);
-
- WriteModification result = extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
-
- assertThat(result.transformedItem()).isNotNull();
- assertThat(result.updateExpression()).isNull();
- assertThat(result.transformedItem()).hasSize(2);
- assertThat(isValidUuid(result.transformedItem().get("keyAttribute").s())).isTrue();
- }
-
- @Test
- public void nonStringAttributeAnnotatedWithAutoGeneratedKey_throwsIllegalArgumentException() {
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() ->
- StaticTableSchema.builder(AutogeneratedKeyItem.class)
- .newItemSupplier(AutogeneratedKeyItem::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(AutogeneratedKeyItem::getId)
- .setter(AutogeneratedKeyItem::setId)
- .addTag(primaryPartitionKey()))
- .addAttribute(Integer.class, a -> a.name("intAttribute")
- .getter(AutogeneratedKeyItem::getIntAttribute)
- .setter(AutogeneratedKeyItem::setIntAttribute)
- .addTag(autoGeneratedKeyAttribute()))
- .addAttribute(String.class, a -> a.name("simpleString")
- .getter(AutogeneratedKeyItem::getSimpleString)
- .setter(AutogeneratedKeyItem::setSimpleString))
- .build()
- )
- .withMessage(
- "Attribute 'intAttribute' of Class type class java.lang.Integer is not a suitable Java Class type "
- + "to be used as a Auto Generated Key attribute. Only String Class type is supported.");
- }
-
- @Test
- public void autoGeneratedKey_onSecondaryPartitionKey_generatesUuid() {
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
-
- Map items = ITEM_WITH_KEY_SCHEMA.itemToMap(item, true);
-
- WriteModification result = extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_KEY_SCHEMA.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
-
- Map transformed = result.transformedItem();
- assertThat(transformed).isNotNull();
- assertThat(isValidUuid(transformed.get("keyAttribute").s())).isTrue();
- }
-
- @Test
- public void autoGeneratedKey_onSecondarySortKey_generatesUuid() {
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
-
- Map items = LSI_SK_AUTOGEN_SCHEMA.itemToMap(item, true);
-
- WriteModification result = extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(LSI_SK_AUTOGEN_SCHEMA.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
-
- Map transformed = result.transformedItem();
- assertThat(transformed).isNotNull();
- assertThat(isValidUuid(transformed.get("simpleString").s())).isTrue();
- }
-
- @Test
- public void autoGeneratedKey_onNonKeyAttribute_throwsIllegalArgumentException() {
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
-
- Map items = INVALID_NONKEY_AUTOGEN_SCHEMA.itemToMap(item, true);
-
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(INVALID_NONKEY_AUTOGEN_SCHEMA.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build())
- )
- .withMessage("@DynamoDbAutoGeneratedKey can only be applied to key attributes: "
- + "primary partition key, primary sort key, or GSI/LSI partition/sort keys. "
- + "Invalid placement on attribute: keyAttribute");
- }
-
- @Test
- public void conflictingAnnotations_onSameAttribute_throwsIllegalArgumentException() {
- // Create a schema with both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid on the same attribute
- StaticTableSchema conflictingSchema =
- StaticTableSchema
- .builder(AutogeneratedKeyItem.class)
- .newItemSupplier(AutogeneratedKeyItem::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(AutogeneratedKeyItem::getId)
- .setter(AutogeneratedKeyItem::setId)
- .addTag(primaryPartitionKey())
- // Both annotations on the same attribute
- .addTag(autoGeneratedKeyAttribute())
- .addTag(autoGeneratedUuidAttribute()))
- .addAttribute(String.class, a -> a.name("simpleString")
- .getter(AutogeneratedKeyItem::getSimpleString)
- .setter(AutogeneratedKeyItem::setSimpleString))
- .build();
-
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
-
- Map items = conflictingSchema.itemToMap(item, true);
-
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(conflictingSchema.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build())
- )
- .withMessage(
- "Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
- }
-
- @Test
- public void conflictingAnnotations_onSecondaryKey_throwsIllegalArgumentException() {
- // Create a schema with both annotations on a GSI key
- StaticTableSchema conflictingGsiSchema =
- StaticTableSchema
- .builder(AutogeneratedKeyItem.class)
- .newItemSupplier(AutogeneratedKeyItem::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(AutogeneratedKeyItem::getId)
- .setter(AutogeneratedKeyItem::setId)
- .addTag(primaryPartitionKey()))
- .addAttribute(String.class, a -> a.name("keyAttribute")
- .getter(AutogeneratedKeyItem::getKeyAttribute)
- .setter(AutogeneratedKeyItem::setKeyAttribute)
- .addTag(secondaryPartitionKey("gsi1"))
- // Both annotations on the same GSI key
- .addTag(autoGeneratedKeyAttribute())
- .addTag(autoGeneratedUuidAttribute()))
- .addAttribute(String.class, a -> a.name("simpleString")
- .getter(AutogeneratedKeyItem::getSimpleString)
- .setter(AutogeneratedKeyItem::setSimpleString))
- .build();
-
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
-
- Map items = conflictingGsiSchema.itemToMap(item, true);
-
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(conflictingGsiSchema.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build()))
- .withMessage(
- "Attribute 'keyAttribute' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
- }
-
- @Test
- public void conflictDetection_worksRegardlessOfExtensionOrder() {
- StaticTableSchema conflictingSchema =
- StaticTableSchema
- .builder(AutogeneratedKeyItem.class)
- .newItemSupplier(AutogeneratedKeyItem::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(AutogeneratedKeyItem::getId)
- .setter(AutogeneratedKeyItem::setId)
- .addTag(primaryPartitionKey())
- .addTag(autoGeneratedKeyAttribute())
- .addTag(autoGeneratedUuidAttribute()))
- .build();
-
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
-
- Map items = conflictingSchema.itemToMap(item, true);
-
- // Test that the conflict is detected regardless of which extension runs first
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(conflictingSchema.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build()))
- .withMessage(
- "Attribute 'id' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
- }
-
- @Test
- public void beforeWrite_noAttributesTaggedWithAutogeneratedKey_returnsEmptyModification() {
- StaticTableSchema schemaWithoutTags =
- StaticTableSchema.builder(AutogeneratedKeyItem.class)
- .newItemSupplier(AutogeneratedKeyItem::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(AutogeneratedKeyItem::getId)
- .setter(AutogeneratedKeyItem::setId)
- .addTag(primaryPartitionKey()))
- .build();
-
- AutogeneratedKeyItem item = new AutogeneratedKeyItem();
- item.setId(RECORD_ID);
- Map items = schemaWithoutTags.itemToMap(item, true);
-
- WriteModification result = extension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(schemaWithoutTags.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
-
- assertThat(result).isEqualTo(WriteModification.builder().build());
- }
-
- private static class AutogeneratedKeyItem {
-
- private String id;
- private String keyAttribute;
- private String simpleString;
- private Integer intAttribute;
-
- AutogeneratedKeyItem() {
- }
-
- public Integer getIntAttribute() {
- return intAttribute;
- }
-
- public void setIntAttribute(Integer intAttribute) {
- this.intAttribute = intAttribute;
- }
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getKeyAttribute() {
- return keyAttribute;
- }
-
- public void setKeyAttribute(String keyAttribute) {
- this.keyAttribute = keyAttribute;
- }
-
- public String getSimpleString() {
- return simpleString;
- }
-
- public void setSimpleString(String simpleString) {
- this.simpleString = simpleString;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof AutogeneratedKeyItem)) {
- return false;
- }
- AutogeneratedKeyItem that = (AutogeneratedKeyItem) o;
- return Objects.equals(id, that.id)
- && Objects.equals(keyAttribute, that.keyAttribute)
- && Objects.equals(simpleString, that.simpleString);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, keyAttribute, simpleString);
- }
- }
-}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
index 33da228c5472..fcf3f67f764a 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
@@ -19,6 +19,7 @@
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@@ -26,6 +27,7 @@
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.enhanced.dynamodb.OperationContext;
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGenerateStrategy;
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext;
import software.amazon.awssdk.enhanced.dynamodb.internal.operations.OperationName;
@@ -65,99 +67,127 @@ public class AutoGeneratedUuidExtensionTest {
.setter(ItemWithUuid::setSimpleString))
.build();
+ private static final StaticTableSchema ITEM_WITH_CREATE_UUID_MAPPER =
+ StaticTableSchema.builder(ItemWithUuid.class)
+ .newItemSupplier(ItemWithUuid::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithUuid::getId)
+ .setter(ItemWithUuid::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(String.class, a -> a.name("uuidAttribute")
+ .getter(ItemWithUuid::getUuidAttribute)
+ .setter(ItemWithUuid::setUuidAttribute)
+ .addTag(AutoGeneratedUuidExtension.AttributeTags
+ .autoGeneratedUuidAttribute(
+ DynamoDbAutoGenerateStrategy.CREATE)))
+ .build();
+
+ private static final StaticTableSchema ITEM_WITHOUT_UUID_MAPPER =
+ StaticTableSchema.builder(ItemWithUuid.class)
+ .newItemSupplier(ItemWithUuid::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithUuid::getId)
+ .setter(ItemWithUuid::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(String.class, a -> a.name("simpleString")
+ .getter(ItemWithUuid::getSimpleString)
+ .setter(ItemWithUuid::setSimpleString))
+ .build();
+
@Test
- public void beforeWrite_withNoMetadata_returnsNoWriteModifications() {
- StaticTableSchema schemaWithoutUuidAttribute =
- StaticTableSchema.builder(ItemWithUuid.class)
- .newItemSupplier(ItemWithUuid::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithUuid::getId)
- .setter(ItemWithUuid::setId)
- .addTag(primaryPartitionKey()))
- .build();
+ public void beforeWrite_putItem_whenStrategyAlwaysAndValueMissing_generatesUuid() {
+ assertUuidBehavior(DynamoDbAutoGenerateStrategy.ALWAYS, OperationName.PUT_ITEM, false, false);
+ }
- ItemWithUuid item = new ItemWithUuid();
- item.setId(RECORD_ID);
- Map items = schemaWithoutUuidAttribute.itemToMap(item, true);
+ @Test
+ public void beforeWrite_putItem_whenStrategyAlwaysAndValueExists_regeneratesUuid() {
+ assertUuidBehavior(DynamoDbAutoGenerateStrategy.ALWAYS, OperationName.PUT_ITEM, true, false);
+ }
- WriteModification result = atomicCounterExtension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(schemaWithoutUuidAttribute.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT).build());
+ @Test
+ public void beforeWrite_updateItem_whenStrategyAlwaysAndValueMissing_generatesUuid() {
+ assertUuidBehavior(DynamoDbAutoGenerateStrategy.ALWAYS, OperationName.UPDATE_ITEM, false, false);
+ }
- assertThat(result).isNotNull();
- assertThat(result.transformedItem()).isNull();
- assertThat(result.updateExpression()).isNull();
- assertThat(result.additionalConditionalExpression()).isNull();
+ @Test
+ public void beforeWrite_updateItem_whenStrategyAlwaysAndValueExists_regeneratesUuid() {
+ assertUuidBehavior(DynamoDbAutoGenerateStrategy.ALWAYS, OperationName.UPDATE_ITEM, true, false);
}
@Test
- public void beforeWrite_updateItemOperation_hasUuidInItem_doesNotCreateUpdateExpressionAndFilters() {
- ItemWithUuid SimpleItem = new ItemWithUuid();
- SimpleItem.setId(RECORD_ID);
- String uuidAttribute = String.valueOf(UUID.randomUUID());
- SimpleItem.setUuidAttribute(uuidAttribute);
+ public void beforeWrite_putItem_whenStrategyCreateAndValueMissing_generatesUuid() {
+ assertUuidBehavior(DynamoDbAutoGenerateStrategy.CREATE, OperationName.PUT_ITEM, false, false);
+ }
- Map items = ITEM_WITH_UUID_MAPPER.itemToMap(SimpleItem, true);
- assertThat(items).hasSize(2);
+ @Test
+ public void beforeWrite_putItem_whenStrategyCreateAndValueExists_preservesUuid() {
+ assertUuidBehavior(DynamoDbAutoGenerateStrategy.CREATE, OperationName.PUT_ITEM, true, true);
+ }
- WriteModification result =
- atomicCounterExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_UUID_MAPPER.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT).build());
-
- Map transformedItem = result.transformedItem();
- assertThat(transformedItem).isNotNull().hasSize(2);
- assertThat(transformedItem).containsEntry("id", AttributeValue.fromS(RECORD_ID));
- isValidUuid(transformedItem.get("uuidAttribute").s());
- assertThat(result.updateExpression()).isNull();
+ @Test
+ public void beforeWrite_updateItem_whenStrategyCreateAndValueMissing_generatesUuid() {
+ assertUuidBehavior(DynamoDbAutoGenerateStrategy.CREATE, OperationName.UPDATE_ITEM, false, false);
+ }
+ @Test
+ public void beforeWrite_updateItem_whenStrategyCreateAndValueExists_preservesUuid() {
+ assertUuidBehavior(DynamoDbAutoGenerateStrategy.CREATE, OperationName.UPDATE_ITEM, true, true);
}
@Test
- public void beforeWrite_updateItemOperation_hasNoUuidInItem_doesNotCreatesUpdateExpressionAndFilters() {
- ItemWithUuid SimpleItem = new ItemWithUuid();
- SimpleItem.setId(RECORD_ID);
+ public void beforeWrite_updateItem_whenStrategyCreateAndValueEmptyString_generatesUuid() {
+ Map rawItems = new HashMap<>();
+ rawItems.put("id", AttributeValue.fromS(RECORD_ID));
+ rawItems.put("uuidAttribute", AttributeValue.fromS(""));
- Map items = ITEM_WITH_UUID_MAPPER.itemToMap(SimpleItem, true);
- assertThat(items).hasSize(1);
+ WriteModification result =
+ atomicCounterExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(rawItems)
+ .tableMetadata(ITEM_WITH_CREATE_UUID_MAPPER.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ String actualUuid = result.transformedItem().get("uuidAttribute").s();
+ assertThat(isValidUuid(actualUuid)).isTrue();
+ }
+
+ @Test
+ public void beforeWrite_updateItem_whenStrategyAlwaysAndValueEmptyString_regeneratesUuid() {
+ Map rawItems = new HashMap<>();
+ rawItems.put("id", AttributeValue.fromS(RECORD_ID));
+ rawItems.put("uuidAttribute", AttributeValue.fromS(""));
WriteModification result =
- atomicCounterExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_UUID_MAPPER.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT).build());
-
- Map transformedItem = result.transformedItem();
- assertThat(transformedItem).isNotNull().hasSize(2);
- assertThat(transformedItem).containsEntry("id", AttributeValue.fromS(RECORD_ID));
- isValidUuid(transformedItem.get("uuidAttribute").s());
- assertThat(result.updateExpression()).isNull();
+ atomicCounterExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(rawItems)
+ .tableMetadata(ITEM_WITH_UUID_MAPPER.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ String actualUuid = result.transformedItem().get("uuidAttribute").s();
+ assertThat(isValidUuid(actualUuid)).isTrue();
}
@Test
- public void beforeWrite_updateItemOperation_UuidNotPresent_newUuidCreated() {
+ public void beforeWrite_whenNoAutoGeneratedUuidConfigured_returnsEmptyWriteModification() {
ItemWithUuid item = new ItemWithUuid();
item.setId(RECORD_ID);
+ item.setSimpleString("value");
- Map items = ITEM_WITH_UUID_MAPPER.itemToMap(item, true);
- assertThat(items).hasSize(1);
+ WriteModification result = atomicCounterExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(ITEM_WITHOUT_UUID_MAPPER.itemToMap(item, true))
+ .tableMetadata(ITEM_WITHOUT_UUID_MAPPER.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
- WriteModification result =
- atomicCounterExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_UUID_MAPPER.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT).build());
- assertThat(result.transformedItem()).isNotNull();
+ assertThat(result.transformedItem()).isNull();
assertThat(result.updateExpression()).isNull();
- assertThat(result.transformedItem()).hasSize(2);
- assertThat(isValidUuid(result.transformedItem().get("uuidAttribute").s())).isTrue();
}
@Test
@@ -180,35 +210,48 @@ void IllegalArgumentException_for_AutogeneratedUuid_withNonStringType() {
.setter(ItemWithUuid::setSimpleString))
.build())
- .withMessage(
- "Attribute 'intAttribute' of Class type class java.lang.Integer is not a suitable Java Class type"
- + " to be used as a Auto Generated Uuid attribute. Only String Class type is supported.");
+ .withMessage("Attribute 'intAttribute' of Class type class java.lang.Integer is not a suitable Java Class type"
+ + " to be used as a Auto Generated Uuid attribute. Only String Class type is supported.");
}
- @Test
- public void beforeWrite_withoutMetadata_returnsEmptyModification() {
- StaticTableSchema schemaWithoutMetadata =
- StaticTableSchema.builder(ItemWithUuid.class)
- .newItemSupplier(ItemWithUuid::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithUuid::getId)
- .setter(ItemWithUuid::setId)
- .addTag(primaryPartitionKey()))
- .build();
-
+ private void assertUuidBehavior(DynamoDbAutoGenerateStrategy strategy,
+ OperationName operationName,
+ boolean hasExistingValue,
+ boolean shouldPreserveExisting) {
ItemWithUuid item = new ItemWithUuid();
item.setId(RECORD_ID);
- Map items = schemaWithoutMetadata.itemToMap(item, true);
+ String existingUuid = null;
+ if (hasExistingValue) {
+ existingUuid = String.valueOf(UUID.randomUUID());
+ item.setUuidAttribute(existingUuid);
+ }
- WriteModification result = atomicCounterExtension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(schemaWithoutMetadata.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
+ StaticTableSchema schema = strategy == DynamoDbAutoGenerateStrategy.CREATE
+ ? ITEM_WITH_CREATE_UUID_MAPPER
+ : ITEM_WITH_UUID_MAPPER;
+ Map items = schema.itemToMap(item, true);
+
+ WriteModification result =
+ atomicCounterExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(schema.tableMetadata())
+ .operationName(operationName)
+ .operationContext(PRIMARY_CONTEXT).build());
- assertThat(result).isEqualTo(WriteModification.builder().build());
+ assertThat(result.updateExpression()).isNull();
+ assertThat(result.transformedItem()).containsEntry("id", AttributeValue.fromS(RECORD_ID));
+ assertThat(result.transformedItem()).containsKey("uuidAttribute");
+ String actualUuid = result.transformedItem().get("uuidAttribute").s();
+
+ if (shouldPreserveExisting) {
+ assertThat(actualUuid).isEqualTo(existingUuid);
+ } else {
+ assertThat(isValidUuid(actualUuid)).isTrue();
+ if (hasExistingValue) {
+ assertThat(actualUuid).isNotEqualTo(existingUuid);
+ }
+ }
}
public static boolean isValidUuid(String uuid) {
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutogeneratedConflictingAnnotationsTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutogeneratedConflictingAnnotationsTest.java
deleted file mode 100644
index 933bc9f64f86..000000000000
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutogeneratedConflictingAnnotationsTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.extensions;
-
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension.AttributeTags.autoGeneratedKeyAttribute;
-import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
-
-import java.util.Map;
-import org.junit.jupiter.api.Test;
-import software.amazon.awssdk.enhanced.dynamodb.OperationContext;
-import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
-import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
-import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext;
-import software.amazon.awssdk.enhanced.dynamodb.internal.operations.OperationName;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
-import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
-
-/**
- * Tests to verify that conflicting annotations (@DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid) are properly detected
- * and throw exceptions regardless of extension load order.
- */
-public class AutogeneratedConflictingAnnotationsTest {
-
- private static final String RECORD_ID = "1";
- private static final String TABLE_NAME = "table-name";
- private static final OperationContext PRIMARY_CONTEXT =
- DefaultOperationContext.create(TABLE_NAME, TableMetadata.primaryIndexName());
-
- private final AutoGeneratedKeyExtension keyExtension = AutoGeneratedKeyExtension.create();
- private final AutoGeneratedUuidExtension uuidExtension = AutoGeneratedUuidExtension.create();
-
- /**
- * Schema with both annotations on the same attribute to test conflict detection.
- */
- private static final StaticTableSchema CONFLICTING_SCHEMA =
- StaticTableSchema.builder(RecordWithAutogenerated.class)
- .newItemSupplier(RecordWithAutogenerated::new)
- .addAttribute(String.class, a -> a.name("autogeneratedKeyField")
- .getter(RecordWithAutogenerated::getAutogeneratedKeyField)
- .setter(RecordWithAutogenerated::setAutogeneratedKeyField)
- .addTag(primaryPartitionKey())
- // Both annotations on the same attribute
- .addTag(autoGeneratedKeyAttribute())
- .addTag(autoGeneratedUuidAttribute()))
- .build();
-
- @Test
- public void autogeneratedKeyExtensionFirst_detectsConflictWithUuidExtension() {
- RecordWithAutogenerated item = new RecordWithAutogenerated();
- item.setAutogeneratedKeyField(RECORD_ID);
-
- Map items = CONFLICTING_SCHEMA.itemToMap(item, true);
-
- // AutoGeneratedKeyExtension runs first and detects conflict
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> keyExtension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(CONFLICTING_SCHEMA.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build())
- )
- .withMessage(
- "Attribute 'autogeneratedKeyField' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
- + "annotations. These annotations have conflicting behaviors and cannot be used together on the same attribute.");
- }
-
- @Test
- public void autogeneratedUuidExtensionFirst_detectsConflictWithKeyExtension() {
- RecordWithAutogenerated item = new RecordWithAutogenerated();
- item.setAutogeneratedKeyField(RECORD_ID);
-
- Map items = CONFLICTING_SCHEMA.itemToMap(item, true);
-
- // AutoGeneratedUuidExtension runs first and detects conflict
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> uuidExtension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(CONFLICTING_SCHEMA.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build())
- )
- .withMessage("Attribute 'autogeneratedKeyField' cannot have both "
- + "@DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid annotations. "
- + "These annotations have conflicting behaviors and cannot be used together on the same attribute.");
- }
-
- @Test
- public void autogeneratedAnnotations_onSeparateAttributes_doesNotDetectConflict() {
- // Schema with annotations on different attributes - does not detect conflict
- StaticTableSchema separateSchema =
- StaticTableSchema.builder(RecordWithAutogenerated.class)
- .newItemSupplier(RecordWithAutogenerated::new)
- .addAttribute(String.class, a -> a.name("autogeneratedKeyField")
- .getter(RecordWithAutogenerated::getAutogeneratedKeyField)
- .setter(RecordWithAutogenerated::setAutogeneratedKeyField)
- .addTag(primaryPartitionKey())
- .addTag(autoGeneratedKeyAttribute()))
- .addAttribute(String.class, a -> a.name("autogeneratedUuidField")
- .getter(RecordWithAutogenerated::getAutogeneratedUuidField)
- .setter(RecordWithAutogenerated::setAutogeneratedUuidField)
- .addTag(autoGeneratedUuidAttribute()))
- .build();
-
- RecordWithAutogenerated item = new RecordWithAutogenerated();
-
- Map items = separateSchema.itemToMap(item, true);
-
- // Both extensions should work without conflict when on different attributes
- keyExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(separateSchema.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
-
- uuidExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(separateSchema.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
- }
-
- public static class RecordWithAutogenerated {
- private String autogeneratedKeyField;
- private String autogeneratedUuidField;
-
- public String getAutogeneratedKeyField() {
- return autogeneratedKeyField;
- }
-
- public void setAutogeneratedKeyField(String autogeneratedKeyField) {
- this.autogeneratedKeyField = autogeneratedKeyField;
- }
-
- public String getAutogeneratedUuidField() {
- return autogeneratedUuidField;
- }
-
- public void setAutogeneratedUuidField(String autogeneratedUuidField) {
- this.autogeneratedUuidField = autogeneratedUuidField;
- }
- }
-}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtilsTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtilsTest.java
deleted file mode 100644
index bc78b5b45f06..000000000000
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/ExtensionsValidationUtilsTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package software.amazon.awssdk.enhanced.dynamodb.extensions;
-
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.when;
-import static software.amazon.awssdk.enhanced.dynamodb.extensions.ExtensionsValidationUtils.validateNoAnnotationConflict;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Optional;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
-
-@RunWith(MockitoJUnitRunner.class)
-public class ExtensionsValidationUtilsTest {
-
- @Mock
- private TableMetadata metadata;
-
- private static final String AUTOGENERATED_KEY_METADATA_KEY =
- "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension:AutoGeneratedKeyAttribute";
- private static final String AUTOGENERATED_UUID_METADATA_KEY =
- "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
-
- private static final String AUTOGENERATED_KEY_ANNOTATION = "@DynamoDbAutoGeneratedKey";
- private static final String AUTOGENERATED_UUID_ANNOTATION = "@DynamoDbAutoGeneratedUuid";
-
- @Test
- public void validateNoAnnotationConflict_whenAnnotationsOverlap_throwsException() {
- when(metadata.customMetadataObject(AUTOGENERATED_KEY_METADATA_KEY, Collection.class))
- .thenReturn(Optional.of(Collections.singleton("sharedAttribute")));
- when(metadata.customMetadataObject(AUTOGENERATED_UUID_METADATA_KEY, Collection.class))
- .thenReturn(Optional.of(Arrays.asList("sharedAttribute", "otherUuidAttribute")));
-
- assertThatThrownBy(() -> validateNoAnnotationConflict(
- metadata,
- AUTOGENERATED_KEY_METADATA_KEY,
- AUTOGENERATED_UUID_METADATA_KEY,
- AUTOGENERATED_KEY_ANNOTATION,
- AUTOGENERATED_UUID_ANNOTATION
- ))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage(
- "Attribute 'sharedAttribute' cannot have both @DynamoDbAutoGeneratedKey and @DynamoDbAutoGeneratedUuid "
- + "annotations. These annotations have conflicting behaviors and cannot be used together on the same attribute.");
- }
-
- @Test
- public void validateNoAnnotationConflict_whenNoAnnotatedFields_doesNotThrowException() {
- assertThatCode(() -> validateNoAnnotationConflict(
- metadata,
- AUTOGENERATED_KEY_METADATA_KEY,
- AUTOGENERATED_UUID_METADATA_KEY,
- AUTOGENERATED_KEY_ANNOTATION,
- AUTOGENERATED_UUID_ANNOTATION
- )).doesNotThrowAnyException();
- }
-
- @Test
- public void validateNoAnnotationConflict_whenAnnotationsDontOverlap_doesNotThrowException() {
- when(metadata.customMetadataObject(AUTOGENERATED_KEY_METADATA_KEY, Collection.class))
- .thenReturn(Optional.of(Collections.singleton("keyAttribute")));
- when(metadata.customMetadataObject(AUTOGENERATED_UUID_METADATA_KEY, Collection.class))
- .thenReturn(Optional.of(Collections.singleton("uuidAttribute")));
-
- assertThatCode(() -> validateNoAnnotationConflict(
- metadata,
- AUTOGENERATED_KEY_METADATA_KEY,
- AUTOGENERATED_UUID_METADATA_KEY,
- AUTOGENERATED_KEY_ANNOTATION,
- AUTOGENERATED_UUID_ANNOTATION
- )).doesNotThrowAnyException();
- }
-
- @Test
- public void validateNoAnnotationConflict_whenSecondAnnotationIsNotUsed_doesNotThrow() {
- when(metadata.customMetadataObject(AUTOGENERATED_KEY_METADATA_KEY, Collection.class))
- .thenReturn(Optional.empty());
-
- assertThatCode(() -> validateNoAnnotationConflict(
- metadata,
- AUTOGENERATED_KEY_METADATA_KEY,
- AUTOGENERATED_UUID_METADATA_KEY,
- AUTOGENERATED_KEY_ANNOTATION,
- AUTOGENERATED_UUID_ANNOTATION
- )).doesNotThrowAnyException();
- }
-}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java
index e84378201b20..3f30fdc8ecdf 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java
@@ -265,9 +265,9 @@ public static Stream customStartAtAndIncrementValues() {
@MethodSource("customFailingStartAtAndIncrementValues")
public void customStartingValueAndIncrement_shouldThrow(Long startAt, Long incrementBy) {
assertThrows(IllegalArgumentException.class, () -> VersionedRecordExtension.builder()
- .startAt(startAt)
- .incrementBy(incrementBy)
- .build());
+ .startAt(startAt)
+ .incrementBy(incrementBy)
+ .build());
}
public static Stream customFailingStartAtAndIncrementValues() {
@@ -375,9 +375,9 @@ public void customStartingValueAndIncrementWithAnnotation_worksAsExpected() {
@Test
public void customAnnotationValuesAndBuilderValues_annotationShouldTakePrecedence() {
VersionedRecordExtension recordExtension = VersionedRecordExtension.builder()
- .startAt(5L)
- .incrementBy(2L)
- .build();
+ .startAt(5L)
+ .incrementBy(2L)
+ .build();
FakeVersionedThroughAnnotationItem item = new FakeVersionedThroughAnnotationItem();
item.setId(UUID.randomUUID().toString());
@@ -516,7 +516,7 @@ public void customIncrementForExistingVersion_worksAsExpected(Long startAt, Long
@ParameterizedTest
@MethodSource("customIncrementForExistingVersionValues")
public void customIncrementForExistingVersion_withImmutableSchema_worksAsExpected(Long startAt, Long incrementBy,
- Long existingVersion, String expectedNextVersion) {
+ Long existingVersion, String expectedNextVersion) {
VersionedRecordExtension.Builder recordExtensionBuilder = VersionedRecordExtension.builder();
if (startAt != null) {
recordExtensionBuilder.startAt(startAt);
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
deleted file mode 100644
index 2902465297f2..000000000000
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
+++ /dev/null
@@ -1,537 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates.
- * Licensed under the Apache License, Version 2.0.
- */
-
-package software.amazon.awssdk.enhanced.dynamodb.functionaltests;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertEquals;
-import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.AutoGeneratedUuidRecordTest.assertValidUuid;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey;
-import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.updateBehavior;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.assertj.core.api.Assertions;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
-import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
-import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedKeyExtension;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedKey;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
-import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
-import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
-import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
-
-/**
- * Tests for @DynamoDbAutoGeneratedKey annotation functionality.
- *
- * Tests cover: - Basic UUID generation on all 4 key types (primary PK/SK, GSI PK/SK) - UpdateBehavior control (WRITE_ALWAYS vs
- * WRITE_IF_NOT_EXISTS) for secondary index keys - Primary key limitations (UpdateBehavior has no effect) - Error handling for
- * invalid usage - Integration with other extensions (VersionedRecord)
- */
-@RunWith(Parameterized.class)
-public class AutoGeneratedKeyRecordTest extends LocalDynamoDbSyncTestBase {
-
- private final DynamoDbTable mappedTable;
-
- public AutoGeneratedKeyRecordTest(String testName, TableSchema schema) {
- this.mappedTable = DynamoDbEnhancedClient.builder()
- .dynamoDbClient(getDynamoDbClient())
- .extensions(AutoGeneratedKeyExtension.create())
- .build()
- .table(getConcreteTableName("AutoGenKey-table"), schema);
- }
-
- @Parameters(name = "{index}: {0}")
- public static Collection