Skip to content

Commit 01436d3

Browse files
authored
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
1 parent 92ca159 commit 01436d3

File tree

10 files changed

+1670
-9
lines changed

10 files changed

+1670
-9
lines changed

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
2626
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
2727
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
28-
import software.amazon.awssdk.enhanced.dynamodb.KeyAttributeMetadata;
2928

3029
/**
3130
* Interface representing Document API for DynamoDB. Document API operations are used to carry open content i.e. data with no
@@ -145,8 +144,6 @@ static Builder builder() {
145144
* @param attributeName Name of the attribute.
146145
* @return the value of the specified attribute in the current document as SdkBytes; or null if the attribute either
147146
* doesn't exist or the attribute value is null.
148-
* @throws UnsupportedOperationException If the attribute value involves a byte buffer which is not backed by an accessible
149-
* array
150147
*/
151148
SdkBytes getSdkBytes(String attributeName);
152149

@@ -189,6 +186,15 @@ static Builder builder() {
189186

190187
<T> List<T> getList(String attributeName, EnhancedType<T> type);
191188

189+
190+
/**
191+
* Gets the List of values for the given attribute in the current document.
192+
* @param attributeName Name of the attribute.
193+
* @return value of the specified attribute in the current document as a list; or null if the
194+
* attribute either doesn't exist or the attribute value is null.
195+
*/
196+
List<?> getList(String attributeName);
197+
192198
/**
193199
* Gets the Map with Key as String and values as type T for the given attribute in the current document.
194200
* <p>Note that any numeric type of map is always canonicalized into {@link SdkNumber}, and therefore if <code>T</code>
@@ -261,13 +267,16 @@ <T extends Number> Map<String, T> getMapOfNumbers(String attributeName,
261267
* @return value of the specified attribute in the current document as a JSON string with pretty indentation; or null if the
262268
* attribute either doesn't exist or the attribute value is null.
263269
*/
264-
String getJSONPretty(String attributeName);
270+
String getJsonPretty(String attributeName);
265271

266272
/**
267273
* Gets the {@link Boolean} value for the specified attribute.
268274
*
269275
* @param attributeName Name of the attribute.
270276
* @return value of the specified attribute in the current document as a non-null Boolean.
277+
* @throws RuntimeException
278+
* if either the attribute doesn't exist or if the attribute
279+
* value cannot be converted into a boolean value.
271280
*/
272281
Boolean getBoolean(String attributeName);
273282

@@ -440,13 +449,12 @@ interface Builder {
440449
Builder addJson(String attributeName, String json);
441450

442451
/**
443-
* Convenience builder methods that sets an attribute of this document for the specified key attribute name and value.
444-
*
445-
* @param keyAttrName Name of the attribute that needs to be added in the Document.
446-
* @param keyAttrValue The value that needs to be set.
452+
* Appends an attribute of name attributeName with specified value of the given EnhancedDocument.
453+
* @param attributeName Name of the attribute that needs to be added in the Document.
454+
* @param enhancedDocument that needs to be added as a value to a key attribute.
447455
* @return Builder instance to construct a {@link EnhancedDocument}
448456
*/
449-
Builder keyComponent(KeyAttributeMetadata keyAttrName, Object keyAttrValue);
457+
Builder addEnhancedDocument(String attributeName, EnhancedDocument enhancedDocument);
450458

451459
/**
452460
* Appends collection of attributeConverterProvider to the document builder. These

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Arrays;
2020
import java.util.Collections;
2121
import java.util.List;
22+
import java.util.Objects;
2223
import software.amazon.awssdk.annotations.SdkInternalApi;
2324
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
2425
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
@@ -67,4 +68,22 @@ public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
6768
.map(p -> p.converterFor(enhancedType))
6869
.findFirst().orElse(null);
6970
}
71+
72+
@Override
73+
public boolean equals(Object o) {
74+
if (this == o) {
75+
return true;
76+
}
77+
if (o == null || getClass() != o.getClass()) {
78+
return false;
79+
}
80+
ChainConverterProvider that = (ChainConverterProvider) o;
81+
return Objects.equals(providerChain, that.providerChain);
82+
}
83+
84+
@Override
85+
public int hashCode() {
86+
return providerChain != null ? providerChain.hashCode() : 0;
87+
}
88+
7089
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute;
17+
18+
import java.util.LinkedHashMap;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.stream.Collectors;
22+
import software.amazon.awssdk.annotations.Immutable;
23+
import software.amazon.awssdk.annotations.SdkInternalApi;
24+
import software.amazon.awssdk.annotations.ThreadSafe;
25+
import software.amazon.awssdk.core.SdkBytes;
26+
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
27+
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
28+
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
29+
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.TypeConvertingVisitor;
30+
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
31+
import software.amazon.awssdk.protocols.jsoncore.internal.ArrayJsonNode;
32+
import software.amazon.awssdk.protocols.jsoncore.internal.BooleanJsonNode;
33+
import software.amazon.awssdk.protocols.jsoncore.internal.NullJsonNode;
34+
import software.amazon.awssdk.protocols.jsoncore.internal.NumberJsonNode;
35+
import software.amazon.awssdk.protocols.jsoncore.internal.ObjectJsonNode;
36+
import software.amazon.awssdk.protocols.jsoncore.internal.StringJsonNode;
37+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
38+
39+
/**
40+
* An Internal converter between JsonNode and {@link AttributeValue}.
41+
*
42+
* <p>
43+
* This converts the Attribute Value read from the DDB to JsonNode.
44+
*/
45+
@SdkInternalApi
46+
@ThreadSafe
47+
@Immutable
48+
public final class JsonItemAttributeConverter implements AttributeConverter<JsonNode> {
49+
private static final Visitor VISITOR = new Visitor();
50+
51+
private JsonItemAttributeConverter() {
52+
}
53+
54+
public static JsonItemAttributeConverter create() {
55+
return new JsonItemAttributeConverter();
56+
}
57+
58+
@Override
59+
public EnhancedType<JsonNode> type() {
60+
return EnhancedType.of(JsonNode.class);
61+
}
62+
63+
@Override
64+
public AttributeValueType attributeValueType() {
65+
return AttributeValueType.M;
66+
}
67+
68+
@Override
69+
public AttributeValue transformFrom(JsonNode input) {
70+
JsonNodeToAttributeValueMapConverter attributeValueMapConverter = JsonNodeToAttributeValueMapConverter.instance();
71+
return input.visit(attributeValueMapConverter);
72+
}
73+
74+
@Override
75+
public JsonNode transformTo(AttributeValue input) {
76+
return EnhancedAttributeValue.fromAttributeValue(input).convert(VISITOR);
77+
}
78+
79+
private static final class Visitor extends TypeConvertingVisitor<JsonNode> {
80+
private Visitor() {
81+
super(JsonNode.class, JsonItemAttributeConverter.class);
82+
}
83+
84+
@Override
85+
public JsonNode convertMap(Map<String, AttributeValue> value) {
86+
if (value == null) {
87+
return null;
88+
}
89+
Map<String, JsonNode> jsonNodeMap = new LinkedHashMap<>();
90+
value.entrySet().forEach(
91+
k -> {
92+
JsonNode jsonNode = this.convert(EnhancedAttributeValue.fromAttributeValue(k.getValue()));
93+
jsonNodeMap.put(k.getKey(), jsonNode == null ? NullJsonNode.instance() : jsonNode);
94+
});
95+
return new ObjectJsonNode(jsonNodeMap);
96+
}
97+
98+
@Override
99+
public JsonNode convertString(String value) {
100+
if (value == null) {
101+
return null;
102+
}
103+
return new StringJsonNode(value);
104+
}
105+
106+
@Override
107+
public JsonNode convertNumber(String value) {
108+
if (value == null) {
109+
return null;
110+
}
111+
return new NumberJsonNode(value);
112+
}
113+
114+
@Override
115+
public JsonNode convertBytes(SdkBytes value) {
116+
if (value == null) {
117+
return null;
118+
}
119+
return new StringJsonNode(value.asUtf8String());
120+
}
121+
122+
@Override
123+
public JsonNode convertBoolean(Boolean value) {
124+
if (value == null) {
125+
return null;
126+
}
127+
return new BooleanJsonNode(value);
128+
}
129+
130+
@Override
131+
public JsonNode convertSetOfStrings(List<String> value) {
132+
if (value == null) {
133+
return null;
134+
}
135+
return new ArrayJsonNode(value.stream().map(s -> new StringJsonNode(s)).collect(Collectors.toList()));
136+
}
137+
138+
@Override
139+
public JsonNode convertSetOfNumbers(List<String> value) {
140+
if (value == null) {
141+
return null;
142+
}
143+
return new ArrayJsonNode(value.stream().map(s -> new NumberJsonNode(s)).collect(Collectors.toList()));
144+
}
145+
146+
@Override
147+
public JsonNode convertSetOfBytes(List<SdkBytes> value) {
148+
if (value == null) {
149+
return null;
150+
}
151+
return new ArrayJsonNode(value.stream().map(sdkByte ->
152+
new StringJsonNode(sdkByte.asUtf8String())
153+
).collect(Collectors.toList()));
154+
}
155+
156+
@Override
157+
public JsonNode convertListOfAttributeValues(List<AttributeValue> value) {
158+
if (value == null) {
159+
return null;
160+
}
161+
return new ArrayJsonNode(value.stream().map(
162+
attributeValue -> {
163+
EnhancedAttributeValue enhancedAttributeValue = EnhancedAttributeValue.fromAttributeValue(attributeValue);
164+
return enhancedAttributeValue.isNull() ? NullJsonNode.instance() : enhancedAttributeValue.convert(VISITOR);
165+
}).collect(Collectors.toList()));
166+
}
167+
}
168+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute;
17+
18+
import java.util.LinkedHashMap;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.stream.Collectors;
22+
import software.amazon.awssdk.annotations.SdkInternalApi;
23+
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
24+
import software.amazon.awssdk.protocols.jsoncore.JsonNodeVisitor;
25+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
26+
27+
@SdkInternalApi
28+
public class JsonNodeToAttributeValueMapConverter implements JsonNodeVisitor<AttributeValue> {
29+
30+
private static final JsonNodeToAttributeValueMapConverter INSTANCE = new JsonNodeToAttributeValueMapConverter();
31+
32+
private JsonNodeToAttributeValueMapConverter() {
33+
}
34+
35+
public static JsonNodeToAttributeValueMapConverter instance() {
36+
return INSTANCE;
37+
}
38+
39+
@Override
40+
public AttributeValue visitNull() {
41+
return AttributeValue.builder().build();
42+
}
43+
44+
@Override
45+
public AttributeValue visitBoolean(boolean bool) {
46+
return AttributeValue.builder().bool(bool).build();
47+
}
48+
49+
@Override
50+
public AttributeValue visitNumber(String number) {
51+
return AttributeValue.builder().n(number).build();
52+
}
53+
54+
@Override
55+
public AttributeValue visitString(String string) {
56+
return AttributeValue.builder().s(string).build();
57+
}
58+
59+
@Override
60+
public AttributeValue visitArray(List<JsonNode> array) {
61+
return AttributeValue.builder().l(array.stream()
62+
.map(node -> node.visit(this))
63+
.collect(Collectors.toList()))
64+
.build();
65+
}
66+
67+
@Override
68+
public AttributeValue visitObject(Map<String, JsonNode> object) {
69+
return AttributeValue.builder().m(object.entrySet().stream()
70+
.collect(Collectors.toMap(
71+
entry -> entry.getKey(),
72+
entry -> entry.getValue().visit(this),
73+
(left, right) -> left, LinkedHashMap::new))).build();
74+
}
75+
76+
@Override
77+
public AttributeValue visitEmbeddedObject(Object embeddedObject) {
78+
throw new UnsupportedOperationException("Embedded objects are not supported within Document types.");
79+
}
80+
}

0 commit comments

Comments
 (0)