/*
* Copyright 2011-2019 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.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* 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 com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.mobile.config.AWSConfiguration;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapperConfig.ConsistentReads;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapperConfig.PaginationLoadingStrategy;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapperConfig.SaveBehavior;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBTableSchemaParser.TableIndexesInfo;
import com.amazonaws.retry.RetryUtils;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.model.AttributeAction;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.AttributeValueUpdate;
import com.amazonaws.services.dynamodbv2.model.BatchGetItemRequest;
import com.amazonaws.services.dynamodbv2.model.BatchGetItemResult;
import com.amazonaws.services.dynamodbv2.model.BatchWriteItemRequest;
import com.amazonaws.services.dynamodbv2.model.BatchWriteItemResult;
import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
import com.amazonaws.services.dynamodbv2.model.Condition;
import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
import com.amazonaws.services.dynamodbv2.model.ConditionalOperator;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.DeleteItemRequest;
import com.amazonaws.services.dynamodbv2.model.DeleteRequest;
import com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue;
import com.amazonaws.services.dynamodbv2.model.GetItemRequest;
import com.amazonaws.services.dynamodbv2.model.GetItemResult;
import com.amazonaws.services.dynamodbv2.model.KeysAndAttributes;
import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
import com.amazonaws.services.dynamodbv2.model.PutItemResult;
import com.amazonaws.services.dynamodbv2.model.PutRequest;
import com.amazonaws.services.dynamodbv2.model.QueryRequest;
import com.amazonaws.services.dynamodbv2.model.QueryResult;
import com.amazonaws.services.dynamodbv2.model.ReturnValue;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.ScanResult;
import com.amazonaws.services.dynamodbv2.model.Select;
import com.amazonaws.services.dynamodbv2.model.UpdateItemRequest;
import com.amazonaws.services.dynamodbv2.model.UpdateItemResult;
import com.amazonaws.services.dynamodbv2.model.WriteRequest;
import com.amazonaws.services.s3.model.Region;
import com.amazonaws.util.VersionInfoUtils;
import com.amazonaws.logging.Log;
import com.amazonaws.logging.LogFactory;
import org.json.JSONObject;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
/**
* Object mapper for domain-object interaction with DynamoDB.
*
* To use, define a domain class that represents an item in a DynamoDB table and
* annotate it with the annotations found in the
* com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper package. In order to
* allow the mapper to correctly persist the data, each modeled property in the
* domain class should be accessible via getter and setter methods, and each
* property annotation should be either applied to the getter method or the
* class field. A minimal example using getter annotations:
*
*
* @DynamoDBTable(tableName = "TestTable")
* public class TestClass {
*
* private Long key;
* private double rangeKey;
* private Long version;
*
* private Set<Integer> integerSetAttribute;
*
* @DynamoDBHashKey
* public Long getKey() {
* return key;
* }
*
* public void setKey(Long key) {
* this.key = key;
* }
*
* @DynamoDBRangeKey
* public double getRangeKey() {
* return rangeKey;
* }
*
* public void setRangeKey(double rangeKey) {
* this.rangeKey = rangeKey;
* }
*
* @DynamoDBAttribute(attributeName = "integerSetAttribute")
* public Set<Integer> getIntegerAttribute() {
* return integerSetAttribute;
* }
*
* public void setIntegerAttribute(Set<Integer> integerAttribute) {
* this.integerSetAttribute = integerAttribute;
* }
*
* @DynamoDBVersionAttribute
* public Long getVersion() {
* return version;
* }
*
* public void setVersion(Long version) {
* this.version = version;
* }
* }
*
*
* Save instances of annotated classes to DynamoDB, retrieve them, and delete
* them using the {@link DynamoDBMapper} class, as in the following example.
*
*
* When using the save, load, and delete methods, {@link DynamoDBMapper} will
* throw {@link DynamoDBMappingException}s to indicate that domain classes are
* incorrectly annotated or otherwise incompatible with this class. Service
* exceptions will always be propagated as {@link AmazonClientException}, and
* DynamoDB-specific subclasses such as {@link ConditionalCheckFailedException}
* will be used when possible.
*
* This class is thread-safe and can be shared between threads. It's also very
* lightweight, so it doesn't need to be.
*
* @see DynamoDBTable
* @see DynamoDBHashKey
* @see DynamoDBRangeKey
* @see DynamoDBAutoGeneratedKey
* @see DynamoDBAttribute
* @see DynamoDBVersionAttribute
* @see DynamoDBIgnore
* @see DynamoDBMarshalling
* @see DynamoDBMapperConfig
*/
@SuppressWarnings("checkstyle:hiddenfield")
public class DynamoDBMapper {
private final S3ClientCache s3cc;
private final AmazonDynamoDB db;
private final DynamoDBMapperConfig config;
private final DynamoDBReflector reflector = new DynamoDBReflector();
private final DynamoDBTableSchemaParser schemaParser = new DynamoDBTableSchemaParser();
private final VersionIncrementor incrementor = new VersionIncrementor();
private final AttributeTransformer transformer;
/** The max back off time for batch write */
static final long MAX_BACKOFF_IN_MILLISECONDS = 1000 * 3;
private static final long THREAD_SLEEP_TWO_SECONDS = 1000 * 2;
/** The max number of items allowed in a BatchWrite request */
static final int MAX_ITEMS_PER_BATCH = 25;
private static final int MAX_BATCH_GET_COUNT = 100;
/**
* This retry count is applicable only when every batch get item request
* results in no data retrieved from server and the un processed keys is
* same as request items
*/
static final int BATCH_GET_MAX_RETRY_COUNT_ALL_KEYS = 5;
private static final int EXPONENTIAL_BACKOFF_OFFSET = 500;
private static final int EXPONENTIAL_BACKOFF_RANDOMIZATION_OFFSET = 100;
/**
* User agent for requests made using the {@link DynamoDBMapper}.
*/
private static final String USER_AGENT =
DynamoDBMapper.class.getName() + "/" + VersionInfoUtils.getVersion();
private static final String USER_AGENT_BATCH_OPERATION =
DynamoDBMapper.class.getName() + "_batch_operation/" + VersionInfoUtils.getVersion();
private static String userAgentFromConfig = "";
private static void setUserAgentFromConfig(String userAgent) {
synchronized (DynamoDBMapper.userAgentFromConfig) {
DynamoDBMapper.userAgentFromConfig = userAgent;
}
}
private static String getUserAgentFromConfig() {
synchronized (DynamoDBMapper.userAgentFromConfig) {
if (DynamoDBMapper.userAgentFromConfig == null
|| DynamoDBMapper.userAgentFromConfig.trim().isEmpty()) {
return "";
}
return DynamoDBMapper.userAgentFromConfig.trim() + "/";
}
}
private static final String NO_RANGE_KEY = new String();
private static final Log log = LogFactory.getLog(DynamoDBMapper.class);
/**
* Builder class for DynamoDBMapper
*/
public static class Builder {
private AmazonDynamoDB dynamoDB;
private DynamoDBMapperConfig config;
private AttributeTransformer transformer;
private AWSCredentialsProvider s3CredentialProvider;
private AWSConfiguration awsConfig;
protected Builder() { }
/**
*
* @param dynamoDBClient The service object to use for all service calls
* @return builder
*/
public Builder dynamoDBClient(AmazonDynamoDB dynamoDBClient) {
this.dynamoDB = dynamoDBClient;
return this;
}
/**
* The configuration to use for all service calls. It can be overridden
* on a per-operation basis. If no configuration is provided,
* {@link DynamoDBMapperConfig#DEFAULT} will be used,
*
* @param dynamoConfig config
* @return builder
* @see DynamoDBMapperConfig#DEFAULT
*/
public Builder dynamoDBMapperConfig(DynamoDBMapperConfig dynamoConfig) {
this.config = dynamoConfig;
return this;
}
/**
*
* @param attributeTransformer The custom attribute transformer to invoke when
* serializing or deserializing an object.
* @return builder
*/
public Builder attributeTransformer(AttributeTransformer attributeTransformer) {
this.transformer = attributeTransformer;
return this;
}
/**
*
* @param s3CredentialsProvider The credentials provider for accessing S3.
* Relevant only if {@link S3Link} is involved.
* @return builder
*/
public Builder awsCredentialsProviderForS3(AWSCredentialsProvider s3CredentialsProvider) {
this.s3CredentialProvider = s3CredentialsProvider;
return this;
}
/**
* The region of the AmazonDynamoDB object will be set to
* the region found in the AWSConfiguration.
*
* Example awsconfiguration.json
* {
* "DynamoDBObjectMapper": {
* "Default": {
* "Region": "us-east-1"
* }
* }
* }
* @param awsConfig the config
* @return builder
*/
public Builder awsConfiguration(AWSConfiguration awsConfig) {
this.awsConfig = awsConfig;
return this;
}
/**
*
* @return the constructed DynamoDBMapper
*/
public DynamoDBMapper build() {
if (this.dynamoDB == null) {
throw new IllegalArgumentException("AmazonDynamoDB client is required please set using .dynamoDBClient(yourClient)");
}
if (this.awsConfig != null) {
try {
final JSONObject ddbConfig = awsConfig.optJsonObject("DynamoDBObjectMapper");
final String regionString = ddbConfig.getString("Region");
dynamoDB.setRegion(com.amazonaws.regions.Region.getRegion(com.amazonaws.regions.Regions.fromName(regionString)));
DynamoDBMapper.setUserAgentFromConfig(awsConfig.getUserAgent());
} catch (Exception e) {
throw new IllegalArgumentException("Failed to read Region from AWSConfiguration please check your setup or awsconfiguration.json file", e);
}
}
return new DynamoDBMapper(
this.dynamoDB,
this.config == null ? DynamoDBMapperConfig.DEFAULT : this.config,
this.transformer,
this.s3CredentialProvider,
this.awsConfig);
}
}
/**
* Minimum calls required.
* DynamoDBMapper.builder().dynamoDBClient(client).build()
*
* @return The builder object to construct a DynamoDBMapper.
*/
public static Builder builder() {
return new Builder();
}
private DynamoDBMapper(
final AmazonDynamoDB dynamoDB,
final DynamoDBMapperConfig config,
final AttributeTransformer transformer,
final AWSCredentialsProvider s3CredentialsProvider,
final AWSConfiguration awsConfig) {
this.db = dynamoDB;
this.config = config;
this.transformer = transformer;
if (s3CredentialsProvider == null) {
this.s3cc = null;
} else {
this.s3cc = new S3ClientCache(s3CredentialsProvider);
}
}
/**
* Constructs a new mapper with the service object given, using the default
* configuration.
*
* @param dynamoDB The service object to use for all service calls.
* @see DynamoDBMapperConfig#DEFAULT
* @deprecated Please use DynamoDBMapper.builder()
* .dynamoDBClient(dynamoDB)
* .build();
*/
public DynamoDBMapper(final AmazonDynamoDB dynamoDB) {
this(dynamoDB, DynamoDBMapperConfig.DEFAULT, null, null);
}
/**
* Constructs a new mapper with the service object and configuration given.
*
* @param dynamoDB The service object to use for all service calls.
* @param config The default configuration to use for all service calls. It
* can be overridden on a per-operation basis.
* @deprecated Please use
* DynamoDBMapper.builder()
* .dynamoDBClient(dynamoDB)
* .dynamoDBMapperConfig(config)
* .build();
*/
public DynamoDBMapper(
final AmazonDynamoDB dynamoDB,
final DynamoDBMapperConfig config) {
this(dynamoDB, config, null, null);
}
/**
* Constructs a new mapper with the service object and S3 client cache
* given, using the default configuration.
*
* @param ddb The service object to use for all service calls.
* @param s3CredentialProvider The credentials provider for accessing S3.
* Relevant only if {@link S3Link} is involved.
* @see DynamoDBMapperConfig#DEFAULT
* @deprecated Please use DynamoDBMapper.builder()
* .dynamoDBClient(dynamoDB)
* .awsCredentialsProviderForS3(creds)
* .build();
*/
public DynamoDBMapper(
final AmazonDynamoDB ddb,
final AWSCredentialsProvider s3CredentialProvider) {
this(ddb, DynamoDBMapperConfig.DEFAULT, s3CredentialProvider);
}
/**
* Constructs a new mapper with the given service object, configuration, and
* transform hook.
*
* @param dynamoDB the service object to use for all service calls
* @param config the default configuration to use for all service calls. It
* can be overridden on a per-operation basis
* @param transformer The custom attribute transformer to invoke when
* serializing or deserializing an object.
* @deprecated Please use DynamoDBMapper.builder()
* .dynamoDBClient(dynamoDB)
* .dynamoDBMapperConfig(config)
* .attributeTransformer(transformer)
* .build();
*/
public DynamoDBMapper(
final AmazonDynamoDB dynamoDB,
final DynamoDBMapperConfig config,
final AttributeTransformer transformer) {
this(dynamoDB, config, transformer, null);
}
/**
* Constructs a new mapper with the service object, configuration, and S3
* client cache given.
*
* @param dynamoDB The service object to use for all service calls.
* @param config The default configuration to use for all service calls. It
* can be overridden on a per-operation basis.
* @param s3CredentialProvider The credentials provider for accessing S3.
* Relevant only if {@link S3Link} is involved.
* @deprecated Please use DynamoDBMapper.builder()
* .dynamoDBClient(dynamoDB)
* .dynamoDBMapperConfig(config)
* .awsCredentialsProviderForS3(creds)
* .build();
*/
public DynamoDBMapper(
final AmazonDynamoDB dynamoDB,
final DynamoDBMapperConfig config,
final AWSCredentialsProvider s3CredentialProvider) {
this(dynamoDB, config, null, validate(s3CredentialProvider));
}
/**
* Throws an exception if the given credentials provider is {@code null}.
*/
private static AWSCredentialsProvider validate(
final AWSCredentialsProvider provider) {
if (provider == null) {
throw new IllegalArgumentException(
"s3 credentials provider must not be null");
}
return provider;
}
/**
* Constructor with all parameters.
*
* @param dynamoDB The service object to use for all service calls.
* @param config The default configuration to use for all service calls. It
* can be overridden on a per-operation basis.
* @param transformer The custom attribute transformer to invoke when
* serializing or deserializing an object.
* @param s3CredentialsProvider The credentials provider for accessing S3.
* Relevant only if {@link S3Link} is involved.
* @deprecated Please use DynamoDBMapper.builder()
* .dynamoDBClient(dynamoDB)
* .dynamoDBMapperConfig(config)
* .attributeTransformer(transformer)
* .awsCredentialsProviderForS3(creds)
* .build();
*/
public DynamoDBMapper(
final AmazonDynamoDB dynamoDB,
final DynamoDBMapperConfig config,
final AttributeTransformer transformer,
final AWSCredentialsProvider s3CredentialsProvider) {
this.db = dynamoDB;
this.config = config;
this.transformer = transformer;
if (s3CredentialsProvider == null) {
this.s3cc = null;
} else {
this.s3cc = new S3ClientCache(s3CredentialsProvider);
}
}
/**
* Loads an object with the hash key given and a configuration override.
* This configuration overrides the default provided at object construction.
*
* @param the class type
* @param clazz the class
* @param hashKey the hashkey object
* @param config the {@link DynamoDBMapperConfig}
* @see DynamoDBMapper#load(Class, Object, Object, DynamoDBMapperConfig)
* @return instance of clazz.
*/
@SuppressWarnings("checkstyle:hiddenfield")
public T load(Class clazz, Object hashKey, DynamoDBMapperConfig config) {
return load(clazz, hashKey, null, config);
}
/**
* Loads an object with the hash key given, using the default configuration.
*
* @param the class type
* @param clazz the class
* @param hashKey the hashkey object
* @see DynamoDBMapper#load(Class, Object, Object, DynamoDBMapperConfig)
* @return instance of clazz.
*/
public T load(Class clazz, Object hashKey) {
return load(clazz, hashKey, null, config);
}
/**
* Loads an object with a hash and range key, using the default
* configuration.
*
* @param clazz the class to type the obect to.
* @param hashKey the hash key
* @param rangeKey the range key
* @param the type of the class.
* @see DynamoDBMapper#load(Class, Object, Object, DynamoDBMapperConfig)
* @return an object of type T
*/
public T load(Class clazz, Object hashKey, Object rangeKey) {
return load(clazz, hashKey, rangeKey, config);
}
/**
* Returns an object whose keys match those of the prototype key object
* given, or null if no such item exists.
*
* @param keyObject An object of the class to load with the keys values to
* match.
* @param the type of class.
* @see DynamoDBMapper#load(Object, DynamoDBMapperConfig)
* @return an object of type T
*/
public T load(T keyObject) {
return load(keyObject, this.config);
}
/**
* Returns an object whose keys match those of the prototype key object
* given, or null if no such item exists.
*
* @param keyObject An object of the class to load with the keys values to
* match.
* @param config Configuration for the service call to retrieve the object
* from DynamoDB. This configuration overrides the default given
* at construction.
* @param the type of class.
* @return an object of type T
*/
public T load(T keyObject, DynamoDBMapperConfig config) {
@SuppressWarnings("unchecked")
final Class clazz = (Class) keyObject.getClass();
config = mergeConfig(config);
final ItemConverter converter = getConverter(config);
final String tableName = getTableName(clazz, keyObject, config);
final GetItemRequest rq = new GetItemRequest()
.withRequestMetricCollector(config.getRequestMetricCollector());
final Map key = getKey(converter, keyObject, clazz);
rq.setKey(key);
rq.setTableName(tableName);
rq.setConsistentRead(config.getConsistentReads() == ConsistentReads.CONSISTENT);
final GetItemResult item = db.getItem(applyUserAgent(rq));
final Map itemAttributes = item.getItem();
if (itemAttributes == null) {
return null;
}
final T object = privateMarshallIntoObject(
converter,
toParameters(itemAttributes, clazz, tableName, config));
return object;
}
/**
* Returns a key map for the key object given.
*
* @param keyObject The key object, corresponding to an item in a dynamo
* table.
* @param the type of class.
* @return the key map for a key object given
*/
@SuppressWarnings("unchecked")
private Map getKey(
ItemConverter converter,
T keyObject) {
return getKey(converter, keyObject, (Class) keyObject.getClass());
}
private Map getKey(
ItemConverter converter,
T keyObject,
Class clazz) {
final Map key = new HashMap();
for (final Method keyGetter : reflector.getPrimaryKeyGetters(clazz)) {
final Object getterResult =
ReflectionUtils.safeInvoke(keyGetter, keyObject);
final AttributeValue keyAttributeValue =
converter.convert(keyGetter, getterResult);
if (keyAttributeValue == null) {
throw new DynamoDBMappingException(
"Null key found for " + keyGetter);
}
key.put(reflector.getAttributeName(keyGetter), keyAttributeValue);
}
if (key.isEmpty()) {
throw new DynamoDBMappingException(
"Class must be annotated with " + DynamoDBHashKey.class
+ " and " + DynamoDBRangeKey.class);
}
return key;
}
/**
* Returns an object with the given hash key, or null if no such object
* exists.
*
* @param clazz The class to load, corresponding to a DynamoDB table.
* @param hashKey The key of the object.
* @param rangeKey The range key of the object, or null for tables without a
* range key.
* @param config Configuration for the service call to retrieve the object
* from DynamoDB. This configuration overrides the default given
* at construction.
* @param the type of class.
* @return an object with the given hash key, or null if no such object
* exists.
*/
public T load(Class clazz, Object hashKey, Object rangeKey,
DynamoDBMapperConfig config) {
config = mergeConfig(config);
final T keyObject = createKeyObject(clazz, hashKey, rangeKey);
return load(keyObject, config);
}
/**
* Creates a key prototype object for the class given with the single hash
* and range key given.
*
* @param the type of class.
*/
T createKeyObject(Class clazz, Object hashKey, Object rangeKey) {
T keyObject = null;
try {
keyObject = clazz.newInstance();
} catch (final Exception e) {
throw new DynamoDBMappingException("Failed to instantiate class", e);
}
boolean seenHashKey = false;
boolean seenRangeKey = false;
for (final Method getter : reflector.getPrimaryKeyGetters(clazz)) {
if (ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBHashKey.class)) {
if (seenHashKey) {
throw new DynamoDBMappingException(
"Found more than one method annotated with "
+ DynamoDBHashKey.class
+ " for class "
+ clazz
+ ". Use load(Object) for tables with more than a single hash and range key.");
}
seenHashKey = true;
ReflectionUtils.safeInvoke(reflector.getSetter(getter), keyObject, hashKey);
} else if (ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBRangeKey.class)) {
if (seenRangeKey) {
throw new DynamoDBMappingException(
"Found more than one method annotated with "
+ DynamoDBRangeKey.class
+ " for class "
+ clazz
+ ". Use load(Object) for tables with more than a single hash and range key.");
}
seenRangeKey = true;
ReflectionUtils.safeInvoke(reflector.getSetter(getter), keyObject, rangeKey);
}
}
if (!seenHashKey) {
throw new DynamoDBMappingException("No method annotated with " + DynamoDBHashKey.class
+ " for class "
+ clazz + ".");
} else if (rangeKey != null && !seenRangeKey) {
throw new DynamoDBMappingException("No method annotated with " + DynamoDBRangeKey.class
+ " for class "
+ clazz + ".");
}
return keyObject;
}
/**
* Returns a map of attribute name to EQ condition for the key prototype
* object given. This method considers attributes annotated with either
* {@link DynamoDBHashKey} or {@link DynamoDBIndexHashKey}.
*
* @param obj The prototype object that includes the hash key value.
* @return A map of hash key attribute name to EQ condition for the key
* prototype object, or an empty map if obj is null.
*/
private Map getHashKeyEqualsConditions(
ItemConverter converter,
Object obj) {
final Map conditions = new HashMap();
if (obj == null) {
return conditions;
}
for (final Method getter : reflector.getRelevantGetters(obj.getClass())) {
if (ReflectionUtils.getterOrFieldHasAnnotation(
getter, DynamoDBHashKey.class)
|| ReflectionUtils.getterOrFieldHasAnnotation(
getter, DynamoDBIndexHashKey.class)) {
final Object getterReturnResult =
ReflectionUtils.safeInvoke(getter, obj);
if (getterReturnResult != null) {
conditions.put(
reflector.getAttributeName(getter),
new Condition()
.withComparisonOperator(ComparisonOperator.EQ)
.withAttributeValueList(
converter.convert(getter, getterReturnResult)));
}
}
}
return conditions;
}
/**
* Returns the table name for the class given.
*/
protected final String getTableName(final Class> clazz,
final DynamoDBMapperConfig config) {
return internalGetTableName(clazz, null, config);
}
/**
* Returns the table name for the class or object given.
*/
protected final String getTableName(final Class> clazz,
final Object object,
final DynamoDBMapperConfig config) {
return internalGetTableName(clazz, object, config);
}
static String internalGetTableName(final Class> clazz,
final Object object,
final DynamoDBMapperConfig config) {
// Resolve by object, if possible and desired
final DynamoDBMapperConfig.ObjectTableNameResolver objectResolver = config
.getObjectTableNameResolver();
if (object != null && objectResolver != null) {
return objectResolver.getTableName(object, config);
}
// Resolve by class
DynamoDBMapperConfig.TableNameResolver classResolver = config.getTableNameResolver();
if (classResolver == null) {
classResolver = DynamoDBMapperConfig.DefaultTableNameResolver.INSTANCE;
}
return classResolver.getTableName(clazz, config);
}
/**
* Creates and fills in the attributes on an instance of the class given
* with the attributes given.
*
* This is accomplished by looking for getter methods annotated with an
* appropriate annotation, then looking for matching attribute names in the
* item attribute map.
*
* This method is no longer called by load/scan/query methods. If you are
* overriding this method, please switch to using an AttributeTransformer
*
* @param clazz The class to instantiate and hydrate
* @param itemAttributes The set of item attributes, keyed by attribute
* name.
* @param the type of object.
* @return an object of type T
*/
public T marshallIntoObject(
Class clazz,
Map itemAttributes) {
final ItemConverter converter = getConverter(config);
final String tableName = getTableName(clazz, config);
return privateMarshallIntoObject(
converter,
toParameters(itemAttributes, clazz, tableName, config));
}
/**
* The one true implementation of marshallIntoObject.
*/
private T privateMarshallIntoObject(
ItemConverter converter,
AttributeTransformer.Parameters parameters) {
final Class clazz = parameters.getModelClass();
final Map values = untransformAttributes(parameters);
return converter.unconvert(clazz, values);
}
/**
* Unmarshalls the list of item attributes into objects of type clazz.
*
* This method is no longer called by load/scan/query methods. If you are
* overriding this method, please switch to using an AttributeTransformer
*
* @param clazz the class to marshall into.
* @param itemAttributes the item attributes.
* @param the type of object.
* @return an list of objects of type T
* @see DynamoDBMapper#marshallIntoObject(Class, Map)
*/
public List marshallIntoObjects(Class clazz,
List