Skip to content

DDB Enhanced: Easier attribute value conversion support #1765

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

Closed
wants to merge 2 commits into from
Closed
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 @@ -102,6 +102,16 @@ static <T> BeanTableSchema<T> fromBean(Class<T> beanClass) {
*/
AttributeValue attributeValue(T item, String key);

/**
* Converts a single attribute value using the converter for given attribute of the modelled object.
*
* @param value The value to be converted.
* @param key The attribute name describing which attribute's converter to use.
* @return A single {@link AttributeValue} representing the value as it was an attribute of given name in the model
* object or null if the value is null.
*/
AttributeValue convertAttributeValue(Object value, String key);

/**
* Returns the object that describes the structure of the table being modelled by the mapper. This includes
* information such as the table name, index keys and attribute tags.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,31 @@
import java.util.function.Consumer;
import java.util.function.Function;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttribute;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

@SdkInternalApi
public final class ResolvedStaticAttribute<T> {
public final class ResolvedStaticAttribute<T, R> {
private final String attributeName;
private final Function<T, AttributeValue> getAttributeMethod;
private final BiConsumer<T, AttributeValue> updateItemMethod;
private final StaticTableMetadata tableMetadata;
private final AttributeValueType attributeValueType;
private final AttributeType<R> attributeType;

private ResolvedStaticAttribute(String attributeName,
Function<T, AttributeValue> getAttributeMethod,
BiConsumer<T, AttributeValue> updateItemMethod,
StaticTableMetadata tableMetadata,
AttributeValueType attributeValueType) {
AttributeType<R> attributeType) {
this.attributeName = attributeName;
this.getAttributeMethod = getAttributeMethod;
this.updateItemMethod = updateItemMethod;
this.tableMetadata = tableMetadata;
this.attributeValueType = attributeValueType;
this.attributeType = attributeType;
}

public static <T, R> ResolvedStaticAttribute<T> create(StaticAttribute<T, R> staticAttribute,
public static <T, R> ResolvedStaticAttribute<T, R> create(StaticAttribute<T, R> staticAttribute,
AttributeType<R> attributeType) {
Function<T, AttributeValue> getAttributeValueWithTransform = item -> {
R value = staticAttribute.getter().apply(item);
Expand Down Expand Up @@ -78,21 +77,21 @@ public static <T, R> ResolvedStaticAttribute<T> create(StaticAttribute<T, R> sta
getAttributeValueWithTransform,
updateItemWithTransform,
tableMetadataBuilder.build(),
attributeType.attributeValueType());
attributeType);
}

/**
* Return a transformed copy of this attribute that knows how to get/set from a different type of object given a
* function that can convert the containing object itself. It does this by modifying the get/set functions of
* type T to type R given a transformation function F(T) = R.
* type T to type S given a transformation function F(T) = S.
* @param transform A function that converts the object storing the attribute from the source type to the
* destination type.
* @param createComponent A consumer to create a new instance of the component object when required. A null value
* will bypass this logic.
* @param <R> The type being transformed to.
* @return A new Attribute that be contained by an object of type R.
* @param <S> The type being transformed to.
* @return A new Attribute that be contained by an object of type S.
*/
public <R> ResolvedStaticAttribute<R> transform(Function<R, T> transform, Consumer<R> createComponent) {
public <S> ResolvedStaticAttribute<S, R> transform(Function<S, T> transform, Consumer<S> createComponent) {
return new ResolvedStaticAttribute<>(
attributeName,
item -> {
Expand All @@ -110,13 +109,17 @@ public <R> ResolvedStaticAttribute<R> transform(Function<R, T> transform, Consum
updateItemMethod.accept(transform.apply(item), value);
},
tableMetadata,
attributeValueType);
attributeType);
}

public String attributeName() {
return attributeName;
}

public AttributeType<R> attributeType() {
return attributeType;
}

public Function<T, AttributeValue> attributeGetterMethod() {
return getAttributeMethod;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ public AttributeValue attributeValue(T item, String key) {
return wrappedTableSchema.attributeValue(item, key);
}

/**
* {@inheritDoc}
* @param value The value to be converted.
* @param key The attribute name describing which attribute's converter to use.
*/
@Override
public AttributeValue convertAttributeValue(Object value, String key) {
return wrappedTableSchema.convertAttributeValue(value, key);
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public Builder<T, R> toBuilder() {
}


ResolvedStaticAttribute<T> resolve(AttributeConverterProvider attributeConverterProvider) {
ResolvedStaticAttribute<T, R> resolve(AttributeConverterProvider attributeConverterProvider) {
return ResolvedStaticAttribute.create(this,
StaticAttributeType.create(converterFrom(attributeConverterProvider)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
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.AttributeType;
import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.ResolvedStaticAttribute;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

Expand Down Expand Up @@ -71,9 +72,9 @@
*/
@SdkPublicApi
public final class StaticTableSchema<T> implements TableSchema<T> {
private final List<ResolvedStaticAttribute<T>> attributeMappers;
private final List<ResolvedStaticAttribute<T, ?>> attributeMappers;
private final Supplier<T> newItemSupplier;
private final Map<String, ResolvedStaticAttribute<T>> indexedMappers;
private final Map<String, ResolvedStaticAttribute<T, ?>> indexedMappers;
private final StaticTableMetadata tableMetadata;
private final EnhancedType<T> itemType;
private final AttributeConverterProvider attributeConverterProvider;
Expand All @@ -85,12 +86,12 @@ private StaticTableSchema(Builder<T> builder) {
ConverterProviderResolver.resolveProviders(builder.attributeConverterProviders);

// Resolve declared attributes and find converters for them
Stream<ResolvedStaticAttribute<T>> attributesStream = builder.attributes == null ?
Stream<ResolvedStaticAttribute<T, ?>> attributesStream = builder.attributes == null ?
Stream.empty() : builder.attributes.stream().map(a -> a.resolve(this.attributeConverterProvider));

// Merge resolved declared attributes and additional attributes that were added by extend or flatten
List<ResolvedStaticAttribute<T>> mutableAttributeMappers = new ArrayList<>();
Map<String, ResolvedStaticAttribute<T>> mutableIndexedMappers = new HashMap<>();
List<ResolvedStaticAttribute<T, ?>> mutableAttributeMappers = new ArrayList<>();
Map<String, ResolvedStaticAttribute<T, ?>> mutableIndexedMappers = new HashMap<>();
Stream.concat(attributesStream, builder.additionalAttributes.stream()).forEach(
resolvedAttribute -> {
String attributeName = resolvedAttribute.attributeName();
Expand Down Expand Up @@ -136,7 +137,7 @@ public static <T> Builder<T> builder(Class<T> itemClass) {
*/
public static final class Builder<T> {
private final Class<T> itemClass;
private final List<ResolvedStaticAttribute<T>> additionalAttributes = new ArrayList<>();
private final List<ResolvedStaticAttribute<T, ?>> additionalAttributes = new ArrayList<>();

private List<StaticAttribute<T, ?>> attributes;
private Supplier<T> newItemSupplier;
Expand Down Expand Up @@ -243,7 +244,7 @@ public <R> Builder<T> flatten(StaticTableSchema<R> otherTableSchema,
* the super-class into the {@link StaticTableSchema} of the sub-class.
*/
public Builder<T> extend(StaticTableSchema<? super T> superTableSchema) {
Stream<ResolvedStaticAttribute<T>> attributeStream =
Stream<ResolvedStaticAttribute<T, ?>> attributeStream =
upcastingTransformForAttributes(superTableSchema.attributeMappers);
attributeStream.forEach(this.additionalAttributes::add);
return this;
Expand Down Expand Up @@ -335,8 +336,8 @@ public StaticTableSchema<T> build() {
return new StaticTableSchema<>(this);
}

private static <T extends R, R> Stream<ResolvedStaticAttribute<T>> upcastingTransformForAttributes(
Collection<ResolvedStaticAttribute<R>> superAttributes) {
private static <T extends R, R> Stream<ResolvedStaticAttribute<T, ?>> upcastingTransformForAttributes(
Collection<ResolvedStaticAttribute<R, ?>> superAttributes) {
return superAttributes.stream().map(attribute -> attribute.transform(x -> x, null));
}
}
Expand All @@ -355,7 +356,7 @@ public T mapToItem(Map<String, AttributeValue> attributeMap) {
String key = entry.getKey();
AttributeValue value = entry.getValue();
if (!isNullAttributeValue(value)) {
ResolvedStaticAttribute<T> attributeMapper = indexedMappers.get(key);
ResolvedStaticAttribute<T, ?> attributeMapper = indexedMappers.get(key);

if (attributeMapper != null) {
if (item == null) {
Expand Down Expand Up @@ -403,15 +404,14 @@ public Map<String, AttributeValue> itemToMap(T item, Collection<String> attribut

@Override
public AttributeValue attributeValue(T item, String key) {
ResolvedStaticAttribute<T> attributeMapper = indexedMappers.get(key);

if (attributeMapper == null) {
throw new IllegalArgumentException(String.format("TableSchema does not know how to retrieve requested "
+ "attribute '%s' from mapped object.", key));
}

AttributeValue attributeValue = attributeMapper.attributeGetterMethod().apply(item);
AttributeValue attributeValue = findAttribute(key).attributeGetterMethod().apply(item);
return isNullAttributeValue(attributeValue) ? null : attributeValue;
}

@Override
public AttributeValue convertAttributeValue(Object value, String key) {
AttributeType<Object> attributeType = (AttributeType<Object>) findAttribute(key).attributeType();
AttributeValue attributeValue = attributeType.objectToAttributeValue(value);
return isNullAttributeValue(attributeValue) ? null : attributeValue;
}

Expand All @@ -437,4 +437,15 @@ private T constructNewItem() {

return newItemSupplier.get();
}

private ResolvedStaticAttribute<T, ?> findAttribute(String key) {
ResolvedStaticAttribute<T, ?> attributeMapper = indexedMappers.get(key);

if (attributeMapper == null) {
throw new IllegalArgumentException(String.format("TableSchema does not know how to retrieve requested "
+ "attribute '%s' from mapped object.", key));
}

return attributeMapper;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,12 @@ public void attributeValue_returnsValue() {
assertThat(beanTableSchema.attributeValue(simpleBean, "integerAttribute"), is(numberValue(123)));
}

@Test
public void convertAttributeValue_returnsValue() {
BeanTableSchema<SimpleBean> beanTableSchema = BeanTableSchema.create(SimpleBean.class);
assertThat(beanTableSchema.convertAttributeValue(123, "integerAttribute"), is(numberValue(123)));
}

@Test
public void enumBean_invalidEnum() {
BeanTableSchema<EnumBean> beanTableSchema = BeanTableSchema.create(EnumBean.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ public void resolve_uses_customConverter() {
.attributeConverter(attributeConverter)
.build();

ResolvedStaticAttribute<SimpleItem> resolvedAttribute =
ResolvedStaticAttribute<SimpleItem, String> resolvedAttribute =
staticAttribute.resolve(AttributeConverterProvider.defaultProvider());

Function<SimpleItem, AttributeValue> attributeValueFunction = resolvedAttribute.attributeGetterMethod();
Expand Down