15
15
16
16
package software .amazon .awssdk .enhanced .dynamodb .mapper ;
17
17
18
+ import static software .amazon .awssdk .enhanced .dynamodb .internal .DynamoDbEnhancedLogger .BEAN_LOGGER ;
19
+
18
20
import java .beans .BeanInfo ;
19
21
import java .beans .IntrospectionException ;
20
22
import java .beans .Introspector ;
21
23
import java .beans .PropertyDescriptor ;
22
24
import java .lang .annotation .Annotation ;
25
+ import java .lang .reflect .Constructor ;
23
26
import java .lang .reflect .InvocationTargetException ;
24
27
import java .lang .reflect .Method ;
25
28
import java .lang .reflect .Modifier ;
96
99
* Creating an {@link BeanTableSchema} is a moderately expensive operation, and should be performed sparingly. This is
97
100
* usually done once at application startup.
98
101
*
102
+ * If this table schema is not behaving as you expect, enable debug logging for 'software.amazon.awssdk.enhanced.dynamodb.beans'.
103
+ *
99
104
* @param <T> The type of object that this {@link TableSchema} maps to.
100
105
*/
101
106
@ SdkPublicApi
@@ -122,6 +127,7 @@ public static <T> BeanTableSchema<T> create(Class<T> beanClass) {
122
127
}
123
128
124
129
private static <T > BeanTableSchema <T > create (Class <T > beanClass , MetaTableSchemaCache metaTableSchemaCache ) {
130
+ debugLog (beanClass , () -> "Creating bean schema" );
125
131
// Fetch or create a new reference to this yet-to-be-created TableSchema in the cache
126
132
MetaTableSchema <T > metaTableSchema = metaTableSchemaCache .getOrCreate (beanClass );
127
133
@@ -158,7 +164,8 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
158
164
DynamoDbBean dynamoDbBean = beanClass .getAnnotation (DynamoDbBean .class );
159
165
160
166
if (dynamoDbBean == null ) {
161
- throw new IllegalArgumentException ("A DynamoDb bean class must be annotated with @DynamoDbBean" );
167
+ throw new IllegalArgumentException ("A DynamoDb bean class must be annotated with @DynamoDbBean, but " +
168
+ beanClass .getTypeName () + " was not." );
162
169
}
163
170
164
171
BeanInfo beanInfo ;
@@ -174,12 +181,12 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
174
181
StaticTableSchema .Builder <T > builder = StaticTableSchema .builder (beanClass )
175
182
.newItemSupplier (newObjectSupplier );
176
183
177
- builder .attributeConverterProviders (createConverterProvidersFromAnnotation (dynamoDbBean ));
184
+ builder .attributeConverterProviders (createConverterProvidersFromAnnotation (beanClass , dynamoDbBean ));
178
185
179
186
List <StaticAttribute <T , ?>> attributes = new ArrayList <>();
180
187
181
188
Arrays .stream (beanInfo .getPropertyDescriptors ())
182
- .filter (BeanTableSchema :: isMappableProperty )
189
+ .filter (p -> isMappableProperty ( beanClass , p ) )
183
190
.forEach (propertyDescriptor -> {
184
191
DynamoDbFlatten dynamoDbFlatten = getPropertyAnnotation (propertyDescriptor , DynamoDbFlatten .class );
185
192
@@ -221,12 +228,14 @@ private static AttributeConfiguration resolveAttributeConfiguration(PropertyDesc
221
228
.build ();
222
229
}
223
230
224
- private static List <AttributeConverterProvider > createConverterProvidersFromAnnotation (DynamoDbBean dynamoDbBean ) {
231
+ private static List <AttributeConverterProvider > createConverterProvidersFromAnnotation (Class <?> beanClass ,
232
+ DynamoDbBean dynamoDbBean ) {
225
233
Class <? extends AttributeConverterProvider >[] providerClasses = dynamoDbBean .converterProviders ();
226
234
227
235
return Arrays .stream (providerClasses )
228
- .map (c -> (AttributeConverterProvider ) newObjectSupplierForClass (c ).get ())
229
- .collect (Collectors .toList ());
236
+ .peek (c -> debugLog (beanClass , () -> "Adding Converter: " + c .getTypeName ()))
237
+ .map (c -> (AttributeConverterProvider ) newObjectSupplierForClass (c ).get ())
238
+ .collect (Collectors .toList ());
230
239
}
231
240
232
241
private static <T > StaticAttribute .Builder <T , ?> staticAttributeBuilder (PropertyDescriptor propertyDescriptor ,
@@ -358,7 +367,9 @@ private static void addTagsToAttribute(StaticAttribute.Builder<?, ?> attributeBu
358
367
359
368
private static <R > Supplier <R > newObjectSupplierForClass (Class <R > clazz ) {
360
369
try {
361
- return ObjectConstructor .create (clazz , clazz .getConstructor ());
370
+ Constructor <R > constructor = clazz .getConstructor ();
371
+ debugLog (clazz , () -> "Constructor: " + constructor );
372
+ return ObjectConstructor .create (clazz , constructor );
362
373
} catch (NoSuchMethodException e ) {
363
374
throw new IllegalArgumentException (
364
375
String .format ("Class '%s' appears to have no default constructor thus cannot be used with the " +
@@ -368,12 +379,14 @@ private static <R> Supplier<R> newObjectSupplierForClass(Class<R> clazz) {
368
379
369
380
private static <T , R > Function <T , R > getterForProperty (PropertyDescriptor propertyDescriptor , Class <T > beanClass ) {
370
381
Method readMethod = propertyDescriptor .getReadMethod ();
382
+ debugLog (beanClass , () -> "Property " + propertyDescriptor .getDisplayName () + " read method: " + readMethod );
371
383
return BeanAttributeGetter .create (beanClass , readMethod );
372
384
}
373
385
374
386
private static <T , R > BiConsumer <T , R > setterForProperty (PropertyDescriptor propertyDescriptor ,
375
387
Class <T > beanClass ) {
376
388
Method writeMethod = propertyDescriptor .getWriteMethod ();
389
+ debugLog (beanClass , () -> "Property " + propertyDescriptor .getDisplayName () + " write method: " + writeMethod );
377
390
return BeanAttributeSetter .create (beanClass , writeMethod );
378
391
}
379
392
@@ -386,10 +399,27 @@ private static String attributeNameForProperty(PropertyDescriptor propertyDescri
386
399
return propertyDescriptor .getName ();
387
400
}
388
401
389
- private static boolean isMappableProperty (PropertyDescriptor propertyDescriptor ) {
390
- return propertyDescriptor .getReadMethod () != null
391
- && propertyDescriptor .getWriteMethod () != null
392
- && getPropertyAnnotation (propertyDescriptor , DynamoDbIgnore .class ) == null ;
402
+ private static boolean isMappableProperty (Class <?> beanClass , PropertyDescriptor propertyDescriptor ) {
403
+
404
+ if (propertyDescriptor .getReadMethod () == null ) {
405
+ debugLog (beanClass , () -> "Ignoring bean property " + propertyDescriptor .getDisplayName () + " because it has no "
406
+ + "read (get/is) method." );
407
+ return false ;
408
+ }
409
+
410
+ if (propertyDescriptor .getWriteMethod () == null ) {
411
+ debugLog (beanClass , () -> "Ignoring bean property " + propertyDescriptor .getDisplayName () + " because it has no "
412
+ + "write (set) method." );
413
+ return false ;
414
+ }
415
+
416
+ if (getPropertyAnnotation (propertyDescriptor , DynamoDbIgnore .class ) != null ) {
417
+ debugLog (beanClass , () -> "Ignoring bean property " + propertyDescriptor .getDisplayName () + " because it has "
418
+ + "@DynamoDbIgnore." );
419
+ return false ;
420
+ }
421
+
422
+ return true ;
393
423
}
394
424
395
425
private static <R extends Annotation > R getPropertyAnnotation (PropertyDescriptor propertyDescriptor ,
@@ -401,6 +431,9 @@ private static <R extends Annotation> R getPropertyAnnotation(PropertyDescriptor
401
431
return getterAnnotation ;
402
432
}
403
433
434
+ // TODO: It's a common mistake that superclasses might have annotations that the child classes do not inherit, but the
435
+ // customer expects them to be inherited. We should either allow inheriting those annotations, allow specifying an
436
+ // annotation to inherit them, or log when this situation happens.
404
437
return setterAnnotation ;
405
438
}
406
439
@@ -409,5 +442,9 @@ private static List<? extends Annotation> propertyAnnotations(PropertyDescriptor
409
442
Arrays .stream (propertyDescriptor .getWriteMethod ().getAnnotations ()))
410
443
.collect (Collectors .toList ());
411
444
}
445
+
446
+ private static void debugLog (Class <?> beanClass , Supplier <String > logMessage ) {
447
+ BEAN_LOGGER .debug (() -> beanClass .getTypeName () + " - " + logMessage .get ());
448
+ }
412
449
}
413
450
0 commit comments