Skip to content

Commit ae4a5c0

Browse files
authored
Document API for DynamoDB usung EnhancedDynamoDB impementation issue #36 (#3849)
* Adding new Interface EnhancedDocument (#3702) * Adding new Interface EnhancedDocument * Fix review comments from Anna-karin and David * Addresed Zoe's comment * DefaultEnhancedDocument implementation (#3718) * DefaultEnhancedDocument implementation * Updated Null check in the conveter itself while iterating Arrays of AttributeValue * handled review comments * Update test cases for JsonAttributeCOnverter * Removed ctor and added a builder * Removed ctor for toBuilder * Implement Static factory methods of EnhancedDocument (#3752) * DocumentTableSchema Implementation (#3758) * DocumentTableSchema Implementation * Handle review comments -1 * Handle review comments 2 * The builder for EnhancedDocument should not rely on the order in which attribute converters are added to it (#3780) * Handled surface api comments of removing Generic access as Objects (#3811) * TableSchema API to create table and functional tests * Surface API Review * Surface API Review - compilation issues * Surface API Review - Review comments * Surface API Review comments from Matt * Compilation issue and toStringMethod for JsonNode * Updated after handling Matt's comments * Functional Test added * Update in test cases * Removed functional tests , will create new PR for this * Review comments handled * Explicutly adding the dependency in th pom.xml * Removed @code from @snippet line in javadoc * Remove extra spaces in Json and make it same as Items as in V1 (#3835) * Remove extra spaces in Json and make it same as Items as in V1 * Moved Json string helper functions to seperate class * Delete unwanted class * Functional Test Cases for Document DDB API and Surface API Review 2 comments (#3843) * Functional Test Cases for Document DDB API and Surface API Review 2 comments * Removed extra newlines from test cases * Handled Review comments * Removed primitive boolean getter and replaced with Boolean getter * Spotbug issue fixed and using StringUtils * StringUtils corrected the right package * Sonar quebe test bug fixed * Handled PR comments, \n 1. Moved DocumentTableSchema from mapper to document package * Removed @inherit wherever not required
1 parent fb926a8 commit ae4a5c0

32 files changed

+7245
-6
lines changed

docs/design/services/dynamodb/high-level-library/DocumentAPI.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ providers.
4444

4545
// New API in TableSchema to create a DocumentTableSchema
4646
DocumentTableSchema documentTableSchema =
47-
TableSchema.fromDocumentSchemaBuilder()
47+
TableSchema.documentSchemaBuilder()
4848
.addIndexPartitionKey(primaryIndexName(), "sample_hash_name", AttributeValueType.S)
4949
.addIndexSortKey("gsi_index", "sample_sort_name", AttributeValueType.N)
5050
.addAttributeConverterProviders(cutomAttributeConverters)
@@ -78,7 +78,7 @@ EnhancedDocument documentTableItem = documentTable.getItem(
7878
Number sampleSortvalue = documentTableItem.get("sample_sort_name", EnhancedType.of(Number.class));
7979

8080
// Accessing an attribute from document using specific getters.
81-
sampleSortvalue = documentTableItem.getSdkNumber("sample_sort_name");
81+
sampleSortvalue = documentTableItem.getNumber("sample_sort_name");
8282

8383
// Accessing an attribute of custom class using custom converters.
8484
CustomClass customClass = documentTableItem.get("custom_nested_map", new CustomAttributeConverter()));

services-custom/dynamodb-enhanced/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@
106106
<artifactId>aws-core</artifactId>
107107
<version>${awsjavasdk.version}</version>
108108
</dependency>
109+
<dependency>
110+
<groupId>software.amazon.awssdk</groupId>
111+
<artifactId>json-utils</artifactId>
112+
<version>${awsjavasdk.version}</version>
113+
</dependency>
109114
<dependency>
110115
<groupId>software.amazon.awssdk</groupId>
111116
<artifactId>http-client-spi</artifactId>

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.OptionalLongAttributeConverter;
6262
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.PeriodAttributeConverter;
6363
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.SdkBytesAttributeConverter;
64+
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.SdkNumberAttributeConverter;
6465
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.SetAttributeConverter;
6566
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.ShortAttributeConverter;
6667
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter;
@@ -88,6 +89,8 @@
8889
@ThreadSafe
8990
@Immutable
9091
public final class DefaultAttributeConverterProvider implements AttributeConverterProvider {
92+
private static final DefaultAttributeConverterProvider INSTANCE = getDefaultBuilder().build();
93+
9194
private static final Logger log = Logger.loggerFor(DefaultAttributeConverterProvider.class);
9295

9396
private final ConcurrentHashMap<EnhancedType<?>, AttributeConverter<?>> converterCache =
@@ -117,10 +120,9 @@ public DefaultAttributeConverterProvider() {
117120
* Returns an attribute converter provider with all default converters set.
118121
*/
119122
public static DefaultAttributeConverterProvider create() {
120-
return getDefaultBuilder().build();
123+
return INSTANCE;
121124
}
122125

123-
124126
/**
125127
* Equivalent to {@code builder(EnhancedType.of(Object.class))}.
126128
*/
@@ -246,7 +248,8 @@ private static Builder getDefaultBuilder() {
246248
.addConverter(UuidAttributeConverter.create())
247249
.addConverter(ZonedDateTimeAsStringAttributeConverter.create())
248250
.addConverter(ZoneIdAttributeConverter.create())
249-
.addConverter(ZoneOffsetAttributeConverter.create());
251+
.addConverter(ZoneOffsetAttributeConverter.create())
252+
.addConverter(SdkNumberAttributeConverter.create());
250253
}
251254

252255
/**

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

+12
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.Map;
2121
import software.amazon.awssdk.annotations.SdkPublicApi;
2222
import software.amazon.awssdk.annotations.ThreadSafe;
23+
import software.amazon.awssdk.enhanced.dynamodb.document.DocumentTableSchema;
24+
import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
2325
import software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema;
2426
import software.amazon.awssdk.enhanced.dynamodb.mapper.ImmutableTableSchema;
2527
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema;
@@ -84,6 +86,16 @@ static <T> BeanTableSchema<T> fromBean(Class<T> beanClass) {
8486
return BeanTableSchema.create(beanClass);
8587
}
8688

89+
/**
90+
* Provides interfaces to interact with DynamoDB tables as {@link EnhancedDocument} where the complete Schema of the table is
91+
* not required.
92+
*
93+
* @return A {@link DocumentTableSchema.Builder} for instantiating DocumentTableSchema.
94+
*/
95+
static DocumentTableSchema.Builder documentSchemaBuilder() {
96+
return DocumentTableSchema.builder();
97+
}
98+
8799
/**
88100
* Scans an immutable class that has been annotated with DynamoDb immutable annotations and then returns a
89101
* {@link ImmutableTableSchema} implementation of this interface that can map records to and from items of that
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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.document;
17+
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.Collection;
21+
import java.util.Collections;
22+
import java.util.LinkedHashSet;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Set;
26+
import java.util.stream.Collectors;
27+
import software.amazon.awssdk.annotations.NotThreadSafe;
28+
import software.amazon.awssdk.annotations.SdkPublicApi;
29+
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
30+
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
31+
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
32+
import software.amazon.awssdk.enhanced.dynamodb.DefaultAttributeConverterProvider;
33+
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
34+
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
35+
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
36+
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
37+
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.ConverterProviderResolver;
38+
import software.amazon.awssdk.enhanced.dynamodb.internal.document.DefaultEnhancedDocument;
39+
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema;
40+
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
41+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
42+
43+
44+
/**
45+
* Implementation of {@link TableSchema} that builds a table schema based on DynamoDB Items.
46+
* <p>
47+
* In Amazon DynamoDB, an item is a collection of attributes. Each attribute has a name and a value. An attribute value can be a
48+
* scalar, a set, or a document type
49+
* <p>
50+
* A DocumentTableSchema is used to create a {@link DynamoDbTable} which provides read and writes access to DynamoDB table as
51+
* {@link EnhancedDocument}.
52+
* <p> DocumentTableSchema specifying primaryKey, sortKey and a customAttributeConverter can be created as below
53+
* {@snippet :
54+
* DocumentTableSchema documentTableSchema = DocumentTableSchema.builder()
55+
* .primaryKey("sampleHashKey", AttributeValueType.S)
56+
* .sortKey("sampleSortKey", AttributeValueType.S)
57+
* .attributeConverterProviders(customAttributeConverter, AttributeConverterProvider.defaultProvider())
58+
* .build();
59+
*}
60+
* <p> DocumentTableSchema can also be created without specifying primaryKey and sortKey in which cases the
61+
* {@link TableMetadata} of DocumentTableSchema will error if we try to access attributes from metaData. Also if
62+
* attributeConverterProviders are not provided then {@link DefaultAttributeConverterProvider} will be used
63+
* {@snippet :
64+
* DocumentTableSchema documentTableSchema = DocumentTableSchema.builder().build();
65+
*}
66+
*
67+
* @see <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html" target="_top">Working
68+
* with items and attributes</a>
69+
*/
70+
@SdkPublicApi
71+
public final class DocumentTableSchema implements TableSchema<EnhancedDocument> {
72+
private final TableMetadata tableMetadata;
73+
private final List<AttributeConverterProvider> attributeConverterProviders;
74+
75+
private DocumentTableSchema(Builder builder) {
76+
this.attributeConverterProviders = builder.attributeConverterProviders;
77+
this.tableMetadata = builder.staticTableMetaDataBuilder.build();
78+
}
79+
80+
public static Builder builder() {
81+
return new Builder();
82+
}
83+
84+
@Override
85+
public EnhancedDocument mapToItem(Map<String, AttributeValue> attributeMap) {
86+
if (attributeMap == null) {
87+
return null;
88+
}
89+
DefaultEnhancedDocument.DefaultBuilder builder =
90+
(DefaultEnhancedDocument.DefaultBuilder) DefaultEnhancedDocument.builder();
91+
attributeMap.forEach(builder::putObject);
92+
return builder.attributeConverterProviders(attributeConverterProviders)
93+
.build();
94+
}
95+
96+
/**
97+
* {@inheritDoc}
98+
*
99+
* This flag does not have significance for the Document API, unlike Java objects where the default value of an undefined
100+
* Object is null.In contrast to mapped classes, where a schema is present, the DocumentSchema is unaware of the entire
101+
* schema.Therefore, if an attribute is not present, it signifies that it is null, and there is no need to handle it in a
102+
* separate way.However, if the user explicitly wants to nullify certain attributes, then the user needs to set those
103+
* attributes as null in the Document that needs to be updated.
104+
*
105+
*/
106+
@Override
107+
public Map<String, AttributeValue> itemToMap(EnhancedDocument item, boolean ignoreNulls) {
108+
if (item == null) {
109+
return null;
110+
}
111+
List<AttributeConverterProvider> providers = mergeAttributeConverterProviders(item);
112+
return item.toBuilder().attributeConverterProviders(providers).build().toMap();
113+
}
114+
115+
private List<AttributeConverterProvider> mergeAttributeConverterProviders(EnhancedDocument item) {
116+
if (item.attributeConverterProviders() != null && !item.attributeConverterProviders().isEmpty()) {
117+
Set<AttributeConverterProvider> providers = new LinkedHashSet<>();
118+
providers.addAll(item.attributeConverterProviders());
119+
providers.addAll(attributeConverterProviders);
120+
return providers.stream().collect(Collectors.toList());
121+
}
122+
return attributeConverterProviders;
123+
}
124+
125+
@Override
126+
public Map<String, AttributeValue> itemToMap(EnhancedDocument item, Collection<String> attributes) {
127+
if (item.toMap() == null) {
128+
return null;
129+
}
130+
131+
List<AttributeConverterProvider> providers = mergeAttributeConverterProviders(item);
132+
return item.toBuilder().attributeConverterProviders(providers).build().toMap().entrySet()
133+
.stream()
134+
.filter(entry -> attributes.contains(entry.getKey()))
135+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
136+
}
137+
138+
@Override
139+
public AttributeValue attributeValue(EnhancedDocument item, String attributeName) {
140+
if (item == null || item.toMap() == null) {
141+
return null;
142+
}
143+
List<AttributeConverterProvider> providers = mergeAttributeConverterProviders(item);
144+
return item.toBuilder()
145+
.attributeConverterProviders(providers)
146+
.build()
147+
.toMap()
148+
.get(attributeName);
149+
}
150+
151+
@Override
152+
public TableMetadata tableMetadata() {
153+
return tableMetadata;
154+
}
155+
156+
@Override
157+
public EnhancedType<EnhancedDocument> itemType() {
158+
return EnhancedType.of(EnhancedDocument.class);
159+
}
160+
161+
@Override
162+
public List<String> attributeNames() {
163+
return tableMetadata.keyAttributes().stream().map(key -> key.name()).collect(Collectors.toList());
164+
}
165+
166+
@Override
167+
public boolean isAbstract() {
168+
return false;
169+
}
170+
171+
@NotThreadSafe
172+
public static final class Builder {
173+
174+
private final StaticTableMetadata.Builder staticTableMetaDataBuilder = StaticTableMetadata.builder();
175+
176+
/**
177+
* By Default the defaultConverterProvider is used for converting AttributeValue to primitive types.
178+
*/
179+
private List<AttributeConverterProvider> attributeConverterProviders =
180+
Collections.singletonList(ConverterProviderResolver.defaultConverterProvider());
181+
182+
/**
183+
* Adds information about a partition key associated with a specific index.
184+
*
185+
* @param indexName the name of the index to associate the partition key with
186+
* @param attributeName the name of the attribute that represents the partition key
187+
* @param attributeValueType the {@link AttributeValueType} of the partition key
188+
* @throws IllegalArgumentException if a partition key has already been defined for this index
189+
*/
190+
public Builder addIndexPartitionKey(String indexName, String attributeName, AttributeValueType attributeValueType) {
191+
staticTableMetaDataBuilder.addIndexPartitionKey(indexName, attributeName, attributeValueType);
192+
return this;
193+
}
194+
195+
/**
196+
* Adds information about a sort key associated with a specific index.
197+
*
198+
* @param indexName the name of the index to associate the sort key with
199+
* @param attributeName the name of the attribute that represents the sort key
200+
* @param attributeValueType the {@link AttributeValueType} of the sort key
201+
* @throws IllegalArgumentException if a sort key has already been defined for this index
202+
*/
203+
public Builder addIndexSortKey(String indexName, String attributeName, AttributeValueType attributeValueType) {
204+
staticTableMetaDataBuilder.addIndexSortKey(indexName, attributeName, attributeValueType);
205+
return this;
206+
}
207+
208+
/**
209+
* Specifies the {@link AttributeConverterProvider}s to use with the table schema. The list of attribute converter
210+
* providers must provide {@link AttributeConverter}s for Custom types. The attribute converter providers will be loaded
211+
* in the strict order they are supplied here.
212+
* <p>
213+
* By default, {@link DefaultAttributeConverterProvider} will be used, and it will provide standard converters for most
214+
* primitive and common Java types. Configuring this will override the default behavior, so it is recommended to always
215+
* append `DefaultAttributeConverterProvider` when you configure the custom attribute converter providers.
216+
* <p>
217+
* {@snippet :
218+
* builder.attributeConverterProviders(customAttributeConverter, AttributeConverterProvider.defaultProvider());
219+
*}
220+
*
221+
* @param attributeConverterProviders a list of attribute converter providers to use with the table schema
222+
*/
223+
public Builder attributeConverterProviders(AttributeConverterProvider... attributeConverterProviders) {
224+
this.attributeConverterProviders = Arrays.asList(attributeConverterProviders);
225+
return this;
226+
}
227+
228+
/**
229+
* Specifies the {@link AttributeConverterProvider}s to use with the table schema. The list of attribute converter
230+
* providers must provide {@link AttributeConverter}s for all types used in the schema. The attribute converter providers
231+
* will be loaded in the strict order they are supplied here.
232+
* <p>
233+
* By default, {@link DefaultAttributeConverterProvider} will be used, and it will provide standard converters for most
234+
* primitive and common Java types. Configuring this will override the default behavior, so it is recommended to always
235+
* append `DefaultAttributeConverterProvider` when you configure the custom attribute converter providers.
236+
* <p>
237+
* {@snippet :
238+
* List<AttributeConverterProvider> providers = new ArrayList<>( customAttributeConverter,
239+
* AttributeConverterProvider.defaultProvider());
240+
* builder.attributeConverterProviders(providers);
241+
*}
242+
*
243+
* @param attributeConverterProviders a list of attribute converter providers to use with the table schema
244+
*/
245+
public Builder attributeConverterProviders(List<AttributeConverterProvider> attributeConverterProviders) {
246+
this.attributeConverterProviders = new ArrayList<>(attributeConverterProviders);
247+
return this;
248+
}
249+
250+
/**
251+
* Builds a {@link StaticImmutableTableSchema} based on the values this builder has been configured with
252+
*/
253+
public DocumentTableSchema build() {
254+
return new DocumentTableSchema(this);
255+
}
256+
}
257+
}

0 commit comments

Comments
 (0)