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 extends AttributeConverterProvider>[] 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:
+ *
+ * - The converter(s) must provide {@link AttributeConverter}s for all types used in the schema.
+ * - The table schema DefaultAttributeConverterProvider 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.
+ * - If you provide a list of attribute converter providers, you can add DefaultAttributeConverterProvider
+ * to the end of the list to fall back on the defaults.
+ * - Providing an empty list {} will cause no providers to get loaded.
+ *
+ *
+ * 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 extends AttributeConverterProvider>[] converterProviders() default {};
+ Class extends AttributeConverterProvider>[] 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);
}