Skip to content

Commit 8778f33

Browse files
committed
Fixed document bean classes used as parameterized types of collections. Also fixed recognition of parameterized classes as potential document beans
1 parent 9d9fd67 commit 8778f33

File tree

5 files changed

+305
-14
lines changed

5 files changed

+305
-14
lines changed

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.lang.reflect.InvocationTargetException;
2424
import java.lang.reflect.Method;
2525
import java.lang.reflect.Modifier;
26+
import java.lang.reflect.ParameterizedType;
2627
import java.lang.reflect.Type;
2728
import java.util.ArrayList;
2829
import java.util.Arrays;
@@ -230,29 +231,56 @@ private static Optional<AttributeConverterProvider> converterProviderAnnotation(
230231
Optional.empty();
231232
}
232233

233-
@SuppressWarnings("unchecked")
234234
private static <T> StaticAttribute.Builder<T, ?> staticAttributeBuilder(PropertyDescriptor propertyDescriptor,
235235
Class<T> beanClass) {
236236

237237
Type propertyType = propertyDescriptor.getReadMethod().getGenericReturnType();
238-
EnhancedType<?> propertyTypeToken = null;
238+
EnhancedType<?> propertyTypeToken = convertTypeToEnhancedType(propertyType);
239+
return StaticAttribute.builder(beanClass, propertyTypeToken)
240+
.name(attributeNameForProperty(propertyDescriptor))
241+
.getter(getterForProperty(propertyDescriptor, beanClass))
242+
.setter(setterForProperty(propertyDescriptor, beanClass));
243+
}
239244

