Skip to content
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

Add DynamoDbIgnoreNulls to support ignoreNulls in nested beans #2397

Merged
merged 2 commits into from Apr 13, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,6 @@
{
"category": "DynamoDB Enhanced Client",
"contributor": "",
"type": "feature",
"description": "Added `DynamoDbIgnoreNulls` attribute level annotation that specifies attributes with null values should be ignored. See [#2303](https://github.com/aws/aws-sdk-java-v2/issues/2303)"
}
Expand Up @@ -574,6 +574,10 @@ public boolean equals(Object o) {
return false;
}

if (documentConfiguration != null ? !documentConfiguration.equals(enhancedType.documentConfiguration) :
enhancedType.documentConfiguration != null) {
return false;
}
zoewangg marked this conversation as resolved.
Show resolved Hide resolved
return tableSchema != null ? tableSchema.equals(enhancedType.tableSchema) : enhancedType.tableSchema == null;
}

Expand All @@ -583,6 +587,7 @@ public int hashCode() {
result = 31 * result + rawClass.hashCode();
result = 31 * result + (rawClassParameters != null ? rawClassParameters.hashCode() : 0);
result = 31 * result + (tableSchema != null ? tableSchema.hashCode() : 0);
result = 31 * result + (documentConfiguration != null ? documentConfiguration.hashCode() : 0);
return result;
}

