-
Notifications
You must be signed in to change notification settings - Fork 1k
Add support for strategy-based UUID auto-generation (@DynamoDbAutoGeneratedUuid) #6373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 31 commits
7cec4a2
8b80b18
d615605
da14ad5
031ef44
a47b1d7
0a29161
1bf7a3a
8c4826d
8a3048f
79ba90f
459fae8
8ae8405
08012f9
67cd161
a4bfb1e
fd877c7
bbd03ac
e654ef5
8c3d7d1
6d66b77
5bad65a
b935acf
45c4000
407703e
9a44ed2
dea26e0
defb044
8b057b5
68ca368
ae9e8ad
b72323f
0cdc2c2
9fe582f
9e890bb
65a14d0
3a95cab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "type": "feature", | ||
| "category": "Amazon DynamoDB Enhanced Client", | ||
| "contributor": "", | ||
| "description": "Added strategy-based UUID generation to DynamoDbAutoGeneratedUuid with ALWAYS (default) and CREATE modes." | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,6 @@ | |
|
|
||
| package software.amazon.awssdk.enhanced.dynamodb.extensions; | ||
|
|
||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
@@ -27,17 +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.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. | ||
| * This extension facilitates the automatic generation of a UUID value for tagged string attributes. | ||
| * UUID values are generated using {@link java.util.UUID#randomUUID()}. | ||
| * <p> | ||
| * 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 | ||
|
|
@@ -56,30 +55,36 @@ | |
| *} | ||
| * </p> | ||
| * <p> | ||
| * 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. | ||
| * </p> | ||
| * <p> | ||
| * 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}: | ||
| * <ul> | ||
| * <li>{@code ALWAYS}: generate a new UUID on every write handled by this extension.</li> | ||
| * <li>{@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.</li> | ||
| * </ul> | ||
| * <p> | ||
| * {@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. | ||
| * </p> | ||
| */ | ||
| @SdkPublicApi | ||
| @ThreadSafe | ||
| public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientExtension { | ||
| private static final String CUSTOM_METADATA_KEY = | ||
| "software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute"; | ||
| private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ATTRIBUTE = new AutoGeneratedUuidAttribute(); | ||
| 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() { | ||
| } | ||
|
|
@@ -101,42 +106,69 @@ public static AutoGeneratedUuidExtension create() { | |
| public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) { | ||
|
|
||
|
|
||
| Collection<String> customMetadataObject = context.tableMetadata() | ||
| .customMetadataObject(CUSTOM_METADATA_KEY, Collection.class) | ||
| .orElse(null); | ||
| Map<String, DynamoDbAutoGenerateStrategy> customMetadataObject = | ||
| context.tableMetadata() | ||
| .customMetadataObject(CUSTOM_METADATA_KEY, Map.class) | ||
| .orElse(null); | ||
|
|
||
| if (customMetadataObject == null) { | ||
| return WriteModification.builder().build(); | ||
| } | ||
|
|
||
| Map<String, AttributeValue> 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(); | ||
| } | ||
|
|
||
| private void insertUuidInItemToTransform(Map<String, AttributeValue> itemToTransform, | ||
| String key) { | ||
| String key, | ||
| DynamoDbAutoGenerateStrategy strategy) { | ||
| if (strategy == DynamoDbAutoGenerateStrategy.CREATE && !isMissingValue(itemToTransform.get(key))) { | ||
| return; | ||
| } | ||
| 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()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. v1 did not treat an empty string as a "missing value". Whether its the right behavior is arguable. I think it's better if we stick to the existing v1 behavior of treating an empty string as a not missing. |
||
| } | ||
|
|
||
| public static final class AttributeTags { | ||
|
|
||
| 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 <R> void validateType(String attributeName, EnhancedType<R> type, | ||
|
|
@@ -156,7 +188,8 @@ public <R> void validateType(String attributeName, EnhancedType<R> type, | |
| @Override | ||
| public Consumer<StaticTableMetadata.Builder> 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); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the v1 SDK this strategy enum is shared between other annotations like |
||
| */ | ||
| @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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please dont apply suggestion through the github UI. This is meant to replace 124-128