diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.java index 813ebf1a54ea..a20608535895 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.java @@ -16,7 +16,7 @@ package software.amazon.awssdk.enhanced.dynamodb; import software.amazon.awssdk.annotations.SdkPublicApi; -import software.amazon.awssdk.enhanced.dynamodb.internal.DefaultAttributeConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.ConverterProviderResolver; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; /** @@ -40,6 +40,6 @@ public interface AttributeConverterProvider { * standard Java type converters included. */ static AttributeConverterProvider defaultProvider() { - return DefaultAttributeConverterProvider.create(); + return ConverterProviderResolver.defaultConverterProvider(); } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/DefaultAttributeConverterProvider.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DefaultAttributeConverterProvider.java similarity index 92% rename from services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/DefaultAttributeConverterProvider.java rename to services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DefaultAttributeConverterProvider.java index 7b358b1e4143..1935d159cde9 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/DefaultAttributeConverterProvider.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DefaultAttributeConverterProvider.java @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package software.amazon.awssdk.enhanced.dynamodb.internal; +package software.amazon.awssdk.enhanced.dynamodb; import java.util.ArrayList; import java.util.List; @@ -22,11 +22,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import software.amazon.awssdk.annotations.Immutable; -import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.ThreadSafe; -import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; -import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider; -import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; import software.amazon.awssdk.enhanced.dynamodb.internal.converter.PrimitiveConverter; import software.amazon.awssdk.enhanced.dynamodb.internal.converter.StringConverter; import software.amazon.awssdk.enhanced.dynamodb.internal.converter.StringConverterProvider; @@ -76,11 +73,15 @@ import software.amazon.awssdk.utils.Validate; /** + * This class is the default attribute converter provider in the DDB Enhanced library. When instantiated + * using the constructor {@link #DefaultAttributeConverterProvider()} or the {@link #create()} method, it's loaded + * with the currently supported attribute converters in the library. *

- * Given an input, this will identify a converter that can convert the specific Java type and invoke it. If a converter cannot - * be found, it will invoke a "parent" converter, which would be expected to be able to convert the value (or throw an exception). + * Given an input, the method {@link #converterFor(EnhancedType)} will identify a converter that can convert the + * specific Java type and invoke it. If a converter cannot be found, it will invoke a "parent" converter, + * which would be expected to be able to convert the value (or throw an exception). */ -@SdkInternalApi +@SdkPublicApi @ThreadSafe @Immutable public final class DefaultAttributeConverterProvider implements AttributeConverterProvider { @@ -102,6 +103,21 @@ private DefaultAttributeConverterProvider(Builder builder) { } } + /** + * Returns an attribute converter provider with all default converters set. + */ + public DefaultAttributeConverterProvider() { + this(getDefaultBuilder()); + } + + /** + * Returns an attribute converter provider with all default converters set. + */ + public static DefaultAttributeConverterProvider create() { + return getDefaultBuilder().build(); + } + + /** * Equivalent to {@code builder(EnhancedType.of(Object.class))}. */ @@ -179,7 +195,7 @@ private AttributeConverter createSetConverter(EnhancedType type) { return (AttributeConverter) SetAttributeConverter.setConverter(innerConverter); } - public static DefaultAttributeConverterProvider create() { + private static Builder getDefaultBuilder() { return DefaultAttributeConverterProvider.builder() .addConverter(AtomicBooleanAttributeConverter.create()) .addConverter(AtomicIntegerAttributeConverter.create()) @@ -217,8 +233,7 @@ public static DefaultAttributeConverterProvider create() { .addConverter(UuidAttributeConverter.create()) .addConverter(ZonedDateTimeAsStringAttributeConverter.create()) .addConverter(ZoneIdAttributeConverter.create()) - .addConverter(ZoneOffsetAttributeConverter.create()) - .build(); + .addConverter(ZoneOffsetAttributeConverter.create()); } /** diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProvider.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProvider.java new file mode 100644 index 000000000000..a455051adb5f --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProvider.java @@ -0,0 +1,70 @@ +/* + * 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.internal.converter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; + +/** + * A {@link AttributeConverterProvider} that allows multiple providers to be chained in a specified order + * to act as a single composite provider. When searching for an attribute converter for a type, + * the providers will be called in forward/ascending order, attempting to find a converter from the + * first provider, then the second, and so on, until a match is found or the operation fails. + */ +@SdkInternalApi +public final class ChainConverterProvider implements AttributeConverterProvider { + private final List providerChain; + + private ChainConverterProvider(List providers) { + this.providerChain = new ArrayList<>(providers); + } + + /** + * Construct a new instance of {@link ChainConverterProvider}. + * @param providers A list of {@link AttributeConverterProvider} to chain together. + * @return A constructed {@link ChainConverterProvider} object. + */ + public static ChainConverterProvider create(AttributeConverterProvider... providers) { + return new ChainConverterProvider(Arrays.asList(providers)); + } + + /** + * Construct a new instance of {@link ChainConverterProvider}. + * @param providers A list of {@link AttributeConverterProvider} to chain together. + * @return A constructed {@link ChainConverterProvider} object. + */ + public static ChainConverterProvider create(List providers) { + return new ChainConverterProvider(providers); + } + + public List chainedProviders() { + return Collections.unmodifiableList(this.providerChain); + } + + @Override + public AttributeConverter converterFor(EnhancedType enhancedType) { + return this.providerChain.stream() + .filter(provider -> provider.converterFor(enhancedType) != null) + .map(p -> p.converterFor(enhancedType)) + .findFirst().orElse(null); + } +} diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ConverterProviderResolver.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ConverterProviderResolver.java new file mode 100644 index 000000000000..7f3cdf99ffe5 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ConverterProviderResolver.java @@ -0,0 +1,63 @@ +/* + * 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.internal.converter; + +import java.util.List; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.DefaultAttributeConverterProvider; + +/** + * Static module to assist with the initialization of attribute converter providers for a StaticTableSchema. + */ +@SdkInternalApi +public final class ConverterProviderResolver { + + private static final AttributeConverterProvider DEFAULT_ATTRIBUTE_CONVERTER = + DefaultAttributeConverterProvider.create(); + + private ConverterProviderResolver() { + } + + /** + * Static provider for the default attribute converters that are bundled with the DynamoDB Enhanced Client. + * This provider will be used by default unless overridden in the static table schema builder or using bean + * annotations. + */ + public static AttributeConverterProvider defaultConverterProvider() { + return DEFAULT_ATTRIBUTE_CONVERTER; + } + + /** + * Resolves a list of attribute converter providers into a single provider. If the list is a singleton, + * it will just return that provider, otherwise it will combine them into a + * {@link ChainConverterProvider} using the order provided in the list. + * + * @param providers A list of providers to be combined in strict order + * @return A single provider that combines all the supplied providers or null if no providers were supplied + */ + public static AttributeConverterProvider resolveProviders(List providers) { + if (providers == null || providers.isEmpty()) { + return null; + } + + if (providers.size() == 1) { + return providers.get(0); + } + + return ChainConverterProvider.create(providers); + } +} 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 02f1ac85237e..4e1ca9c7d10e 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 @@ -192,15 +192,14 @@ private static StaticTableSchema createStaticTableSchema(Class beanCla StaticTableSchema.Builder builder = StaticTableSchema.builder(beanClass) .newItemSupplier(newObjectSupplier); - Optional attributeConverterProvider = converterProviderAnnotation(dynamoDbBean); - attributeConverterProvider.ifPresent(builder::attributeConverterProvider); + builder.attributeConverterProviders(createConverterProvidersFromAnnotation(dynamoDbBean)); List> attributes = new ArrayList<>(); Arrays.stream(beanInfo.getPropertyDescriptors()) .filter(BeanTableSchema::isMappableProperty) .forEach(propertyDescriptor -> { - DynamoDbFlatten dynamoDbFlatten = propertyAnnotation(propertyDescriptor, DynamoDbFlatten.class); + DynamoDbFlatten dynamoDbFlatten = getPropertyAnnotation(propertyDescriptor, DynamoDbFlatten.class); if (dynamoDbFlatten != null) { builder.flatten(createStaticTableSchema(dynamoDbFlatten.dynamoDbBeanClass()), @@ -210,7 +209,8 @@ private static StaticTableSchema createStaticTableSchema(Class beanCla StaticAttribute.Builder attributeBuilder = staticAttributeBuilder(propertyDescriptor, beanClass); - Optional attributeConverter = attributeConverterAnnotation(propertyDescriptor); + Optional attributeConverter = + createAttributeConverterFromAnnotation(propertyDescriptor); attributeConverter.ifPresent(attributeBuilder::attributeConverter); addTagsToAttribute(attributeBuilder, propertyDescriptor); @@ -223,12 +223,12 @@ private static StaticTableSchema createStaticTableSchema(Class beanCla return builder.build(); } - private static Optional converterProviderAnnotation(DynamoDbBean dynamoDbBean) { - Class[] converterClasses = dynamoDbBean.converterProviders(); - //TODO: temporary solution to pick one AttributeConverterProvider. - return converterClasses.length > 0 ? - Optional.of((AttributeConverterProvider) newObjectSupplierForClass(converterClasses[0]).get()) : - Optional.empty(); + private static List createConverterProvidersFromAnnotation(DynamoDbBean dynamoDbBean) { + Class[] providerClasses = dynamoDbBean.converterProviders(); + + return Arrays.stream(providerClasses) + .map(c -> (AttributeConverterProvider) newObjectSupplierForClass(c).get()) + .collect(Collectors.toList()); } private static StaticAttribute.Builder staticAttributeBuilder(PropertyDescriptor propertyDescriptor, @@ -283,16 +283,19 @@ private static EnhancedType convertTypeToEnhancedType(Type type) { return EnhancedType.of(type); } - private static Optional attributeConverterAnnotation(PropertyDescriptor propertyDescriptor) { - DynamoDbConvertedBy attributeConverterBean = propertyAnnotation(propertyDescriptor, DynamoDbConvertedBy.class); + private static Optional createAttributeConverterFromAnnotation( + PropertyDescriptor propertyDescriptor) { + DynamoDbConvertedBy attributeConverterBean = + getPropertyAnnotation(propertyDescriptor, DynamoDbConvertedBy.class); Optional> optionalClass = Optional.ofNullable(attributeConverterBean) .map(DynamoDbConvertedBy::value); return optionalClass.map(clazz -> (AttributeConverter) newObjectSupplierForClass(clazz).get()); } /** - * This method scans all the annotations on a property and looks for a meta-annotation of {@link BeanTableSchemaAttributeTag}. - * If the meta-annotation is found, it attempts to create an annotation tag based on a standard named static method + * This method scans all the annotations on a property and looks for a meta-annotation of + * {@link BeanTableSchemaAttributeTag}. If the meta-annotation is found, it attempts to create + * an annotation tag based on a standard named static method * of the class that tag has been annotated with passing in the original property annotation as an argument. */ private static void addTagsToAttribute(StaticAttribute.Builder attributeBuilder, @@ -359,7 +362,7 @@ private static BiConsumer setterForProperty(PropertyDescriptor prop } private static String attributeNameForProperty(PropertyDescriptor propertyDescriptor) { - DynamoDbAttribute dynamoDbAttribute = propertyAnnotation(propertyDescriptor, DynamoDbAttribute.class); + DynamoDbAttribute dynamoDbAttribute = getPropertyAnnotation(propertyDescriptor, DynamoDbAttribute.class); if (dynamoDbAttribute != null) { return dynamoDbAttribute.value(); } @@ -370,11 +373,11 @@ private static String attributeNameForProperty(PropertyDescriptor propertyDescri private static boolean isMappableProperty(PropertyDescriptor propertyDescriptor) { return propertyDescriptor.getReadMethod() != null && propertyDescriptor.getWriteMethod() != null - && propertyAnnotation(propertyDescriptor, DynamoDbIgnore.class) == null; + && getPropertyAnnotation(propertyDescriptor, DynamoDbIgnore.class) == null; } - private static R propertyAnnotation(PropertyDescriptor propertyDescriptor, - Class annotationType) { + private static R getPropertyAnnotation(PropertyDescriptor propertyDescriptor, + Class annotationType) { R getterAnnotation = propertyDescriptor.getReadMethod().getAnnotation(annotationType); R setterAnnotation = propertyDescriptor.getWriteMethod().getAnnotation(annotationType); diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.java index 6a916ebbdcd7..497fa6441108 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.java @@ -33,8 +33,10 @@ import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.DefaultAttributeConverterProvider; import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.ConverterProviderResolver; import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.ResolvedStaticAttribute; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; @@ -69,9 +71,6 @@ */ @SdkPublicApi public final class StaticTableSchema implements TableSchema { - private static final AttributeConverterProvider DEFAULT_ATTRIBUTE_CONVERTER = - AttributeConverterProvider.defaultProvider(); - private final List> attributeMappers; private final Supplier newItemSupplier; private final Map> indexedMappers; @@ -82,9 +81,8 @@ public final class StaticTableSchema implements TableSchema { private StaticTableSchema(Builder builder) { StaticTableMetadata.Builder tableMetadataBuilder = StaticTableMetadata.builder(); - this.attributeConverterProvider = builder.attributeConverterProvider != null ? - builder.attributeConverterProvider : - DEFAULT_ATTRIBUTE_CONVERTER; + this.attributeConverterProvider = + ConverterProviderResolver.resolveProviders(builder.attributeConverterProviders); // Resolve declared attributes and find converters for them Stream> attributesStream = builder.attributes == null ? @@ -143,7 +141,8 @@ public static final class Builder { private List> attributes; private Supplier newItemSupplier; private List tags; - private AttributeConverterProvider attributeConverterProvider; + private List attributeConverterProviders = + Collections.singletonList(ConverterProviderResolver.defaultConverterProvider()); private Builder(Class itemClass) { this.itemClass = itemClass; @@ -282,18 +281,53 @@ public Builder addTag(StaticTableTag staticTableTag) { } /** - * A higher-precedence {@link AttributeConverterProvider} than the default one provided by the table schema. - * The {@link AttributeConverterProvider} must provide {@link AttributeConverter}s for all types used in the schema. + * Specifies the {@link AttributeConverterProvider}s to use with the table schema. + * The list of attribute converter providers must provide {@link AttributeConverter}s for all types used + * in the schema. The attribute converter providers will be loaded in the strict order they are supplied here. + *

+ * Calling this method will override the default attribute converter provider + * {@link DefaultAttributeConverterProvider}, which provides standard converters for most primitive + * and common Java types, so that provider must included in the supplied list if it is to be + * used. Providing an empty list here will cause no providers to get loaded. + *

+ * Adding one custom attribute converter provider and using the default as fallback: + * {@code + * builder.attributeConverterProviders(customAttributeConverter, AttributeConverterProvider.defaultProvider()) + * } + * + * @param attributeConverterProviders a list of attribute converter providers to use with the table schema + */ + public Builder attributeConverterProviders(AttributeConverterProvider... attributeConverterProviders) { + this.attributeConverterProviders = Arrays.asList(attributeConverterProviders); + return this; + } + + /** + * Specifies the {@link AttributeConverterProvider}s to use with the table schema. + * The list of attribute converter providers must provide {@link AttributeConverter}s for all types used + * in the schema. The attribute converter providers will be loaded in the strict order they are supplied here. *

- * The table schema has a default, internal, AttributeConverterProvider which provides standard converters - * for most primitive and common Java types. Use custom AttributeConverterProvider when you have specific - * needs for type conversion that the defaults do not cover. + * Calling this method will override the default attribute converter provider + * {@link DefaultAttributeConverterProvider}, which provides standard converters + * for most primitive and common Java types, so that provider must included in the supplied list if it is to be + * used. Providing an empty list here will cause no providers to get loaded. + *

+ * Adding one custom attribute converter provider and using the default as fallback: + * {@code + * List providers = new ArrayList<>( + * customAttributeConverter, + * AttributeConverterProvider.defaultProvider()); + * builder.attributeConverterProviders(providers); + * } + * + * @param attributeConverterProviders a list of attribute converter providers to use with the table schema */ - public Builder attributeConverterProvider(AttributeConverterProvider attributeConverterProvider) { - this.attributeConverterProvider = attributeConverterProvider; + public Builder attributeConverterProviders(List attributeConverterProviders) { + this.attributeConverterProviders = new ArrayList<>(attributeConverterProviders); return this; } + /** * Builds a {@link StaticTableSchema} based on the values this builder has been configured with */ diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbBean.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbBean.java index a122c095795b..5a5a28294938 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbBean.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbBean.java @@ -22,6 +22,7 @@ import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.DefaultAttributeConverterProvider; import software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema; /** @@ -29,15 +30,30 @@ * a {@link BeanTableSchema} must have this annotation. If a class is used as a document within another DynamoDbBean, * it will also require this annotation. *

+ * Attribute Converter Providers
* Using {@link AttributeConverterProvider}s is optional and, if used, the supplied provider supersedes the default - * converter provided by the table schema. The converter must provide {@link AttributeConverter}s for all types used - * in the schema. The table schema default AttributeConverterProvider provides standard converters for most primitive - * and common Java types. Use custom AttributeConverterProviders when you have specific needs for type conversion - * that the defaults do not cover. + * converter provided by the table schema. + *

+ * Note: + *

+ * + * Example using attribute converter providers with one custom provider and the default provider: + * {@code + * (converterProviders = {CustomAttributeConverter.class, DefaultAttributeConverterProvider.class}); + * } */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @SdkPublicApi public @interface DynamoDbBean { - Class[] converterProviders() default {}; + Class[] converterProviders() + default { DefaultAttributeConverterProvider.class }; } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProviderTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProviderTest.java new file mode 100644 index 000000000000..069ae50a2d4b --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProviderTest.java @@ -0,0 +1,75 @@ +package software.amazon.awssdk.enhanced.dynamodb.internal.converter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; + +@RunWith(MockitoJUnitRunner.class) +public class ChainConverterProviderTest { + + @Mock + private AttributeConverterProvider mockConverterProvider1; + + @Mock + private AttributeConverterProvider mockConverterProvider2; + + @Mock + private AttributeConverter mockAttributeConverter1; + + @Mock + private AttributeConverter mockAttributeConverter2; + + @Test + public void checkSingleProviderChain() { + ChainConverterProvider chain = ChainConverterProvider.create(mockConverterProvider1); + List providerQueue = chain.chainedProviders(); + assertThat(providerQueue.size()).isEqualTo(1); + assertThat(providerQueue.get(0)).isEqualTo(mockConverterProvider1); + } + + @Test + public void checkMultipleProviderChain() { + ChainConverterProvider chain = ChainConverterProvider.create(mockConverterProvider1, mockConverterProvider2); + List providerQueue = chain.chainedProviders(); + assertThat(providerQueue.size()).isEqualTo(2); + assertThat(providerQueue.get(0)).isEqualTo(mockConverterProvider1); + assertThat(providerQueue.get(1)).isEqualTo(mockConverterProvider2); + } + + @Test + public void resolveSingleProviderChain() { + when(mockConverterProvider1.converterFor(any())).thenReturn(mockAttributeConverter1); + ChainConverterProvider chain = ChainConverterProvider.create(mockConverterProvider1); + assertThat(chain.converterFor(EnhancedType.of(String.class))).isSameAs(mockAttributeConverter1); + } + + @Test + public void resolveMultipleProviderChain_noMatch() { + ChainConverterProvider chain = ChainConverterProvider.create(mockConverterProvider1, mockConverterProvider2); + assertThat(chain.converterFor(EnhancedType.of(String.class))).isNull(); + } + + @Test + public void resolveMultipleProviderChain_matchSecond() { + when(mockConverterProvider2.converterFor(any())).thenReturn(mockAttributeConverter2); + ChainConverterProvider chain = ChainConverterProvider.create(mockConverterProvider1, mockConverterProvider2); + assertThat(chain.converterFor(EnhancedType.of(String.class))).isSameAs(mockAttributeConverter2); + } + + @Test + public void resolveMultipleProviderChain_matchFirst() { + when(mockConverterProvider1.converterFor(any())).thenReturn(mockAttributeConverter1); + ChainConverterProvider chain = ChainConverterProvider.create(mockConverterProvider1, mockConverterProvider2); + assertThat(chain.converterFor(EnhancedType.of(String.class))).isSameAs(mockAttributeConverter1); + } + +} \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ConverterProviderResolverTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ConverterProviderResolverTest.java new file mode 100644 index 000000000000..0cd3294c085a --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ConverterProviderResolverTest.java @@ -0,0 +1,55 @@ +package software.amazon.awssdk.enhanced.dynamodb.internal.converter; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.DefaultAttributeConverterProvider; + +@RunWith(MockitoJUnitRunner.class) +public class ConverterProviderResolverTest { + + @Mock + private AttributeConverterProvider mockConverterProvider1; + + @Mock + private AttributeConverterProvider mockConverterProvider2; + + @Test + public void resolveProviders_null() { + assertThat(ConverterProviderResolver.resolveProviders(null)).isNull(); + } + + @Test + public void resolveProviders_empty() { + assertThat(ConverterProviderResolver.resolveProviders(emptyList())).isNull(); + } + + @Test + public void resolveProviders_singleton() { + assertThat(ConverterProviderResolver.resolveProviders(singletonList(mockConverterProvider1))) + .isSameAs(mockConverterProvider1); + } + + @Test + public void resolveProviders_multiple() { + AttributeConverterProvider result = ConverterProviderResolver.resolveProviders( + Arrays.asList(mockConverterProvider1, mockConverterProvider2)); + assertThat(result).isNotNull(); + assertThat(result).isInstanceOf(ChainConverterProvider.class); + } + + @Test + public void defaultProvider_returnsInstance() { + AttributeConverterProvider defaultProvider = ConverterProviderResolver.defaultConverterProvider(); + assertThat(defaultProvider).isNotNull(); + assertThat(defaultProvider).isInstanceOf(DefaultAttributeConverterProvider.class); + } + +} \ No newline at end of file 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 6d361f72e4ba..0dd88c8eabea 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 @@ -44,9 +44,9 @@ import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.AttributeConverterBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.AttributeConverterNoConstructorBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.CommonTypesBean; -import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ConverterBean; -import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ConverterNoConstructorBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.DocumentBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.EmptyConverterProvidersInvalidBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.EmptyConverterProvidersValidBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.EnumBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ExtendedBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FlattenedBean; @@ -54,6 +54,8 @@ import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.InvalidBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ListBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.MapBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.MultipleConverterProvidersBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.NoConstructorConverterProvidersBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ParameterizedAbstractBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ParameterizedDocumentBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.PrimitiveTypesBean; @@ -62,6 +64,7 @@ import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SetBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SetterAnnotatedBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SimpleBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SingleConverterProvidersBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.SortKeyBean; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; @@ -866,29 +869,10 @@ public void itemType_returnsCorrectClass() { } @Test - public void usesCustomAttributeConverterProvider() { - BeanTableSchema beanTableSchema = BeanTableSchema.create(ConverterBean.class); - - ConverterBean converterBean = new ConverterBean(); - converterBean.setId("id-value"); - converterBean.setIntegerAttribute(123); - - Map itemMap = beanTableSchema.itemToMap(converterBean, false); - - assertThat(itemMap.size(), is(2)); - assertThat(itemMap, hasEntry("id", stringValue("id-value-custom"))); - assertThat(itemMap, hasEntry("integerAttribute", numberValue(133))); - - ConverterBean reverse = beanTableSchema.mapToItem(itemMap); - assertThat(reverse.getId(), is(equalTo("id-value-custom"))); - assertThat(reverse.getIntegerAttribute(), is(equalTo(133))); - } - - @Test - public void converterProviderWithoutConstructor_throwsIllegalArgumentException() { + public void attributeConverterWithoutConstructor_throwsIllegalArgumentException() { exception.expect(IllegalArgumentException.class); exception.expectMessage("default constructor"); - BeanTableSchema.create(ConverterNoConstructorBean.class); + BeanTableSchema.create(AttributeConverterNoConstructorBean.class); } @Test @@ -915,9 +899,74 @@ public void usesCustomAttributeConverter() { } @Test - public void attributeConverterWithoutConstructor_throwsIllegalArgumentException() { + public void converterProviderWithoutConstructor_throwsIllegalArgumentException() { exception.expect(IllegalArgumentException.class); exception.expectMessage("default constructor"); - BeanTableSchema.create(AttributeConverterNoConstructorBean.class); + BeanTableSchema.create(NoConstructorConverterProvidersBean.class); + } + + @Test + public void usesCustomAttributeConverterProvider() { + BeanTableSchema beanTableSchema = BeanTableSchema.create(SingleConverterProvidersBean.class); + + SingleConverterProvidersBean converterBean = new SingleConverterProvidersBean(); + converterBean.setId("id-value"); + converterBean.setIntegerAttribute(123); + + Map itemMap = beanTableSchema.itemToMap(converterBean, false); + + assertThat(itemMap.size(), is(2)); + assertThat(itemMap, hasEntry("id", stringValue("id-value-custom"))); + assertThat(itemMap, hasEntry("integerAttribute", numberValue(133))); + + SingleConverterProvidersBean reverse = beanTableSchema.mapToItem(itemMap); + assertThat(reverse.getId(), is(equalTo("id-value-custom"))); + assertThat(reverse.getIntegerAttribute(), is(equalTo(133))); + } + + @Test + public void usesCustomAttributeConverterProviders() { + BeanTableSchema beanTableSchema = + BeanTableSchema.create(MultipleConverterProvidersBean.class); + + MultipleConverterProvidersBean converterBean = new MultipleConverterProvidersBean(); + converterBean.setId("id-value"); + converterBean.setIntegerAttribute(123); + + Map itemMap = beanTableSchema.itemToMap(converterBean, false); + + assertThat(itemMap.size(), is(2)); + assertThat(itemMap, hasEntry("id", stringValue("id-value-custom"))); + assertThat(itemMap, hasEntry("integerAttribute", numberValue(133))); + + MultipleConverterProvidersBean reverse = beanTableSchema.mapToItem(itemMap); + assertThat(reverse.getId(), is(equalTo("id-value-custom"))); + assertThat(reverse.getIntegerAttribute(), is(equalTo(133))); + } + + @Test + public void emptyConverterProviderList_fails_whenAttributeConvertersAreMissing() { + exception.expect(NullPointerException.class); + BeanTableSchema.create(EmptyConverterProvidersInvalidBean.class); + } + + @Test + public void emptyConverterProviderList_correct_whenAttributeConvertersAreSupplied() { + BeanTableSchema beanTableSchema = + BeanTableSchema.create(EmptyConverterProvidersValidBean.class); + + EmptyConverterProvidersValidBean converterBean = new EmptyConverterProvidersValidBean(); + converterBean.setId("id-value"); + converterBean.setIntegerAttribute(123); + + Map itemMap = beanTableSchema.itemToMap(converterBean, false); + + assertThat(itemMap.size(), is(2)); + assertThat(itemMap, hasEntry("id", stringValue("id-value-custom"))); + assertThat(itemMap, hasEntry("integerAttribute", numberValue(133))); + + EmptyConverterProvidersValidBean reverse = beanTableSchema.mapToItem(itemMap); + assertThat(reverse.getId(), is(equalTo("id-value-custom"))); + assertThat(reverse.getIntegerAttribute(), is(equalTo(133))); } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchemaTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchemaTest.java index c5789aad3029..7ef020a15d52 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchemaTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchemaTest.java @@ -780,10 +780,16 @@ public Consumer modifyMetadata() { } @Mock - private AttributeConverterProvider provider; + private AttributeConverterProvider provider1; @Mock - private AttributeConverter attributeConverter; + private AttributeConverterProvider provider2; + + @Mock + private AttributeConverter attributeConverter1; + + @Mock + private AttributeConverter attributeConverter2; @Rule public ExpectedException exception = ExpectedException.none(); @@ -1366,8 +1372,8 @@ public void instantiateFlattenedAbstractClassShouldThrowException() { } @Test - public void addAttributeConverterProvider() { - when(provider.converterFor(EnhancedType.of(String.class))).thenReturn(attributeConverter); + public void addSingleAttributeConverterProvider() { + when(provider1.converterFor(EnhancedType.of(String.class))).thenReturn(attributeConverter1); StaticTableSchema tableSchema = StaticTableSchema.builder(FakeMappedItem.class) @@ -1375,10 +1381,10 @@ public void addAttributeConverterProvider() { .addAttribute(String.class, a -> a.name("aString") .getter(FakeMappedItem::getAString) .setter(FakeMappedItem::setAString)) - .attributeConverterProvider(provider) + .attributeConverterProviders(provider1) .build(); - assertThat(tableSchema.attributeConverterProvider(), is(provider)); + assertThat(tableSchema.attributeConverterProvider(), is(provider1)); } @Test @@ -1386,8 +1392,8 @@ public void usesCustomAttributeConverterProvider() { String originalString = "test-string"; String expectedString = "test-string-custom"; - when(provider.converterFor(EnhancedType.of(String.class))).thenReturn(attributeConverter); - when(attributeConverter.transformFrom(any())).thenReturn(AttributeValue.builder().s(expectedString).build()); + when(provider1.converterFor(EnhancedType.of(String.class))).thenReturn(attributeConverter1); + when(attributeConverter1.transformFrom(any())).thenReturn(AttributeValue.builder().s(expectedString).build()); StaticTableSchema tableSchema = StaticTableSchema.builder(FakeMappedItem.class) @@ -1395,11 +1401,69 @@ public void usesCustomAttributeConverterProvider() { .addAttribute(String.class, a -> a.name("aString") .getter(FakeMappedItem::getAString) .setter(FakeMappedItem::setAString)) - .attributeConverterProvider(provider) + .attributeConverterProviders(provider1) .build(); + Map resultMap = + tableSchema.itemToMap(FakeMappedItem.builder().aString(originalString).build(), false); + assertThat(resultMap.get("aString").s(), is(expectedString)); + } + + @Test + public void usesCustomAttributeConverterProviders() { + String originalString = "test-string"; + String expectedString = "test-string-custom"; + + when(provider2.converterFor(EnhancedType.of(String.class))).thenReturn(attributeConverter2); + when(attributeConverter2.transformFrom(any())).thenReturn(AttributeValue.builder().s(expectedString).build()); + + StaticTableSchema tableSchema = + StaticTableSchema.builder(FakeMappedItem.class) + .newItemSupplier(FakeMappedItem::new) + .addAttribute(String.class, a -> a.name("aString") + .getter(FakeMappedItem::getAString) + .setter(FakeMappedItem::setAString)) + .attributeConverterProviders(provider1, provider2) + .build(); + + Map resultMap = + tableSchema.itemToMap(FakeMappedItem.builder().aString(originalString).build(), false); + assertThat(resultMap.get("aString").s(), is(expectedString)); + } + + @Test + public void noConverterProvider_throwsException_whenMissingAttributeConverters() { + exception.expect(NullPointerException.class); + + StaticTableSchema tableSchema = + StaticTableSchema.builder(FakeMappedItem.class) + .newItemSupplier(FakeMappedItem::new) + .addAttribute(String.class, a -> a.name("aString") + .getter(FakeMappedItem::getAString) + .setter(FakeMappedItem::setAString)) + .attributeConverterProviders(Collections.emptyList()) + .build(); + } + + @Test + public void noConverterProvider_handlesCorrectly_whenAttributeConvertersAreSupplied() { + String originalString = "test-string"; + String expectedString = "test-string-custom"; + + when(attributeConverter1.transformFrom(any())).thenReturn(AttributeValue.builder().s(expectedString).build()); + + StaticTableSchema tableSchema = + StaticTableSchema.builder(FakeMappedItem.class) + .newItemSupplier(FakeMappedItem::new) + .addAttribute(String.class, a -> a.name("aString") + .getter(FakeMappedItem::getAString) + .setter(FakeMappedItem::setAString) + .attributeConverter(attributeConverter1)) + .attributeConverterProviders(Collections.emptyList()) + .build(); + Map resultMap = tableSchema.itemToMap(FakeMappedItem.builder().aString(originalString).build(), - false); + false); assertThat(resultMap.get("aString").s(), is(expectedString)); } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/AttributeConverterBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/AttributeConverterBean.java index 62e29b3c7616..3f27afd6173f 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/AttributeConverterBean.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/AttributeConverterBean.java @@ -61,12 +61,13 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; AttributeConverterBean that = (AttributeConverterBean) o; return Objects.equals(id, that.id) && - Objects.equals(integerAttribute, that.integerAttribute); + Objects.equals(integerAttribute, that.integerAttribute) && + Objects.equals(attributeItem, that.attributeItem); } @Override public int hashCode() { - return Objects.hash(id, integerAttribute); + return Objects.hash(id, integerAttribute, attributeItem); } public static class CustomAttributeConverter implements AttributeConverter { @@ -76,12 +77,12 @@ public CustomAttributeConverter() { @Override public AttributeValue transformFrom(AttributeItem input) { - return EnhancedAttributeValue.fromString(input.innerValue).toAttributeValue(); + return EnhancedAttributeValue.fromString(input.getInnerValue()).toAttributeValue(); } @Override public AttributeItem transformTo(AttributeValue input) { - return null; + return new AttributeItem(input.s()); } @Override @@ -96,7 +97,14 @@ public AttributeValueType attributeValueType() { } public static class AttributeItem { - String innerValue; + private String innerValue; + + public AttributeItem() { + } + + AttributeItem(String value) { + innerValue = value; + } public String getInnerValue() { return innerValue; @@ -105,5 +113,18 @@ public String getInnerValue() { public void setInnerValue(String innerValue) { this.innerValue = innerValue; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AttributeItem that = (AttributeItem) o; + return Objects.equals(innerValue, that.innerValue); + } + + @Override + public int hashCode() { + return Objects.hash(innerValue); + } } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/EmptyConverterProvidersInvalidBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/EmptyConverterProvidersInvalidBean.java new file mode 100644 index 000000000000..60c2d2d9fc9c --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/EmptyConverterProvidersInvalidBean.java @@ -0,0 +1,90 @@ +/* + * 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.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnhancedAttributeValue; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbConvertedBy; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +@DynamoDbBean(converterProviders = {}) +public class EmptyConverterProvidersInvalidBean { + private String id; + private Integer integerAttribute; + + @DynamoDbPartitionKey + @DynamoDbConvertedBy(CustomStringAttributeConverter.class) + public String getId() { + return this.id; + } + public void setId(String id) { + this.id = id; + } + + public Integer getIntegerAttribute() { + return integerAttribute; + } + public void setIntegerAttribute(Integer integerAttribute) { + this.integerAttribute = integerAttribute; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EmptyConverterProvidersInvalidBean that = (EmptyConverterProvidersInvalidBean) o; + return Objects.equals(id, that.id) && + Objects.equals(integerAttribute, that.integerAttribute); + } + + @Override + public int hashCode() { + return Objects.hash(id, integerAttribute); + } + + public static class CustomStringAttributeConverter implements AttributeConverter { + final static String DEFAULT_SUFFIX = "-custom"; + + public CustomStringAttributeConverter() { + } + + @Override + public AttributeValue transformFrom(String input) { + return EnhancedAttributeValue.fromString(input + DEFAULT_SUFFIX).toAttributeValue(); + } + + @Override + public String transformTo(AttributeValue input) { + return input.s(); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(String.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.S; + } + } + +} diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/EmptyConverterProvidersValidBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/EmptyConverterProvidersValidBean.java new file mode 100644 index 000000000000..ceee30289a91 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/EmptyConverterProvidersValidBean.java @@ -0,0 +1,119 @@ +/* + * 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.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnhancedAttributeValue; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.string.IntegerStringConverter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbConvertedBy; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +@DynamoDbBean(converterProviders = {}) +public class EmptyConverterProvidersValidBean { + private String id; + private Integer integerAttribute; + + @DynamoDbPartitionKey + @DynamoDbConvertedBy(CustomStringAttributeConverter.class) + public String getId() { + return this.id; + } + public void setId(String id) { + this.id = id; + } + + @DynamoDbConvertedBy(CustomIntegerAttributeConverter.class) + public Integer getIntegerAttribute() { + return integerAttribute; + } + public void setIntegerAttribute(Integer integerAttribute) { + this.integerAttribute = integerAttribute; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EmptyConverterProvidersValidBean that = (EmptyConverterProvidersValidBean) o; + return Objects.equals(id, that.id) && + Objects.equals(integerAttribute, that.integerAttribute); + } + + @Override + public int hashCode() { + return Objects.hash(id, integerAttribute); + } + + public static class CustomStringAttributeConverter implements AttributeConverter { + final static String DEFAULT_SUFFIX = "-custom"; + + public CustomStringAttributeConverter() { + } + + @Override + public AttributeValue transformFrom(String input) { + return EnhancedAttributeValue.fromString(input + DEFAULT_SUFFIX).toAttributeValue(); + } + + @Override + public String transformTo(AttributeValue input) { + return input.s(); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(String.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.S; + } + } + + public static class CustomIntegerAttributeConverter implements AttributeConverter { + final static Integer DEFAULT_INCREMENT = 10; + + public CustomIntegerAttributeConverter() { + } + + @Override + public AttributeValue transformFrom(Integer input) { + return EnhancedAttributeValue.fromNumber(IntegerStringConverter.create().toString(input + DEFAULT_INCREMENT)) + .toAttributeValue(); + } + + @Override + public Integer transformTo(AttributeValue input) { + return Integer.valueOf(input.n()); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(Integer.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.N; + } + } +} diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/MultipleConverterProvidersBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/MultipleConverterProvidersBean.java new file mode 100644 index 000000000000..b3a3578c299f --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/MultipleConverterProvidersBean.java @@ -0,0 +1,139 @@ +/* + * 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.Map; +import java.util.Objects; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnhancedAttributeValue; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.string.IntegerStringConverter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.utils.ImmutableMap; + +@DynamoDbBean(converterProviders = { + MultipleConverterProvidersBean.FirstAttributeConverterProvider.class, + MultipleConverterProvidersBean.SecondAttributeConverterProvider.class}) +public class MultipleConverterProvidersBean { + private String id; + private Integer integerAttribute; + + @DynamoDbPartitionKey + public String getId() { + return this.id; + } + public void setId(String id) { + this.id = id; + } + + public Integer getIntegerAttribute() { + return integerAttribute; + } + public void setIntegerAttribute(Integer integerAttribute) { + this.integerAttribute = integerAttribute; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MultipleConverterProvidersBean that = (MultipleConverterProvidersBean) o; + return Objects.equals(id, that.id) && + Objects.equals(integerAttribute, that.integerAttribute); + } + + @Override + public int hashCode() { + return Objects.hash(id, integerAttribute); + } + + public static class FirstAttributeConverterProvider implements AttributeConverterProvider { + @SuppressWarnings("unchecked") + @Override + public AttributeConverter converterFor(EnhancedType enhancedType) { + return null; + } + } + + public static class SecondAttributeConverterProvider implements AttributeConverterProvider { + + private final Map, AttributeConverter> converterCache = ImmutableMap.of( + EnhancedType.of(String.class), new CustomStringAttributeConverter(), + EnhancedType.of(Integer.class), new CustomIntegerAttributeConverter() + ); + + @SuppressWarnings("unchecked") + @Override + public AttributeConverter converterFor(EnhancedType enhancedType) { + return (AttributeConverter) converterCache.get(enhancedType); + } + } + + private static class CustomStringAttributeConverter implements AttributeConverter { + + final static String DEFAULT_SUFFIX = "-custom"; + + @Override + public AttributeValue transformFrom(String input) { + return EnhancedAttributeValue.fromString(input + DEFAULT_SUFFIX).toAttributeValue(); + } + + @Override + public String transformTo(AttributeValue input) { + return input.s(); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(String.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.S; + } + } + + private static class CustomIntegerAttributeConverter implements AttributeConverter { + + final static Integer DEFAULT_INCREMENT = 10; + + @Override + public AttributeValue transformFrom(Integer input) { + return EnhancedAttributeValue.fromNumber(IntegerStringConverter.create().toString(input + DEFAULT_INCREMENT)) + .toAttributeValue(); + } + + @Override + public Integer transformTo(AttributeValue input) { + return Integer.valueOf(input.n()); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(Integer.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.N; + } + } +} diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/ConverterNoConstructorBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/NoConstructorConverterProvidersBean.java similarity index 87% rename from services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/ConverterNoConstructorBean.java rename to services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/NoConstructorConverterProvidersBean.java index 3d849d9400b0..72212fe4a2d5 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/ConverterNoConstructorBean.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/NoConstructorConverterProvidersBean.java @@ -20,8 +20,8 @@ import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; -@DynamoDbBean(converterProviders = ConverterNoConstructorBean.CustomAttributeConverterProvider.class) -public class ConverterNoConstructorBean extends AbstractBean { +@DynamoDbBean(converterProviders = NoConstructorConverterProvidersBean.CustomAttributeConverterProvider.class) +public class NoConstructorConverterProvidersBean extends AbstractBean { public static class CustomAttributeConverterProvider implements AttributeConverterProvider { diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/ConverterBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/SingleConverterProvidersBean.java similarity index 95% rename from services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/ConverterBean.java rename to services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/SingleConverterProvidersBean.java index 6500ad16d2f4..5e715b297e63 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/ConverterBean.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/SingleConverterProvidersBean.java @@ -28,8 +28,8 @@ import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.utils.ImmutableMap; -@DynamoDbBean(converterProviders = ConverterBean.CustomAttributeConverterProvider.class) -public class ConverterBean { +@DynamoDbBean(converterProviders = SingleConverterProvidersBean.CustomAttributeConverterProvider.class) +public class SingleConverterProvidersBean { private String id; private Integer integerAttribute; @@ -52,7 +52,7 @@ public void setIntegerAttribute(Integer integerAttribute) { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ConverterBean that = (ConverterBean) o; + SingleConverterProvidersBean that = (SingleConverterProvidersBean) o; return Objects.equals(id, that.id) && Objects.equals(integerAttribute, that.integerAttribute); }