14
14
*/
15
15
package com .amazonaws .services .dynamodbv2 .datamodeling ;
16
16
17
- import java .lang .reflect .Method ;
18
17
import java .util .Collections ;
19
18
import java .util .EnumSet ;
20
19
import java .util .HashMap ;
21
20
import java .util .Map ;
22
21
import java .util .Set ;
23
22
import java .util .concurrent .ConcurrentHashMap ;
24
23
24
+ import com .amazonaws .services .dynamodbv2 .datamodeling .DynamoDBMappingsRegistry .Mapping ;
25
+ import com .amazonaws .services .dynamodbv2 .datamodeling .DynamoDBMappingsRegistry .Mappings ;
25
26
import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .DoNotEncrypt ;
26
27
import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .DoNotTouch ;
27
28
import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .DynamoDBEncryptor ;
28
29
import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .EncryptionContext ;
29
30
import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .EncryptionFlags ;
31
+ import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .HandleUnknownAttributes ;
32
+ import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .TableAadOverride ;
30
33
import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .providers .EncryptionMaterialsProvider ;
31
34
import com .amazonaws .services .dynamodbv2 .model .AttributeValue ;
32
35
38
41
public class AttributeEncryptor implements AttributeTransformer {
39
42
private static final DynamoDBReflector reflector = new DynamoDBReflector ();
40
43
private final DynamoDBEncryptor encryptor ;
41
- private final Map <Class <?>, Map <String , Set <EncryptionFlags >>> flagCache =
42
- new ConcurrentHashMap <Class <?>, Map <String , Set <EncryptionFlags >>>();
44
+ private final Map <Class <?>, ModelClassMetadata > metadataCache = new ConcurrentHashMap <>();
43
45
44
46
public AttributeEncryptor (final DynamoDBEncryptor encryptor ) {
45
47
this .encryptor = encryptor ;
@@ -56,11 +58,11 @@ public DynamoDBEncryptor getEncryptor() {
56
58
@ Override
57
59
public Map <String , AttributeValue > transform (final Parameters <?> parameters ) {
58
60
// one map of attributeFlags per model class
59
- final Map < String , Set < EncryptionFlags >> attributeFlags = getAttributeFlags (parameters );
61
+ final ModelClassMetadata metadata = getModelClassMetadata (parameters );
60
62
try {
61
63
return encryptor .encryptRecord (
62
64
parameters .getAttributeValues (),
63
- attributeFlags ,
65
+ metadata . getEncryptionFlags () ,
64
66
paramsToContext (parameters ));
65
67
} catch (Exception ex ) {
66
68
throw new DynamoDBMappingException (ex );
@@ -69,7 +71,7 @@ public Map<String, AttributeValue> transform(final Parameters<?> parameters) {
69
71
70
72
@ Override
71
73
public Map <String , AttributeValue > untransform (final Parameters <?> parameters ) {
72
- final Map <String , Set <EncryptionFlags >> attributeFlags = getAttributeFlags (parameters );
74
+ final Map <String , Set <EncryptionFlags >> attributeFlags = getEncryptionFlags (parameters );
73
75
74
76
try {
75
77
return encryptor .decryptRecord (
@@ -81,49 +83,177 @@ public Map<String, AttributeValue> untransform(final Parameters<?> parameters) {
81
83
}
82
84
}
83
85
84
- private <T > Map <String , Set <EncryptionFlags >> getAttributeFlags (Parameters <T > parameters ) {
86
+ /*
87
+ * For any attributes we see from DynamoDB that aren't modeled in the mapper class,
88
+ * we either ignore them (the default behavior), or include them for encryption/signing
89
+ * based on the presence of the @HandleUnknownAttributes annotation (unless the class
90
+ * has @DoNotTouch, then we don't include them).
91
+ */
92
+ private Map <String , Set <EncryptionFlags >> getEncryptionFlags (final Parameters <?> parameters ) {
93
+ final ModelClassMetadata metadata = getModelClassMetadata (parameters );
94
+
95
+ // If the class is annotated with @DoNotTouch, then none of the attributes are
96
+ // encrypted or signed, so we don't need to bother looking for unknown attributes.
97
+ if (metadata .getDoNotTouch ()) {
98
+ return metadata .getEncryptionFlags ();
99
+ }
100
+
101
+ final Set <EncryptionFlags > unknownAttributeBehavior = metadata .getUnknownAttributeBehavior ();
102
+ final Map <String , Set <EncryptionFlags >> attributeFlags = new HashMap <>();
103
+ attributeFlags .putAll (metadata .getEncryptionFlags ());
104
+
105
+ for (final String attributeName : parameters .getAttributeValues ().keySet ()) {
106
+ if (!attributeFlags .containsKey (attributeName ) &&
107
+ !encryptor .getSignatureFieldName ().equals (attributeName ) &&
108
+ !encryptor .getMaterialDescriptionFieldName ().equals (attributeName )) {
109
+
110
+ attributeFlags .put (attributeName , unknownAttributeBehavior );
111
+ }
112
+ }
113
+
114
+ return attributeFlags ;
115
+ }
116
+
117
+ private <T > ModelClassMetadata getModelClassMetadata (Parameters <T > parameters ) {
85
118
// Due to the lack of explicit synchronization, it is possible that
86
119
// elements in the cache will be added multiple times. Since they will
87
120
// all be identical, this is okay. Avoiding explicit synchronization
88
121
// means that in the general (retrieval) case, should never block and
89
122
// should be extremely fast.
90
123
final Class <T > clazz = parameters .getModelClass ();
91
- Map <String , Set <EncryptionFlags >> attributeFlags = flagCache .get (clazz );
92
- if (attributeFlags == null ) {
93
- attributeFlags = new HashMap <String , Set <EncryptionFlags >>();
124
+ ModelClassMetadata metadata = metadataCache .get (clazz );
94
125
95
- final boolean encryptionEnabled = ! clazz . isAnnotationPresent ( DoNotEncrypt . class );
96
- final boolean doNotTouch = clazz . isAnnotationPresent ( DoNotTouch . class );
126
+ if ( metadata == null ) {
127
+ Map < String , Set < EncryptionFlags >> attributeFlags = new HashMap <>( );
97
128
98
- if (!doNotTouch ) {
99
- final Method hashKeyGetter = reflector .getPrimaryHashKeyGetter (clazz );
100
- final Method rangeKeyGetter = reflector .getPrimaryRangeKeyGetter (clazz );
129
+ final boolean handleUnknownAttributes = handleUnknownAttributes (clazz );
130
+ final EnumSet <EncryptionFlags > unknownAttributeBehavior = EnumSet .noneOf (EncryptionFlags .class );
101
131
102
- for (Method getter : reflector .getRelevantGetters (clazz )) {
132
+ if (shouldTouch (clazz )) {
133
+ Mappings mappings = DynamoDBMappingsRegistry .instance ().mappingsOf (clazz );
134
+
135
+ for (Mapping mapping : mappings .getMappings ()) {
103
136
final EnumSet <EncryptionFlags > flags = EnumSet .noneOf (EncryptionFlags .class );
104
- if (!getter .isAnnotationPresent (DoNotTouch .class )) {
105
- if (encryptionEnabled && !getter .isAnnotationPresent (DoNotEncrypt .class )
106
- && !getter .equals (hashKeyGetter ) && !getter .equals (rangeKeyGetter )
107
- && !reflector .isVersionAttributeGetter (getter )) {
137
+ if (shouldTouch (mapping )) {
138
+ if (shouldEncryptAttribute (clazz , mapping )) {
108
139
flags .add (EncryptionFlags .ENCRYPT );
109
140
}
110
141
flags .add (EncryptionFlags .SIGN );
111
142
}
112
- attributeFlags .put (reflector .getAttributeName (getter ),
113
- Collections .unmodifiableSet (flags ));
143
+ attributeFlags .put (mapping .getAttributeName (), Collections .unmodifiableSet (flags ));
144
+ }
145
+
146
+ if (handleUnknownAttributes ) {
147
+ unknownAttributeBehavior .add (EncryptionFlags .SIGN );
148
+
149
+ if (shouldEncrypt (clazz )) {
150
+ unknownAttributeBehavior .add (EncryptionFlags .ENCRYPT );
151
+ }
114
152
}
115
153
}
116
- flagCache .put (clazz , Collections .unmodifiableMap (attributeFlags ));
154
+
155
+ metadata = new ModelClassMetadata (Collections .unmodifiableMap (attributeFlags ), doNotTouch (clazz ),
156
+ Collections .unmodifiableSet (unknownAttributeBehavior ));
157
+ metadataCache .put (clazz , metadata );
117
158
}
118
- return attributeFlags ;
159
+ return metadata ;
160
+ }
161
+
162
+ /**
163
+ * @return True if {@link DoNotTouch} is not present on the class level. False otherwise
164
+ */
165
+ private boolean shouldTouch (Class <?> clazz ) {
166
+ return !doNotTouch (clazz );
167
+ }
168
+
169
+ /**
170
+ * @return True if {@link DoNotTouch} is not present on the getter level. False otherwise.
171
+ */
172
+ private boolean shouldTouch (Mapping mapping ) {
173
+ return !doNotTouch (mapping );
174
+ }
175
+
176
+ /**
177
+ * @return True if {@link DoNotTouch} IS present on the class level. False otherwise.
178
+ */
179
+ private boolean doNotTouch (Class <?> clazz ) {
180
+ return clazz .isAnnotationPresent (DoNotTouch .class );
181
+ }
182
+
183
+ /**
184
+ * @return True if {@link DoNotTouch} IS present on the getter level. False otherwise.
185
+ */
186
+ private boolean doNotTouch (Mapping mapping ) {
187
+ return mapping .getter ().isAnnotationPresent (DoNotTouch .class );
188
+ }
189
+
190
+ /**
191
+ * @return True if {@link DoNotEncrypt} is NOT present on the class level. False otherwise.
192
+ */
193
+ private boolean shouldEncrypt (Class <?> clazz ) {
194
+ return !doNotEncrypt (clazz );
195
+ }
196
+
197
+ /**
198
+ * @return True if {@link DoNotEncrypt} IS present on the class level. False otherwise.
199
+ */
200
+ private boolean doNotEncrypt (Class <?> clazz ) {
201
+ return clazz .isAnnotationPresent (DoNotEncrypt .class );
202
+ }
203
+
204
+ /**
205
+ * @return True if {@link DoNotEncrypt} IS present on the getter level. False otherwise.
206
+ */
207
+ private boolean doNotEncrypt (Mapping mapping ) {
208
+ return mapping .getter ().isAnnotationPresent (DoNotEncrypt .class );
209
+ }
210
+
211
+ /**
212
+ * @return True if the attribute should be encrypted, false otherwise.
213
+ */
214
+ private boolean shouldEncryptAttribute (final Class <?> clazz , final Mapping mapping ) {
215
+ return !(doNotEncrypt (clazz ) || doNotEncrypt (mapping ) || mapping .isPrimaryKey () || mapping .isVersion ());
119
216
}
120
-
217
+
121
218
private static EncryptionContext paramsToContext (Parameters <?> params ) {
219
+ final Class <?> clazz = params .getModelClass ();
220
+ final TableAadOverride override = clazz .getAnnotation (TableAadOverride .class );
221
+ final String tableName = ((override == null ) ? params .getTableName () : override .tableName ());
222
+
122
223
return new EncryptionContext .Builder ()
123
- .withHashKeyName (params .getHashKeyName ())
124
- .withRangeKeyName (params .getRangeKeyName ())
125
- .withTableName (params .getTableName ())
126
- .withModeledClass (params .getModelClass ())
127
- .withAttributeValues (params .getAttributeValues ()).build ();
224
+ .withHashKeyName (params .getHashKeyName ())
225
+ .withRangeKeyName (params .getRangeKeyName ())
226
+ .withTableName (tableName )
227
+ .withModeledClass (params .getModelClass ())
228
+ .withAttributeValues (params .getAttributeValues ()).build ();
229
+ }
230
+
231
+ private boolean handleUnknownAttributes (Class <?> clazz ) {
232
+ return clazz .getAnnotation (HandleUnknownAttributes .class ) != null ;
233
+ }
234
+
235
+ private static class ModelClassMetadata {
236
+ private final Map <String , Set <EncryptionFlags >> encryptionFlags ;
237
+ private final boolean doNotTouch ;
238
+ private final Set <EncryptionFlags > unknownAttributeBehavior ;
239
+
240
+ public ModelClassMetadata (Map <String , Set <EncryptionFlags >> encryptionFlags ,
241
+ boolean doNotTouch , Set <EncryptionFlags > unknownAttributeBehavior ) {
242
+ this .encryptionFlags = encryptionFlags ;
243
+ this .doNotTouch = doNotTouch ;
244
+ this .unknownAttributeBehavior = unknownAttributeBehavior ;
245
+ }
246
+
247
+ public Map <String , Set <EncryptionFlags >> getEncryptionFlags () {
248
+ return encryptionFlags ;
249
+ }
250
+
251
+ public boolean getDoNotTouch () {
252
+ return doNotTouch ;
253
+ }
254
+
255
+ public Set <EncryptionFlags > getUnknownAttributeBehavior () {
256
+ return unknownAttributeBehavior ;
257
+ }
128
258
}
129
259
}
0 commit comments