diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java index fd899c804010..b90c391ecf6d 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java @@ -71,10 +71,16 @@ private DefaultEnhancedDocument(Map attributeValueMap) { } public DefaultEnhancedDocument(DefaultBuilder builder) { - attributeValueMap = Collections.unmodifiableMap(builder.getAttributeValueMap()); - attributeConverterProviders = ChainConverterProvider.create(builder.attributeConverterProviders); + List providers = builder.attributeConverterProviders; + attributeConverterProviders = + ChainConverterProvider.create(providers != null && !providers.isEmpty() + ? providers + : Collections.singletonList(AttributeConverterProvider.defaultProvider())); + attributeValueMap = Collections.unmodifiableMap(objectMapToAttributeMap(builder.attributeValueMap, + attributeConverterProviders)); } + public static DefaultBuilder builder() { return new DefaultBuilder(); @@ -339,31 +345,19 @@ public String toJsonPretty() { public static class DefaultBuilder implements EnhancedDocument.Builder { - Map attributeValueMap = new LinkedHashMap<>(); + Map attributeValueMap = new LinkedHashMap<>(); List attributeConverterProviders = new ArrayList<>(); - public DefaultBuilder() { - } - - public Map getAttributeValueMap() { - return attributeValueMap; + private DefaultBuilder() { } @Override public Builder add(String attributeName, Object value) { - ChainConverterProvider attributeConverterProvider = providerFromBuildAndAppendDefault(); - attributeValueMap.put(attributeName, convert(value, attributeConverterProvider)); + attributeValueMap.put(attributeName, value); return this; } - private ChainConverterProvider providerFromBuildAndAppendDefault() { - List converterProviders = new ArrayList<>(attributeConverterProviders); - converterProviders.add(DefaultAttributeConverterProvider.create()); - ChainConverterProvider attributeConverterProvider = ChainConverterProvider.create(converterProviders); - return attributeConverterProvider; - } - @Override public Builder addString(String attributeName, String value) { Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null"); @@ -440,7 +434,7 @@ public Builder addSdkBytesSet(String attributeName, Set values) { public Builder addList(String attributeName, List value) { Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null"); if (!isNullValueAdded(attributeName, value)) { - attributeValueMap.put(attributeName, convert(value, providerFromBuildAndAppendDefault())); + attributeValueMap.put(attributeName, value); } return this; } @@ -449,7 +443,7 @@ public Builder addList(String attributeName, List value) { public Builder addMap(String attributeName, Map value) { Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null"); if (!isNullValueAdded(attributeName, value)) { - attributeValueMap.put(attributeName, convert(value, providerFromBuildAndAppendDefault())); + attributeValueMap.put(attributeName, value); } return this; } @@ -507,7 +501,9 @@ public Builder json(String json) { throw new IllegalArgumentException("Could not parse argument json " + json); } AttributeValue attributeValue = JSON_ITEM_ATTRIBUTE_CONVERTER.transformFrom(jsonNode); - this.attributeValueMap = attributeValue.m(); + if (attributeValue != null && attributeValue.hasM()) { + attributeValueMap = new LinkedHashMap<>(attributeValue.m()); + } return this; } @@ -550,4 +546,21 @@ public int hashCode() { result = 31 * result + (attributeConverterProviders != null ? attributeConverterProviders.hashCode() : 0); return result; } + + private static Map objectMapToAttributeMap(Map objectMap, + AttributeConverterProvider attributeConverterProvider) { + if (objectMap == null) { + return null; + } + Map result = new LinkedHashMap<>(objectMap.size()); + objectMap.forEach((key, value) -> { + if (value instanceof AttributeValue) { + result.put(key, (AttributeValue) value); + } else { + result.put(key, convert(value, attributeConverterProvider)); + } + }); + return result; + } + } \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DocumentUtils.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DocumentUtils.java index 956145583eb2..462f7b76d96d 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DocumentUtils.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DocumentUtils.java @@ -146,10 +146,12 @@ public static AttributeValue convert(Object sourceObject, AttributeConverterProv return convertMapToAttributeValue((Map) sourceObject, attributeConverterProvider); } AttributeConverter attributeConverter = attributeConverterProvider.converterFor(EnhancedType.of(sourceObject.getClass())); + if (attributeConverter == null) { + throw new IllegalStateException("Converter not found for Class " + sourceObject.getClass().getSimpleName()); + } return attributeConverter.transformFrom(sourceObject); } - /** * Coverts AttributeValue to simple java objects like String, SdkNumber, Boolean, List, Set, SdkBytes or Maps. */ diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomAttributeForDocumentConverterProvider.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomAttributeForDocumentConverterProvider.java new file mode 100644 index 000000000000..f2da91c4fa43 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomAttributeForDocumentConverterProvider.java @@ -0,0 +1,95 @@ +/* + * 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.converters.document; + +import java.util.Map; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnhancedAttributeValue; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.string.IntegerStringConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.utils.ImmutableMap; + +public class CustomAttributeForDocumentConverterProvider implements AttributeConverterProvider { + private final Map, AttributeConverter> converterCache = ImmutableMap.of( + EnhancedType.of(CustomClassForDocumentAPI.class), new CustomClassForDocumentAttributeConverter(), + EnhancedType.of(Integer.class), new CustomIntegerAttributeConverter() + ); + + @SuppressWarnings("unchecked") + @Override + public AttributeConverter converterFor(EnhancedType enhancedType) { + return (AttributeConverter) converterCache.get(enhancedType); + } + + public static CustomAttributeForDocumentConverterProvider create(){ + return new CustomAttributeForDocumentConverterProvider(); + } + + + private static class CustomStringAttributeConverter implements AttributeConverter { + + final static String DEFAULT_SUFFIX = "-custom"; + + @Override + public AttributeValue transformFrom(String input) { + return EnhancedAttributeValue.fromString(input + DEFAULT_SUFFIX).toAttributeValue(); + } + + @Override + public String transformTo(AttributeValue input) { + return input.s(); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(String.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.S; + } + } + + private static class CustomIntegerAttributeCo implements AttributeConverter { + + final static Integer DEFAULT_INCREMENT = 10; + + @Override + public AttributeValue transformFrom(Integer input) { + return EnhancedAttributeValue.fromNumber(IntegerStringConverter.create().toString(input + DEFAULT_INCREMENT)) + .toAttributeValue(); + } + + @Override + public Integer transformTo(AttributeValue input) { + return Integer.valueOf(input.n()); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(Integer.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.N; + } + } +} \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomClassForDocumentAPI.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomClassForDocumentAPI.java new file mode 100644 index 000000000000..403418c6f688 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomClassForDocumentAPI.java @@ -0,0 +1,214 @@ +/* + * 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.converters.document; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.core.SdkBytes; + +public class CustomClassForDocumentAPI { + public String string() { + return string; + } + + public Set stringSet() { + return stringSet; + } + + public SdkBytes binary() { + return binary; + } + + public Set binarySet() { + return binarySet; + } + + public boolean aBoolean() { + return aBoolean; + } + + public Set booleanSet() { + return booleanSet; + } + + public Long longNumber() { + return longNumber; + } + + public Set longSet() { + return longSet; + } + + public BigDecimal bigDecimal() { + return bigDecimal; + } + + public Set bigDecimalSet() { + return bigDecimalSet; + } + + public List customClassList() { + return customClassForDocumentAPIList; + } + + public List instantList() { + return instantList; + } + + public Map customClassMap() { + return customClassMap; + } + + public CustomClassForDocumentAPI innerCustomClass() { + return innerCustomClassForDocumentAPI; + } + + private final String string; + private final Set stringSet; + private final SdkBytes binary; + private final Set binarySet; + private final boolean aBoolean; + private final Set booleanSet; + private final Long longNumber; + private final Set longSet; + private final BigDecimal bigDecimal; + private final Set bigDecimalSet; + private final List customClassForDocumentAPIList; + private final List instantList; + private final Map customClassMap; + private final CustomClassForDocumentAPI innerCustomClassForDocumentAPI; + + public static Builder builder(){ + return new Builder(); + } + + public CustomClassForDocumentAPI(Builder builder) { + this.string = builder.string; + this.stringSet = builder.stringSet; + this.binary = builder.binary; + this.binarySet = builder.binarySet; + this.aBoolean = builder.aBoolean; + this.booleanSet = builder.booleanSet; + this.longNumber = builder.longNumber; + this.longSet = builder.longSet; + this.bigDecimal = builder.bigDecimal; + this.bigDecimalSet = builder.bigDecimalSet; + this.customClassForDocumentAPIList = builder.customClassForDocumentAPIList; + this.instantList = builder.instantList; + this.customClassMap = builder.customClassMap; + this.innerCustomClassForDocumentAPI = builder.innerCustomClassForDocumentAPI; + } + + public static final class Builder { + private String string; + private Set stringSet; + private SdkBytes binary; + private Set binarySet; + private boolean aBoolean; + private Set booleanSet; + private Long longNumber; + private Set longSet; + private BigDecimal bigDecimal; + private Set bigDecimalSet; + private List customClassForDocumentAPIList; + private List instantList; + private Map customClassMap; + private CustomClassForDocumentAPI innerCustomClassForDocumentAPI; + + private Builder() { + } + + + public Builder string(String string) { + this.string = string; + return this; + } + + public Builder stringSet(Set stringSet) { + this.stringSet = stringSet; + return this; + } + + public Builder binary(SdkBytes binary) { + this.binary = binary; + return this; + } + + public Builder binarySet(Set binarySet) { + this.binarySet = binarySet; + return this; + } + + public Builder aBoolean(boolean aBoolean) { + this.aBoolean = aBoolean; + return this; + } + + public Builder booleanSet(Set booleanSet) { + this.booleanSet = booleanSet; + return this; + } + + public Builder longNumber(Long longNumber) { + this.longNumber = longNumber; + return this; + } + + public Builder longSet(Set longSet) { + this.longSet = longSet; + return this; + } + + public Builder bigDecimal(BigDecimal bigDecimal) { + this.bigDecimal = bigDecimal; + return this; + } + + public Builder bigDecimalSet(Set bigDecimalSet) { + this.bigDecimalSet = bigDecimalSet; + return this; + } + + public Builder customClassList(List customClassForDocumentAPIList) { + this.customClassForDocumentAPIList = customClassForDocumentAPIList; + return this; + } + + public Builder instantList(List instantList) { + this.instantList = instantList; + return this; + } + + public Builder customClassMap(Map customClassMap) { + this.customClassMap = customClassMap; + return this; + } + + public Builder innerCustomClass(CustomClassForDocumentAPI innerCustomClassForDocumentAPI) { + this.innerCustomClassForDocumentAPI = innerCustomClassForDocumentAPI; + return this; + } + + public CustomClassForDocumentAPI build() { + return new CustomClassForDocumentAPI(this); + } + } + + +} diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomClassForDocumentAPIBuilder.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomClassForDocumentAPIBuilder.java new file mode 100644 index 000000000000..b773ba3cbbac --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomClassForDocumentAPIBuilder.java @@ -0,0 +1,17 @@ +/* + * 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.converters.document; + diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomClassForDocumentAttributeConverter.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomClassForDocumentAttributeConverter.java new file mode 100644 index 000000000000..55cbb7c65684 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomClassForDocumentAttributeConverter.java @@ -0,0 +1,127 @@ +/* + * 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.converters.document; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.BigDecimalAttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.BooleanAttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.ByteArrayAttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnhancedAttributeValue; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.ListAttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.LongAttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.SetAttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class CustomClassForDocumentAttributeConverter implements AttributeConverter { + + final static Integer DEFAULT_INCREMENT = 10; + + @Override + public AttributeValue transformFrom(CustomClassForDocumentAPI input) { + + if(input == null){ + return null; + } + Map attributeValueMap = new HashMap<>(); + + if(input.string() != null){ + attributeValueMap.put("foo", AttributeValue.fromS(input.string())); + } + + if(input.stringSet() != null){ + attributeValueMap.put("stringSet", AttributeValue.fromSs(input.stringSet().stream().collect(Collectors.toList()))); + } + + if(input.booleanSet() != null){ + attributeValueMap.put("booleanSet", + AttributeValue.fromL(input.booleanSet().stream().map(b -> AttributeValue.fromBool(b)).collect(Collectors.toList()))); + } + + if(input.bigDecimalSet() != null){ + attributeValueMap.put("stringSet", + AttributeValue.fromNs(input.bigDecimalSet().stream().map(b -> b.toString()).collect(Collectors.toList()))); + } + + if(input.customClassList() != null){ + attributeValueMap.put("customClassList", convertCustomList(input.customClassList())); + } + + if (input.innerCustomClass() != null){ + attributeValueMap.put("innerCustomClass", transformFrom(input.innerCustomClass())); + } + return EnhancedAttributeValue.fromMap(attributeValueMap).toAttributeValue(); + } + + + private static AttributeValue convertCustomList(List customClassForDocumentAPIList){ + List convertCustomList = + customClassForDocumentAPIList.stream().map(customClassForDocumentAPI -> create().transformFrom(customClassForDocumentAPI)).collect(Collectors.toList()); + return AttributeValue.fromL(convertCustomList); + + } + + @Override + public CustomClassForDocumentAPI transformTo(AttributeValue input) { + + Map customAttr = input.m(); + + CustomClassForDocumentAPI.Builder builder = CustomClassForDocumentAPI.builder(); + builder.string(StringAttributeConverter.create().transformTo(customAttr.get("foo"))); + builder.stringSet(SetAttributeConverter.setConverter(StringAttributeConverter.create()).transformTo(customAttr.get("stringSet"))); + builder.binary(SdkBytes.fromByteArray(ByteArrayAttributeConverter.create().transformTo(customAttr.get("binary")))); + + builder.binarySet(SetAttributeConverter.setConverter(ByteArrayAttributeConverter.create()).transformTo(customAttr.get("binarySet"))); + + builder.aBoolean(BooleanAttributeConverter.create().transformTo(customAttr.get("aBoolean"))); + builder.booleanSet(SetAttributeConverter.setConverter(BooleanAttributeConverter.create()).transformTo(customAttr.get( + "booleanSet"))); + + builder.longNumber(LongAttributeConverter.create().transformTo(customAttr.get("longNumber"))); + builder.longSet(SetAttributeConverter.setConverter(LongAttributeConverter.create()).transformTo(customAttr.get("longSet"))); + + builder.bigDecimal(BigDecimalAttributeConverter.create().transformTo(customAttr.get("bigDecimal"))); + builder.bigDecimalSet(SetAttributeConverter.setConverter(BigDecimalAttributeConverter.create()).transformTo(customAttr.get("bigDecimalSet"))); + + builder.customClassList(ListAttributeConverter.create(create()).transformTo(customAttr.get("customClassList"))); + builder.innerCustomClass(create().transformTo(customAttr.get("innerCustomClass"))); + + return builder.build(); + } + + public static CustomClassForDocumentAttributeConverter create() { + return new CustomClassForDocumentAttributeConverter(); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(CustomClassForDocumentAPI.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.M; + } + + +} diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomIntegerAttributeConverter.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomIntegerAttributeConverter.java new file mode 100644 index 000000000000..084740169201 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/converters/document/CustomIntegerAttributeConverter.java @@ -0,0 +1,49 @@ +/* + * 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.converters.document; + +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnhancedAttributeValue; +import software.amazon.awssdk.enhanced.dynamodb.internal.converter.string.IntegerStringConverter; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class CustomIntegerAttributeConverter implements AttributeConverter { + + final static Integer DEFAULT_INCREMENT = 10; + + @Override + public AttributeValue transformFrom(Integer input) { + return EnhancedAttributeValue.fromNumber(IntegerStringConverter.create().toString(input + DEFAULT_INCREMENT)) + .toAttributeValue(); + } + + @Override + public Integer transformTo(AttributeValue input) { + return Integer.valueOf(input.n()); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(Integer.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.N; + } +} \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocumentTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocumentTest.java index fa08ec34ce7f..cfa08b4d672f 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocumentTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocumentTest.java @@ -34,6 +34,8 @@ import org.junit.jupiter.params.provider.MethodSource; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.converters.document.CustomAttributeForDocumentConverterProvider; +import software.amazon.awssdk.enhanced.dynamodb.converters.document.CustomClassForDocumentAPI; public class EnhancedDocumentTest { @@ -70,7 +72,7 @@ void createFromJson(EnhancedDocument enhancedDocument) { assertThat(enhancedDocument.getList("numberList") .stream() - .map( o ->Integer.parseInt(o.toString()) ) + .map(o -> Integer.parseInt(o.toString())) .collect(Collectors.toList())) .isEqualTo(Arrays.stream(NUMBER_STRING_ARRAY) .map(s -> Integer.parseInt(s)) @@ -89,13 +91,13 @@ void createFromJson(EnhancedDocument enhancedDocument) { } @Test - public void nullArgsInStaticConstructor(){ + void nullArgsInStaticConstructor() { assertThat(EnhancedDocument.fromMap(null)).isNull(); assertThat(EnhancedDocument.fromJson(null)).isNull(); } @Test - void accessingStringSetFromBuilderMethods(){ + void accessingStringSetFromBuilderMethods() { Set stringSet = Stream.of(STRINGS_ARRAY).collect(Collectors.toSet()); EnhancedDocument enhancedDocument = EnhancedDocument.builder() @@ -114,44 +116,135 @@ void toBuilderOverwritingOldJson() { assertThat(fromBuilder.toJson()).isEqualTo(INNER_JSON); } - @Test - void builder_with_NullKeys() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addString(null, "Sample")) + @Test + void builder_with_NullKeys() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addString(null, "Sample")) + .withMessage(EMPTY_OR_NULL_ERROR); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addNull(null)) + .withMessage(EMPTY_OR_NULL_ERROR); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addNumber(null, 3)) + .withMessage(EMPTY_OR_NULL_ERROR); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addList(null, Arrays.asList())) + .withMessage(EMPTY_OR_NULL_ERROR); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addSdkBytes(null, SdkBytes.fromUtf8String("a"))) + .withMessage(EMPTY_OR_NULL_ERROR); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addMap(null, new HashMap<>())) + .withMessage(EMPTY_OR_NULL_ERROR); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addStringSet(null, Stream.of("a").collect(Collectors.toSet()))) + .withMessage(EMPTY_OR_NULL_ERROR); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addNumberSet(null, Stream.of(1).collect(Collectors.toSet()))) + .withMessage(EMPTY_OR_NULL_ERROR); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addStringSet(null, Stream.of("a").collect(Collectors.toSet()))) + .withMessage(EMPTY_OR_NULL_ERROR); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> EnhancedDocument.builder().addSdkBytesSet(null, Stream.of(SdkBytes.fromUtf8String("a")).collect(Collectors.toSet()))) .withMessage(EMPTY_OR_NULL_ERROR); + } + + @Test + void errorWhen_NoAttributeConverter_IsProviderIsDefined() { + CustomClassForDocumentAPI customObject = CustomClassForDocumentAPI.builder().string("str_one").aBoolean(false).build(); + + assertThatExceptionOfType(IllegalStateException.class).isThrownBy( + () -> EnhancedDocument.builder().add("customObject", customObject).build()) + .withMessage("Converter not found for EnhancedType(software.amazon.awssdk.enhanced.dynamodb.converters.document.CustomClassForDocumentAPI)"); + + assertThatExceptionOfType(IllegalStateException.class).isThrownBy( + () -> EnhancedDocument.builder().addList("customObject", Arrays.asList(customObject)).build()) + .withMessage("Converter not found for EnhancedType(software.amazon.awssdk.enhanced.dynamodb.converters.document.CustomClassForDocumentAPI)"); + + Map customClassMap = new LinkedHashMap<>(); + customClassMap.put("one", customObject); + + assertThatExceptionOfType(IllegalStateException.class).isThrownBy( + () -> EnhancedDocument.builder().addMap("customObject", customClassMap).build()) + .withMessage("Converter not found for EnhancedType(software.amazon.awssdk.enhanced.dynamodb.converters.document.CustomClassForDocumentAPI)"); + } + + @Test + void attributeConverter_OrderInBuilder_Doesnot_Matter_forSimpleAdd() { + CustomClassForDocumentAPI customObject = CustomClassForDocumentAPI.builder().string("str_one") + .longNumber(26L) + .aBoolean(false).build(); + EnhancedDocument afterCustomClass = EnhancedDocument.builder() + .attributeConverterProviders(CustomAttributeForDocumentConverterProvider.create()) + .addString("direct_attr", "sample_value") + .add("customObject", customObject).build(); + + EnhancedDocument beforeCustomClass = EnhancedDocument.builder() + .addString("direct_attr", "sample_value") + .add("customObject", customObject) + .attributeConverterProviders(CustomAttributeForDocumentConverterProvider.create()) + .build(); + assertThat(afterCustomClass.toJson()).isEqualTo("{\"direct_attr\": \"sample_value\",\"customObject\": {\"foo\": " + + "\"str_one\"}}"); + assertThat(beforeCustomClass.toJson()).isEqualTo(afterCustomClass.toJson()); + } + + @Test + void attributeConverter_OrderInBuilder_Doesnot_Matter_ForListAdd() { + CustomClassForDocumentAPI customObjectOne = CustomClassForDocumentAPI.builder().string("str_one") + .longNumber(26L) + .aBoolean(false).build(); + + CustomClassForDocumentAPI customObjectTwo = CustomClassForDocumentAPI.builder().string("str_two") + .longNumber(27L) + .aBoolean(true).build(); + EnhancedDocument afterCustomClass = EnhancedDocument.builder() + .attributeConverterProviders(CustomAttributeForDocumentConverterProvider.create()) + .addString("direct_attr", "sample_value") + .addList("customObject", Arrays.asList(customObjectOne, + customObjectTwo)).build(); + EnhancedDocument beforeCustomClass = EnhancedDocument.builder() + .addString("direct_attr", "sample_value") + .addList("customObject", Arrays.asList(customObjectOne, + customObjectTwo)) + .attributeConverterProviders(CustomAttributeForDocumentConverterProvider.create()) + .build(); + assertThat(afterCustomClass.toJson()).isEqualTo("{\"direct_attr\": \"sample_value\",\"customObject\": [{\"foo\": " + + "\"str_one\"}, {\"foo\": \"str_two\"}]}"); + assertThat(beforeCustomClass.toJson()).isEqualTo(afterCustomClass.toJson()); + } + + @Test + void attributeConverter_OrderInBuilder_Doesnot_Matter_forMapAdd() { + CustomClassForDocumentAPI customObjectOne = CustomClassForDocumentAPI.builder().string("str_one") + .longNumber(26L) + .aBoolean(false).build(); + CustomClassForDocumentAPI customObjectTwo = CustomClassForDocumentAPI.builder().string("str_two") + .longNumber(27L) + .aBoolean(true) + .build(); + Map map = new LinkedHashMap<>(); + map.put("one", customObjectOne); + map.put("two", customObjectTwo); + + EnhancedDocument afterCustomClass = EnhancedDocument.builder() + .attributeConverterProviders(CustomAttributeForDocumentConverterProvider.create()) + .addString("direct_attr", "sample_value") + .addMap("customObject", map) + .build(); + EnhancedDocument beforeCustomClass = EnhancedDocument.builder() + .addString("direct_attr", "sample_value") + .addMap("customObject", map) + .attributeConverterProviders(CustomAttributeForDocumentConverterProvider.create()) + .build(); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addNull(null)) - .withMessage(EMPTY_OR_NULL_ERROR); - - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addNumber(null, 3)) - .withMessage(EMPTY_OR_NULL_ERROR); - - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addList(null, Arrays.asList())) - .withMessage(EMPTY_OR_NULL_ERROR); - - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addSdkBytes(null, SdkBytes.fromUtf8String("a"))) - .withMessage(EMPTY_OR_NULL_ERROR); - - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addMap(null, new HashMap<>())) - .withMessage(EMPTY_OR_NULL_ERROR); - - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addStringSet(null, Stream.of("a").collect(Collectors.toSet()))) - .withMessage(EMPTY_OR_NULL_ERROR); - - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addNumberSet(null, Stream.of(1).collect(Collectors.toSet()))) - .withMessage(EMPTY_OR_NULL_ERROR); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addStringSet(null, Stream.of("a").collect(Collectors.toSet()))) - .withMessage(EMPTY_OR_NULL_ERROR); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() ->EnhancedDocument.builder().addSdkBytesSet(null, Stream.of(SdkBytes.fromUtf8String("a")).collect(Collectors.toSet()))) - .withMessage(EMPTY_OR_NULL_ERROR); } }