1414 */
1515package com .amazonaws .services .dynamodbv2 .datamodeling ;
1616
17- import java .lang .reflect .Method ;
1817import java .util .Collections ;
1918import java .util .EnumSet ;
2019import java .util .HashMap ;
2120import java .util .Map ;
2221import java .util .Set ;
2322import java .util .concurrent .ConcurrentHashMap ;
2423
24+ import com .amazonaws .services .dynamodbv2 .datamodeling .DynamoDBMappingsRegistry .Mapping ;
25+ import com .amazonaws .services .dynamodbv2 .datamodeling .DynamoDBMappingsRegistry .Mappings ;
2526import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .DoNotEncrypt ;
2627import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .DoNotTouch ;
2728import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .DynamoDBEncryptor ;
2829import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .EncryptionContext ;
2930import 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 ;
3033import com .amazonaws .services .dynamodbv2 .datamodeling .encryption .providers .EncryptionMaterialsProvider ;
3134import com .amazonaws .services .dynamodbv2 .model .AttributeValue ;
3235
3841public class AttributeEncryptor implements AttributeTransformer {
3942 private static final DynamoDBReflector reflector = new DynamoDBReflector ();
4043 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 <>();
4345
4446 public AttributeEncryptor (final DynamoDBEncryptor encryptor ) {
4547 this .encryptor = encryptor ;
@@ -56,11 +58,11 @@ public DynamoDBEncryptor getEncryptor() {
5658 @ Override
5759 public Map <String , AttributeValue > transform (final Parameters <?> parameters ) {
5860 // one map of attributeFlags per model class
59- final Map < String , Set < EncryptionFlags >> attributeFlags = getAttributeFlags (parameters );
61+ final ModelClassMetadata metadata = getModelClassMetadata (parameters );
6062 try {
6163 return encryptor .encryptRecord (
6264 parameters .getAttributeValues (),
63- attributeFlags ,
65+ metadata . getEncryptionFlags () ,
6466 paramsToContext (parameters ));
6567 } catch (Exception ex ) {
6668 throw new DynamoDBMappingException (ex );
@@ -69,7 +71,7 @@ public Map<String, AttributeValue> transform(final Parameters<?> parameters) {
6971
7072 @ Override
7173 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 );
7375
7476 try {
7577 return encryptor .decryptRecord (
@@ -81,49 +83,177 @@ public Map<String, AttributeValue> untransform(final Parameters<?> parameters) {
8183 }
8284 }
8385
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 ) {
85118 // Due to the lack of explicit synchronization, it is possible that
86119 // elements in the cache will be added multiple times. Since they will
87120 // all be identical, this is okay. Avoiding explicit synchronization
88121 // means that in the general (retrieval) case, should never block and
89122 // should be extremely fast.
90123 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 );
94125
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 <>( );
97128
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 );
101131
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 ()) {
103136 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 )) {
108139 flags .add (EncryptionFlags .ENCRYPT );
109140 }
110141 flags .add (EncryptionFlags .SIGN );
111142 }
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+ }
114152 }
115153 }
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 );
117158 }
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 ());
119216 }
120-
217+
121218 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+
122223 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+ }
128258 }
129259}
0 commit comments