diff --git a/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-2d32cd6.json b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-2d32cd6.json new file mode 100644 index 000000000000..3d2f41148b41 --- /dev/null +++ b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-2d32cd6.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "Amazon DynamoDB Enhanced Client", + "contributor": "", + "description": "Add support in BeanTableSchema for beans that use fluent setters." +} diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java index 2be87783aa9c..a4a661dc274b 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java @@ -67,6 +67,7 @@ import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnoreNulls; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPreserveEmptyObject; +import software.amazon.awssdk.utils.StringUtils; /** * Implementation of {@link TableSchema} that builds a table schema based on properties and annotations of a bean @@ -216,6 +217,7 @@ private static StaticTableSchema createStaticTableSchema(Class beanCla try { beanInfo = Introspector.getBeanInfo(beanClass); + enhanceDescriptorsWithFluentSetters(beanClass, beanInfo); } catch (IntrospectionException e) { throw new IllegalArgumentException(e); } @@ -260,6 +262,30 @@ private static StaticTableSchema createStaticTableSchema(Class beanCla return builder.build(); } + // Enhance beanInfo descriptors with fluent setter when the default set method is absent + private static void enhanceDescriptorsWithFluentSetters(Class beanClass, BeanInfo beanInfo) { + Arrays.stream(beanInfo.getPropertyDescriptors()) + .filter(descriptor -> descriptor.getWriteMethod() == null) + .forEach(descriptor -> findFluentSetter(beanClass, descriptor.getName()) + .ifPresent(method -> { + try { + descriptor.setWriteMethod(method); + } catch (IntrospectionException e) { + throw new RuntimeException("Failed to set write method for " + descriptor.getName(), e); + } + })); + } + + private static Optional findFluentSetter(Class beanClass, String propertyName) { + String setterName = "set" + StringUtils.capitalize(propertyName); + + return Arrays.stream(beanClass.getMethods()) + .filter(m -> m.getName().equals(setterName) + && m.getParameterCount() == 1 + && m.getReturnType().equals(beanClass)) + .findFirst(); + } + private static AttributeConfiguration resolveAttributeConfiguration(PropertyDescriptor propertyDescriptor) { boolean shouldPreserveEmptyObject = getPropertyAnnotation(propertyDescriptor, DynamoDbPreserveEmptyObject.class) != null; diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchemaTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchemaTest.java index 792a1821da91..275ab2f8d423 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchemaTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchemaTest.java @@ -55,6 +55,7 @@ import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ExtendedBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FlattenedBeanBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FlattenedImmutableBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FluentSetterBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.IgnoredAttributeBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.InvalidBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ListBean; @@ -1160,4 +1161,19 @@ public void emptyConverterProviderList_correct_whenAttributeConvertersAreSupplie assertThat(reverse.getId(), is(equalTo("id-value-custom"))); assertThat(reverse.getIntegerAttribute(), is(equalTo(133))); } + + @Test + public void fluentSetterBean_correctlyMapsBeanAttributes() { + BeanTableSchema beanTableSchema = + BeanTableSchema.create(FluentSetterBean.class); + + FluentSetterBean fluentSetterBean = new FluentSetterBean() + .setAttribute1(1) + .setAttribute2("testAttribute2") + .setAttribute3("testAttribute3"); + + Map itemMap = beanTableSchema.itemToMap(fluentSetterBean, false); + + assertThat(beanTableSchema.mapToItem(itemMap), is(equalTo(fluentSetterBean))); + } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/FluentSetterBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/FluentSetterBean.java new file mode 100644 index 000000000000..ae0f6bf2d27f --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/FluentSetterBean.java @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans; + +import java.util.Objects; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; + +@DynamoDbBean +public class FluentSetterBean { + + private Integer attribute1; + private String attribute2; + private String attribute3; + + @DynamoDbPartitionKey + public Integer getAttribute1() { + return attribute1; + } + + public FluentSetterBean setAttribute1(Integer attribute1) { + this.attribute1 = attribute1; + return this; + } + + @DynamoDbSortKey + public String getAttribute2() { + return attribute2; + } + + public FluentSetterBean setAttribute2(String attribute2) { + this.attribute2 = attribute2; + return this; + } + + public String getAttribute3() { + return attribute3; + } + + public FluentSetterBean setAttribute3(String attribute3) { + this.attribute3 = attribute3; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FluentSetterBean fluentSetterBean = (FluentSetterBean) o; + return Objects.equals(attribute1, fluentSetterBean.attribute1) && + Objects.equals(attribute2, fluentSetterBean.attribute2) && + Objects.equals(attribute3, fluentSetterBean.attribute3); + } + + @Override + public int hashCode() { + return Objects.hash(attribute1, attribute2, attribute3); + } +}