Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
@SdkPublicApi
public interface EnhancedDocument {

DefaultAttributeConverterProvider defaultProvider = DefaultAttributeConverterProvider.create();
DefaultAttributeConverterProvider DEFAULT_ATTRIBUTE_CONVERTER_PROVIDER = DefaultAttributeConverterProvider.create();
Comment thread
joviegas marked this conversation as resolved.
Outdated


/**
Expand All @@ -54,7 +54,7 @@ static EnhancedDocument fromJson(String json) {
}
return DefaultEnhancedDocument.builder()
.json(json)
.addAttributeConverterProvider(defaultProvider)
.addAttributeConverterProvider(DEFAULT_ATTRIBUTE_CONVERTER_PROVIDER)
.build();
}

Expand All @@ -70,7 +70,7 @@ static EnhancedDocument fromMap(Map<String, Object> attributes) {
}
DefaultEnhancedDocument.DefaultBuilder defaultBuilder = DefaultEnhancedDocument.builder();
attributes.entrySet().forEach(key -> defaultBuilder.add(key.getKey(), key.getValue()));
return defaultBuilder.addAttributeConverterProvider(defaultProvider)
return defaultBuilder.addAttributeConverterProvider(DEFAULT_ATTRIBUTE_CONVERTER_PROVIDER)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.Validate;

/**
* Default implementation of {@link EnhancedDocument}. This class is used by SDK to create Enhanced Documents.
Expand Down Expand Up @@ -365,86 +367,117 @@ private ChainConverterProvider providerFromBuildAndAppendDefault() {

@Override
public Builder addString(String attributeName, String value) {
attributeValueMap.put(attributeName, AttributeValue.fromS(value));
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, value)) {
attributeValueMap.put(attributeName, AttributeValue.fromS(value));
}
return this;
}

@Override
public Builder addNumber(String attributeName, Number value) {
attributeValueMap.put(attributeName, AttributeValue.fromN(value != null ? String.valueOf(value) : null));
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, value)) {
attributeValueMap.put(attributeName, AttributeValue.fromN(String.valueOf(value)));
}
return this;
}

@Override
public Builder addSdkBytes(String attributeName, SdkBytes value) {
attributeValueMap.put(attributeName, AttributeValue.fromB(value));
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, value)) {
attributeValueMap.put(attributeName, AttributeValue.fromB(value));
}
return this;
}

@Override
public Builder addBoolean(String attributeName, boolean value) {
attributeValueMap.put(attributeName, AttributeValue.fromBool(value));
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, value)) {
attributeValueMap.put(attributeName, AttributeValue.fromBool(value));
}
return this;
}

@Override
public Builder addNull(String attributeName) {
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
attributeValueMap.put(attributeName, NULL_ATTRIBUTE_VALUE);
return this;
}

@Override
public Builder addStringSet(String attributeName, Set<String> values) {
attributeValueMap.put(attributeName, AttributeValue.fromSs(values.stream().collect(Collectors.toList())));
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, values)) {
attributeValueMap.put(attributeName, AttributeValue.fromSs(values.stream().collect(Collectors.toList())));
}
return this;
}

@Override
public Builder addNumberSet(String attributeName, Set<Number> values) {
List<String> collect = values.stream().map(value -> value.toString()).collect(Collectors.toList());
attributeValueMap.put(attributeName, AttributeValue.fromNs(collect));
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, values)) {
List<String> collect = values.stream().map(value -> value.toString()).collect(Collectors.toList());
attributeValueMap.put(attributeName, AttributeValue.fromNs(collect));

}
return this;
}

@Override
public Builder addSdkBytesSet(String attributeName, Set<SdkBytes> values) {
attributeValueMap.put(attributeName, AttributeValue.fromBs(values.stream().collect(Collectors.toList())));
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, values)) {
attributeValueMap.put(attributeName, AttributeValue.fromBs(values.stream().collect(Collectors.toList())));
}
return this;
}

@Override
public Builder addList(String attributeName, List<?> value) {
attributeValueMap.put(attributeName, convert(value, providerFromBuildAndAppendDefault()));
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, value)) {
attributeValueMap.put(attributeName, convert(value, providerFromBuildAndAppendDefault()));
}
return this;
}

@Override
public Builder addMap(String attributeName, Map<String, ?> value) {
attributeValueMap.put(attributeName, convert(value, providerFromBuildAndAppendDefault()));
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, value)) {
attributeValueMap.put(attributeName, convert(value, providerFromBuildAndAppendDefault()));
}
return this;
}

@Override
public Builder addJson(String attributeName, String json) {
JsonItemAttributeConverter jsonItemAttributeConverter = JsonItemAttributeConverter.create();
JsonNodeParser build = JsonNodeParser.builder().build();
JsonNode jsonNode = build.parse(json);
AttributeValue attributeValue = jsonItemAttributeConverter.transformFrom(jsonNode);
attributeValueMap.put(attributeName, attributeValue);
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, json)) {
JsonItemAttributeConverter jsonItemAttributeConverter = JsonItemAttributeConverter.create();
JsonNodeParser build = JsonNodeParser.builder().build();
JsonNode jsonNode = build.parse(json);
AttributeValue attributeValue = jsonItemAttributeConverter.transformFrom(jsonNode);
attributeValueMap.put(attributeName, attributeValue);
}
return this;
}

@Override
public Builder addEnhancedDocument(String attributeName, EnhancedDocument enhancedDocument) {
if (enhancedDocument == null) {
attributeValueMap.put(attributeName, NULL_ATTRIBUTE_VALUE);
return this;
Validate.isTrue(!StringUtils.isEmpty(attributeName), "attributeName cannot empty or null");
if (!isNullValueAdded(attributeName, enhancedDocument)) {
DefaultEnhancedDocument defaultEnhancedDocument =
enhancedDocument instanceof DefaultEnhancedDocument
Comment thread
joviegas marked this conversation as resolved.
Outdated
? (DefaultEnhancedDocument) enhancedDocument
: (DefaultEnhancedDocument) enhancedDocument.toBuilder().json(enhancedDocument.toJson()).build();
attributeValueMap.put(attributeName, AttributeValue.fromM(defaultEnhancedDocument.attributeValueMap));
}
DefaultEnhancedDocument defaultEnhancedDocument =
enhancedDocument instanceof DefaultEnhancedDocument
? (DefaultEnhancedDocument) enhancedDocument
: (DefaultEnhancedDocument) enhancedDocument.toBuilder().json(enhancedDocument.toJson()).build();
attributeValueMap.put(attributeName, AttributeValue.fromM(defaultEnhancedDocument.attributeValueMap));
return this;
}

Expand Down Expand Up @@ -492,6 +525,14 @@ public DefaultBuilder attributeValueMap(Map<String, AttributeValue> attributeVal
this.attributeValueMap = attributeValueMap != null ? new LinkedHashMap<>(attributeValueMap) : null;
return this;
}

private boolean isNullValueAdded(String attributeName, Object value) {
if (value == null) {
addNull(attributeName);
return true;
}
return false;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* 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;

import static software.amazon.awssdk.enhanced.dynamodb.TableMetadata.primaryIndexName;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import software.amazon.awssdk.annotations.NotThreadSafe;
import software.amazon.awssdk.annotations.SdkPublicApi;
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.DefaultAttributeConverterProvider;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.ConverterProviderResolver;
import software.amazon.awssdk.enhanced.dynamodb.internal.document.DefaultEnhancedDocument;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;


/**
* Implementation of {@link TableSchema} that builds a table schema based on DynamoDB Items. This class always maps the DynamoDB
* items as {@link EnhancedDocument}
Comment thread
joviegas marked this conversation as resolved.
Outdated
*/
@SdkPublicApi
public class DocumentTableSchema implements TableSchema<EnhancedDocument> {
Comment thread
joviegas marked this conversation as resolved.
Outdated

private final TableMetadata tableMetadata;
private final List<AttributeConverterProvider> attributeConverterProviders;

public DocumentTableSchema(Builder builder) {
Comment thread
joviegas marked this conversation as resolved.
Outdated
this.attributeConverterProviders = builder.attributeConverterProviders;
this.tableMetadata = builder.staticTableMetaDataBuilder.build();
}

public static Builder builder() {
return new Builder();
}

@Override
public EnhancedDocument mapToItem(Map<String, AttributeValue> attributeMap) {
if (attributeMap == null) {
return null;
}
DefaultEnhancedDocument.DefaultBuilder builder = DefaultEnhancedDocument.builder();
return builder.attributeValueMap(attributeMap)
.attributeConverterProviders(attributeConverterProviders)
.build();
}

@Override
public Map<String, AttributeValue> itemToMap(EnhancedDocument item, boolean ignoreNulls) {
Comment thread
joviegas marked this conversation as resolved.
if (item instanceof DefaultEnhancedDocument) {
Map<String, AttributeValue> attributeValueMap = ((DefaultEnhancedDocument) item).getAttributeValueMap();
Comment thread
joviegas marked this conversation as resolved.
Outdated
return attributeValueMap;
}
throw new IllegalArgumentException("EnhancedDocument item is not instance of DefaultEnhancedDocument");
Comment thread
joviegas marked this conversation as resolved.
Outdated
}

@Override
public Map<String, AttributeValue> itemToMap(EnhancedDocument item, Collection<String> attributes) {
if (item instanceof DefaultEnhancedDocument) {
Map<String, AttributeValue> result = new HashMap<>();
attributes.forEach(attribute ->
result.put(attribute, ((DefaultEnhancedDocument) item).getAttributeValueMap().get(attribute)));
return result;
}
throw new IllegalArgumentException("EnhancedDocument item is not instance of DefaultEnhancedDocument");
}

@Override
public AttributeValue attributeValue(EnhancedDocument item, String attributeName) {
if (item instanceof DefaultEnhancedDocument) {
return ((DefaultEnhancedDocument) item).getAttributeValueMap().get(attributeName);
}
throw new IllegalArgumentException("EnhancedDocument item is not instance of DefaultEnhancedDocument");
}

@Override
public TableMetadata tableMetadata() {
return tableMetadata;
}

@Override
public EnhancedType<EnhancedDocument> itemType() {
return EnhancedType.of(EnhancedDocument.class);
}

@Override
public List<String> attributeNames() {
return tableMetadata.primaryKeys().stream().collect(Collectors.toList());
}

@Override
public boolean isAbstract() {
return false;
}

@NotThreadSafe
public static final class Builder {

private final StaticTableMetadata.Builder staticTableMetaDataBuilder = StaticTableMetadata.builder();

/**
* By Default the defaultConverterProvider is used for converting AttributeValue to primitive types.
*/
private List<AttributeConverterProvider> attributeConverterProviders =
Collections.singletonList(ConverterProviderResolver.defaultConverterProvider());

/**
* Adds information about a partition key associated with a specific index.
*
* @param attributeName the name of the attribute that represents the partition key
* @param attributeValueType the {@link AttributeValueType} of the partition key
*/
public Builder primaryKey(String attributeName, AttributeValueType attributeValueType) {
staticTableMetaDataBuilder.addIndexPartitionKey(primaryIndexName(), attributeName, attributeValueType);
return this;
}

/**
* Adds information about a sort key associated with a specific index.
*
* @param attributeName the name of the attribute that represents the sort key
* @param attributeValueType the {@link AttributeValueType} of the sort key
*/
public Builder sortKey(String attributeName, AttributeValueType attributeValueType) {
staticTableMetaDataBuilder.addIndexSortKey(primaryIndexName(), attributeName, attributeValueType);
return this;
}

/**
* Specifies the {@link AttributeConverterProvider}s to use with the table schema. The list of attribute converter
* providers must provide {@link AttributeConverter}s for Custom types. The attribute converter providers will be loaded
* in the strict order they are supplied here.
* <p>
* If no AttributeConverterProvider are provided then {@link DefaultAttributeConverterProvider} is used, which provides
Comment thread
joviegas marked this conversation as resolved.
Outdated
* standard converters for most primitive and common Java types, so that provider must be included in the supplied list if
* it is to be used. Providing an empty list here will cause no providers to get loaded.
* <p>
* Adding one custom attribute converter provider and using the default as fallback:
* {@code builder.attributeConverterProviders(customAttributeConverter, AttributeConverterProvider.defaultProvider()) }
*
* @param attributeConverterProviders a list of attribute converter providers to use with the table schema
*/
public Builder attributeConverterProviders(AttributeConverterProvider... attributeConverterProviders) {
this.attributeConverterProviders = Arrays.asList(attributeConverterProviders);
return this;
}

/**
* Specifies the {@link AttributeConverterProvider}s to use with the table schema. The list of attribute converter
* providers must provide {@link AttributeConverter}s for all types used in the schema. The attribute converter providers
* will be loaded in the strict order they are supplied here.
* <p>
* If no AttributeConverterProvider are provided then {@link DefaultAttributeConverterProvider} will be used, which
* provides standard converters for most primitive and common Java types, so that provider must be included in the
* supplied list if it is to be used. Providing an empty list here will cause no providers to get loaded.
* <p>
* Adding one custom attribute converter provider and using the default as fallback:
* {@code List<AttributeConverterProvider> providers = new ArrayList<>( customAttributeConverter,
* AttributeConverterProvider.defaultProvider()); builder.attributeConverterProviders(providers); }
*
* @param attributeConverterProviders a list of attribute converter providers to use with the table schema
*/
public Builder attributeConverterProviders(List<AttributeConverterProvider> attributeConverterProviders) {
this.attributeConverterProviders = new ArrayList<>(attributeConverterProviders);
return this;
}

/**
* Builds a {@link StaticImmutableTableSchema} based on the values this builder has been configured with
*/
public DocumentTableSchema build() {
return new DocumentTableSchema(this);
}
}
}
Loading