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
+ }
0 commit comments