Skip to content

Commit df22f81

Browse files
committed
Add DynamoDbIgnoreNulls to support ignoreNulls in nested beans
1 parent b3be94a commit df22f81

File tree

15 files changed

+575
-30
lines changed

15 files changed

+575
-30
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "DynamoDB Enhanced Client",
3+
"contributor": "",
4+
"type": "feature",
5+
"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)"
6+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,10 @@ public boolean equals(Object o) {
574574
return false;
575575
}
576576

577+
if (documentConfiguration != null ? !documentConfiguration.equals(enhancedType.documentConfiguration) :
578+
enhancedType.documentConfiguration != null) {
579+
return false;
580+
}
577581
return tableSchema != null ? tableSchema.equals(enhancedType.tableSchema) : enhancedType.tableSchema == null;
578582
}
579583

@@ -583,6 +587,7 @@ public int hashCode() {
583587
result = 31 * result + rawClass.hashCode();
584588
result = 31 * result + (rawClassParameters != null ? rawClassParameters.hashCode() : 0);
585589
result = 31 * result + (tableSchema != null ? tableSchema.hashCode() : 0);
590+
result = 31 * result + (documentConfiguration != null ? documentConfiguration.hashCode() : 0);
586591
return result;
587592
}
588593

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedTypeDocumentConfiguration.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727
public final class EnhancedTypeDocumentConfiguration implements ToCopyableBuilder<EnhancedTypeDocumentConfiguration.Builder,
2828
EnhancedTypeDocumentConfiguration> {
2929
private final boolean preserveEmptyObject;
30+
private final boolean ignoreNulls;
3031

3132
public EnhancedTypeDocumentConfiguration(Builder builder) {
3233
this.preserveEmptyObject = builder.preserveEmptyObject != null && builder.preserveEmptyObject;
34+
this.ignoreNulls = builder.ignoreNulls != null && builder.ignoreNulls;
3335
}
3436

3537
/**
@@ -40,6 +42,37 @@ public boolean preserveEmptyObject() {
4042
return preserveEmptyObject;
4143
}
4244

45+
/**
46+
* @return whether to ignore attributes with null values in the associated {@link EnhancedType}.
47+
*/
48+
public boolean ignoreNulls() {
49+
return ignoreNulls;
50+
}
51+
52+
@Override
53+
public boolean equals(Object o) {
54+
if (this == o) {
55+
return true;
56+
}
57+
if (o == null || getClass() != o.getClass()) {
58+
return false;
59+
}
60+
61+
EnhancedTypeDocumentConfiguration that = (EnhancedTypeDocumentConfiguration) o;
62+
63+
if (preserveEmptyObject != that.preserveEmptyObject) {
64+
return false;
65+
}
66+
return ignoreNulls == that.ignoreNulls;
67+
}
68+
69+
@Override
70+
public int hashCode() {
71+
int result = (preserveEmptyObject ? 1 : 0);
72+
result = 31 * result + (ignoreNulls ? 1 : 0);
73+
return result;
74+
}
75+
4376
@Override
4477
public Builder toBuilder() {
4578
return builder().preserveEmptyObject(preserveEmptyObject);
@@ -51,19 +84,29 @@ public static Builder builder() {
5184

5285
public static final class Builder implements CopyableBuilder<Builder, EnhancedTypeDocumentConfiguration> {
5386
private Boolean preserveEmptyObject;
87+
private Boolean ignoreNulls;
5488

5589
private Builder() {
5690
}
5791

5892
/**
5993
* Specifies whether to initialize the associated {@link EnhancedType} as empty class when
60-
* mapping it to a Java object
94+
* mapping it to a Java object. By default, the value is false
6195
*/
6296
public Builder preserveEmptyObject(Boolean preserveEmptyObject) {
6397
this.preserveEmptyObject = preserveEmptyObject;
6498
return this;
6599
}
66100

101+
/**
102+
* Specifies whether to ignore attributes with null values in the associated {@link EnhancedType}.
103+
* By default, the value is false
104+
*/
105+
public Builder ignoreNulls(Boolean ignoreNulls) {
106+
this.ignoreNulls = ignoreNulls;
107+
return this;
108+
}
109+
67110
@Override
68111
public EnhancedTypeDocumentConfiguration build() {
69112
return new EnhancedTypeDocumentConfiguration(this);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb.internal;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
20+
/**
21+
* Internal configuration for attribute
22+
*/
23+
@SdkInternalApi
24+
public final class AttributeConfiguration {
25+
private final boolean preserveEmptyObject;
26+
private final boolean ignoreNulls;
27+
28+
public AttributeConfiguration(Builder builder) {
29+
this.preserveEmptyObject = builder.preserveEmptyObject;
30+
this.ignoreNulls = builder.ignoreNulls;
31+
}
32+
33+
public boolean preserveEmptyObject() {
34+
return preserveEmptyObject;
35+
}
36+
37+
public boolean ignoreNulls() {
38+
return ignoreNulls;
39+
}
40+
41+
public static Builder builder() {
42+
return new Builder();
43+
}
44+
45+
public static final class Builder {
46+
private boolean preserveEmptyObject;
47+
private boolean ignoreNulls;
48+
49+
private Builder() {
50+
}
51+
52+
public Builder preserveEmptyObject(boolean preserveEmptyObject) {
53+
this.preserveEmptyObject = preserveEmptyObject;
54+
return this;
55+
}
56+
57+
public Builder ignoreNulls(boolean ignoreNulls) {
58+
this.ignoreNulls = ignoreNulls;
59+
return this;
60+
}
61+
62+
public AttributeConfiguration build() {
63+
return new AttributeConfiguration(this);
64+
}
65+
}
66+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/DocumentAttributeConverter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class DocumentAttributeConverter<T> implements AttributeConverter<T> {
3232
private final TableSchema<T> tableSchema;
3333
private final EnhancedType<T> enhancedType;
3434
private final boolean preserveEmptyObject;
35+
private final boolean ignoreNulls;
3536

3637
private DocumentAttributeConverter(TableSchema<T> tableSchema,
3738
EnhancedType<T> enhancedType) {
@@ -40,6 +41,9 @@ private DocumentAttributeConverter(TableSchema<T> tableSchema,
4041
this.preserveEmptyObject = enhancedType.documentConfiguration()
4142
.map(EnhancedTypeDocumentConfiguration::preserveEmptyObject)
4243
.orElse(false);
44+
this.ignoreNulls = enhancedType.documentConfiguration()
45+
.map(EnhancedTypeDocumentConfiguration::ignoreNulls)
46+
.orElse(false);
4347

4448
}
4549

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

5155
@Override
5256
public AttributeValue transformFrom(T input) {
53-
return AttributeValue.builder().m(tableSchema.itemToMap(input, false)).build();
57+
return AttributeValue.builder().m(tableSchema.itemToMap(input, ignoreNulls)).build();
5458
}
5559

5660
@Override

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.Map;
3232
import java.util.Optional;
3333
import java.util.function.BiConsumer;
34+
import java.util.function.Consumer;
3435
import java.util.function.Function;
3536
import java.util.function.Supplier;
3637
import java.util.stream.Collectors;
@@ -40,7 +41,9 @@
4041
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
4142
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
4243
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
44+
import software.amazon.awssdk.enhanced.dynamodb.EnhancedTypeDocumentConfiguration;
4345
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
46+
import software.amazon.awssdk.enhanced.dynamodb.internal.AttributeConfiguration;
4447
import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.BeanAttributeGetter;
4548
import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.BeanAttributeSetter;
4649
import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.MetaTableSchema;
@@ -52,6 +55,7 @@
5255
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbConvertedBy;
5356
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten;
5457
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore;
58+
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnoreNulls;
5559
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable;
5660
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPreserveEmptyObject;
5761

@@ -184,10 +188,11 @@ private static <T> StaticTableSchema<T> createStaticTableSchema(Class<T> beanCla
184188
getterForProperty(propertyDescriptor, beanClass),
185189
setterForProperty(propertyDescriptor, beanClass));
186190
} else {
187-
boolean shouldPreserveEmptyObject = getPropertyAnnotation(propertyDescriptor,
188-
DynamoDbPreserveEmptyObject.class) != null;
191+
AttributeConfiguration attributeConfiguration =
192+
resolveAttributeConfiguration(propertyDescriptor);
193+
189194
StaticAttribute.Builder<T, ?> attributeBuilder =
190-
staticAttributeBuilder(propertyDescriptor, beanClass, metaTableSchemaCache, shouldPreserveEmptyObject);
195+
staticAttributeBuilder(propertyDescriptor, beanClass, metaTableSchemaCache, attributeConfiguration);
191196

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

211+
private static AttributeConfiguration resolveAttributeConfiguration(PropertyDescriptor propertyDescriptor) {
212+
boolean shouldPreserveEmptyObject = getPropertyAnnotation(propertyDescriptor,
213+
DynamoDbPreserveEmptyObject.class) != null;
214+
215+
boolean shouldIgnoreNulls = getPropertyAnnotation(propertyDescriptor,
216+
DynamoDbIgnoreNulls.class) != null;
217+
218+
return AttributeConfiguration.builder()
219+
.preserveEmptyObject(shouldPreserveEmptyObject)
220+
.ignoreNulls(shouldIgnoreNulls)
221+
.build();
222+
}
223+
206224
private static List<AttributeConverterProvider> createConverterProvidersFromAnnotation(DynamoDbBean dynamoDbBean) {
207225
Class<? extends AttributeConverterProvider>[] providerClasses = dynamoDbBean.converterProviders();
208226

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

219237
Type propertyType = propertyDescriptor.getReadMethod().getGenericReturnType();
220-
EnhancedType<?> propertyTypeToken = convertTypeToEnhancedType(propertyType, metaTableSchemaCache, preserveEmptyObject);
238+
EnhancedType<?> propertyTypeToken = convertTypeToEnhancedType(propertyType, metaTableSchemaCache, attributeConfiguration);
221239
return StaticAttribute.builder(beanClass, propertyTypeToken)
222240
.name(attributeNameForProperty(propertyDescriptor))
223241
.getter(getterForProperty(propertyDescriptor, beanClass))
@@ -233,7 +251,7 @@ private static List<AttributeConverterProvider> createConverterProvidersFromAnno
233251
*/
234252
@SuppressWarnings("unchecked")
235253
private static EnhancedType<?> convertTypeToEnhancedType(Type type, MetaTableSchemaCache metaTableSchemaCache,
236-
boolean preserveEmptyObject) {
254+
AttributeConfiguration attributeConfiguration) {
237255
Class<?> clazz = null;
238256

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

243261
if (List.class.equals(rawType)) {
244262
EnhancedType<?> enhancedType = convertTypeToEnhancedType(parameterizedType.getActualTypeArguments()[0],
245-
metaTableSchemaCache, preserveEmptyObject);
263+
metaTableSchemaCache, attributeConfiguration);
246264
return EnhancedType.listOf(enhancedType);
247265
}
248266

249267
if (Map.class.equals(rawType)) {
250268
EnhancedType<?> enhancedType = convertTypeToEnhancedType(parameterizedType.getActualTypeArguments()[1],
251-
metaTableSchemaCache, preserveEmptyObject);
269+
metaTableSchemaCache, attributeConfiguration);
252270
return EnhancedType.mapOf(EnhancedType.of(parameterizedType.getActualTypeArguments()[0]),
253271
enhancedType);
254272
}
@@ -261,16 +279,20 @@ private static EnhancedType<?> convertTypeToEnhancedType(Type type, MetaTableSch
261279
}
262280

263281
if (clazz != null) {
282+
Consumer<EnhancedTypeDocumentConfiguration.Builder> attrConfiguration =
283+
b -> b.preserveEmptyObject(attributeConfiguration.preserveEmptyObject())
284+
.ignoreNulls(attributeConfiguration.ignoreNulls());
285+
264286
if (clazz.getAnnotation(DynamoDbImmutable.class) != null) {
265287
return EnhancedType.documentOf(
266288
(Class<Object>) clazz,
267289
(TableSchema<Object>) ImmutableTableSchema.recursiveCreate(clazz, metaTableSchemaCache),
268-
b -> b.preserveEmptyObject(preserveEmptyObject));
290+
attrConfiguration);
269291
} else if (clazz.getAnnotation(DynamoDbBean.class) != null) {
270292
return EnhancedType.documentOf(
271293
(Class<Object>) clazz,
272294
(TableSchema<Object>) BeanTableSchema.recursiveCreate(clazz, metaTableSchemaCache),
273-
b -> b.preserveEmptyObject(preserveEmptyObject));
295+
attrConfiguration);
274296
}
275297
}
276298

0 commit comments

Comments
 (0)