Skip to content

Commit 93aedbf

Browse files
Fix ignored conflicting boolean bean getters
Reconcile JavaBeans property descriptors when an ignored boolean isX() getter masks a valid getX()/setX() property. Fixes #7328
1 parent 3705017 commit 93aedbf

5 files changed

Lines changed: 166 additions & 1 deletion

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "Amazon DynamoDB Enhanced Client",
4+
"description": "Fix bean property mapping when an ignored boolean `isX` getter conflicts with a valid `getX`/`setX` pair. Fixes [#7328](https://github.com/aws/aws-sdk-java-v2/issues/7328).",
5+
"contributor": "IamPritamAcharya"
6+
}

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

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
231231

232232
try {
233233
beanInfo = Introspector.getBeanInfo(beanClass);
234+
enhanceDescriptorsWithIgnoredBooleanGetters(beanClass, beanInfo);
234235
enhanceDescriptorsWithFluentSetters(beanClass, beanInfo);
235236
} catch (IntrospectionException e) {
236237
throw new IllegalArgumentException(e);
@@ -290,6 +291,58 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
290291
return builder.build(context);
291292
}
292293

294+
// Introspector prefers a boolean isX() over getX(), even when isX() is ignored. If their types differ, this also
295+
// prevents Introspector from associating the setter for getX(), so restore that valid getter/setter pair.
296+
private static <T> void enhanceDescriptorsWithIgnoredBooleanGetters(Class<T> beanClass, BeanInfo beanInfo) {
297+
Arrays.stream(beanInfo.getPropertyDescriptors())
298+
.filter(descriptor -> descriptor.getReadMethod() != null && descriptor.getWriteMethod() == null)
299+
.filter(descriptor -> isIgnoredBooleanGetter(descriptor.getReadMethod(), descriptor.getName()))
300+
.forEach(descriptor -> findAlternativeGetter(beanClass, descriptor.getName())
301+
.ifPresent(getter -> findSetter(beanClass, descriptor.getName(), getter.getReturnType())
302+
.ifPresent(setter -> setPropertyMethods(descriptor, getter, setter))));
303+
}
304+
305+
private static boolean isIgnoredBooleanGetter(Method method, String propertyName) {
306+
return method.getName().equals("is" + StringUtils.capitalize(propertyName))
307+
&& method.getReturnType().equals(boolean.class)
308+
&& (method.getAnnotation(DynamoDbIgnore.class) != null || method.getAnnotation(Transient.class) != null);
309+
}
310+
311+
private static Optional<Method> findAlternativeGetter(Class<?> beanClass, String propertyName) {
312+
try {
313+
Method getter = beanClass.getMethod("get" + StringUtils.capitalize(propertyName));
314+
if (getter.getReturnType().equals(void.class) || Modifier.isStatic(getter.getModifiers()) ||
315+
getter.getAnnotation(DynamoDbIgnore.class) != null || getter.getAnnotation(Transient.class) != null) {
316+
return Optional.empty();
317+
}
318+
return Optional.of(getter);
319+
} catch (NoSuchMethodException e) {
320+
return Optional.empty();
321+
}
322+
}
323+
324+
private static Optional<Method> findSetter(Class<?> beanClass, String propertyName, Class<?> propertyType) {
325+
try {
326+
Method setter = beanClass.getMethod("set" + StringUtils.capitalize(propertyName), propertyType);
327+
if (!Modifier.isStatic(setter.getModifiers()) &&
328+
(setter.getReturnType().equals(void.class) || setter.getReturnType().equals(beanClass))) {
329+
return Optional.of(setter);
330+
}
331+
return Optional.empty();
332+
} catch (NoSuchMethodException e) {
333+
return Optional.empty();
334+
}
335+
}
336+
337+
private static void setPropertyMethods(PropertyDescriptor descriptor, Method getter, Method setter) {
338+
try {
339+
descriptor.setReadMethod(getter);
340+
descriptor.setWriteMethod(setter);
341+
} catch (IntrospectionException e) {
342+
throw new RuntimeException("Failed to set methods for " + descriptor.getName(), e);
343+
}
344+
}
345+
293346
// Enhance beanInfo descriptors with fluent setter when the default set method is absent
294347
private static <T> void enhanceDescriptorsWithFluentSetters(Class<T> beanClass, BeanInfo beanInfo) {
295348
Arrays.stream(beanInfo.getPropertyDescriptors())
@@ -603,4 +656,3 @@ static void clearSchemaCache() {
603656
BEAN_TABLE_SCHEMA_CACHE.clear();
604657
}
605658
}
606-

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FlattenedNestedImmutableBean;
7070
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FluentSetterBean;
7171
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.IgnoredAttributeBean;
72+
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.IgnoredConflictingGetterBean;
7273
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.InvalidBean;
7374
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ListBean;
7475
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.MapBean;
@@ -92,6 +93,7 @@
9293
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SingleConverterProvidersBean;
9394
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SortKeyBean;
9495
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ThreeSortKeyBean;
96+
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.TransientConflictingGetterBean;
9597
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.TwoPartitionKeyBean;
9698
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.VectorAndGsiBean;
9799
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.VectorIndexBean;
@@ -185,6 +187,35 @@ public void transient_propertyIsIgnored() {
185187
assertThat(itemMap).containsEntry("id", stringValue("id-value"));
186188
}
187189

