Skip to content

Commit 9f9a56d

Browse files
authored
Add Extension for @DynamoDBGeneratedUuid for EnhancedDynamoDB client (#4810)
* Add Extension for @DynamoDBGeneratedUuid for EnhancedDynamoDB client * Handled PR comments * Handled PR comments
1 parent fdc394d commit 9f9a56d

File tree

6 files changed

+948
-0
lines changed

6 files changed

+948
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS DynamoDB Enhanced Client",
4+
"contributor": "",
5+
"description": "Added support for `@DynamoDBAutoGeneratedUUID` to facilitate the automatic updating of DynamoDB attributes with random UUID."
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb.extensions;
17+
18+
import java.util.Collection;
19+
import java.util.Collections;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
import java.util.UUID;
23+
import java.util.function.Consumer;
24+
import software.amazon.awssdk.annotations.SdkPublicApi;
25+
import software.amazon.awssdk.annotations.ThreadSafe;
26+
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
27+
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
28+
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbExtensionContext;
29+
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
30+
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
31+
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
32+
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
33+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
34+
import software.amazon.awssdk.utils.Validate;
35+
36+
37+
/**
38+
* This extension facilitates the automatic generation of a unique UUID (Universally Unique Identifier) for a specified attribute
39+
* every time a new record is written to the database. The generated UUID is obtained using the
40+
* {@link java.util.UUID#randomUUID()} method.
41+
* <p>
42+
* This extension is not loaded by default when you instantiate a
43+
* {@link software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient}. Therefore, you need to specify it in a custom
44+
* extension when creating the enhanced client.
45+
* <p>
46+
* Example to add AutoGeneratedUuidExtension along with default extensions is
47+
* {@snippet :
48+
* DynamoDbEnhancedClient.builder().extensions(Stream.concat(ExtensionResolver.defaultExtensions().stream(),
49+
* Stream.of(AutoGeneratedUuidExtension.create())).collect(Collectors.toList())).build();
50+
*}
51+
* </p>
52+
* <p>
53+
* Example to just add AutoGeneratedUuidExtension without default extensions is
54+
* {@snippet :
55+
* DynamoDbEnhancedClient.builder().extensions(AutoGeneratedUuidExtension.create()).build();
56+
*}
57+
* </p>
58+
* <p>
59+
* To utilize the auto-generated UUID feature, first, create a field in your model that will store the UUID for the attribute.
60+
* This class field must be of type {@link java.lang.String}, and you need to tag it as the autoGeneratedUuidAttribute. If you are
61+
* using the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema}, then you should use the
62+
* {@link software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid} annotation. If you are using
63+
* the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema}, then you should use the
64+
* {@link
65+
* software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags#autoGeneratedUuidAttribute()}
66+
* static attribute tag.
67+
* </p>
68+
* <p>
69+
* Every time a new record is successfully put into the database, the specified attribute will be automatically populated with a
70+
* unique UUID generated using {@link java.util.UUID#randomUUID()}. If the UUID needs to be created only for `putItem` and should
71+
* not be generated for an `updateItem`, then
72+
* {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior#WRITE_IF_NOT_EXISTS} must be along with
73+
* {@link DynamoDbUpdateBehavior}
74+
*
75+
* </p>
76+
*/
77+
@SdkPublicApi
78+
@ThreadSafe
79+
public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientExtension {
80+
private static final String CUSTOM_METADATA_KEY =
81+
"software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
82+
private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ATTRIBUTE = new AutoGeneratedUuidAttribute();
83+
84+
private AutoGeneratedUuidExtension() {
85+
}
86+
87+
/**
88+
* @return an Instance of {@link AutoGeneratedUuidExtension}
89+
*/
90+
public static AutoGeneratedUuidExtension create() {
91+
return new AutoGeneratedUuidExtension();
92+
}
93+
94+
/**
95+
* Modifies the WriteModification UUID string with the attribute updated with the extension.
96+
*
97+
* @param context The {@link DynamoDbExtensionContext.BeforeWrite} context containing the state of the execution.
98+
* @return WriteModification String updated with attribute updated with Extension.
99+
*/
100+
@Override
101+
public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
102+
103+
104+
Collection<String> customMetadataObject = context.tableMetadata()
105+
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class)
106+
.orElse(null);
107+
108+
if (customMetadataObject == null) {
109+
return WriteModification.builder().build();
110+
}
111+
112+
Map<String, AttributeValue> itemToTransform = new HashMap<>(context.items());
113+
customMetadataObject.forEach(key -> insertUuidInItemToTransform(itemToTransform, key));
114+
return WriteModification.builder()
115+
.transformedItem(Collections.unmodifiableMap(itemToTransform))
116+
.build();
117+
}
118+
119+
private void insertUuidInItemToTransform(Map<String, AttributeValue> itemToTransform,
120+
String key) {
121+
itemToTransform.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
122+
}
123+
124+
public static final class AttributeTags {
125+
126+
private AttributeTags() {
127+
}
128+
129+
/**
130+
* Tags which indicate that the given attribute is supported wih Auto Generated UUID Record Extension.
131+
*
132+
* @return Tag name for AutoGenerated UUID Records
133+
*/
134+
public static StaticAttributeTag autoGeneratedUuidAttribute() {
135+
return AUTO_GENERATED_UUID_ATTRIBUTE;
136+
}
137+
}
138+
139+
private static class AutoGeneratedUuidAttribute implements StaticAttributeTag {
140+
141+
@Override
142+
public <R> void validateType(String attributeName, EnhancedType<R> type,
143+
AttributeValueType attributeValueType) {
144+
145+
Validate.notNull(type, "type is null");
146+
Validate.notNull(type.rawClass(), "rawClass is null");
147+
Validate.notNull(attributeValueType, "attributeValueType is null");
148+
149+
if (!type.rawClass().equals(String.class)) {
150+
throw new IllegalArgumentException(String.format(
151+
"Attribute '%s' of Class type %s is not a suitable Java Class type to be used as a Auto Generated "
152+
+ "Uuid attribute. Only String Class type is supported.", attributeName, type.rawClass()));
153+
}
154+
}
155+
156+
@Override
157+
public Consumer<StaticTableMetadata.Builder> modifyMetadata(String attributeName,
158+
AttributeValueType attributeValueType) {
159+
return metadata -> metadata.addCustomMetadataObject(CUSTOM_METADATA_KEY, Collections.singleton(attributeName))
160+
.markAttributeAsKey(attributeName, attributeValueType);
161+
}
162+
}
163+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb.extensions.annotations;
17+
18+
import java.lang.annotation.ElementType;
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
import java.lang.annotation.Target;
22+
import java.util.UUID;
23+
import software.amazon.awssdk.annotations.SdkPublicApi;
24+
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.AutoGeneratedUuidTag;
25+
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.BeanTableSchemaAttributeTag;
26+
27+
/**
28+
* Denotes this attribute as recording the auto generated UUID string for the record. Every time a record with this
29+
* attribute is written to the database it will update the attribute with a {@link UUID#randomUUID} string.
30+
*/
31+
@SdkPublicApi
32+
@Target(ElementType.METHOD)
33+
@Retention(RetentionPolicy.RUNTIME)
34+
@BeanTableSchemaAttributeTag(AutoGeneratedUuidTag.class)
35+
public @interface DynamoDbAutoGeneratedUuid {
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb.internal.extensions;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension;
20+
import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
21+
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
22+
23+
@SdkInternalApi
24+
public final class AutoGeneratedUuidTag {
25+
26+
private AutoGeneratedUuidTag() {
27+
}
28+
29+
public static StaticAttributeTag attributeTagFor(DynamoDbAutoGeneratedUuid annotation) {
30+
return AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute();
31+
}
32+
33+
}

0 commit comments

Comments
 (0)