1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.socialsignin.spring.data.dynamodb.repository.support;
17
18 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
19 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
20 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexHashKey;
21 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexRangeKey;
22 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMarshaller;
23 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMarshalling;
24 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
25 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
26 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted;
27 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter;
28 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBVersionAttribute;
29 import org.springframework.util.Assert;
30 import org.springframework.util.ReflectionUtils;
31 import org.springframework.util.StringUtils;
32
33 import java.lang.annotation.Annotation;
34 import java.lang.reflect.Field;
35 import java.lang.reflect.InvocationTargetException;
36 import java.lang.reflect.Method;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Optional;
42
43
44
45
46
47 public class DynamoDBEntityMetadataSupport<T, ID> implements DynamoDBHashKeyExtractingEntityMetadata<T> {
48
49 private final Class<T> domainType;
50 private boolean hasRangeKey;
51 private String hashKeyPropertyName;
52 private List<String> globalIndexHashKeyPropertyNames;
53 private List<String> globalIndexRangeKeyPropertyNames;
54
55 private String dynamoDBTableName;
56 private Map<String, String[]> globalSecondaryIndexNames = new HashMap<>();
57
58 @Override
59 public String getDynamoDBTableName() {
60 return dynamoDBTableName;
61 }
62
63
64
65
66
67
68
69
70 public DynamoDBEntityMetadataSupport(final Class<T> domainType) {
71
72 Assert.notNull(domainType, "Domain type must not be null!");
73 this.domainType = domainType;
74
75 DynamoDBTable table = this.domainType.getAnnotation(DynamoDBTable.class);
76 Assert.notNull(table, "Domain type must by annotated with DynamoDBTable!");
77
78 this.dynamoDBTableName = table.tableName();
79 this.hashKeyPropertyName = null;
80 this.globalSecondaryIndexNames = new HashMap<>();
81 this.globalIndexHashKeyPropertyNames = new ArrayList<>();
82 this.globalIndexRangeKeyPropertyNames = new ArrayList<>();
83 ReflectionUtils.doWithMethods(domainType, method -> {
84 if (method.getAnnotation(DynamoDBHashKey.class) != null) {
85 hashKeyPropertyName = getPropertyNameForAccessorMethod(method);
86 }
87 if (method.getAnnotation(DynamoDBRangeKey.class) != null) {
88 hasRangeKey = true;
89 }
90 DynamoDBIndexRangeKey dynamoDBRangeKeyAnnotation = method.getAnnotation(DynamoDBIndexRangeKey.class);
91 DynamoDBIndexHashKey dynamoDBHashKeyAnnotation = method.getAnnotation(DynamoDBIndexHashKey.class);
92
93 if (dynamoDBRangeKeyAnnotation != null) {
94 addGlobalSecondaryIndexNames(method, dynamoDBRangeKeyAnnotation);
95 }
96 if (dynamoDBHashKeyAnnotation != null) {
97 addGlobalSecondaryIndexNames(method, dynamoDBHashKeyAnnotation);
98 }
99 });
100 ReflectionUtils.doWithFields(domainType, field -> {
101 if (field.getAnnotation(DynamoDBHashKey.class) != null) {
102 hashKeyPropertyName = getPropertyNameForField(field);
103 }
104 if (field.getAnnotation(DynamoDBRangeKey.class) != null) {
105 hasRangeKey = true;
106 }
107 DynamoDBIndexRangeKey dynamoDBRangeKeyAnnotation = field.getAnnotation(DynamoDBIndexRangeKey.class);
108 DynamoDBIndexHashKey dynamoDBHashKeyAnnotation = field.getAnnotation(DynamoDBIndexHashKey.class);
109
110 if (dynamoDBRangeKeyAnnotation != null) {
111 addGlobalSecondaryIndexNames(field, dynamoDBRangeKeyAnnotation);
112 }
113 if (dynamoDBHashKeyAnnotation != null) {
114 addGlobalSecondaryIndexNames(field, dynamoDBHashKeyAnnotation);
115 }
116 });
117 Assert.notNull(hashKeyPropertyName, "Unable to find hash key field or getter method on " + domainType + "!");
118 }
119
120 public DynamoDBEntityInformation<T, ID> getEntityInformation() {
121
122 if (hasRangeKey) {
123 DynamoDBHashAndRangeKeyExtractingEntityMetadataImpl<T, ID> metadata = new DynamoDBHashAndRangeKeyExtractingEntityMetadataImpl<T, ID>(
124 domainType);
125 return new DynamoDBIdIsHashAndRangeKeyEntityInformationImpl<>(domainType, metadata);
126 } else {
127 return new DynamoDBIdIsHashKeyEntityInformationImpl<>(domainType, this);
128 }
129 }
130
131
132
133
134
135
136 @Override
137 public Class<T> getJavaType() {
138 return domainType;
139 }
140
141 @Override
142 public boolean isHashKeyProperty(String propertyName) {
143 return hashKeyPropertyName.equals(propertyName);
144 }
145
146 protected boolean isFieldAnnotatedWith(final String propertyName, final Class<? extends Annotation> annotation) {
147
148 Field field = findField(propertyName);
149 return field != null && field.getAnnotation(annotation) != null;
150 }
151
152 private String toGetMethodName(String propertyName) {
153 String methodName = propertyName.substring(0, 1).toUpperCase();
154 if (propertyName.length() > 1) {
155 methodName = methodName + propertyName.substring(1);
156 }
157 return "get" + methodName;
158 }
159
160 protected String toSetterMethodNameFromAccessorMethod(Method method) {
161 String accessorMethodName = method.getName();
162 if (accessorMethodName.startsWith("get")) {
163 return "set" + accessorMethodName.substring(3);
164 } else if (accessorMethodName.startsWith("is")) {
165 return "is" + accessorMethodName.substring(2);
166 }
167 return null;
168 }
169
170 private String toIsMethodName(String propertyName) {
171 String methodName = propertyName.substring(0, 1).toUpperCase();
172 if (propertyName.length() > 1) {
173 methodName = methodName + propertyName.substring(1);
174 }
175 return "is" + methodName;
176 }
177
178 private Method findMethod(String propertyName) {
179 Method method = ReflectionUtils.findMethod(domainType, toGetMethodName(propertyName));
180 if (method == null) {
181 method = ReflectionUtils.findMethod(domainType, toIsMethodName(propertyName));
182 }
183 return method;
184
185 }
186
187 private Field findField(String propertyName) {
188 return ReflectionUtils.findField(domainType, propertyName);
189 }
190
191 public String getOverriddenAttributeName(Method method) {
192
193 if (method != null) {
194 if (method.getAnnotation(DynamoDBAttribute.class) != null
195 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBAttribute.class).attributeName())) {
196 return method.getAnnotation(DynamoDBAttribute.class).attributeName();
197 }
198 if (method.getAnnotation(DynamoDBHashKey.class) != null
199 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBHashKey.class).attributeName())) {
200 return method.getAnnotation(DynamoDBHashKey.class).attributeName();
201 }
202 if (method.getAnnotation(DynamoDBRangeKey.class) != null
203 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBRangeKey.class).attributeName())) {
204 return method.getAnnotation(DynamoDBRangeKey.class).attributeName();
205 }
206 if (method.getAnnotation(DynamoDBIndexRangeKey.class) != null
207 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBIndexRangeKey.class).attributeName())) {
208 return method.getAnnotation(DynamoDBIndexRangeKey.class).attributeName();
209 }
210 if (method.getAnnotation(DynamoDBIndexHashKey.class) != null
211 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBIndexHashKey.class).attributeName())) {
212 return method.getAnnotation(DynamoDBIndexHashKey.class).attributeName();
213 }
214 if (method.getAnnotation(DynamoDBVersionAttribute.class) != null
215 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBVersionAttribute.class).attributeName())) {
216 return method.getAnnotation(DynamoDBVersionAttribute.class).attributeName();
217 }
218 }
219 return null;
220
221 }
222
223 @Override
224 public Optional<String> getOverriddenAttributeName(final String propertyName) {
225
226 Method method = findMethod(propertyName);
227 if (method != null) {
228 if (method.getAnnotation(DynamoDBAttribute.class) != null
229 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBAttribute.class).attributeName())) {
230 return Optional.of(method.getAnnotation(DynamoDBAttribute.class).attributeName());
231 }
232 if (method.getAnnotation(DynamoDBHashKey.class) != null
233 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBHashKey.class).attributeName())) {
234 return Optional.of(method.getAnnotation(DynamoDBHashKey.class).attributeName());
235 }
236 if (method.getAnnotation(DynamoDBRangeKey.class) != null
237 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBRangeKey.class).attributeName())) {
238 return Optional.of(method.getAnnotation(DynamoDBRangeKey.class).attributeName());
239 }
240 if (method.getAnnotation(DynamoDBIndexRangeKey.class) != null
241 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBIndexRangeKey.class).attributeName())) {
242 return Optional.of(method.getAnnotation(DynamoDBIndexRangeKey.class).attributeName());
243 }
244 if (method.getAnnotation(DynamoDBIndexHashKey.class) != null
245 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBIndexHashKey.class).attributeName())) {
246 return Optional.of(method.getAnnotation(DynamoDBIndexHashKey.class).attributeName());
247 }
248 if (method.getAnnotation(DynamoDBVersionAttribute.class) != null
249 && !StringUtils.isEmpty(method.getAnnotation(DynamoDBVersionAttribute.class).attributeName())) {
250 return Optional.of(method.getAnnotation(DynamoDBVersionAttribute.class).attributeName());
251 }
252 }
253
254 Field field = findField(propertyName);
255 if (field != null) {
256 if (field.getAnnotation(DynamoDBAttribute.class) != null
257 && !StringUtils.isEmpty(field.getAnnotation(DynamoDBAttribute.class).attributeName())) {
258 return Optional.of(field.getAnnotation(DynamoDBAttribute.class).attributeName());
259 }
260 if (field.getAnnotation(DynamoDBHashKey.class) != null
261 && !StringUtils.isEmpty(field.getAnnotation(DynamoDBHashKey.class).attributeName())) {
262 return Optional.of(field.getAnnotation(DynamoDBHashKey.class).attributeName());
263 }
264 if (field.getAnnotation(DynamoDBRangeKey.class) != null
265 && !StringUtils.isEmpty(field.getAnnotation(DynamoDBRangeKey.class).attributeName())) {
266 return Optional.of(field.getAnnotation(DynamoDBRangeKey.class).attributeName());
267 }
268 if (field.getAnnotation(DynamoDBIndexRangeKey.class) != null
269 && !StringUtils.isEmpty(field.getAnnotation(DynamoDBIndexRangeKey.class).attributeName())) {
270 return Optional.of(field.getAnnotation(DynamoDBIndexRangeKey.class).attributeName());
271 }
272 if (field.getAnnotation(DynamoDBIndexHashKey.class) != null
273 && !StringUtils.isEmpty(field.getAnnotation(DynamoDBIndexHashKey.class).attributeName())) {
274 return Optional.of(field.getAnnotation(DynamoDBIndexHashKey.class).attributeName());
275 }
276 if (field.getAnnotation(DynamoDBVersionAttribute.class) != null
277 && !StringUtils.isEmpty(field.getAnnotation(DynamoDBVersionAttribute.class).attributeName())) {
278 return Optional.of(field.getAnnotation(DynamoDBVersionAttribute.class).attributeName());
279 }
280 }
281 return Optional.empty();
282
283 }
284
285 @Override
286 @SuppressWarnings("deprecation")
287 public <V extends DynamoDBMarshaller<?>> V getMarshallerForProperty(final String propertyName) {
288
289 DynamoDBMarshalling annotation = null;
290
291 Method method = findMethod(propertyName);
292 if (method != null) {
293 annotation = method.getAnnotation(DynamoDBMarshalling.class);
294 }
295
296 if (annotation == null) {
297 Field field = findField(propertyName);
298 if (field != null) {
299 annotation = field.getAnnotation(DynamoDBMarshalling.class);
300 }
301 }
302
303 if (annotation != null) {
304 try {
305 @SuppressWarnings("unchecked")
306 Class<V> marshallerClazz = (Class<V>) annotation.marshallerClass();
307 return marshallerClazz.getDeclaredConstructor().newInstance();
308 } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
309 | InvocationTargetException e) {
310 throw new RuntimeException(e);
311 }
312 }
313
314 return null;
315 }
316
317 @Override
318 public DynamoDBTypeConverter<?, ?> getTypeConverterForProperty(final String propertyName) {
319 DynamoDBTypeConverted annotation = null;
320
321 Method method = findMethod(propertyName);
322 if (method != null) {
323 annotation = method.getAnnotation(DynamoDBTypeConverted.class);
324 }
325
326 if (annotation == null) {
327 Field field = findField(propertyName);
328 if (field != null) {
329 annotation = field.getAnnotation(DynamoDBTypeConverted.class);
330 }
331 }
332
333 if (annotation != null) {
334 try {
335 return annotation.converter().getDeclaredConstructor().newInstance();
336 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
337 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
338 throw new RuntimeException(e);
339 }
340 }
341
342 return null;
343 }
344
345 protected String getPropertyNameForAccessorMethod(Method method) {
346 String methodName = method.getName();
347 String propertyName = null;
348 if (methodName.startsWith("get")) {
349 propertyName = methodName.substring(3);
350 } else if (methodName.startsWith("is")) {
351 propertyName = methodName.substring(2);
352 }
353 Assert.notNull(propertyName, "Hash or range key annotated accessor methods must start with 'get' or 'is'");
354
355 String firstLetter = propertyName.substring(0, 1);
356 String remainder = propertyName.substring(1);
357 return firstLetter.toLowerCase() + remainder;
358 }
359
360 protected String getPropertyNameForField(Field field) {
361 return field.getName();
362 }
363
364 @Override
365 public String getHashKeyPropertyName() {
366 return hashKeyPropertyName;
367 }
368
369 private void addGlobalSecondaryIndexNames(Method method, DynamoDBIndexRangeKey dynamoDBIndexRangeKey) {
370
371 if (dynamoDBIndexRangeKey.globalSecondaryIndexNames() != null
372 && dynamoDBIndexRangeKey.globalSecondaryIndexNames().length > 0) {
373 String propertyName = getPropertyNameForAccessorMethod(method);
374
375 globalSecondaryIndexNames.put(propertyName,
376 method.getAnnotation(DynamoDBIndexRangeKey.class).globalSecondaryIndexNames());
377 globalIndexRangeKeyPropertyNames.add(propertyName);
378
379 }
380 if (dynamoDBIndexRangeKey.globalSecondaryIndexName() != null
381 && dynamoDBIndexRangeKey.globalSecondaryIndexName().trim().length() > 0) {
382 String propertyName = getPropertyNameForAccessorMethod(method);
383 globalSecondaryIndexNames.put(propertyName,
384 new String[]{method.getAnnotation(DynamoDBIndexRangeKey.class).globalSecondaryIndexName()});
385 globalIndexRangeKeyPropertyNames.add(propertyName);
386
387 }
388
389 }
390
391 private void addGlobalSecondaryIndexNames(Field field, DynamoDBIndexRangeKey dynamoDBIndexRangeKey) {
392
393 if (dynamoDBIndexRangeKey.globalSecondaryIndexNames() != null
394 && dynamoDBIndexRangeKey.globalSecondaryIndexNames().length > 0) {
395 String propertyName = getPropertyNameForField(field);
396
397 globalSecondaryIndexNames.put(propertyName,
398 field.getAnnotation(DynamoDBIndexRangeKey.class).globalSecondaryIndexNames());
399 globalIndexRangeKeyPropertyNames.add(propertyName);
400
401 }
402 if (dynamoDBIndexRangeKey.globalSecondaryIndexName() != null
403 && dynamoDBIndexRangeKey.globalSecondaryIndexName().trim().length() > 0) {
404 String propertyName = getPropertyNameForField(field);
405 globalSecondaryIndexNames.put(propertyName,
406 new String[]{field.getAnnotation(DynamoDBIndexRangeKey.class).globalSecondaryIndexName()});
407 globalIndexRangeKeyPropertyNames.add(propertyName);
408
409 }
410
411 }
412
413 private void addGlobalSecondaryIndexNames(Method method, DynamoDBIndexHashKey dynamoDBIndexHashKey) {
414
415 if (dynamoDBIndexHashKey.globalSecondaryIndexNames() != null
416 && dynamoDBIndexHashKey.globalSecondaryIndexNames().length > 0) {
417 String propertyName = getPropertyNameForAccessorMethod(method);
418
419 globalSecondaryIndexNames.put(propertyName,
420 method.getAnnotation(DynamoDBIndexHashKey.class).globalSecondaryIndexNames());
421 globalIndexHashKeyPropertyNames.add(propertyName);
422
423 }
424 if (dynamoDBIndexHashKey.globalSecondaryIndexName() != null
425 && dynamoDBIndexHashKey.globalSecondaryIndexName().trim().length() > 0) {
426 String propertyName = getPropertyNameForAccessorMethod(method);
427
428 globalSecondaryIndexNames.put(propertyName,
429 new String[]{method.getAnnotation(DynamoDBIndexHashKey.class).globalSecondaryIndexName()});
430 globalIndexHashKeyPropertyNames.add(propertyName);
431
432 }
433 }
434
435 private void addGlobalSecondaryIndexNames(Field field, DynamoDBIndexHashKey dynamoDBIndexHashKey) {
436
437 if (dynamoDBIndexHashKey.globalSecondaryIndexNames() != null
438 && dynamoDBIndexHashKey.globalSecondaryIndexNames().length > 0) {
439 String propertyName = getPropertyNameForField(field);
440
441 globalSecondaryIndexNames.put(propertyName,
442 field.getAnnotation(DynamoDBIndexHashKey.class).globalSecondaryIndexNames());
443 globalIndexHashKeyPropertyNames.add(propertyName);
444
445 }
446 if (dynamoDBIndexHashKey.globalSecondaryIndexName() != null
447 && dynamoDBIndexHashKey.globalSecondaryIndexName().trim().length() > 0) {
448 String propertyName = getPropertyNameForField(field);
449
450 globalSecondaryIndexNames.put(propertyName,
451 new String[]{field.getAnnotation(DynamoDBIndexHashKey.class).globalSecondaryIndexName()});
452 globalIndexHashKeyPropertyNames.add(propertyName);
453
454 }
455 }
456
457 @Override
458 public Map<String, String[]> getGlobalSecondaryIndexNamesByPropertyName() {
459 return globalSecondaryIndexNames;
460 }
461
462 @Override
463 public boolean isGlobalIndexHashKeyProperty(String propertyName) {
464 return globalIndexHashKeyPropertyNames.contains(propertyName);
465 }
466
467 @Override
468 public boolean isGlobalIndexRangeKeyProperty(String propertyName) {
469 return globalIndexRangeKeyPropertyNames.contains(propertyName);
470 }
471
472 }