/*
* Copyright 2011-2014 Amazon Technologies, Inc.
*
* 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.services.dynamodb.datamodeling;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.services.dynamodb.AmazonDynamoDB;
import com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapperConfig.ConsistentReads;
import com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapperConfig.SaveBehavior;
import com.amazonaws.services.dynamodb.model.AttributeValue;
import com.amazonaws.services.dynamodb.model.AttributeValueUpdate;
import com.amazonaws.services.dynamodb.model.BatchGetItemRequest;
import com.amazonaws.services.dynamodb.model.BatchGetItemResult;
import com.amazonaws.services.dynamodb.model.BatchResponse;
import com.amazonaws.services.dynamodb.model.BatchWriteItemRequest;
import com.amazonaws.services.dynamodb.model.BatchWriteItemResult;
import com.amazonaws.services.dynamodb.model.ConditionalCheckFailedException;
import com.amazonaws.services.dynamodb.model.DeleteItemRequest;
import com.amazonaws.services.dynamodb.model.DeleteRequest;
import com.amazonaws.services.dynamodb.model.ExpectedAttributeValue;
import com.amazonaws.services.dynamodb.model.GetItemRequest;
import com.amazonaws.services.dynamodb.model.GetItemResult;
import com.amazonaws.services.dynamodb.model.Key;
import com.amazonaws.services.dynamodb.model.KeysAndAttributes;
import com.amazonaws.services.dynamodb.model.PutItemRequest;
import com.amazonaws.services.dynamodb.model.PutRequest;
import com.amazonaws.services.dynamodb.model.QueryRequest;
import com.amazonaws.services.dynamodb.model.QueryResult;
import com.amazonaws.services.dynamodb.model.ScanRequest;
import com.amazonaws.services.dynamodb.model.ScanResult;
import com.amazonaws.services.dynamodb.model.UpdateItemRequest;
import com.amazonaws.services.dynamodb.model.WriteRequest;
import com.amazonaws.util.VersionInfoUtils;
/**
* Object mapper for domain-object interaction with DynamoDB.
*
* To use, annotate domain classes with the annotations found in the
* com.amazonaws.services.dynamodb.datamodeling package. A minimal example:
*
*
* @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.
*
*
* DynamoDBMapper mapper = new DynamoDBMapper(dynamoDBClient);
* Long hashKey = 105L;
* double rangeKey = 1.0d;
* TestClass obj = mapper.load(TestClass.class, hashKey, rangeKey);
* obj.getIntegerAttribute().add(42);
* mapper.save(obj);
* mapper.delete(obj);
*
*
* 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
*
* @deprecated Use {@link com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper} instead.
*/
@Deprecated
public class DynamoDBMapper {
private final AmazonDynamoDB db;
private final DynamoDBMapperConfig config;
private static final DynamoDBReflector reflector = new DynamoDBReflector();
/**
* User agent for requests made using the {@link DynamoDBMapper}.
*/
private static final String USER_AGENT = DynamoDBMapper.class.getName() + "/" + VersionInfoUtils.getVersion();
/**
* 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
*/
public DynamoDBMapper(AmazonDynamoDB dynamoDB) {
this(dynamoDB, DynamoDBMapperConfig.DEFAULT);
}
/**
* 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.
*/
public DynamoDBMapper(AmazonDynamoDB dynamoDB, DynamoDBMapperConfig config) {
this.db = dynamoDB;
this.config = config;
}
/**
* Loads an object with the hash key given and a configuration override.
* This configuration overrides the default provided at object construction.
*
* @see DynamoDBMapper#load(Class, Object, Object, DynamoDBMapperConfig)
*/
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.
*
* @see DynamoDBMapper#load(Class, Object, Object, DynamoDBMapperConfig)
*/
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.
*
* @see DynamoDBMapper#load(Class, Object, Object, DynamoDBMapperConfig)
*/
public T load(Class clazz, Object hashKey, Object rangeKey) {
return load(clazz, hashKey, rangeKey, config);
}
/**
* 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.
*/
public T load(Class clazz, Object hashKey, Object rangeKey, DynamoDBMapperConfig config) {
config = mergeConfig(config);
String tableName = getTableName(clazz, config);
// Fill in the hash key element in the service request
Method hashKeyGetter = reflector.getHashKeyGetter(clazz);
AttributeValue hashKeyElement = getHashKeyElement(hashKey, hashKeyGetter);
// Determine the range key, if provided
AttributeValue rangeKeyElement = null;
if ( rangeKey != null ) {
Method rangeKeyMethod = reflector.getRangeKeyGetter(clazz);
if ( rangeKeyMethod == null ) {
throw new DynamoDBMappingException("Zero-parameter range key property must be annotated with "
+ DynamoDBRangeKey.class);
}
rangeKeyElement = getRangeKeyElement(rangeKey, rangeKeyMethod);
}
GetItemResult item = db.getItem(applyUserAgent(new GetItemRequest().withTableName(tableName)
.withKey(new Key().withHashKeyElement(hashKeyElement).withRangeKeyElement(rangeKeyElement))
.withConsistentRead(config.getConsistentReads() == ConsistentReads.CONSISTENT)));
Map itemAttributes = item.getItem();
if ( itemAttributes == null ) {
return null;
}
return marshallIntoObject(clazz, itemAttributes);
}
private String getTableName(Class clazz, DynamoDBMapperConfig config) {
DynamoDBTable table = reflector.getTable(clazz);
String tableName = table.tableName();
if ( config.getTableNameOverride() != null ) {
if ( config.getTableNameOverride().getTableName() != null ) {
tableName = config.getTableNameOverride().getTableName();
} else {
tableName = config.getTableNameOverride().getTableNamePrefix() + tableName;
}
}
return tableName;
}
private AttributeValue getHashKeyElement(Object hashKey, Method hashKeyGetter) {
return getSimpleAttributeValue(hashKeyGetter, hashKey);
}
private AttributeValue getRangeKeyElement(Object rangeKey, Method rangeKeyGetter) {
return getSimpleAttributeValue(rangeKeyGetter, rangeKey);
}
/**
* 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.
*
* @param clazz
* The class to instantiate and hydrate
* @param itemAttributes
* The set of item attributes, keyed by attribute name.
*/
public T marshallIntoObject(Class clazz, Map itemAttributes) {
T toReturn = null;
try {
toReturn = clazz.newInstance();
} catch ( InstantiationException e ) {
throw new DynamoDBMappingException("Failed to instantiate new instance of class", e);
} catch ( IllegalAccessException e ) {
throw new DynamoDBMappingException("Failed to instantiate new instance of class", e);
}
if ( itemAttributes == null || itemAttributes.isEmpty() )
return toReturn;
itemAttributes = untransformAttributes(clazz, itemAttributes);
for ( Method m : reflector.getRelevantGetters(clazz) ) {
String attributeName = reflector.getAttributeName(m);
if ( itemAttributes.containsKey(attributeName) ) {
setValue(toReturn, m, itemAttributes.get(attributeName));
}
}
return toReturn;
}
/**
* Marshalls the list of item attributes into objects of type clazz
*
* @see DynamoDBMapper#marshallIntoObject(Class, Map)
*/
public List marshallIntoObjects(Class clazz, List> itemAttributes) {
List result = new ArrayList();
for (Map item : itemAttributes) {
result.add(marshallIntoObject(clazz, item));
}
return result;
}
/**
* Sets the value in the return object corresponding to the service result.
*/
private void setValue(final T toReturn, final Method getter, AttributeValue value) {
Method setter = reflector.getSetter(getter);
ArgumentUnmarshaller unmarhsaller = reflector.getArgumentUnmarshaller(toReturn, getter, setter);
unmarhsaller.typeCheck(value, setter);
Object argument;
try {
argument = unmarhsaller.unmarshall(value);
} catch ( IllegalArgumentException e ) {
throw new DynamoDBMappingException("Couldn't unmarshall value " + value + " for " + setter, e);
} catch ( ParseException e ) {
throw new DynamoDBMappingException("Error attempting to parse date string " + value + " for "+ setter, e);
}
safeInvoke(setter, toReturn, argument);
}
/**
* Returns an {@link AttributeValue} corresponding to the getter and return
* result given, treating it as a non-versioned attribute.
*/
private AttributeValue getSimpleAttributeValue(final Method getter, final Object getterReturnResult) {
if ( getterReturnResult == null )
return null;
ArgumentMarshaller marshaller = reflector.getArgumentMarshaller(getter);
return marshaller.marshall(getterReturnResult);
}
/**
* Saves the object given into DynamoDB, using the default configuration.
*
* @see DynamoDBMapper#save(Object, DynamoDBMapperConfig)
*/
public void save(T object) {
save(object, config);
}
/**
* Saves an item in DynamoDB. The service method used is determined by the
* {@link DynamoDBMapperConfig#getSaveBehavior()} value, to use either
* {@link AmazonDynamoDB#putItem(PutItemRequest)} or
* {@link AmazonDynamoDB#updateItem(UpdateItemRequest)}. For updates, a null
* value for an object property will remove it from that item in DynamoDB.
* For puts, a null value will not be passed to the service. The effect is
* therefore the same, except when the item in DynamoDB contains attributes
* that aren't modeled by the domain object given.
*
* @param object
* The object to save into DynamoDB
* @param config
* The configuration to use, which overrides the default provided
* at object construction.
*/
public void save(T object, DynamoDBMapperConfig config) {
config = mergeConfig(config);
@SuppressWarnings("unchecked")
Class extends T> clazz = (Class extends T>) object.getClass();
String tableName = getTableName(clazz, config);
Method hashKeyGetter = reflector.getHashKeyGetter(clazz);
AttributeValue hashKeyElement = getHashKeyElement(safeInvoke(hashKeyGetter, object), hashKeyGetter);
AttributeValue rangeKeyElement = null;
Method rangeKeyGetter = reflector.getRangeKeyGetter(clazz);
if ( rangeKeyGetter != null ) {
rangeKeyElement = getRangeKeyElement(safeInvoke(rangeKeyGetter, object), rangeKeyGetter);
}
Key objectKey = new Key().withHashKeyElement(hashKeyElement).withRangeKeyElement(rangeKeyElement);
Map updateValues = new HashMap();
Map expectedValues = new HashMap();
boolean forcePut = config.getSaveBehavior() == SaveBehavior.CLOBBER;
boolean nonKeyAttributePresent = false;
List inMemoryUpdates = new LinkedList();
/*
* First look at the keys and construct updates for them independently
*/
List keyGetterMethods = new LinkedList();
keyGetterMethods.add(hashKeyGetter);
if ( rangeKeyGetter != null ) {
keyGetterMethods.add(rangeKeyGetter);
}
/*
* Determine if there are any auto-assigned keys to assign. If so, force
* a put and assign the keys
*/
if ( !forcePut ) {
for ( Method method : keyGetterMethods ) {
Object getterResult = safeInvoke(method, object);
if ( getterResult == null && reflector.isAssignableKey(method) ) {
forcePut = true;
}
}
}
/*
* If we're doing a put, that means that we need update values for the
* key attributes.
*/
if ( forcePut ) {
for ( Method method : keyGetterMethods ) {
Object getterResult = safeInvoke(method, object);
String attributeName = reflector.getAttributeName(method);
if ( getterResult == null && reflector.isAssignableKey(method) ) {
AttributeValue newVersionValue = getAutoGeneratedKeyAttributeValue(method, getterResult);
updateValues.put(attributeName,
new AttributeValueUpdate().withAction("PUT").withValue(newVersionValue));
inMemoryUpdates.add(new ValueUpdate(method, newVersionValue, object));
if ( config.getSaveBehavior() != SaveBehavior.CLOBBER ) {
// Add an expect clause to make sure that the item
// doesn't already exist, since it's supposed to be new
ExpectedAttributeValue expected = new ExpectedAttributeValue();
expected.setExists(false);
expectedValues.put(attributeName, expected);
}
} else {
AttributeValue currentValue = getSimpleAttributeValue(method, getterResult);
if ( currentValue != null ) {
updateValues.put(attributeName,
new AttributeValueUpdate().withValue(currentValue).withAction("PUT"));
} else {
throw new DynamoDBMappingException("Null value for non-generated key for method " + method);
}
}
}
} else {
/*
* If we don't have the required keys by this point, it's an error
*/
if ( hashKeyElement == null ) {
throw new DynamoDBMappingException("No value provided for hash key for object " + object);
}
if ( rangeKeyGetter != null && rangeKeyElement == null ) {
throw new DynamoDBMappingException("No value provided for range key for object " + object);
}
}
// Look at every getter and construct an update object for it
for ( Method method : reflector.getRelevantGetters(clazz) ) {
// Skip any key methods, since they are handled separately
if ( method.equals(hashKeyGetter) || method.equals(rangeKeyGetter) )
continue;
nonKeyAttributePresent = true;
Object getterResult = safeInvoke(method, object);
String attributeName = reflector.getAttributeName(method);
/*
* If this is a versioned field, update it
*/
if ( reflector.isVersionAttributeGetter(method) ) {
if ( config.getSaveBehavior() != SaveBehavior.CLOBBER ) {
// First establish the expected (current) value for the
// update call
ExpectedAttributeValue expected = new ExpectedAttributeValue();
// For new objects, insist that the value doesn't exist.
// For existing ones, insist it has the old value.
AttributeValue currentValue = getSimpleAttributeValue(method, getterResult);
expected.setExists(currentValue != null);
if ( currentValue != null ) {
expected.setValue(currentValue);
}
expectedValues.put(attributeName, expected);
}
AttributeValue newVersionValue = getVersionAttributeValue(method, getterResult);
updateValues
.put(attributeName, new AttributeValueUpdate().withAction("PUT").withValue(newVersionValue));
inMemoryUpdates.add(new ValueUpdate(method, newVersionValue, object));
}
/*
* Otherwise apply the update value for this attribute.
*/
else {
AttributeValue currentValue = getSimpleAttributeValue(method, getterResult);
if ( currentValue != null ) {
updateValues.put(attributeName, new AttributeValueUpdate().withValue(currentValue)
.withAction("PUT"));
} else if ( config.getSaveBehavior() != SaveBehavior.CLOBBER ) {
updateValues.put(attributeName, new AttributeValueUpdate().withAction("DELETE"));
}
}
}
/*
* Do a put or an update, according to the configuration. For a put (not
* the default), we need to munge the data type.
*/
if ( config.getSaveBehavior() == SaveBehavior.CLOBBER || forcePut ) {
db.putItem(applyUserAgent(new PutItemRequest().withTableName(tableName)
.withItem(transformAttributes(clazz, convertToItem(updateValues)))
.withExpected(expectedValues)));
} else if ( !nonKeyAttributePresent ) {
keyOnlyPut(clazz, tableName, objectKey, hashKeyGetter, rangeKeyGetter);
} else {
db.updateItem(applyUserAgent(new UpdateItemRequest().withTableName(tableName).withKey(objectKey)
.withAttributeUpdates(transformAttributeUpdates(clazz, objectKey, updateValues)).withExpected(expectedValues)));
}
/*
* Finally, after the service call has succeeded, update the in-memory
* object with new field values as appropriate.
*/
for ( ValueUpdate update : inMemoryUpdates ) {
update.apply();
}
}
/**
* Edge case to deal with the problem reported here:
* https://forums.aws.amazon.com/thread.jspa?threadID=86798&tstart=25
*
* DynamoDB cannot process an updateItem request that only contains the key
* attribute(s), so we have to do a putItem. Somewhat confusingly, we also
* insist that an item with the key(s) given doesn't already exist. This
* isn't perfect, but we should be doing a putItem at all in this case, so
* it's the best we can do.
*/
private void keyOnlyPut(Class> clazz, String tableName, Key objectKey,
Method hashKeyGetter, Method rangeKeyGetter) {
Map attributes = new HashMap();
Map expectedValues = new HashMap();
String hashKeyAttributeName = reflector.getAttributeName(hashKeyGetter);
attributes.put(hashKeyAttributeName, objectKey.getHashKeyElement());
expectedValues.put(hashKeyAttributeName, new ExpectedAttributeValue().withExists(false));
if (rangeKeyGetter != null) {
String rangeKeyAttributeName = reflector.getAttributeName(rangeKeyGetter);
attributes.put(rangeKeyAttributeName, objectKey.getRangeKeyElement());
expectedValues.put(rangeKeyAttributeName, new ExpectedAttributeValue().withExists(false));
}
attributes = transformAttributes(clazz, attributes);
db.putItem(applyUserAgent(new PutItemRequest().withTableName(tableName).withItem(attributes)
.withExpected(expectedValues)));
}
/**
* Deletes the given object from its DynamoDB table.
*/
public void delete(Object object) {
delete(object, this.config);
}
/**
* Deletes the given object from its DynamoDB table.
*
* @param config
* Config override object. If {@link SaveBehavior#CLOBBER} is
* supplied, version fields will not be considered when deleting
* the object.
*/
public void delete(Object object, DynamoDBMapperConfig config) {
config = mergeConfig(config);
Class> clazz = object.getClass();
String tableName = getTableName(clazz, config);
Method hashKeyGetter = reflector.getHashKeyGetter(clazz);
AttributeValue hashKeyElement = getHashKeyElement(safeInvoke(hashKeyGetter, object), hashKeyGetter);
AttributeValue rangeKeyElement = null;
Method rangeKeyGetter = reflector.getRangeKeyGetter(clazz);
if ( rangeKeyGetter != null ) {
rangeKeyElement = getRangeKeyElement(safeInvoke(rangeKeyGetter, object), rangeKeyGetter);
}
Key objectKey = new Key().withHashKeyElement(hashKeyElement).withRangeKeyElement(rangeKeyElement);
/*
* If there is a version field, make sure we assert its value. If the
* version field is null (only should happen in unusual circumstances),
* pretend it doesn't have a version field after all.
*/
Map expectedValues = new HashMap();
if ( config.getSaveBehavior() != SaveBehavior.CLOBBER ) {
for ( Method method : reflector.getRelevantGetters(clazz) ) {
if ( reflector.isVersionAttributeGetter(method) ) {
Object getterResult = safeInvoke(method, object);
String attributeName = reflector.getAttributeName(method);
ExpectedAttributeValue expected = new ExpectedAttributeValue();
AttributeValue currentValue = getSimpleAttributeValue(method, getterResult);
expected.setExists(currentValue != null);
if ( currentValue != null )
expected.setValue(currentValue);
expectedValues.put(attributeName, expected);
break;
}
}
}
db.deleteItem(applyUserAgent(new DeleteItemRequest().withKey(objectKey).withTableName(tableName).withExpected(expectedValues)));
}
/**
* Deletes the objects given using one or more calls to the
* {@link AmazonDynamoDB#batchWriteItem(BatchWriteItemRequest)} API.
*
* @see DynamoDBMapper#batchWrite(List, List, DynamoDBMapperConfig)
*/
public void batchDelete(List extends Object> objectsToDelete) {
batchWrite(Collections.emptyList(), objectsToDelete, this.config);
}
/**
* Deletes the objects given using one or more calls to the
* {@link AmazonDynamoDB#batchWriteItem(BatchWriteItemRequest)} API.
*
* @see DynamoDBMapper#batchWrite(List, List, DynamoDBMapperConfig)
*/
public void batchDelete(Object... objectsToDelete) {
batchWrite(Collections.emptyList(), Arrays.asList(objectsToDelete), this.config);
}
/**
* Saves the objects given using one or more calls to the
* {@link AmazonDynamoDB#batchWriteItem(BatchWriteItemRequest)} API.
*
* @see DynamoDBMapper#batchWrite(List, List, DynamoDBMapperConfig)
*/
public void batchSave(List extends Object> objectsToSave) {
batchWrite(objectsToSave, Collections.emptyList(), this.config);
}
/**
* Saves the objects given using one or more calls to the
* {@link AmazonDynamoDB#batchWriteItem(BatchWriteItemRequest)} API.
*
* @see DynamoDBMapper#batchWrite(List, List, DynamoDBMapperConfig)
*/
public void batchSave(Object... objectsToSave) {
batchWrite(Arrays.asList(objectsToSave), Collections.emptyList(), this.config);
}
/**
* Saves and deletes the objects given using one or more calls to the
* {@link AmazonDynamoDB#batchWriteItem(BatchWriteItemRequest)} API.
*
* @see DynamoDBMapper#batchWrite(List, List, DynamoDBMapperConfig)
*/
public void batchWrite(List extends Object> objectsToWrite, List extends Object> objectsToDelete) {
batchWrite(objectsToWrite, objectsToDelete, this.config);
}
/**
* Saves and deletes the objects given using one or more calls to the
* {@link AmazonDynamoDB#batchWriteItem(BatchWriteItemRequest)} API.
*
* @param objectsToWrite
* A list of objects to save to DynamoDB. No version checks are
* performed, as required by the
* {@link AmazonDynamoDB#batchWriteItem(BatchWriteItemRequest)}
* API.
* @param objectsToDelete
* A list of objects to delete from DynamoDB. No version checks
* are performed, as required by the
* {@link AmazonDynamoDB#batchWriteItem(BatchWriteItemRequest)}
* API.
* @param config
* Only {@link DynamoDBMapperConfig#getTableNameOverride()} is
* considered; if specified, all objects in the two parameter
* lists will be considered to belong to the given table
* override.
*/
public void batchWrite(List extends Object> objectsToWrite, List extends Object> objectsToDelete, DynamoDBMapperConfig config) {
config = mergeConfig(config);
HashMap> requestItems = new HashMap>();
List inMemoryUpdates = new LinkedList();
for ( Object toWrite : objectsToWrite ) {
Class> clazz = toWrite.getClass();
String tableName = getTableName(clazz, config);
Map attributeValues = new HashMap();
// Look at every getter and construct a value object for it
for ( Method method : reflector.getRelevantGetters(clazz) ) {
Object getterResult = safeInvoke(method, toWrite);
String attributeName = reflector.getAttributeName(method);
AttributeValue currentValue = null;
if ( getterResult == null && reflector.isAssignableKey(method) ) {
currentValue = getAutoGeneratedKeyAttributeValue(method, getterResult);
inMemoryUpdates.add(new ValueUpdate(method, currentValue, toWrite));
} else {
currentValue = getSimpleAttributeValue(method, getterResult);
}
if ( currentValue != null ) {
attributeValues.put(attributeName, currentValue);
}
}
if ( !requestItems.containsKey(tableName) ) {
requestItems.put(tableName, new LinkedList());
}
requestItems.get(tableName).add(
new WriteRequest().withPutRequest(new PutRequest().withItem(transformAttributes(clazz, attributeValues))));
}
for ( Object toDelete : objectsToDelete ) {
Class> clazz = toDelete.getClass();
String tableName = getTableName(clazz, config);
Method hashKeyGetter = reflector.getHashKeyGetter(clazz);
AttributeValue hashKeyElement = getHashKeyElement(safeInvoke(hashKeyGetter, toDelete), hashKeyGetter);
AttributeValue rangeKeyElement = null;
Method rangeKeyGetter = reflector.getRangeKeyGetter(clazz);
if ( rangeKeyGetter != null ) {
rangeKeyElement = getRangeKeyElement(safeInvoke(rangeKeyGetter, toDelete), rangeKeyGetter);
}
Key objectKey = new Key().withHashKeyElement(hashKeyElement).withRangeKeyElement(rangeKeyElement);
if ( !requestItems.containsKey(tableName) ) {
requestItems.put(tableName, new LinkedList());
}
requestItems.get(tableName).add(
new WriteRequest().withDeleteRequest(new DeleteRequest().withKey(objectKey)));
}
// Break into chunks of 25 items and make service requests to DynamoDB
while ( !requestItems.isEmpty() ) {
HashMap> batch = new HashMap>();
int i = 0;
Iterator>> tableIter = requestItems.entrySet().iterator();
while ( tableIter.hasNext() && i < 25 ) {
Entry> tableRequest = tableIter.next();
batch.put(tableRequest.getKey(), new LinkedList());
Iterator writeRequestIter = tableRequest.getValue().iterator();
while ( writeRequestIter.hasNext() && i++ < 25 ) {
WriteRequest writeRequest = writeRequestIter.next();
batch.get(tableRequest.getKey()).add(writeRequest);
writeRequestIter.remove();
}
// If we've processed all the write requests for this table,
// remove it from the parent iterator.
if ( !writeRequestIter.hasNext() ) {
tableIter.remove();
}
}
BatchWriteItemResult result = db.batchWriteItem(new BatchWriteItemRequest().withRequestItems(batch));
// add any unprocessed items back into the list to process
for ( Entry> unprocessedItem : result.getUnprocessedItems().entrySet() ) {
if ( !requestItems.containsKey(unprocessedItem.getKey()) ) {
requestItems.put(unprocessedItem.getKey(), new LinkedList());
}
requestItems.get(unprocessedItem.getKey()).addAll(unprocessedItem.getValue());
}
}
// Once the entire batch is processed, update assigned keys in memory
for ( ValueUpdate update : inMemoryUpdates ) {
update.apply();
}
}
/**
* Retrieves the attributes for multiple items from multiple tables using
* their primary keys.
* {@link AmazonDynamoDB#batchGetItem(BatchGetItemRequest)} API.
*
* @see #batchLoad(Map, DynamoDBMapperConfig)
*/
public Map> batchLoad(Map, List> itemsToGet) {
return batchLoad(itemsToGet, this.config);
}
/**
*
* Retrieves the attributes for multiple items from multiple tables using
* their primary keys.
* {@link AmazonDynamoDB#batchGetItem(BatchGetItemRequest)} API.
*
* @param itemsToGet
* Container for the necessary parameters to execute the
* BatchGetItem service method on Amazon DynamoDB.
* {@link AmazonDynamoDB#batchWriteItem(BatchWriteItemRequest)}
* API.
* @param config
* Only {@link DynamoDBMapperConfig#getTableNameOverride()} is
* considered; if specified, all objects in the two parameter
* lists will be considered to belong to the given table
* override.
*/
public Map> batchLoad(Map, List> itemsToGet, DynamoDBMapperConfig config) {
config = mergeConfig(config);
boolean consistentReads = (config.getConsistentReads() == ConsistentReads.CONSISTENT);
if ( !validBatchGetRequest(itemsToGet) ) {
return null;
}
Map requestItems = new HashMap();
Map> classesByTableName = new HashMap>();
Map> resultSet = new HashMap>();
int count = 0;
for ( Class> clazz : itemsToGet.keySet() ) {
String tableName = getTableName(clazz, config);
List keyPairs = itemsToGet.get(clazz);
if ( keyPairs == null ) {
continue;
}
classesByTableName.put(tableName, clazz);
Method hashKeyGetter = reflector.getHashKeyGetter(clazz);
for ( KeyPair keyPair : keyPairs ) {
AttributeValue hashKeyElement = getHashKeyElement(keyPair.getHashKey(), hashKeyGetter);
// Determine the range key, if provided
AttributeValue rangeKeyElement = null;
if ( keyPair.getRangeKey() != null ) {
Method rangeKeyMethod = reflector.getRangeKeyGetter(clazz);
if ( rangeKeyMethod == null ) {
throw new DynamoDBMappingException("Zero-parameter range key property must be annotated with "
+ DynamoDBRangeKey.class);
}
rangeKeyElement = getRangeKeyElement(keyPair.getRangeKey(), rangeKeyMethod);
}
if ( !requestItems.containsKey(tableName) ) {
requestItems
.put(tableName,
new KeysAndAttributes().withConsistentRead(consistentReads).withKeys(
new LinkedList()));
}
requestItems.get(tableName).getKeys()
.add(new Key().withHashKeyElement(hashKeyElement).withRangeKeyElement(rangeKeyElement));
// Reach the maximum number which can be handled in a single
// batchGet
if ( ++count == 100 ) {
processBatchGetRequest(classesByTableName, requestItems, resultSet);
requestItems.clear();
count = 0;
}
}
}
if ( count > 0 ) {
processBatchGetRequest(classesByTableName, requestItems, resultSet);
}
return resultSet;
}
private void processBatchGetRequest(Map> tableNameToClassMapper,
Map requestItems, Map> resultSet) {
BatchGetItemResult batchGetItemResult = null;
BatchGetItemRequest batchGetItemRequest = new BatchGetItemRequest();
batchGetItemRequest.setRequestItems(requestItems);
do {
if (batchGetItemResult != null) {
batchGetItemRequest.setRequestItems(batchGetItemResult.getUnprocessedKeys());
}
batchGetItemResult = db.batchGetItem(batchGetItemRequest);
Map responses = batchGetItemResult.getResponses();
for (String tableName : responses.keySet()) {
BatchResponse batchResponse = responses.get(tableName);
List objects = null;
if (resultSet.get(tableName) != null) {
objects = resultSet.get(tableName);
} else {
objects = new LinkedList();
}
List> items = batchResponse.getItems();
for (Map item : items) {
objects.add(marshallIntoObject(tableNameToClassMapper.get(tableName), item));
}
resultSet.put(tableName, objects);
}
// To see whether there are unprocessed keys.
} while (batchGetItemResult.getUnprocessedKeys() != null && batchGetItemResult.getUnprocessedKeys().size() > 0);
}
/**
* Check whether the batchGetRequest meet all the constraints.
*
* @param itemsToGet
*/
private boolean validBatchGetRequest(Map, List> itemsToGet) {
if (itemsToGet == null || itemsToGet.size() == 0) {
return false;
}
for (Class> clazz : itemsToGet.keySet()) {
if (itemsToGet.get(clazz) != null && itemsToGet.get(clazz).size() > 0) {
return true;
}
}
return false;
}
/**
* Swallows the checked exceptions around Method.invoke and repackages them
* as {@link DynamoDBMappingException}
*/
private Object safeInvoke(Method method, Object object, Object... arguments) {
try {
return method.invoke(object, arguments);
} catch ( IllegalAccessException e ) {
throw new DynamoDBMappingException("Couldn't invoke " + method, e);
} catch ( IllegalArgumentException e ) {
throw new DynamoDBMappingException("Couldn't invoke " + method, e);
} catch ( InvocationTargetException e ) {
throw new DynamoDBMappingException("Couldn't invoke " + method, e);
}
}
private final class ValueUpdate {
private Method method;
private AttributeValue newValue;
private Object target;
public ValueUpdate(Method method, AttributeValue newValue, Object target) {
this.method = method;
this.newValue = newValue;
this.target = target;
}
public void apply() {
setValue(target, method, newValue);
}
}
/**
* Converts the {@link AttributeValueUpdate} map given to an equivalent
* {@link AttributeValue} map.
*/
private Map convertToItem(Map putValues) {
Map map = new HashMap();
for ( Entry entry : putValues.entrySet() ) {
/*
* AttributeValueUpdate allows nulls for its values, since they are
* semantically meaningful. AttributeValues never have null values.
*/
if ( entry.getValue().getValue() != null )
map.put(entry.getKey(), entry.getValue().getValue());
}
return map;
}
/**
* Gets the attribute value object corresponding to the
* {@link DynamoDBVersionAttribute} getter, and its result, given. Null
* values are assumed to be new objects and given the smallest possible
* positive value. Non-null values are incremented from their current value.
*/
private AttributeValue getVersionAttributeValue(final Method getter, Object getterReturnResult) {
ArgumentMarshaller marshaller = reflector.getVersionedArgumentMarshaller(getter, getterReturnResult);
return marshaller.marshall(getterReturnResult);
}
/**
* Returns an attribute value corresponding to the key method and value given.
*/
private AttributeValue getAutoGeneratedKeyAttributeValue(Method getter, Object getterResult) {
ArgumentMarshaller marshaller = reflector.getAutoGeneratedKeyArgumentMarshaller(getter);
return marshaller.marshall(getterResult);
}
/**
* Scans through an Amazon DynamoDB table and returns the matching results as
* an unmodifiable list of instantiated objects, using the default configuration.
*
* @see DynamoDBMapper#scan(Class, DynamoDBScanExpression, DynamoDBMapperConfig)
*/
public PaginatedScanList scan(Class clazz, DynamoDBScanExpression scanExpression) {
return scan(clazz, scanExpression, config);
}
/**
* Scans through an Amazon DynamoDB table and returns the matching results as
* an unmodifiable list of instantiated objects. The table to scan is
* determined by looking at the annotations on the specified class, which
* declares where to store the object data in Amazon DynamoDB, and the scan
* expression parameter allows the caller to filter results and control how
* the scan is executed.
*
* Callers should be aware that the returned list is unmodifiable, and any
* attempts to modify the list will result in an
* UnsupportedOperationException.
*
* The unmodifiable list returned is lazily loaded when possible, so calls
* to DynamoDB will be made only as needed.
*
* @param
* The type of the objects being returned.
* @param clazz
* The class annotated with DynamoDB annotations describing how
* to store the object data in Amazon DynamoDB.
* @param scanExpression
* Details on how to run the scan, including any filters to apply
* to limit results.
* @param config
* The configuration to use for this scan, which overrides the
* default provided at object construction.
* @return An unmodifiable list of the objects constructed from the results
* of the scan operation.
* @see PaginatedScanList
*/
public PaginatedScanList scan(Class clazz, DynamoDBScanExpression scanExpression, DynamoDBMapperConfig config) {
config = mergeConfig(config);
ScanRequest scanRequest = createScanRequestFromExpression(clazz, scanExpression, config);
ScanResult scanResult = db.scan(applyUserAgent(scanRequest));
return new PaginatedScanList(this, clazz, db, scanRequest, scanResult);
}
/**
* Scans through an Amazon DynamoDB table and returns a single page of matching
* results. The table to scan is determined by looking at the annotations on
* the specified class, which declares where to store the object data in AWS
* DynamoDB, and the scan expression parameter allows the caller to filter
* results and control how the scan is executed.
*
* @param
* The type of the objects being returned.
* @param clazz
* The class annotated with DynamoDB annotations describing how
* to store the object data in Amazon DynamoDB.
* @param scanExpression
* Details on how to run the scan, including any filters to apply
* to limit results.
* @param config
* The configuration to use for this scan, which overrides the
* default provided at object construction.
*/
public ScanResultPage scanPage(Class clazz, DynamoDBScanExpression scanExpression, DynamoDBMapperConfig config) {
config = mergeConfig(config);
ScanRequest scanRequest = createScanRequestFromExpression(clazz, scanExpression, config);
ScanResult scanResult = db.scan(applyUserAgent(scanRequest));
ScanResultPage result = new ScanResultPage();
result.setResults(marshallIntoObjects(clazz, scanResult.getItems()));
result.setLastEvaluatedKey(scanResult.getLastEvaluatedKey());
return result;
}
/**
* Scans through an Amazon DynamoDB table and returns a single page of matching
* results.
*
* @see DynamoDBMapper#scanPage(Class, DynamoDBScanExpression, DynamoDBMapperConfig)
*/
public ScanResultPage scanPage(Class clazz, DynamoDBScanExpression scanExpression) {
return scanPage(clazz, scanExpression, this.config);
}
/**
* Queries an Amazon DynamoDB table and returns the matching results as an
* unmodifiable list of instantiated objects, using the default
* configuration.
*
* @see DynamoDBMapper#query(Class, DynamoDBQueryExpression,
* DynamoDBMapperConfig)
*/
public PaginatedQueryList query(Class clazz, DynamoDBQueryExpression queryExpression) {
return query(clazz, queryExpression, config);
}
/**
* Queries an Amazon DynamoDB table and returns the matching results as an
* unmodifiable list of instantiated objects. The table to query is
* determined by looking at the annotations on the specified class, which
* declares where to store the object data in Amazon DynamoDB, and the query
* expression parameter allows the caller to filter results and control how
* the query is executed.
*
* Callers should be aware that the returned list is unmodifiable, and any
* attempts to modify the list will result in an
* UnsupportedOperationException.
*
* The unmodifiable list returned is lazily loaded when possible, so calls
* to DynamoDB will be made only as needed.
*
* @param
* The type of the objects being returned.
* @param clazz
* The class annotated with DynamoDB annotations describing how
* to store the object data in Amazon DynamoDB.
* @param queryExpression
* Details on how to run the query, including any conditions on
* the key values
* @param config
* The configuration to use for this query, which overrides the
* default provided at object construction.
* @return An unmodifiable list of the objects constructed from the results
* of the query operation.
* @see PaginatedQueryList
*/
public PaginatedQueryList query(Class clazz, DynamoDBQueryExpression queryExpression, DynamoDBMapperConfig config) {
config = mergeConfig(config);
QueryRequest queryRequest = createQueryRequestFromExpression(clazz, queryExpression, config);
QueryResult queryResult = db.query(applyUserAgent(queryRequest));
return new PaginatedQueryList(this, clazz, db, queryRequest, queryResult);
}
/**
* Queries an Amazon DynamoDB table and returns a single page of matching
* results. The table to query is determined by looking at the annotations
* on the specified class, which declares where to store the object data in
* Amazon DynamoDB, and the query expression parameter allows the caller to
* filter results and control how the query is executed.
*
* @see DynamoDBMapper#queryPage(Class, DynamoDBQueryExpression, DynamoDBMapperConfig)
*/
public QueryResultPage queryPage(Class clazz, DynamoDBQueryExpression queryExpression) {
return queryPage(clazz, queryExpression, this.config);
}
/**
* Queries an Amazon DynamoDB table and returns a single page of matching
* results. The table to query is determined by looking at the annotations
* on the specified class, which declares where to store the object data in
* Amazon DynamoDB, and the query expression parameter allows the caller to
* filter results and control how the query is executed.
*
* @param
* The type of the objects being returned.
* @param clazz
* The class annotated with DynamoDB annotations describing how
* to store the object data in AWS DynamoDB.
* @param queryExpression
* Details on how to run the query, including any conditions on
* the key values
* @param config
* The configuration to use for this query, which overrides the
* default provided at object construction.
*/
public QueryResultPage queryPage(Class clazz, DynamoDBQueryExpression queryExpression, DynamoDBMapperConfig config) {
config = mergeConfig(config);
QueryRequest queryRequest = createQueryRequestFromExpression(clazz, queryExpression, config);
QueryResult scanResult = db.query(applyUserAgent(queryRequest));
QueryResultPage result = new QueryResultPage();
result.setResults(marshallIntoObjects(clazz, scanResult.getItems()));
result.setLastEvaluatedKey(scanResult.getLastEvaluatedKey());
return result;
}
/**
* Evaluates the specified scan expression and returns the count of matching
* items, without returning any of the actual item data, using the default configuration.
*
* @see DynamoDBMapper#count(Class, DynamoDBScanExpression, DynamoDBMapperConfig)
*/
public int count(Class> clazz, DynamoDBScanExpression scanExpression) {
return count(clazz, scanExpression, config);
}
/**
* Evaluates the specified scan expression and returns the count of matching
* items, without returning any of the actual item data.
*
* This operation will scan your entire table, and can therefore be very
* expensive. Use with caution.
*
* @param clazz
* The class mapped to a DynamoDB table.
* @param scanExpression
* The parameters for running the scan.
* @param config
* The configuration to use for this scan, which overrides the
* default provided at object construction.
* @return The count of matching items, without returning any of the actual
* item data.
*/
public int count(Class> clazz, DynamoDBScanExpression scanExpression, DynamoDBMapperConfig config) {
config = mergeConfig(config);
ScanRequest scanRequest = createScanRequestFromExpression(clazz, scanExpression, config);
scanRequest.setCount(true);
// Count scans can also be truncated for large datasets
int count = 0;
ScanResult scanResult = null;
do {
scanResult = db.scan(applyUserAgent(scanRequest));
count += scanResult.getCount();
scanRequest.setExclusiveStartKey(scanResult.getLastEvaluatedKey());
} while (scanResult.getLastEvaluatedKey() != null);
return count;
}
/**
* Evaluates the specified query expression and returns the count of matching
* items, without returning any of the actual item data, using the default configuration.
*
* @see DynamoDBMapper#count(Class, DynamoDBQueryExpression, DynamoDBMapperConfig)
*/
public int count(Class> clazz, DynamoDBQueryExpression queryExpression) {
return count(clazz, queryExpression, config);
}
/**
* Evaluates the specified query expression and returns the count of
* matching items, without returning any of the actual item data.
*
* @param clazz
* The class mapped to a DynamoDB table.
* @param queryExpression
* The parameters for running the scan.
* @param config
* The mapper configuration to use for the query, which overrides
* the default provided at object construction.
* @return The count of matching items, without returning any of the actual
* item data.
*/
public int count(Class> clazz, DynamoDBQueryExpression queryExpression, DynamoDBMapperConfig config) {
config = mergeConfig(config);
QueryRequest queryRequest = createQueryRequestFromExpression(clazz, queryExpression, config);
queryRequest.setCount(true);
// Count queries can also be truncated for large datasets
int count = 0;
QueryResult queryResult = null;
do {
queryResult = db.query(applyUserAgent(queryRequest));
count += queryResult.getCount();
queryRequest.setExclusiveStartKey(queryResult.getLastEvaluatedKey());
} while (queryResult.getLastEvaluatedKey() != null);
return count;
}
/**
* Merges the config object given with the one specified at construction and
* returns the result.
*/
private DynamoDBMapperConfig mergeConfig(DynamoDBMapperConfig config) {
if ( config != this.config )
config = new DynamoDBMapperConfig(this.config, config);
return config;
}
private ScanRequest createScanRequestFromExpression(Class> clazz, DynamoDBScanExpression scanExpression, DynamoDBMapperConfig config) {
ScanRequest scanRequest = new ScanRequest();
scanRequest.setTableName(getTableName(clazz, config));
scanRequest.setScanFilter(scanExpression.getScanFilter());
scanRequest.setLimit(scanExpression.getLimit());
scanRequest.setExclusiveStartKey(scanExpression.getExclusiveStartKey());
return scanRequest;
}
private QueryRequest createQueryRequestFromExpression(Class> clazz, DynamoDBQueryExpression queryExpression, DynamoDBMapperConfig config) {
QueryRequest queryRequest = new QueryRequest();
queryRequest.setConsistentRead(queryExpression.isConsistentRead());
queryRequest.setTableName(getTableName(clazz, config));
queryRequest.setHashKeyValue(queryExpression.getHashKeyValue());
queryRequest.setScanIndexForward(queryExpression.isScanIndexForward());
queryRequest.setRangeKeyCondition(queryExpression.getRangeKeyCondition());
queryRequest.setLimit(queryExpression.getLimit());
queryRequest.setExclusiveStartKey(queryExpression.getExclusiveStartKey());
return queryRequest;
}
/**
* By default, just calls {@link #untransformAttributes(String, String, Map)}.
* @param clazz
* @param attributeValues
* @return the decrypted attribute values
*/
protected Map untransformAttributes(Class> clazz, Map attributeValues) {
Method hashKeyGetter = reflector.getHashKeyGetter(clazz);
String hashKeyName = reflector.getAttributeName(hashKeyGetter);
Method rangeKeyGetter = reflector.getRangeKeyGetter(clazz);
String rangeKeyName = rangeKeyGetter == null ? null : reflector.getAttributeName(rangeKeyGetter);
return untransformAttributes(hashKeyName, rangeKeyName, attributeValues);
}
/**
* Transforms the attribute values after loading from DynamoDb.
* Only ever called by {@link #untransformAttributes(Class, Map)}.
* By default, returns the attributes unchanged. Currently, the
* values associated with the hash key and range key must be left unchanged.
*
* @param hashKey the attribute name of the hash key
* @param rangeKey the attribute name of the range key (or null if there is none)
* @param attributeValues
* @return the decrypted attributes
*/
protected Map untransformAttributes(String hashKey, String rangeKey,
Map attributeValues) {
return attributeValues;
}
/**
* By default, just calls {@link #transformAttributes(String, String, Map)}.
* @param clazz
* @param attributeValues
* @return the decrypted attribute values
*/
protected Map transformAttributes(Class> clazz, Map attributeValues) {
Method hashKeyGetter = reflector.getHashKeyGetter(clazz);
String hashKeyName = reflector.getAttributeName(hashKeyGetter);
Method rangeKeyGetter = reflector.getRangeKeyGetter(clazz);
String rangeKeyName = rangeKeyGetter == null ? null : reflector.getAttributeName(rangeKeyGetter);
return transformAttributes(hashKeyName, rangeKeyName, attributeValues);
}
/**
* Transform attribute values prior to storing in DynamoDB.
* Only ever called by {@link #transformAttributes(Class, Map)}.
* By default, returns the attributes unchanged.
*
* @param hashKey the attribute name of the hash key
* @param rangeKey the attribute name of the range key (or null if there is none)
* @param attributeValues
* @return the encrypted attributes
*/
protected Map transformAttributes(String hashKey, String rangeKey,
Map attributeValues) {
return attributeValues;
}
/**
* A transformation expects to see all values, including keys, when
* determining the transformation, therefore we must insert them if they are
* not already present. However, we must remove the keys prior to actual
* storage as this method is called when updating DynamoDB, which does
* not permit the modification of key attributes as part of an update call.
**/
private Map transformAttributeUpdates(Class> clazz,
Key objectKey, Map updateValues) {
Map item = convertToItem(updateValues);
boolean hashKeyAdded = false;
boolean rangeKeyAdded = false;
String hashKey = reflector.getAttributeName(reflector.getHashKeyGetter(clazz));
if(!item.containsKey(hashKey)) {
item.put(hashKey, objectKey.getHashKeyElement());
hashKeyAdded = true;
}
String rangeKey = null;
Method rangeKeyGetter = reflector.getRangeKeyGetter(clazz);
if (rangeKeyGetter != null) {
rangeKey = reflector.getAttributeName(rangeKeyGetter);
if (!item.containsKey(rangeKey)) {
item.put(rangeKey, objectKey.getRangeKeyElement());
rangeKeyAdded = true;
}
}
item = transformAttributes(clazz, item);
// Remove the keys if we added them before.
if (hashKeyAdded) {
item.remove(hashKey);
}
if (rangeKeyAdded) {
item.remove(rangeKey);
}
for(String key: item.keySet()) {
if (updateValues.containsKey(key)) {
updateValues.get(key).getValue()
.withB(item.get(key).getB())
.withBS(item.get(key).getBS())
.withN(item.get(key).getN())
.withNS(item.get(key).getNS())
.withS(item.get(key).getS())
.withSS(item.get(key).getSS());
} else {
updateValues.put(key, new AttributeValueUpdate(item.get(key), "PUT"));
}
}
return updateValues;
}
static X applyUserAgent(X request) {
request.getRequestClientOptions().appendUserAgent(USER_AGENT);
return request;
}
}