190+
@Test
191+
public void dynamoDbIgnore_conflictingBooleanGetterDoesNotHideMappedProperty() {
192+
BeanTableSchema<IgnoredConflictingGetterBean> beanTableSchema =
193+
BeanTableSchema.create(IgnoredConflictingGetterBean.class);
194+
IgnoredConflictingGetterBean bean = new IgnoredConflictingGetterBean();
195+
bean.setA(123);
196+
197+
Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(bean, false);
198+
199+
assertThat(beanTableSchema.attributeNames()).containsExactly("A");
200+
assertThat(itemMap).containsOnlyKeys("A");
201+
assertThat(itemMap).containsEntry("A", numberValue(123));
202+
assertThat(beanTableSchema.mapToItem(itemMap).getA()).isEqualTo(123);
203+
}
204+
205+
@Test
206+
public void transient_conflictingBooleanGetterDoesNotHideMappedProperty() {
207+
BeanTableSchema<TransientConflictingGetterBean> beanTableSchema =
208+
BeanTableSchema.create(TransientConflictingGetterBean.class);
209+
TransientConflictingGetterBean bean = new TransientConflictingGetterBean();
210+
bean.setValue(123);
211+
212+
Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(bean, false);
213+
214+
assertThat(itemMap).containsOnlyKeys("value");
215+
assertThat(itemMap).containsEntry("value", numberValue(123));
216+
assertThat(beanTableSchema.mapToItem(itemMap).getValue()).isEqualTo(123);
217+
}
218+
188219
@Test
189220
public void setterAnnotations_alsoWork() {
190221
BeanTableSchema<SetterAnnotatedBean> beanTableSchema = BeanTableSchema.create(SetterAnnotatedBean.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.DynamoDbAttribute;
19+
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
20+
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore;
21+
22+
@DynamoDbBean
23+
public class IgnoredConflictingGetterBean {
24+
private int a;
25+
26+
@DynamoDbAttribute("A")
27+
public int getA() {
28+
return a;
29+
}
30+
31+
public void setA(int a) {
32+
this.a = a;
33+
}
34+
35+
@DynamoDbIgnore
36+
public boolean isA() {
37+
return false;
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.beans.Transient;
19+
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
20+
21+
@DynamoDbBean
22+
public class TransientConflictingGetterBean {
23+
private int value;
24+
25+
public int getValue() {
26+
return value;
27+
}
28+
29+
public void setValue(int value) {
30+
this.value = value;
31+
}
32+
33+
@Transient
34+
public boolean isValue() {
35+
return false;
36+
}
37+
}

0 commit comments

Comments
 (0)