diff --git a/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-22723fc.json b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-22723fc.json new file mode 100644 index 000000000000..1521a58f98e6 --- /dev/null +++ b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-22723fc.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "Amazon DynamoDB Enhanced Client", + "contributor": "", + "description": "Added the support to flatten a Map into top level attributes of the object" +} diff --git a/services-custom/dynamodb-enhanced/README.md b/services-custom/dynamodb-enhanced/README.md index fd61a647cc85..78ee91ce64e8 100644 --- a/services-custom/dynamodb-enhanced/README.md +++ b/services-custom/dynamodb-enhanced/README.md @@ -682,3 +682,53 @@ private static final StaticTableSchema CUSTOMER_TABLE_SCHEMA = ``` Just as for annotations, you can flatten as many different eligible classes as you like using the builder pattern. + +#### Using composition + +Using composition, the @DynamoDBFlattenMap annotation support to flatten a Map: +```java +@DynamoDbBean +public class Customer { + private String name; + private String city; + private String address; + + private Map detailsMap; + + public String getName() { return this.name; } + public void setName(String name) { this.name = name;} + public String getCity() { return this.city; } + public void setCity(String city) { this.city = city;} + public String getAddress() { return this.address; } + public void setAddress(String address) { this.name = address;} + + @DynamoDbFlattenMap + public Map getDetailsMap() { return this.detailsMap; } + public void setDetailsMap(Map record) { this.detailsMap = detailsMap;} +} +``` +You can flatten only one map present on a record, otherwise it will be thrown an exception + +Flat map composite classes using StaticTableSchema: + +```java +@Data +public class Customer { + private String name; + private String city; + private String address; + private Map detailsMap; + //getters and setters for all attributes +} + +private static final StaticTableSchema CUSTOMER_TABLE_SCHEMA = + StaticTableSchema.builder(Customer.class) + .newItemSupplier(Customer::new) + .addAttribute(String.class, a -> a.name("name") + .getter(Customer::getName) + .setter(Customer::setName)) + // Because we are flattening a Map object, we supply a getter and setter so the + // mapper knows how to access it + .flattenMap(Map::detailsMap, Map::detailsMap) + .build(); +``` \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedIntegrationTestBase.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedIntegrationTestBase.java index 8a8e35470c20..86b63dfc82f1 100644 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedIntegrationTestBase.java +++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedIntegrationTestBase.java @@ -21,6 +21,7 @@ import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -75,6 +76,36 @@ protected static DynamoDbAsyncClient createAsyncDynamoDbClient() { .setter(Record::setStringAttribute)) .build(); + protected static final TableSchema RECORD_WITH_FLATTEN_MAP_TABLE_SCHEMA = + StaticTableSchema.builder(Record.class) + .newItemSupplier(Record::new) + .addAttribute(String.class, a -> a.name("id") + .getter(Record::getId) + .setter(Record::setId) + .tags(primaryPartitionKey(), secondaryPartitionKey("index1"))) + .addAttribute(Integer.class, a -> a.name("sort") + .getter(Record::getSort) + .setter(Record::setSort) + .tags(primarySortKey(), secondarySortKey("index1"))) + .addAttribute(Integer.class, a -> a.name("value") + .getter(Record::getValue) + .setter(Record::setValue)) + .addAttribute(String.class, a -> a.name("gsi_id") + .getter(Record::getGsiId) + .setter(Record::setGsiId) + .tags(secondaryPartitionKey("gsi_keys_only"))) + .addAttribute(Integer.class, a -> a.name("gsi_sort") + .getter(Record::getGsiSort) + .setter(Record::setGsiSort) + .tags(secondarySortKey("gsi_keys_only"))) + .addAttribute(String.class, a -> a.name("stringAttribute") + .getter(Record::getStringAttribute) + .setter(Record::setStringAttribute)) + .flatten("attributesMap", + Record::getAttributesMap, + Record::setAttributesMap) + .build(); + protected static final List RECORDS = IntStream.range(0, 9) @@ -87,6 +118,22 @@ protected static DynamoDbAsyncClient createAsyncDynamoDbClient() { .setGsiSort(i)) .collect(Collectors.toList()); + protected static final List RECORDS_WITH_FLATTEN_MAP = + IntStream.range(0, 9) + .mapToObj(i -> new Record() + .setId("id-value") + .setSort(i) + .setValue(i) + .setStringAttribute(getStringAttrValue(10 * 1024)) + .setGsiId("gsi-id-value") + .setGsiSort(i) + .setAttributesMap(new HashMap() {{ + put("mapAttribute1", "mapValue1"); + put("mapAttribute2", "mapValue2"); + put("mapAttribute3", "mapValue3"); + }})) + .collect(Collectors.toList()); + protected static final List KEYS_ONLY_RECORDS = RECORDS.stream() .map(record -> new Record() diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/ScanQueryWithFlattenMapIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/ScanQueryWithFlattenMapIntegrationTest.java new file mode 100644 index 000000000000..a85e21cbc96e --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/ScanQueryWithFlattenMapIntegrationTest.java @@ -0,0 +1,117 @@ +/* + * 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; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.numberValue; +import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue; +import static software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional.sortBetween; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import software.amazon.awssdk.enhanced.dynamodb.model.Page; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.Record; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class ScanQueryWithFlattenMapIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { + + private static final String TABLE_NAME = createTestTableName(); + + private static DynamoDbClient dynamoDbClient; + private static DynamoDbEnhancedClient enhancedClient; + private static DynamoDbTable mappedTable; + + @BeforeClass + public static void setup() { + dynamoDbClient = createDynamoDbClient(); + enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build(); + mappedTable = enhancedClient.table(TABLE_NAME, RECORD_WITH_FLATTEN_MAP_TABLE_SCHEMA); + mappedTable.createTable(); + dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)); + } + + @AfterClass + public static void teardown() { + try { + dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); + } finally { + dynamoDbClient.close(); + } + } + + private void insertRecords() { + RECORDS_WITH_FLATTEN_MAP.forEach(record -> mappedTable.putItem(r -> r.item(record))); + } + + @Test + public void queryWithFlattenMapRecord_correctlyRetrievesProjectedAttributes() { + insertRecords(); + + Iterator> results = + mappedTable.query(QueryEnhancedRequest.builder() + .queryConditional(sortBetween(k-> k.partitionValue("id-value").sortValue(2), + k-> k.partitionValue("id-value").sortValue(6))) + .attributesToProject("mapAttribute1", "mapAttribute2") + .limit(3) + .build()) + .iterator(); + + Page page1 = results.next(); + assertThat(results.hasNext(), is(true)); + Page page2 = results.next(); + assertThat(results.hasNext(), is(false)); + + Map resultedAttributesMap = new HashMap<>(); + resultedAttributesMap.put("mapAttribute1", "mapValue1"); + resultedAttributesMap.put("mapAttribute2", "mapValue2"); + + List page1Items = page1.items(); + assertThat(page1Items.size(), is(3)); + assertThat(page1Items.get(0).getAttributesMap(), is(resultedAttributesMap)); + assertThat(page1Items.get(1).getAttributesMap(), is(resultedAttributesMap)); + assertThat(page1Items.get(2).getAttributesMap(), is(resultedAttributesMap)); + assertThat(page1.consumedCapacity(), is(nullValue())); + assertThat(page1.lastEvaluatedKey(), is(getKeyMap(4))); + assertThat(page1.count(), equalTo(3)); + assertThat(page1.scannedCount(), equalTo(3)); + + List page2Items = page2.items(); + assertThat(page2Items.size(), is(2)); + assertThat(page2Items.get(0).getAttributesMap(), is(resultedAttributesMap)); + assertThat(page2Items.get(1).getAttributesMap(), is(resultedAttributesMap)); + assertThat(page2.lastEvaluatedKey(), is(nullValue())); + assertThat(page2.count(), equalTo(2)); + assertThat(page2.scannedCount(), equalTo(2)); + } + + private Map getKeyMap(int sort) { + Map result = new HashMap<>(); + result.put("id", stringValue(RECORDS.get(sort).getId())); + result.put("sort", numberValue(RECORDS.get(sort).getSort())); + return Collections.unmodifiableMap(result); + } +} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/Record.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/Record.java index 962b5d8a10f0..9844f904ef57 100644 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/Record.java +++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/Record.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.enhanced.dynamodb.model; +import java.util.Map; import java.util.Objects; public class Record { @@ -27,6 +28,8 @@ public class Record { private String stringAttribute; + private Map attributesMap; + public String getId() { return id; } @@ -81,6 +84,15 @@ public Record setStringAttribute(String stringAttribute) { return this; } + public Map getAttributesMap() { + return attributesMap; + } + + public Record setAttributesMap(Map attributesMap) { + this.attributesMap = attributesMap; + return this; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -91,11 +103,12 @@ public boolean equals(Object o) { Objects.equals(value, record.value) && Objects.equals(gsiId, record.gsiId) && Objects.equals(stringAttribute, record.stringAttribute) && - Objects.equals(gsiSort, record.gsiSort); + Objects.equals(gsiSort, record.gsiSort) && + Objects.equals(attributesMap, record.attributesMap); } @Override public int hashCode() { - return Objects.hash(id, sort, value, gsiId, gsiSort, stringAttribute); + return Objects.hash(id, sort, value, gsiId, gsiSort, stringAttribute, attributesMap); } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java index a4a661dc274b..fd88e7698f82 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.java @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.WeakHashMap; import java.util.function.BiConsumer; @@ -63,6 +64,7 @@ import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; 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.DynamoDbFlattenMap; 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; @@ -222,6 +224,14 @@ private static StaticTableSchema createStaticTableSchema(Class beanCla throw new IllegalArgumentException(e); } + List mappableProperties = Arrays.stream(beanInfo.getPropertyDescriptors()) + .filter(p -> isMappableProperty(beanClass, p)) + .collect(Collectors.toList()); + + if (dynamoDbFlattenMapAnnotationHasInvalidUse(mappableProperties)) { + throw new IllegalArgumentException("More than one @DynamoDbFlattenMap annotation found on the same record"); + } + Supplier newObjectSupplier = newObjectSupplierForClass(beanClass, lookup); StaticTableSchema.Builder builder = StaticTableSchema.builder(beanClass) @@ -231,9 +241,7 @@ private static StaticTableSchema createStaticTableSchema(Class beanCla List> attributes = new ArrayList<>(); - Arrays.stream(beanInfo.getPropertyDescriptors()) - .filter(p -> isMappableProperty(beanClass, p)) - .forEach(propertyDescriptor -> { + mappableProperties.forEach(propertyDescriptor -> { DynamoDbFlatten dynamoDbFlatten = getPropertyAnnotation(propertyDescriptor, DynamoDbFlatten.class); if (dynamoDbFlatten != null) { @@ -241,19 +249,27 @@ private static StaticTableSchema createStaticTableSchema(Class beanCla getterForProperty(propertyDescriptor, beanClass, lookup), setterForProperty(propertyDescriptor, beanClass, lookup)); } else { - AttributeConfiguration attributeConfiguration = - resolveAttributeConfiguration(propertyDescriptor); + DynamoDbFlattenMap dynamoDbFlattenMap = getPropertyAnnotation(propertyDescriptor, DynamoDbFlattenMap.class); + + if (dynamoDbFlattenMap != null) { + builder.flatten(propertyDescriptor.getName(), getterForProperty(propertyDescriptor, beanClass, lookup), + setterForProperty(propertyDescriptor, beanClass, lookup)); - StaticAttribute.Builder attributeBuilder = - staticAttributeBuilder(propertyDescriptor, beanClass, lookup, metaTableSchemaCache, - attributeConfiguration); + } else { + AttributeConfiguration attributeConfiguration = + resolveAttributeConfiguration(propertyDescriptor); - Optional attributeConverter = + StaticAttribute.Builder attributeBuilder = + staticAttributeBuilder(propertyDescriptor, beanClass, lookup, metaTableSchemaCache, + attributeConfiguration); + + Optional attributeConverter = createAttributeConverterFromAnnotation(propertyDescriptor, lookup); - attributeConverter.ifPresent(attributeBuilder::attributeConverter); + attributeConverter.ifPresent(attributeBuilder::attributeConverter); - addTagsToAttribute(attributeBuilder, propertyDescriptor); - attributes.add(attributeBuilder.build()); + addTagsToAttribute(attributeBuilder, propertyDescriptor); + attributes.add(attributeBuilder.build()); + } } }); @@ -286,6 +302,15 @@ private static Optional findFluentSetter(Class beanClass, String prop .findFirst(); } + private static boolean dynamoDbFlattenMapAnnotationHasInvalidUse(List mappableProperties) { + return mappableProperties.stream() + .map(pd -> getPropertyAnnotation(pd, DynamoDbFlattenMap.class)) + .filter(Objects::nonNull) + .skip(1) + .findFirst() + .isPresent(); + } + private static AttributeConfiguration resolveAttributeConfiguration(PropertyDescriptor propertyDescriptor) { boolean shouldPreserveEmptyObject = getPropertyAnnotation(propertyDescriptor, DynamoDbPreserveEmptyObject.class) != null; diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.java index ea86ac9fcec4..51c317923552 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.java @@ -86,6 +86,7 @@ public final class StaticImmutableTableSchema implements TableSchema { private final EnhancedType itemType; private final AttributeConverterProvider attributeConverterProvider; private final Map> indexedFlattenedMappers; + private final FlattenedMapperForMaps indexedFlattenedMapperForMaps; private final List attributeNames; private static class FlattenedMapper { @@ -144,7 +145,57 @@ private AttributeValue attributeValue(T item, String attributeName) { return isNullAttributeValue(attributeValue) ? null : attributeValue; } } - + + private static final class FlattenedMapperForMaps { + private final String name; + private final Function> mapItemGetter; + private final BiConsumer> mapItemSetter; + + private FlattenedMapperForMaps(String name, + Function> mapItemGetter, + BiConsumer> mapItemSetter) { + this.name = name; + this.mapItemGetter = mapItemGetter; + this.mapItemSetter = mapItemSetter; + } + + public String getItemName() { + return name; + } + + private B mapToItem(B thisBuilder, + Supplier thisBuilderConstructor, + Map flattenedMap) { + + if (thisBuilder == null) { + thisBuilder = thisBuilderConstructor.get(); + } + this.mapItemSetter.accept(thisBuilder, flattenedMap); + + return thisBuilder; + } + + private Map itemToMap(T item, boolean ignoreNulls, List attributeNames) { + Map mapItem = this.mapItemGetter.apply(item); + + Map result = new HashMap<>(); + + if (mapItem != null) { + mapItem.forEach((key, value) -> { + if (attributeNames.contains(key)) { + throw new IllegalArgumentException( + "Attempt to add an attribute to a mapper that already has one with the same name. " + + "[Attribute name: " + key + "]"); + } + if (!ignoreNulls || value != null) { + result.put(key, AttributeValue.builder().s(value).build()); + } + }); + } + return result; + } + } + private StaticImmutableTableSchema(Builder builder) { StaticTableMetadata.Builder tableMetadataBuilder = StaticTableMetadata.builder(); @@ -198,6 +249,11 @@ private StaticImmutableTableSchema(Builder builder) { } ); + FlattenedMapperForMaps mutableFlattenedMapperForMaps = null; + if(builder.flattenedMap != null) { + mutableFlattenedMapperForMaps= builder.flattenedMap; + } + // Apply table-tags to table metadata if (builder.tags != null) { builder.tags.forEach(staticTableTag -> staticTableTag.modifyMetadata().accept(tableMetadataBuilder)); @@ -207,6 +263,7 @@ private StaticImmutableTableSchema(Builder builder) { this.indexedMappers = Collections.unmodifiableMap(mutableIndexedMappers); this.attributeNames = Collections.unmodifiableList(new ArrayList<>(mutableAttributeNames)); this.indexedFlattenedMappers = Collections.unmodifiableMap(mutableFlattenedMappers); + this.indexedFlattenedMapperForMaps = mutableFlattenedMapperForMaps; this.newBuilderSupplier = builder.newBuilderSupplier; this.buildItemFunction = builder.buildItemFunction; this.tableMetadata = tableMetadataBuilder.build(); @@ -246,6 +303,7 @@ public static final class Builder { private final List> additionalAttributes = new ArrayList<>(); private final List> flattenedMappers = new ArrayList<>(); + private FlattenedMapperForMaps flattenedMap; private List> attributes; private Supplier newBuilderSupplier; private Function buildItemFunction; @@ -366,12 +424,24 @@ public Builder flatten(TableSchema otherTableSchema, "TableSchema that is able to create items"); } - FlattenedMapper flattenedMapper = + FlattenedMapper flattenedMapper = new FlattenedMapper<>(otherItemGetter, otherItemSetter, otherTableSchema); this.flattenedMappers.add(flattenedMapper); return this; } + /** + * Flattens all the attributes defined in a Map into the database record this schema + * maps to. Functions to get and set an object that the flattened schema maps to is required. + */ + public Builder flatten(String mapName, Function> mapItemGetter, + BiConsumer> mapItemSetter) { + FlattenedMapperForMaps flattenedMap = + new FlattenedMapperForMaps<>(mapName, mapItemGetter, mapItemSetter); + this.flattenedMap = flattenedMap; + return this; + } + /** * Extends the {@link StaticImmutableTableSchema} of a super-class, effectively rolling all the attributes modelled by * the super-class into the {@link StaticImmutableTableSchema} of the sub-class. The extended immutable table schema @@ -463,7 +533,8 @@ public T mapToItem(Map attributeMap, boolean preserveEmp } Map, Map> flattenedAttributeValuesMap = new LinkedHashMap<>(); - + Map, Map> flattenedMapAttributeValues = new LinkedHashMap<>(); + for (Map.Entry entry : attributeMap.entrySet()) { String key = entry.getKey(); AttributeValue value = entry.getValue(); @@ -490,6 +561,19 @@ public T mapToItem(Map attributeMap, boolean preserveEmp flattenedAttributeValues.put(key, value); flattenedAttributeValuesMap.put(flattenedMapper, flattenedAttributeValues); + + } else { + if (indexedFlattenedMapperForMaps != null) { + Map flattenedAttributeValues = + flattenedMapAttributeValues.get(indexedFlattenedMapperForMaps); + + if (flattenedAttributeValues == null) { + flattenedAttributeValues = new HashMap<>(); + } + + flattenedAttributeValues.put(key, value.s()); + flattenedMapAttributeValues.put(indexedFlattenedMapperForMaps, flattenedAttributeValues); + } } } } @@ -499,7 +583,12 @@ public T mapToItem(Map attributeMap, boolean preserveEmp flattenedAttributeValuesMap.entrySet()) { builder = entry.getKey().mapToItem(builder, this::constructNewBuilder, entry.getValue()); } - + + for (Map.Entry, Map> entry : + flattenedMapAttributeValues.entrySet()) { + builder = entry.getKey().mapToItem(builder, this::constructNewBuilder, entry.getValue()); + } + return builder == null ? null : buildItemFunction.apply(builder); } @@ -526,6 +615,10 @@ public Map itemToMap(T item, boolean ignoreNulls) { attributeValueMap.putAll(flattenedMapper.itemToMap(item, ignoreNulls)); }); + if (indexedFlattenedMapperForMaps != null) { + attributeValueMap.putAll(indexedFlattenedMapperForMaps.itemToMap(item, ignoreNulls, attributeNames)); + } + return unmodifiableMap(attributeValueMap); } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.java index 6dc6b2d4f211..215631902acc 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -177,6 +178,17 @@ public Builder flatten(TableSchema otherTableSchema, return this; } + /** + * Flattens all the attributes defined in a Map into the database record this schema + * maps to. Functions to get and set an object that the flattened schema maps to is required. + */ + public Builder flatten(String mapName, + Function> mapItemGetter, + BiConsumer> mapItemSetter) { + this.delegateBuilder.flatten(mapName, mapItemGetter, mapItemSetter); + return this; + } + /** * Extends the {@link StaticTableSchema} of a super-class, effectively rolling all the attributes modelled by * the super-class into the {@link StaticTableSchema} of the sub-class. diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbFlattenMap.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbFlattenMap.java new file mode 100644 index 000000000000..90f958a4a6ee --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbFlattenMap.java @@ -0,0 +1,35 @@ +/* + * 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.mapper.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import software.amazon.awssdk.annotations.SdkPublicApi; + +/** + * This annotation is used to flatten all the attributes of Map type that are stored in the current bean + * object and add them as top level attributes to the record that is read and written to the database. The target map + * attribute to flatten must be specified as part of this annotation. + * The annotation can be applied on a single map of a specific schema. Otherwise, it will throw an exception + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@SdkPublicApi +public @interface DynamoDbFlattenMap { + +} \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/FlattenMapTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/FlattenMapTest.java new file mode 100644 index 000000000000..c579f0c0e37a --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/FlattenMapTest.java @@ -0,0 +1,170 @@ +/* + * 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.functionaltests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.HashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension; +import software.amazon.awssdk.enhanced.dynamodb.internal.client.ExtensionResolver; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.flattenmap.FlattenMapValidBean; + +public class FlattenMapTest extends LocalDynamoDbSyncTestBase { + + private static final TableSchema TABLE_SCHEMA = + TableSchema.fromClass(FlattenMapValidBean.class); + + private final DynamoDbEnhancedClient enhancedClient = + DynamoDbEnhancedClient.builder() + .dynamoDbClient(getDynamoDbClient()) + .extensions(Stream.concat( + ExtensionResolver.defaultExtensions().stream(), + Stream.of(AutoGeneratedTimestampRecordExtension.create())) + .collect(Collectors.toList())) + .build(); + + private final DynamoDbTable mappedTable = + enhancedClient.table(getConcreteTableName("table-name"), TABLE_SCHEMA); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Before + public void createTable() { + mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput())); + } + + @After + public void deleteTable() { + getDynamoDbClient().deleteTable(r -> r.tableName(getConcreteTableName("table-name"))); + } + + @Test + public void updateItemWithFlattenMap_correctlyFlattensMapAttributes() { + + //first update + FlattenMapValidBean record = new FlattenMapValidBean(); + record.setId("111"); + record.setRootAttribute1("rootValue1"); + record.setRootAttribute2("rootValue2"); + record.setAttributesMap(new HashMap() {{ + put("mapAttribute1", "mapValue1"); + put("mapAttribute2", "mapValue2"); + put("mapAttribute3", "mapValue3"); + }}); + + mappedTable.updateItem(record); + + FlattenMapValidBean persistedRecord = mappedTable.getItem(record); + assertThat(persistedRecord.getId()).isEqualTo("111"); + assertThat(persistedRecord.getRootAttribute1()).isEqualTo("rootValue1"); + assertThat(persistedRecord.getRootAttribute2()).isEqualTo("rootValue2"); + assertThat(persistedRecord.getAttributesMap()).hasSize(3); + assertThat(persistedRecord.getAttributesMap()).containsEntry("mapAttribute1", "mapValue1"); + assertThat(persistedRecord.getAttributesMap()).containsEntry("mapAttribute2", "mapValue2"); + assertThat(persistedRecord.getAttributesMap()).containsEntry("mapAttribute3", "mapValue3"); + + + //second update + record = new FlattenMapValidBean(); + record.setId("222"); + record.setRootAttribute2("rootValue2_new"); + record.setAttributesMap(new HashMap() {{ + put("mapAttribute1", "mapValue1_new"); + put("mapAttribute2", "mapValue2_new"); + put("mapAttribute3", "mapValue3"); + }}); + + mappedTable.updateItem(record); + + persistedRecord = mappedTable.getItem(record); + assertThat(persistedRecord.getId()).isEqualTo("222"); + assertThat(persistedRecord.getRootAttribute1()).isNull(); + assertThat(persistedRecord.getRootAttribute2()).isEqualTo("rootValue2_new"); + assertThat(persistedRecord.getAttributesMap()).hasSize(3); + assertThat(persistedRecord.getAttributesMap()).containsEntry("mapAttribute1", "mapValue1_new"); + assertThat(persistedRecord.getAttributesMap()).containsEntry("mapAttribute2", "mapValue2_new"); + assertThat(persistedRecord.getAttributesMap()).containsEntry("mapAttribute3", "mapValue3"); + + + //third update + record = new FlattenMapValidBean(); + record.setId("333"); + record.setRootAttribute1("rootValue1_new"); + record.setRootAttribute2("rootValue2_new"); + record.setAttributesMap(new HashMap() {{ + put("mapAttribute1", "mapValue1_new"); + put("mapAttribute2", "mapValue2_new"); + put("mapAttribute3", "mapValue3_new"); + }}); + + mappedTable.updateItem(record); + + persistedRecord = mappedTable.getItem(record); + assertThat(persistedRecord.getId()).isEqualTo("333"); + assertThat(persistedRecord.getRootAttribute1()).isEqualTo("rootValue1_new"); + assertThat(persistedRecord.getRootAttribute2()).isEqualTo("rootValue2_new"); + assertThat(persistedRecord.getAttributesMap()).hasSize(3); + assertThat(persistedRecord.getAttributesMap()).containsEntry("mapAttribute1", "mapValue1_new"); + assertThat(persistedRecord.getAttributesMap()).containsEntry("mapAttribute2", "mapValue2_new"); + assertThat(persistedRecord.getAttributesMap()).containsEntry("mapAttribute3", "mapValue3_new"); + + + //fourth update + record = new FlattenMapValidBean(); + record.setId("444"); + record.setAttributesMap(new HashMap<>()); + + mappedTable.updateItem(record); + + persistedRecord = mappedTable.getItem(record); + assertThat(persistedRecord.getId()).isEqualTo("444"); + assertThat(persistedRecord.getRootAttribute1()).isNull(); + assertThat(persistedRecord.getRootAttribute2()).isNull(); + assertThat(persistedRecord.getAttributesMap()).isNull(); + } + + @Test + public void updateItemWithFlattenMap_withDuplicateAttributeName_throwsIllegalArgumentException() { + FlattenMapValidBean record = new FlattenMapValidBean(); + record.setId("123"); + record.setRootAttribute1("rootValue1"); + record.setRootAttribute2("rootValue2"); + record.setAttributesMap(new HashMap() {{ + put("mapAttribute1", "mapValue1"); + put("mapAttribute2", "mapValue2"); + put("mapAttribute3", "mapValue3"); + put("id", "newIdValue"); + }}); + + assertThatThrownBy(() -> mappedTable.updateItem(record)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Attempt to add an attribute to a mapper that already has one with the same name. ") + .hasMessageContaining("[Attribute name: id]"); + } + +} \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/InvalidFlattenMapTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/InvalidFlattenMapTest.java new file mode 100644 index 000000000000..1450e348c1b4 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/InvalidFlattenMapTest.java @@ -0,0 +1,36 @@ +/* + * 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.functionaltests; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.flattenmap.FlattenMapInvalidBean; + +public class InvalidFlattenMapTest extends LocalDynamoDbSyncTestBase { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void updateItemWithFlattenMap_withMultipleAnnotatedMaps_throwsIllegalArgumentException() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("More than one @DynamoDbFlattenMap annotation found on the same record"); + + TableSchema.fromClass(FlattenMapInvalidBean.class); + } +} \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/flattenmap/FlattenMapBeanTableSchemaTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/flattenmap/FlattenMapBeanTableSchemaTest.java new file mode 100644 index 000000000000..7d04cc334966 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/flattenmap/FlattenMapBeanTableSchemaTest.java @@ -0,0 +1,176 @@ +/* + * 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.mapper.flattenmap; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; +import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue; + +import java.util.HashMap; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.CompositeRecord; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FlattenRecord; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.NestedRecordWithUpdateBehavior; +import software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.flattenmap.FlattenMapAndFlattenRecordBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.flattenmap.FlattenMapInvalidBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.flattenmap.FlattenMapValidBean; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +@RunWith(MockitoJUnitRunner.class) +public class FlattenMapBeanTableSchemaTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void serializeBean_withFlattenMapAndIgnoreNulls_successfullyFlattensAndOmitNulls() { + FlattenMapValidBean bean = new FlattenMapValidBean(); + bean.setRootAttribute1("rootValue1"); + bean.setRootAttribute2("rootValue2"); + + bean.setAttributesMap(new HashMap() {{ + put("mapAttribute1", "mapValue1"); + put("mapAttribute2", null); + put("mapAttribute3", null); + }}); + + BeanTableSchema beanTableSchema = BeanTableSchema.create(FlattenMapValidBean.class); + Map itemMap = beanTableSchema.itemToMap(bean, true); + + assertThat(itemMap.size(), is(3)); + assertThat(itemMap, hasEntry("rootAttribute1", stringValue("rootValue1"))); + assertThat(itemMap, hasEntry("rootAttribute2", stringValue("rootValue2"))); + assertThat(itemMap, hasEntry("mapAttribute1", stringValue("mapValue1"))); + } + + @Test + public void serializeBean_withFlattenMapAndNotIgnoringNulls_successfullyFlattensAndSetNullAttributes() { + FlattenMapValidBean bean = new FlattenMapValidBean(); + bean.setRootAttribute1("rootValue1"); + bean.setRootAttribute2("rootValue2"); + + bean.setAttributesMap(new HashMap() {{ + put("mapAttribute1", "mapValue1"); + put("mapAttribute2", null); + put("mapAttribute3", null); + }}); + + BeanTableSchema beanTableSchema = BeanTableSchema.create(FlattenMapValidBean.class); + Map itemMap = beanTableSchema.itemToMap(bean, false); + + assertThat(itemMap.size(), is(6)); + assertThat(itemMap, hasEntry("rootAttribute1", stringValue("rootValue1"))); + assertThat(itemMap, hasEntry("rootAttribute2", stringValue("rootValue2"))); + assertThat(itemMap, hasEntry("mapAttribute1", stringValue("mapValue1"))); + assertThat(itemMap, hasEntry("mapAttribute2", AttributeValue.builder().build())); + assertThat(itemMap, hasEntry("mapAttribute3", AttributeValue.builder().build())); + } + + @Test + public void serializeBean_withFlattenMapAndFlattenRecord_successfullyFlattens() { + FlattenMapAndFlattenRecordBean bean = createFlattenMapAndFlattenRecordBean(); + + BeanTableSchema beanTableSchema = BeanTableSchema.create(FlattenMapAndFlattenRecordBean.class); + Map itemMap = beanTableSchema.itemToMap(bean, true); + + assertThat(itemMap.size(), is(6)); + assertThat(itemMap, hasEntry("rootAttribute1", stringValue("rootValue1"))); + assertThat(itemMap, hasEntry("rootAttribute2", stringValue("rootValue2"))); + assertThat(itemMap, hasEntry("mapAttribute1", stringValue("mapValue1"))); + assertThat(itemMap, hasEntry("mapAttribute2", stringValue("mapValue2"))); + assertThat(itemMap, hasEntry("mapAttribute3", stringValue("mapValue3"))); + + AttributeValue resultedNestedRecord = itemMap.get("nestedRecord"); + Assertions.assertThat(resultedNestedRecord).isNotNull(); + } + + @Test + public void serializeBean_withMultipleAnnotatedMaps_throwsIllegalArgumentException() { + FlattenMapInvalidBean bean = new FlattenMapInvalidBean(); + bean.setId("idValue"); + bean.setRootAttribute1("rootValue1"); + bean.setRootAttribute2("rootValue2"); + + bean.setAttributesMap(new HashMap() {{ + put("mapAttribute1", "mapValue1"); + put("mapAttribute2", "mapValue2"); + put("mapAttribute3", "mapValue3"); + }}); + + bean.setSecondaryAttributesMap(new HashMap() {{ + put("secondaryMapAttribute1", "secondaryMapValue1"); + put("secondaryMapAttribute2", "secondaryMapValue2"); + put("secondaryMapAttribute3", "secondaryMapValue3"); + }}); + + exception.expect(IllegalArgumentException.class); + exception.expectMessage("More than one @DynamoDbFlattenMap annotation found on the same record"); + + BeanTableSchema beanTableSchema = BeanTableSchema.create(FlattenMapInvalidBean.class); + beanTableSchema.itemToMap(bean, false); + } + + @Test + public void deserializeBean_withFlattenMap_successfullyCreatesItem() { + BeanTableSchema beanTableSchema = BeanTableSchema.create(FlattenMapValidBean.class); + Map itemMap = new HashMap<>(); + itemMap.put("id", stringValue("123")); + itemMap.put("rootAttribute1", stringValue("rootAttributeValue1")); + itemMap.put("rootAttribute2", stringValue("rootAttributeValue2")); + itemMap.put("mapAttribute1", AttributeValue.builder().s("mapValue1").build()); + itemMap.put("mapAttribute2", AttributeValue.builder().s("mapValue2").build()); + itemMap.put("mapAttribute3", AttributeValue.builder().s("mapValue3").build()); + + FlattenMapValidBean result = beanTableSchema.mapToItem(itemMap); + + assertThat(result.getId(), is("123")); + assertThat(result.getRootAttribute1(), is("rootAttributeValue1")); + assertThat(result.getRootAttribute2(), is("rootAttributeValue2")); + assertThat(result.getAttributesMap().size(), is(3)); + assertThat(itemMap, hasEntry("mapAttribute1", stringValue("mapValue1"))); + assertThat(itemMap, hasEntry("mapAttribute2", stringValue("mapValue2"))); + assertThat(itemMap, hasEntry("mapAttribute3", stringValue("mapValue3"))); + } + + private static FlattenMapAndFlattenRecordBean createFlattenMapAndFlattenRecordBean() { + FlattenMapAndFlattenRecordBean bean = new FlattenMapAndFlattenRecordBean(); + bean.setRootAttribute1("rootValue1"); + bean.setRootAttribute2("rootValue2"); + + FlattenRecord flattenRecord = new FlattenRecord(); + NestedRecordWithUpdateBehavior updateNestedRecord = new NestedRecordWithUpdateBehavior(); + updateNestedRecord.setNestedCounter(100L); + CompositeRecord updateCompositeRecord = new CompositeRecord(); + updateCompositeRecord.setNestedRecord(updateNestedRecord); + flattenRecord.setCompositeRecord(updateCompositeRecord); + bean.setFlattenRecord(flattenRecord); + + bean.setAttributesMap(new HashMap() {{ + put("mapAttribute1", "mapValue1"); + put("mapAttribute2", "mapValue2"); + put("mapAttribute3", "mapValue3"); + }}); + return bean; + } +} diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/flattenmap/FlattenMapAndFlattenRecordBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/flattenmap/FlattenMapAndFlattenRecordBean.java new file mode 100644 index 000000000000..2bcdaacaa21d --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/flattenmap/FlattenMapAndFlattenRecordBean.java @@ -0,0 +1,61 @@ +/* + * 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.mapper.testbeans.flattenmap; + +import java.util.Map; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FlattenRecord; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlattenMap; + +@DynamoDbBean +public class FlattenMapAndFlattenRecordBean { + private String rootAttribute1; + private String rootAttribute2; + private FlattenRecord flattenRecord; + private Map attributesMap; + + public String getRootAttribute1() { + return rootAttribute1; + } + public void setRootAttribute1(String rootAttribute1) { + this.rootAttribute1 = rootAttribute1; + } + + public String getRootAttribute2() { + return rootAttribute2; + } + public void setRootAttribute2(String rootAttribute2) { + this.rootAttribute2 = rootAttribute2; + } + + @DynamoDbFlatten + public FlattenRecord getFlattenRecord() { + return flattenRecord; + } + + public void setFlattenRecord(FlattenRecord flattenRecord) { + this.flattenRecord = flattenRecord; + } + + @DynamoDbFlattenMap + public Map getAttributesMap() { + return attributesMap; + } + public void setAttributesMap(Map abstractMap) { + this.attributesMap = abstractMap; + } +} \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/flattenmap/FlattenMapInvalidBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/flattenmap/FlattenMapInvalidBean.java new file mode 100644 index 000000000000..c36d5791fa29 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/flattenmap/FlattenMapInvalidBean.java @@ -0,0 +1,68 @@ +/* + * 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.mapper.testbeans.flattenmap; + +import java.util.Map; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlattenMap; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; + +@DynamoDbBean +public class FlattenMapInvalidBean { + private String id; + private String rootAttribute1; + private String rootAttribute2; + private Map attributesMap; + private Map secondaryAttributesMap; + + @DynamoDbPartitionKey + public String getId() { + return this.id; + } + public void setId(String id) { + this.id = id; + } + + public String getRootAttribute1() { + return rootAttribute1; + } + public void setRootAttribute1(String rootAttribute1) { + this.rootAttribute1 = rootAttribute1; + } + + public String getRootAttribute2() { + return rootAttribute2; + } + public void setRootAttribute2(String rootAttribute2) { + this.rootAttribute2 = rootAttribute2; + } + + @DynamoDbFlattenMap + public Map getAttributesMap() { + return attributesMap; + } + public void setAttributesMap(Map abstractMap) { + this.attributesMap = abstractMap; + } + + @DynamoDbFlattenMap + public Map getSecondaryAttributesMap() { + return secondaryAttributesMap; + } + public void setSecondaryAttributesMap(Map abstractMap) { + this.secondaryAttributesMap = abstractMap; + } +} \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/flattenmap/FlattenMapValidBean.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/flattenmap/FlattenMapValidBean.java new file mode 100644 index 000000000000..e7d4ac959d5a --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/testbeans/flattenmap/FlattenMapValidBean.java @@ -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.mapper.testbeans.flattenmap; + +import java.util.Map; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItem; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlattenMap; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; + +@DynamoDbBean +public class FlattenMapValidBean { + private String id; + private String rootAttribute1; + private String rootAttribute2; + private Map attributesMap; + + @DynamoDbPartitionKey + public String getId() { + return this.id; + } + public void setId(String id) { + this.id = id; + } + + public String getRootAttribute1() { + return rootAttribute1; + } + public void setRootAttribute1(String rootAttribute1) { + this.rootAttribute1 = rootAttribute1; + } + + public String getRootAttribute2() { + return rootAttribute2; + } + public void setRootAttribute2(String rootAttribute2) { + this.rootAttribute2 = rootAttribute2; + } + + @DynamoDbFlattenMap + public Map getAttributesMap() { + return attributesMap; + } + public void setAttributesMap(Map abstractMap) { + this.attributesMap = abstractMap; + } + + public static FakeItem.Builder builder() { + return new FakeItem.Builder(); + } + + +} \ No newline at end of file