Skip to content

DDB Enhanced adding ChainConverterProvider #1746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -40,6 +40,6 @@ public interface AttributeConverterProvider {
* standard Java type converters included.
*/
static AttributeConverterProvider defaultProvider() {
return DefaultAttributeConverterProvider.create();
return ConverterProviderResolver.defaultConverterProvider();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
* <p>
* 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should spruce up the javadoc a bit to celebrate this class' new status as 'public'. At a minimum mention (preferably with examples) how to use it in a StaticTableSchema builder and a BeanTableSchema annotation.

@ThreadSafe
@Immutable
public final class DefaultAttributeConverterProvider implements AttributeConverterProvider {
Expand All @@ -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))}.
*/
Expand Down Expand Up @@ -179,7 +195,7 @@ private <T> AttributeConverter<T> createSetConverter(EnhancedType<T> type) {
return (AttributeConverter<T>) SetAttributeConverter.setConverter(innerConverter);
}

public static DefaultAttributeConverterProvider create() {
private static Builder getDefaultBuilder() {
return DefaultAttributeConverterProvider.builder()
.addConverter(AtomicBooleanAttributeConverter.create())
.addConverter(AtomicIntegerAttributeConverter.create())
Expand Down Expand Up @@ -217,8 +233,7 @@ public static DefaultAttributeConverterProvider create() {
.addConverter(UuidAttributeConverter.create())
.addConverter(ZonedDateTimeAsStringAttributeConverter.create())
.addConverter(ZoneIdAttributeConverter.create())
.addConverter(ZoneOffsetAttributeConverter.create())
.build();
.addConverter(ZoneOffsetAttributeConverter.create());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AttributeConverterProvider> providerChain;

private ChainConverterProvider(List<AttributeConverterProvider> 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<AttributeConverterProvider> providers) {
return new ChainConverterProvider(providers);
}

public List<AttributeConverterProvider> chainedProviders() {
return Collections.unmodifiableList(this.providerChain);
}

@Override
public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
return this.providerChain.stream()
.filter(provider -> provider.converterFor(enhancedType) != null)
.map(p -> p.converterFor(enhancedType))
.findFirst().orElse(null);
}
}
Original file line number Diff line number Diff line change
@@ -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<AttributeConverterProvider> providers) {
if (providers == null || providers.isEmpty()) {
return null;
}

if (providers.size() == 1) {
return providers.get(0);
}

return ChainConverterProvider.create(providers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,14 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
StaticTableSchema.Builder<T> builder = StaticTableSchema.builder(beanClass)
.newItemSupplier(newObjectSupplier);

Optional<AttributeConverterProvider> attributeConverterProvider = converterProviderAnnotation(dynamoDbBean);
attributeConverterProvider.ifPresent(builder::attributeConverterProvider);
builder.attributeConverterProviders(createConverterProvidersFromAnnotation(dynamoDbBean));

List<StaticAttribute<T, ?>> 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()),
Expand All @@ -210,7 +209,8 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
StaticAttribute.Builder<T, ?> attributeBuilder =
staticAttributeBuilder(propertyDescriptor, beanClass);

Optional<AttributeConverter> attributeConverter = attributeConverterAnnotation(propertyDescriptor);
Optional<AttributeConverter> attributeConverter =
createAttributeConverterFromAnnotation(propertyDescriptor);
attributeConverter.ifPresent(attributeBuilder::attributeConverter);

addTagsToAttribute(attributeBuilder, propertyDescriptor);
Expand All @@ -223,12 +223,12 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
return builder.build();
}

private static Optional<AttributeConverterProvider> 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<AttributeConverterProvider> createConverterProvidersFromAnnotation(DynamoDbBean dynamoDbBean) {
Class<? extends AttributeConverterProvider>[] providerClasses = dynamoDbBean.converterProviders();

return Arrays.stream(providerClasses)
.map(c -> (AttributeConverterProvider) newObjectSupplierForClass(c).get())
.collect(Collectors.toList());
}

private static <T> StaticAttribute.Builder<T, ?> staticAttributeBuilder(PropertyDescriptor propertyDescriptor,
Expand Down Expand Up @@ -283,16 +283,19 @@ private static EnhancedType<?> convertTypeToEnhancedType(Type type) {
return EnhancedType.of(type);
}

private static Optional<AttributeConverter> attributeConverterAnnotation(PropertyDescriptor propertyDescriptor) {
DynamoDbConvertedBy attributeConverterBean = propertyAnnotation(propertyDescriptor, DynamoDbConvertedBy.class);
private static Optional<AttributeConverter> createAttributeConverterFromAnnotation(
PropertyDescriptor propertyDescriptor) {
DynamoDbConvertedBy attributeConverterBean =
getPropertyAnnotation(propertyDescriptor, DynamoDbConvertedBy.class);
Optional<Class<?>> 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,
Expand Down Expand Up @@ -359,7 +362,7 @@ private static <T, R> BiConsumer<T, R> 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();
}
Expand All @@ -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 extends Annotation> R propertyAnnotation(PropertyDescriptor propertyDescriptor,
Class<R> annotationType) {
private static <R extends Annotation> R getPropertyAnnotation(PropertyDescriptor propertyDescriptor,
Class<R> annotationType) {
R getterAnnotation = propertyDescriptor.getReadMethod().getAnnotation(annotationType);
R setterAnnotation = propertyDescriptor.getWriteMethod().getAnnotation(annotationType);

Expand Down
Loading