Expand Down
Expand Up @@ -27,9 +27,11 @@
public final class EnhancedTypeDocumentConfiguration implements ToCopyableBuilder<EnhancedTypeDocumentConfiguration.Builder,
EnhancedTypeDocumentConfiguration> {
private final boolean preserveEmptyObject;
private final boolean ignoreNulls;

public EnhancedTypeDocumentConfiguration(Builder builder) {
this.preserveEmptyObject = builder.preserveEmptyObject != null && builder.preserveEmptyObject;
this.ignoreNulls = builder.ignoreNulls != null && builder.ignoreNulls;
}

/**
Expand All @@ -40,6 +42,37 @@ public boolean preserveEmptyObject() {
return preserveEmptyObject;
}

/**
* @return whether to ignore attributes with null values in the associated {@link EnhancedType}.
*/
public boolean ignoreNulls() {
return ignoreNulls;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

EnhancedTypeDocumentConfiguration that = (EnhancedTypeDocumentConfiguration) o;

if (preserveEmptyObject != that.preserveEmptyObject) {
return false;
}
return ignoreNulls == that.ignoreNulls;
}

@Override
public int hashCode() {
int result = (preserveEmptyObject ? 1 : 0);
result = 31 * result + (ignoreNulls ? 1 : 0);
return result;
}

@Override
public Builder toBuilder() {
return builder().preserveEmptyObject(preserveEmptyObject);
Expand All @@ -51,19 +84,29 @@ public static Builder builder() {

public static final class Builder implements CopyableBuilder<Builder, EnhancedTypeDocumentConfiguration> {
private Boolean preserveEmptyObject;
private Boolean ignoreNulls;

private Builder() {
}

/**
* Specifies whether to initialize the associated {@link EnhancedType} as empty class when
* mapping it to a Java object
* mapping it to a Java object. By default, the value is false
*/
public Builder preserveEmptyObject(Boolean preserveEmptyObject) {
this.preserveEmptyObject = preserveEmptyObject;
return this;
}

/**
* Specifies whether to ignore attributes with null values in the associated {@link EnhancedType}.
* By default, the value is false
*/
public Builder ignoreNulls(Boolean ignoreNulls) {
this.ignoreNulls = ignoreNulls;
return this;
}

@Override
public EnhancedTypeDocumentConfiguration build() {
return new EnhancedTypeDocumentConfiguration(this);
Expand Down
@@ -0,0 +1,66 @@
/*
* 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;

import software.amazon.awssdk.annotations.SdkInternalApi;

/**
* Internal configuration for attribute
*/
@SdkInternalApi
public final class AttributeConfiguration {
private final boolean preserveEmptyObject;
private final boolean ignoreNulls;

public AttributeConfiguration(Builder builder) {
this.preserveEmptyObject = builder.preserveEmptyObject;
this.ignoreNulls = builder.ignoreNulls;
}

public boolean preserveEmptyObject() {
return preserveEmptyObject;
}

public boolean ignoreNulls() {
return ignoreNulls;
}

public static Builder builder() {
return new Builder();
}

public static final class Builder {
private boolean preserveEmptyObject;
private boolean ignoreNulls;

private Builder() {
}

public Builder preserveEmptyObject(boolean preserveEmptyObject) {
this.preserveEmptyObject = preserveEmptyObject;
return this;
}

public Builder ignoreNulls(boolean ignoreNulls) {
this.ignoreNulls = ignoreNulls;
return this;
}

public AttributeConfiguration build() {
return new AttributeConfiguration(this);
}
}
}
Expand Up @@ -32,6 +32,7 @@ public class DocumentAttributeConverter<T> implements AttributeConverter<T> {
private final TableSchema<T> tableSchema;
private final EnhancedType<T> enhancedType;
private final boolean preserveEmptyObject;
private final boolean ignoreNulls;

private DocumentAttributeConverter(TableSchema<T> tableSchema,
EnhancedType<T> enhancedType) {
Expand All @@ -40,6 +41,9 @@ private DocumentAttributeConverter(TableSchema<T> tableSchema,
this.preserveEmptyObject = enhancedType.documentConfiguration()
.map(EnhancedTypeDocumentConfiguration::preserveEmptyObject)
.orElse(false);
this.ignoreNulls = enhancedType.documentConfiguration()
.map(EnhancedTypeDocumentConfiguration::ignoreNulls)
.orElse(false);

}

Expand All @@ -50,7 +54,7 @@ public static <T> DocumentAttributeConverter<T> create(TableSchema<T> tableSchem

@Override
public AttributeValue transformFrom(T input) {
return AttributeValue.builder().m(tableSchema.itemToMap(input, false)).build();
return AttributeValue.builder().m(tableSchema.itemToMap(input, ignoreNulls)).build();
}

@Override
Expand Down
Expand Up @@ -31,6 +31,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
Expand All @@ -40,7 +41,9 @@
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedTypeDocumentConfiguration;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.internal.AttributeConfiguration;
import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.BeanAttributeGetter;
import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.BeanAttributeSetter;
import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.MetaTableSchema;
Expand All @@ -52,6 +55,7 @@
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbConvertedBy;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnoreNulls;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPreserveEmptyObject;

Expand Down Expand Up @@ -184,10 +188,11 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
getterForProperty(propertyDescriptor, beanClass),
setterForProperty(propertyDescriptor, beanClass));
} else {
boolean shouldPreserveEmptyObject = getPropertyAnnotation(propertyDescriptor,
DynamoDbPreserveEmptyObject.class) != null;
AttributeConfiguration attributeConfiguration =
resolveAttributeConfiguration(propertyDescriptor);

StaticAttribute.Builder<T, ?> attributeBuilder =
staticAttributeBuilder(propertyDescriptor, beanClass, metaTableSchemaCache, shouldPreserveEmptyObject);
staticAttributeBuilder(propertyDescriptor, beanClass, metaTableSchemaCache, attributeConfiguration);

Optional<AttributeConverter> attributeConverter =
createAttributeConverterFromAnnotation(propertyDescriptor);
Expand All @@ -203,6 +208,19 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
return builder.build();
}

private static AttributeConfiguration resolveAttributeConfiguration(PropertyDescriptor propertyDescriptor) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like this encapsulation of the configuration settings.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

boolean shouldPreserveEmptyObject = getPropertyAnnotation(propertyDescriptor,
DynamoDbPreserveEmptyObject.class) != null;

boolean shouldIgnoreNulls = getPropertyAnnotation(propertyDescriptor,
DynamoDbIgnoreNulls.class) != null;

return AttributeConfiguration.builder()
.preserveEmptyObject(shouldPreserveEmptyObject)
.ignoreNulls(shouldIgnoreNulls)
.build();
}

private static List<AttributeConverterProvider> createConverterProvidersFromAnnotation(DynamoDbBean dynamoDbBean) {
Class<? extends AttributeConverterProvider>[] providerClasses = dynamoDbBean.converterProviders();

Expand All @@ -214,10 +232,10 @@ private static List<AttributeConverterProvider> createConverterProvidersFromAnno
private static <T> StaticAttribute.Builder<T, ?> staticAttributeBuilder(PropertyDescriptor propertyDescriptor,
Class<T> beanClass,
MetaTableSchemaCache metaTableSchemaCache,
boolean preserveEmptyObject) {
AttributeConfiguration attributeConfiguration) {

Type propertyType = propertyDescriptor.getReadMethod().getGenericReturnType();
EnhancedType<?> propertyTypeToken = convertTypeToEnhancedType(propertyType, metaTableSchemaCache, preserveEmptyObject);
EnhancedType<?> propertyTypeToken = convertTypeToEnhancedType(propertyType, metaTableSchemaCache, attributeConfiguration);
return StaticAttribute.builder(beanClass, propertyTypeToken)
.name(attributeNameForProperty(propertyDescriptor))
.getter(getterForProperty(propertyDescriptor, beanClass))
Expand All @@ -233,7 +251,7 @@ private static List<AttributeConverterProvider> createConverterProvidersFromAnno
*/
@SuppressWarnings("unchecked")
private static EnhancedType<?> convertTypeToEnhancedType(Type type, MetaTableSchemaCache metaTableSchemaCache,
boolean preserveEmptyObject) {
AttributeConfiguration attributeConfiguration) {
Class<?> clazz = null;

if (type instanceof ParameterizedType) {
Expand All @@ -242,13 +260,13 @@ private static EnhancedType<?> convertTypeToEnhancedType(Type type, MetaTableSch

if (List.class.equals(rawType)) {
EnhancedType<?> enhancedType = convertTypeToEnhancedType(parameterizedType.getActualTypeArguments()[0],
metaTableSchemaCache, preserveEmptyObject);
metaTableSchemaCache, attributeConfiguration);
return EnhancedType.listOf(enhancedType);
}

if (Map.class.equals(rawType)) {
EnhancedType<?> enhancedType = convertTypeToEnhancedType(parameterizedType.getActualTypeArguments()[1],
metaTableSchemaCache, preserveEmptyObject);
metaTableSchemaCache, attributeConfiguration);
return EnhancedType.mapOf(EnhancedType.of(parameterizedType.getActualTypeArguments()[0]),
enhancedType);
}
Expand All @@ -261,16 +279,20 @@ private static EnhancedType<?> convertTypeToEnhancedType(Type type, MetaTableSch
}

if (clazz != null) {
Consumer<EnhancedTypeDocumentConfiguration.Builder> attrConfiguration =
b -> b.preserveEmptyObject(attributeConfiguration.preserveEmptyObject())
.ignoreNulls(attributeConfiguration.ignoreNulls());

if (clazz.getAnnotation(DynamoDbImmutable.class) != null) {
return EnhancedType.documentOf(
(Class<Object>) clazz,
(TableSchema<Object>) ImmutableTableSchema.recursiveCreate(clazz, metaTableSchemaCache),
b -> b.preserveEmptyObject(preserveEmptyObject));
attrConfiguration);
} else if (clazz.getAnnotation(DynamoDbBean.class) != null) {
return EnhancedType.documentOf(
(Class<Object>) clazz,
(TableSchema<Object>) BeanTableSchema.recursiveCreate(clazz, metaTableSchemaCache),
b -> b.preserveEmptyObject(preserveEmptyObject));
attrConfiguration);
}
}

Expand Down