Skip to content

Commit

Permalink
Add DynamoDbIgnoreNulls to support ignoreNulls in nested beans
Browse files Browse the repository at this point in the history
  • Loading branch information
zoewangg committed Apr 13, 2021
1 parent b3be94a commit df22f81
Show file tree
Hide file tree
Showing 15 changed files with 575 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -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)"
}
Original file line number Diff line number Diff line change
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;
}
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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) {
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
Loading

0 comments on commit df22f81

Please sign in to comment.