240-
if (propertyType instanceof Class) {
241-
Class<?> clazz = (Class<?>) propertyType;
242-
if (clazz.getAnnotation(DynamoDbBean.class) != null) {
243-
propertyTypeToken = EnhancedType.documentOf((Class<Object>) clazz,
244-
(TableSchema<Object>) createStaticTableSchema(clazz));
245+
/**
246+
* Converts a {@link Type} to an {@link EnhancedType}. Usually {@link EnhancedType#of} is capable of doing this all
247+
* by itself, but for the BeanTableSchema we want to detect if a parameterized class is being passed without a
248+
* converter that is actually a {@link DynamoDbBean} in which case we want to capture its schema and add it to the
249+
* EnhancedType. Unfortunately this means we have to duplicate some of the recursive Type parsing that
250+
* EnhancedClient otherwise does all by itself.
251+
*/
252+
@SuppressWarnings("unchecked")
253+
private static EnhancedType<?> convertTypeToEnhancedType(Type type) {
254+
Class<?> clazz = null;
255+
256+
if (type instanceof ParameterizedType) {
257+
ParameterizedType parameterizedType = (ParameterizedType) type;
258+
Type rawType = parameterizedType.getRawType();
259+
260+
if (List.class.equals(rawType)) {
261+
return EnhancedType.listOf(convertTypeToEnhancedType(parameterizedType.getActualTypeArguments()[0]));
262+
}
263+
264+
if (Map.class.equals(rawType)) {
265+
return EnhancedType.mapOf(EnhancedType.of(parameterizedType.getActualTypeArguments()[0]),
266+
convertTypeToEnhancedType(parameterizedType.getActualTypeArguments()[1]));
245267
}
268+
269+
if (rawType instanceof Class) {
270+
clazz = (Class<?>) rawType;
271+
}
272+
} else if (type instanceof Class) {
273+
clazz = (Class<?>) type;
246274
}
247275

248-
if (propertyTypeToken == null) {
249-
propertyTypeToken = EnhancedType.of(propertyDescriptor.getReadMethod().getGenericReturnType());
276+
if (clazz != null) {
277+
if (clazz.getAnnotation(DynamoDbBean.class) != null) {
278+
return EnhancedType.documentOf((Class<Object>) clazz,
279+
(TableSchema<Object>) createStaticTableSchema(clazz));
280+
}
250281
}
251282

252-
return StaticAttribute.builder(beanClass, propertyTypeToken)
253-
.name(attributeNameForProperty(propertyDescriptor))
254-
.getter(getterForProperty(propertyDescriptor, beanClass))
255-
.setter(setterForProperty(propertyDescriptor, beanClass));
283+
return EnhancedType.of(type);
256284
}
257285

258286
private static Optional<AttributeConverter> attributeConverterAnnotation(PropertyDescriptor propertyDescriptor) {

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchemaTest.java

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.InvalidBean;
5555
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ListBean;
5656
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.MapBean;
57+
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ParameterizedAbstractBean;
58+
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ParameterizedDocumentBean;
5759
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.PrimitiveTypesBean;
5860
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.RemappedAttributeBean;
5961
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SecondaryIndexBean;
@@ -180,13 +182,156 @@ public void documentBean_correctlyMapsAttributes() {
180182
.m(singletonMap("attribute2", stringValue("two")))
181183
.build();
182184

183-
Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(documentBean, false);
185+
Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(documentBean, true);
184186
assertThat(itemMap.size(), is(3));
185187
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
186188
assertThat(itemMap, hasEntry("attribute1", stringValue("one")));
187189
assertThat(itemMap, hasEntry("abstractBean", expectedDocument));
188190
}
189191

192+
@Test
193+
public void documentBean_list_correctlyMapsAttributes() {
194+
BeanTableSchema<DocumentBean> beanTableSchema = BeanTableSchema.create(DocumentBean.class);
195+
AbstractBean abstractBean1 = new AbstractBean();
196+
abstractBean1.setAttribute2("two");
197+
AbstractBean abstractBean2 = new AbstractBean();
198+
abstractBean2.setAttribute2("three");
199+
DocumentBean documentBean = new DocumentBean();
200+
documentBean.setId("id-value");
201+
documentBean.setAttribute1("one");
202+
documentBean.setAbstractBeanList(Arrays.asList(abstractBean1, abstractBean2));
203+
204+
AttributeValue expectedDocument1 = AttributeValue.builder()
205+
.m(singletonMap("attribute2", stringValue("two")))
206+
.build();
207+
AttributeValue expectedDocument2 = AttributeValue.builder()
208+
.m(singletonMap("attribute2", stringValue("three")))
209+
.build();
210+
AttributeValue expectedList = AttributeValue.builder().l(expectedDocument1, expectedDocument2).build();
211+
212+
Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(documentBean, true);
213+
assertThat(itemMap.size(), is(3));
214+
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
215+
assertThat(itemMap, hasEntry("attribute1", stringValue("one")));
216+
assertThat(itemMap, hasEntry("abstractBeanList", expectedList));
217+
}
218+
219+
@Test
220+
public void documentBean_map_correctlyMapsAttributes() {
221+
BeanTableSchema<DocumentBean> beanTableSchema = BeanTableSchema.create(DocumentBean.class);
222+
AbstractBean abstractBean1 = new AbstractBean();
223+
abstractBean1.setAttribute2("two");
224+
AbstractBean abstractBean2 = new AbstractBean();
225+
abstractBean2.setAttribute2("three");
226+
DocumentBean documentBean = new DocumentBean();
227+
documentBean.setId("id-value");
228+
documentBean.setAttribute1("one");
229+
230+
Map<String, AbstractBean> abstractBeanMap = new HashMap<>();
231+
abstractBeanMap.put("key1", abstractBean1);
232+
abstractBeanMap.put("key2", abstractBean2);
233+
documentBean.setAbstractBeanMap(abstractBeanMap);
234+
235+
AttributeValue expectedDocument1 = AttributeValue.builder()
236+
.m(singletonMap("attribute2", stringValue("two")))
237+
.build();
238+
AttributeValue expectedDocument2 = AttributeValue.builder()
239+
.m(singletonMap("attribute2", stringValue("three")))
240+
.build();
241+
Map<String, AttributeValue> expectedAttributeValueMap = new HashMap<>();
242+
expectedAttributeValueMap.put("key1", expectedDocument1);
243+
expectedAttributeValueMap.put("key2", expectedDocument2);
244+
AttributeValue expectedMap = AttributeValue.builder().m(expectedAttributeValueMap).build();
245+
246+
Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(documentBean, true);
247+
assertThat(itemMap.size(), is(3));
248+
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
249+
assertThat(itemMap, hasEntry("attribute1", stringValue("one")));
250+
assertThat(itemMap, hasEntry("abstractBeanMap", expectedMap));
251+
}
252+
253+
@Test
254+
public void parameterizedDocumentBean_correctlyMapsAttributes() {
255+
BeanTableSchema<ParameterizedDocumentBean> beanTableSchema = BeanTableSchema.create(ParameterizedDocumentBean.class);
256+
ParameterizedAbstractBean<String> abstractBean = new ParameterizedAbstractBean<>();
257+
abstractBean.setAttribute2("two");
258+
ParameterizedDocumentBean documentBean = new ParameterizedDocumentBean();
259+
documentBean.setId("id-value");
260+
documentBean.setAttribute1("one");
261+
documentBean.setAbstractBean(abstractBean);
262+
263+
AttributeValue expectedDocument = AttributeValue.builder()
264+
.m(singletonMap("attribute2", stringValue("two")))
265+
.build();
266+
267+
Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(documentBean, true);
268+
assertThat(itemMap.size(), is(3));
269+
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
270+
assertThat(itemMap, hasEntry("attribute1", stringValue("one")));
271+
assertThat(itemMap, hasEntry("abstractBean", expectedDocument));
272+
}
273+
274+
@Test
275+
public void parameterizedDocumentBean_list_correctlyMapsAttributes() {
276+
BeanTableSchema<ParameterizedDocumentBean> beanTableSchema = BeanTableSchema.create(ParameterizedDocumentBean.class);
277+
ParameterizedAbstractBean<String> abstractBean1 = new ParameterizedAbstractBean<>();
278+
abstractBean1.setAttribute2("two");
279+
ParameterizedAbstractBean<String> abstractBean2 = new ParameterizedAbstractBean<>();
280+
abstractBean2.setAttribute2("three");
281+
ParameterizedDocumentBean documentBean = new ParameterizedDocumentBean();
282+
documentBean.setId("id-value");
283+
documentBean.setAttribute1("one");
284+
documentBean.setAbstractBeanList(Arrays.asList(abstractBean1, abstractBean2));
285+
286+
AttributeValue expectedDocument1 = AttributeValue.builder()
287+
.m(singletonMap("attribute2", stringValue("two")))
288+
.build();
289+
AttributeValue expectedDocument2 = AttributeValue.builder()
290+
.m(singletonMap("attribute2", stringValue("three")))
291+
.build();
292+
AttributeValue expectedList = AttributeValue.builder().l(expectedDocument1, expectedDocument2).build();
293+
294+
Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(documentBean, true);
295+
assertThat(itemMap.size(), is(3));
296+
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
297+
assertThat(itemMap, hasEntry("attribute1", stringValue("one")));
298+
assertThat(itemMap, hasEntry("abstractBeanList", expectedList));
299+
}
300+
301+
@Test
302+
public void parameterizedDocumentBean_map_correctlyMapsAttributes() {
303+
BeanTableSchema<ParameterizedDocumentBean> beanTableSchema = BeanTableSchema.create(ParameterizedDocumentBean.class);
304+
ParameterizedAbstractBean<String> abstractBean1 = new ParameterizedAbstractBean<>();
305+
abstractBean1.setAttribute2("two");
306+
ParameterizedAbstractBean<String> abstractBean2 = new ParameterizedAbstractBean<>();
307+
abstractBean2.setAttribute2("three");
308+
ParameterizedDocumentBean documentBean = new ParameterizedDocumentBean();
309+
documentBean.setId("id-value");
310+
documentBean.setAttribute1("one");
311+
312+
Map<String, ParameterizedAbstractBean<String>> abstractBeanMap = new HashMap<>();
313+
abstractBeanMap.put("key1", abstractBean1);
314+
abstractBeanMap.put("key2", abstractBean2);
315+
documentBean.setAbstractBeanMap(abstractBeanMap);
316+
317+
AttributeValue expectedDocument1 = AttributeValue.builder()
318+
.m(singletonMap("attribute2", stringValue("two")))
319+
.build();
320+
AttributeValue expectedDocument2 = AttributeValue.builder()
321+
.m(singletonMap("attribute2", stringValue("three")))
322+
.build();
323+
Map<String, AttributeValue> expectedAttributeValueMap = new HashMap<>();
324+
expectedAttributeValueMap.put("key1", expectedDocument1);
325+
expectedAttributeValueMap.put("key2", expectedDocument2);
326+
AttributeValue expectedMap = AttributeValue.builder().m(expectedAttributeValueMap).build();
327+
328+
Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(documentBean, true);
329+
assertThat(itemMap.size(), is(3));
330+
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
331+
assertThat(itemMap, hasEntry("attribute1", stringValue("one")));
332+
assertThat(itemMap, hasEntry("abstractBeanMap", expectedMap));
333+
}
334+
190335
@Test
191336
public void extendedBean_correctlyExtendsAttributes() {
192337
BeanTableSchema<ExtendedBean> beanTableSchema = BeanTableSchema.create(ExtendedBean.class);

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/DocumentBean.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans;
1717

18+
import java.util.List;
19+
import java.util.Map;
1820
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
1921
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
2022

@@ -23,6 +25,8 @@ public class DocumentBean {
2325
private String id;
2426
private String attribute1;
2527
private AbstractBean abstractBean;
28+
private List<AbstractBean> abstractBeanList;
29+
private Map<String, AbstractBean> abstractBeanMap;
2630

2731
@DynamoDbPartitionKey
2832
public String getId() {
@@ -45,4 +49,20 @@ public AbstractBean getAbstractBean() {
4549
public void setAbstractBean(AbstractBean abstractBean) {
4650
this.abstractBean = abstractBean;
4751
}
52+
53+
public List<AbstractBean> getAbstractBeanList() {
54+
return abstractBeanList;
55+
}
56+
57+
public void setAbstractBeanList(List<AbstractBean> abstractBeanList) {
58+
this.abstractBeanList = abstractBeanList;
59+
}
60+
61+
public Map<String, AbstractBean> getAbstractBeanMap() {
62+
return abstractBeanMap;
63+
}
64+
65+
public void setAbstractBeanMap(Map<String, AbstractBean> abstractBeanMap) {
66+
this.abstractBeanMap = abstractBeanMap;
67+
}
4868
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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.mapper.testbeans;
17+
18+
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
19+
20+
@DynamoDbBean
21+
public class ParameterizedAbstractBean<T> {
22+
private String attribute2;
23+
24+
public String getAttribute2() {
25+
return attribute2;
26+
}
27+
public void setAttribute2(String attribute2) {
28+
this.attribute2 = attribute2;
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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.mapper.testbeans;
17+
18+
import java.util.List;
19+
import java.util.Map;
20+
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
21+
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
22+
23+
@DynamoDbBean
24+
public class ParameterizedDocumentBean {
25+
private String id;
26+
private String attribute1;
27+
private ParameterizedAbstractBean<String> abstractBean;
28+
private List<ParameterizedAbstractBean<String>> abstractBeanList;
29+
private Map<String, ParameterizedAbstractBean<String>> abstractBeanMap;
30+
31+
@DynamoDbPartitionKey
32+
public String getId() {
33+
return this.id;
34+
}
35+
public void setId(String id) {
36+
this.id = id;
37+
}
38+
39+
public String getAttribute1() {
40+
return attribute1;
41+
}
42+
public void setAttribute1(String attribute1) {
43+
this.attribute1 = attribute1;
44+
}
45+
46+
public ParameterizedAbstractBean<String> getAbstractBean() {
47+
return abstractBean;
48+
}
49+
public void setAbstractBean(ParameterizedAbstractBean<String> abstractBean) {
50+
this.abstractBean = abstractBean;
51+
}
52+
53+
public List<ParameterizedAbstractBean<String>> getAbstractBeanList() {
54+
return abstractBeanList;
55+
}
56+
57+
public void setAbstractBeanList(List<ParameterizedAbstractBean<String>> abstractBeanList) {
58+
this.abstractBeanList = abstractBeanList;
59+
}
60+
61+
public Map<String, ParameterizedAbstractBean<String>> getAbstractBeanMap() {
62+
return abstractBeanMap;
63+
}
64+
65+
public void setAbstractBeanMap(Map<String, ParameterizedAbstractBean<String>> abstractBeanMap) {
66+
this.abstractBeanMap = abstractBeanMap;
67+
}
68+
}

0 commit comments

Comments
 (0)