com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBReflector Maven / Gradle / Ivy
Show all versions of aws-sdk-android Show documentation
/*
* Copyright 2011-2015 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.dynamodbv2.datamodeling;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.apache.http.annotation.GuardedBy;
import com.amazonaws.services.dynamodbv2.datamodeling.ArgumentMarshaller.BinaryAttributeMarshaller;
import com.amazonaws.services.dynamodbv2.datamodeling.ArgumentMarshaller.BinarySetAttributeMarshaller;
import com.amazonaws.services.dynamodbv2.datamodeling.ArgumentMarshaller.NumberAttributeMarshaller;
import com.amazonaws.services.dynamodbv2.datamodeling.ArgumentMarshaller.NumberSetAttributeMarshaller;
import com.amazonaws.services.dynamodbv2.datamodeling.ArgumentMarshaller.StringAttributeMarshaller;
import com.amazonaws.services.dynamodbv2.datamodeling.ArgumentMarshaller.StringSetAttributeMarshaller;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.util.DateUtils;
/**
* Reflection assistant for {@link DynamoDBMapper}
* @deprecated These classes have been deprecated, please use the classes in the com.amazonaws.mobileconnectors namespace.
*/
public class DynamoDBReflector {
/*
* Several caches for performance. Collectively, they can make this class
* over twice as fast.
*/
private final Map, Collection> getterCache = new HashMap, Collection>();
private final Map, Method> primaryHashKeyGetterCache = new HashMap, Method>();
private final Map, Method> primaryRangeKeyGetterCache = new HashMap, Method>();
/*
* All caches keyed by a Method use the getter for a particular mapped
* property
*/
private final Map setterCache = new HashMap();
@GuardedBy("readWriteLockAttrName")
private final Map attributeNameCache = new HashMap();
private final Map argumentUnmarshallerCache = new HashMap();
private final Map argumentMarshallerCache = new HashMap();
private final Map versionArgumentMarshallerCache = new HashMap();
private final Map keyArgumentMarshallerCache = new HashMap();
private final Map versionAttributeGetterCache = new HashMap();
private final Map autoGeneratedKeyGetterCache = new HashMap();
private final ReentrantReadWriteLock readWriteLockAttrName = new ReentrantReadWriteLock();
private final ReadLock readLockAttrName = readWriteLockAttrName.readLock();
private final WriteLock writeLockAttrName = readWriteLockAttrName.writeLock();
/**
* Returns the set of getter methods which are relevant when marshalling or
* unmarshalling an object.
*/
Collection getRelevantGetters(Class> clazz) {
synchronized (getterCache) {
if ( !getterCache.containsKey(clazz) ) {
List relevantGetters = new LinkedList();
for ( Method m : clazz.getMethods() ) {
if ( isRelevantGetter(m) ) {
relevantGetters.add(m);
}
}
getterCache.put(clazz, relevantGetters);
}
return getterCache.get(clazz);
}
}
/**
* Returns whether the method given is a getter method we should serialize /
* deserialize to the service. The method must begin with "get" or "is",
* have no arguments, belong to a class that declares its table, and not be
* marked ignored.
*/
private boolean isRelevantGetter(Method m) {
return (m.getName().startsWith("get") || m.getName().startsWith("is"))
&& m.getParameterTypes().length == 0
&& m.getDeclaringClass().getAnnotation(DynamoDBTable.class) != null
&& !ReflectionUtils.getterOrFieldHasAnnotation(m, DynamoDBIgnore.class);
}
/**
* Returns the annotated {@link DynamoDBRangeKey} getter for the class
* given, or null if the class doesn't have one.
*/
Method getPrimaryRangeKeyGetter(Class clazz) {
synchronized (primaryRangeKeyGetterCache) {
if ( !primaryRangeKeyGetterCache.containsKey(clazz) ) {
Method rangeKeyMethod = null;
for ( Method method : getRelevantGetters(clazz) ) {
if ( method.getParameterTypes().length == 0
&& ReflectionUtils.getterOrFieldHasAnnotation(method, DynamoDBRangeKey.class)) {
rangeKeyMethod = method;
break;
}
}
primaryRangeKeyGetterCache.put(clazz, rangeKeyMethod);
}
return primaryRangeKeyGetterCache.get(clazz);
}
}
/**
* Returns all annotated {@link DynamoDBHashKey} and
* {@link DynamoDBRangeKey} getters for the class given, throwing an
* exception if there isn't one.
*
* TODO: caching
*/
Collection getPrimaryKeyGetters(Class clazz) {
List keyGetters = new LinkedList();
for (Method getter : getRelevantGetters(clazz)) {
if (ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBHashKey.class)
|| ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBRangeKey.class)) {
keyGetters.add(getter);
}
}
return keyGetters;
}
/**
* Returns the annotated {@link DynamoDBHashKey} getter for the class given,
* throwing an exception if there isn't one.
*/
Method getPrimaryHashKeyGetter(Class clazz) {
Method hashKeyMethod;
synchronized (primaryHashKeyGetterCache) {
if ( !primaryHashKeyGetterCache.containsKey(clazz) ) {
for ( Method method : getRelevantGetters(clazz) ) {
if ( method.getParameterTypes().length == 0
&& ReflectionUtils.getterOrFieldHasAnnotation(method, DynamoDBHashKey.class)) {
primaryHashKeyGetterCache.put(clazz, method);
break;
}
}
}
hashKeyMethod = primaryHashKeyGetterCache.get(clazz);
}
if ( hashKeyMethod == null ) {
throw new DynamoDBMappingException("Public, zero-parameter hash key property must be annotated with "
+ DynamoDBHashKey.class);
}
return hashKeyMethod;
}
/**
* Returns the {@link DynamoDBTable} annotation of the class given, throwing
* a runtime exception if it isn't annotated.
*/
DynamoDBTable getTable(Class clazz) {
DynamoDBTable table = clazz.getAnnotation(DynamoDBTable.class);
if ( table == null )
throw new DynamoDBMappingException("Class " + clazz + " must be annotated with " + DynamoDBTable.class);
return table;
}
/**
* Returns whether or not this getter has a custom marshaller
*/
private boolean isCustomMarshaller(Method getter) {
return ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBMarshalling.class);
}
/**
* Returns the argument unmarshaller used to unmarshall the getter / setter
* pair given.
*
* Determining how to unmarshall a response, especially a numeric one,
* requires checking it against all supported types. This is expensive, so
* we cache a lookup table of getter method to argument unmarhsaller which
* can be reused.
*
* @param toReturn
* The typed domain object being unmarshalled for the client
* @param getter
* The getter method being considered
* @param setter
* The corresponding setter method being considered
*/
ArgumentUnmarshaller getArgumentUnmarshaller(final T toReturn, final Method getter, final Method setter, S3ClientCache s3cc) {
synchronized (argumentUnmarshallerCache) {
ArgumentUnmarshaller unmarshaller = argumentUnmarshallerCache.get(getter);
if ( unmarshaller != null ) {
return unmarshaller;
}
Class>[] parameterTypes = setter.getParameterTypes();
Class> paramType = parameterTypes[0];
if ( parameterTypes.length != 1 ) {
throw new DynamoDBMappingException("Expected exactly one agument to " + setter);
}
if ( isCustomMarshaller(getter) ) {
unmarshaller = new SUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return getCustomMarshalledValue(toReturn, getter, value);
}
};
} else {
unmarshaller = computeArgumentUnmarshaller(toReturn, getter, setter, paramType, s3cc);
}
argumentUnmarshallerCache.put(getter, unmarshaller);
return unmarshaller;
}
}
/**
* Note this method is synchronized on {@link #argumentUnmarshallerCache} while being executed.
*/
private ArgumentUnmarshaller computeArgumentUnmarshaller(
final T toReturn, final Method getter, final Method setter, Class> paramType, S3ClientCache s3cc)
{
ArgumentUnmarshaller unmarshaller = null;
// If we're dealing with a collection, we need to get the
// underlying type out of it
final boolean isCollection = Set.class.isAssignableFrom(paramType);
if ( isCollection ) {
Type genericType = setter.getGenericParameterTypes()[0];
if ( genericType instanceof ParameterizedType ) {
if (((ParameterizedType) genericType).getActualTypeArguments()[0].toString().equals("byte[]")) {
paramType = byte[].class;
} else {
paramType = (Class>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
}
}
} else if ( Collection.class.isAssignableFrom(paramType) ) {
throw new DynamoDBMappingException("Only java.util.Set collection types are permitted for "
+ DynamoDBAttribute.class);
}
if ( double.class.isAssignableFrom(paramType) || Double.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new NSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getNS() ) {
argument.add(Double.parseDouble(s));
}
return argument;
}
};
} else {
unmarshaller = new NUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return Double.parseDouble(value.getN());
}
};
}
} else if ( BigDecimal.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new NSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getNS() ) {
argument.add(new BigDecimal(s));
}
return argument;
}
};
} else {
unmarshaller = new NUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return new BigDecimal(value.getN());
}
};
}
} else if ( BigInteger.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new NSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getNS() ) {
((Set) argument).add(new BigInteger(s));
}
return argument;
}
};
} else {
unmarshaller = new NUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return new BigInteger(value.getN());
}
};
}
} else if ( int.class.isAssignableFrom(paramType) || Integer.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new NSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getNS() ) {
argument.add(Integer.parseInt(s));
}
return argument;
}
};
} else {
unmarshaller = new NUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return Integer.parseInt(value.getN());
}
};
}
} else if ( float.class.isAssignableFrom(paramType) || Float.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new NSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getNS() ) {
argument.add(Float.parseFloat(s));
}
return argument;
}
};
} else {
unmarshaller = new NUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return Float.parseFloat(value.getN());
}
};
}
} else if ( byte.class.isAssignableFrom(paramType) || Byte.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new NSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getNS() ) {
argument.add(Byte.parseByte(s));
}
return argument;
}
};
} else {
unmarshaller = new NUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return Byte.parseByte(value.getN());
}
};
}
} else if ( long.class.isAssignableFrom(paramType) || Long.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new NSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getNS() ) {
argument.add(Long.parseLong(s));
}
return argument;
}
};
} else {
unmarshaller = new NUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return Long.parseLong(value.getN());
}
};
}
} else if ( short.class.isAssignableFrom(paramType) || Short.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new NSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getNS() ) {
argument.add(Short.parseShort(s));
}
return argument;
}
};
} else {
unmarshaller = new NUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return Short.parseShort(value.getN());
}
};
}
} else if ( boolean.class.isAssignableFrom(paramType) || Boolean.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new NSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getNS() ) {
argument.add(parseBoolean(s));
}
return argument;
}
};
} else {
unmarshaller = new NUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return parseBoolean(value.getN());
}
};
}
} else if ( Date.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new SSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) throws ParseException {
Set argument = new HashSet();
for ( String s : value.getSS() ) {
argument.add(DateUtils.parseISO8601Date(s));
}
return argument;
}
};
} else {
unmarshaller = new SUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) throws ParseException {
return DateUtils.parseISO8601Date(value.getS());
}
};
}
} else if ( Calendar.class.isAssignableFrom(paramType) ) {
if ( isCollection ) {
unmarshaller = new SSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) throws ParseException {
Set argument = new HashSet();
for ( String s : value.getSS() ) {
Calendar cal = GregorianCalendar.getInstance();
cal.setTime(DateUtils.parseISO8601Date(s));
argument.add(cal);
}
return argument;
}
};
} else {
unmarshaller = new SUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) throws ParseException {
Calendar cal = GregorianCalendar.getInstance();
cal.setTime(DateUtils.parseISO8601Date(value.getS()));
return cal;
}
};
}
} else if (ByteBuffer.class.isAssignableFrom(paramType)) {
if ( isCollection ) {
unmarshaller = new BSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) throws ParseException {
Set argument = new HashSet();
for (ByteBuffer b : value.getBS()) {
argument.add(b);
}
return argument;
}
};
} else {
unmarshaller = new BUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) throws ParseException {
return value.getB();
}
};
}
} else if (byte[].class.isAssignableFrom(paramType)) {
if ( isCollection ) {
unmarshaller = new BSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) throws ParseException {
Set argument = new HashSet();
for (ByteBuffer b : value.getBS()) {
byte[] bytes = null;
if (b.hasArray()) {
bytes = b.array();
} else {
bytes = new byte[b.limit()];
b.get(bytes, 0, bytes.length);
}
argument.add(bytes);
}
return argument;
}
};
} else {
unmarshaller = new BUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) throws ParseException {
ByteBuffer byteBuffer = value.getB();
byte[] bytes = null;
if (byteBuffer.hasArray()) {
bytes = byteBuffer.array();
} else {
bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes, 0, bytes.length);
}
return bytes;
}
};
}
} else {
unmarshaller = defaultArgumentUnmarshaller(paramType, isCollection, s3cc);
}
return unmarshaller;
}
/**
* Note this method is synchronized on {@link #argumentUnmarshallerCache} while being executed.
* @param paramType the parameter type or the element type if the parameter is a collection
* @param isCollection true if the parameter is a collection; false otherwise.
* @return the default unmarshaller
*/
private ArgumentUnmarshaller defaultArgumentUnmarshaller
(Class> paramType, boolean isCollection, final S3ClientCache s3cc)
{
if (S3Link.class.isAssignableFrom(paramType)) {
if ( isCollection ) {
throw new DynamoDBMappingException("Collection types are not permitted for " + S3Link.class);
} else {
return new SUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
if ( s3cc == null ) {
throw new IllegalStateException("Mapper must be constructed with S3 AWS Credentials to load S3Link");
}
// value should never be null
String json = value.getS();
return S3Link.fromJson(s3cc, json);
}
};
}
} else {
if ( !String.class.isAssignableFrom(paramType) ) {
throw new DynamoDBMappingException("Expected a String, but was " + paramType);
} else {
if ( isCollection ) {
return new SSUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
Set argument = new HashSet();
for ( String s : value.getSS() ) {
argument.add(s);
}
return argument;
}
};
} else {
return new SUnmarshaller() {
@Override
public Object unmarshall(AttributeValue value) {
return value.getS();
}
};
}
}
}
}
/**
* Marshalls the custom value given into the proper return type.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private T getCustomMarshalledValue(T toReturn, Method getter, AttributeValue value) {
DynamoDBMarshalling annotation = ReflectionUtils.getAnnotationFromGetterOrField(getter, DynamoDBMarshalling.class);
Class extends DynamoDBMarshaller extends Object>> marshallerClass = annotation.marshallerClass();
DynamoDBMarshaller marshaller;
try {
marshaller = marshallerClass.newInstance();
} catch ( InstantiationException e ) {
throw new DynamoDBMappingException("Couldn't instantiate marshaller of class " + marshallerClass, e);
} catch ( IllegalAccessException e ) {
throw new DynamoDBMappingException("Couldn't instantiate marshaller of class " + marshallerClass, e);
}
return (T) marshaller.unmarshall(getter.getReturnType(), value.getS());
}
/**
* Returns an attribute value for the getter method with a custom marshaller.
* Directly returns null when the custom marshaller returns a null String.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private AttributeValue getCustomerMarshallerAttributeValue(Method getter, Object getterReturnResult) {
DynamoDBMarshalling annotation = ReflectionUtils.getAnnotationFromGetterOrField(getter, DynamoDBMarshalling.class);
Class extends DynamoDBMarshaller extends Object>> marshallerClass = annotation.marshallerClass();
DynamoDBMarshaller marshaller;
try {
marshaller = marshallerClass.newInstance();
} catch ( InstantiationException e ) {
throw new DynamoDBMappingException("Failed to instantiate custom marshaller for class " + marshallerClass,
e);
} catch ( IllegalAccessException e ) {
throw new DynamoDBMappingException("Failed to instantiate custom marshaller for class " + marshallerClass,
e);
}
String stringValue = marshaller.marshall(getterReturnResult);
if(stringValue == null) {
return null;
} else {
return new AttributeValue().withS(stringValue);
}
}
/**
* Returns a marshaller that knows how to provide an AttributeValue for the
* result of the getter given.
*/
ArgumentMarshaller getArgumentMarshaller(final Method getter) {
synchronized (argumentMarshallerCache) {
ArgumentMarshaller marshaller = argumentMarshallerCache.get(getter);
if ( marshaller != null ) {
return marshaller;
}
if ( isCustomMarshaller(getter) ) {
// Custom marshaller always returns String attribute value.
marshaller = new StringAttributeMarshaller() {
@Override public AttributeValue marshall(Object obj) {
return getCustomerMarshallerAttributeValue(getter, obj);
}
};
} else {
marshaller = computeArgumentMarshaller(getter);
}
argumentMarshallerCache.put(getter, marshaller);
return marshaller;
}
}
/**
* Note this method is synchronized on {@link #argumentMarshallerCache} while being executed.
*/
private ArgumentMarshaller computeArgumentMarshaller(final Method getter) {
ArgumentMarshaller marshaller;
Class> returnType = getter.getReturnType();
if ( Set.class.isAssignableFrom(returnType) ) {
Type genericType = getter.getGenericReturnType();
if ( genericType instanceof ParameterizedType ) {
if ( ((ParameterizedType) genericType).getActualTypeArguments()[0].toString().equals("byte[]") ) {
returnType = byte[].class;
} else {
returnType = (Class>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
}
}
if ( Date.class.isAssignableFrom(returnType) ) {
marshaller = new StringSetAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
List timestamps = new LinkedList();
for ( Object o : (Set>) obj ) {
timestamps.add(DateUtils.formatISO8601Date((Date) o));
}
return new AttributeValue().withSS(timestamps);
}
};
} else if ( Calendar.class.isAssignableFrom(returnType) ) {
marshaller = new StringSetAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
List timestamps = new LinkedList();
for ( Object o : (Set>) obj ) {
timestamps.add(DateUtils.formatISO8601Date(((Calendar) o).getTime()));
}
return new AttributeValue().withSS(timestamps);
}
};
} else if ( boolean.class.isAssignableFrom(returnType)
|| Boolean.class.isAssignableFrom(returnType) ) {
marshaller = new NumberSetAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
List booleanAttributes = new ArrayList();
for ( Object b : (Set>) obj ) {
if ( b == null || !(Boolean) b ) {
booleanAttributes.add("0");
} else {
booleanAttributes.add("1");
}
}
return new AttributeValue().withNS(booleanAttributes);
}
};
} else if ( returnType.isPrimitive() || Number.class.isAssignableFrom(returnType) ) {
marshaller = new NumberSetAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
List attributes = new ArrayList();
for ( Object o : (Set>) obj ) {
attributes.add(String.valueOf(o));
}
return new AttributeValue().withNS(attributes);
}
};
} else if (ByteBuffer.class.isAssignableFrom(returnType)) {
marshaller = new BinarySetAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
List attributes = new ArrayList();
for ( Object o : (Set>) obj ) {
attributes.add((ByteBuffer) o);
}
return new AttributeValue().withBS(attributes);
}
};
} else if (byte[].class.isAssignableFrom(returnType)) {
marshaller = new BinarySetAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
List attributes = new ArrayList();
for ( Object o : (Set>) obj ) {
attributes.add(ByteBuffer.wrap((byte[])o));
}
return new AttributeValue().withBS(attributes);
}
};
} else {
// subclass may extend the behavior by overriding the
// defaultCollectionArgumentMarshaller method
marshaller = defaultCollectionArgumentMarshaller(returnType);
}
} else if ( Collection.class.isAssignableFrom(returnType) ) {
throw new DynamoDBMappingException("Non-set collections aren't supported: "
+ (getter.getDeclaringClass() + "." + getter.getName()));
} else { // Non-set return type
if ( Date.class.isAssignableFrom(returnType) ) {
marshaller = new StringAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
return new AttributeValue().withS(DateUtils.formatISO8601Date((Date) obj));
}
};
} else if ( Calendar.class.isAssignableFrom(returnType) ) {
marshaller = new StringAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
return new AttributeValue().withS(DateUtils
.formatISO8601Date(((Calendar) obj).getTime()));
}
};
} else if ( boolean.class.isAssignableFrom(returnType)
|| Boolean.class.isAssignableFrom(returnType) ) {
marshaller = new NumberAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
if ( obj == null || !(Boolean) obj ) {
return new AttributeValue().withN("0");
} else {
return new AttributeValue().withN("1");
}
}
};
} else if ( returnType.isPrimitive() || Number.class.isAssignableFrom(returnType) ) {
marshaller = new NumberAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
return new AttributeValue().withN(String.valueOf(obj));
}
};
} else if ( returnType == String.class ) {
marshaller = new StringAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
if ( ((String) obj).length() == 0 )
return null;
return new AttributeValue().withS(String.valueOf(obj));
}
};
} else if ( returnType == ByteBuffer.class ) {
marshaller = new BinaryAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
return new AttributeValue().withB((ByteBuffer)obj);
}
};
} else if ( returnType == byte[].class) {
marshaller = new BinaryAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
return new AttributeValue().withB(ByteBuffer.wrap((byte[])obj));
}
};
} else {
marshaller = defaultArgumentMarshaller(returnType, getter);
}
}
return marshaller;
}
/**
* Note this method is synchronized on {@link #argumentMarshallerCache} while being executed.
* @param returnElementType the element of the return type which is known to be a collection
* @return the default argument marshaller for a collection
*/
private ArgumentMarshaller defaultCollectionArgumentMarshaller(final Class> returnElementType) {
if ( S3Link.class.isAssignableFrom(returnElementType) ) {
throw new DynamoDBMappingException("Collection types not permitted for " + S3Link.class);
} else {
return new StringSetAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
List attributes = new ArrayList();
for ( Object o : (Set>) obj ) {
attributes.add(String.valueOf(o));
}
return new AttributeValue().withSS(attributes);
}
};
}
}
/**
* Note this method is synchronized on {@link #argumentMarshallerCache} while being executed.
* @param returnType the return type
* @return the default argument marshaller
*/
private ArgumentMarshaller defaultArgumentMarshaller(final Class> returnType, final Method getter) {
if ( returnType == S3Link.class ) {
return new StringAttributeMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
S3Link s3link = (S3Link) obj;
if ( s3link.getBucketName() == null || s3link.getKey() == null ) {
// insufficient S3 resource specification
return null;
}
String json = s3link.toJson();
return new AttributeValue().withS(json);
}
};
} else {
throw new DynamoDBMappingException("Unsupported type: " + returnType + " for " + getter);
}
}
/**
* Attempts to parse the string given as a boolean and return its value.
* Throws an exception if the value is anything other than 0 or 1.
*/
private boolean parseBoolean(String s) {
if ( "1".equals(s) ) {
return true;
} else if ( "0".equals(s) ) {
return false;
} else {
throw new IllegalArgumentException("Expected 1 or 0 for boolean value, was " + s);
}
}
/**
* Returns the attribute name corresponding to the given getter method.
*/
String getAttributeName(Method getter) {
String attributeName;
readLockAttrName.lock();
try {
attributeName = attributeNameCache.get(getter);
} finally {
readLockAttrName.unlock();
}
if ( attributeName != null )
return attributeName;
DynamoDBHashKey hashKeyAnnotation = ReflectionUtils.getAnnotationFromGetterOrField(getter, DynamoDBHashKey.class);
if ( hashKeyAnnotation != null ) {
attributeName = hashKeyAnnotation.attributeName();
if ( attributeName != null && attributeName.length() > 0 )
return cacheAttributeName(getter, attributeName);
}
DynamoDBIndexHashKey indexHashKey = ReflectionUtils.getAnnotationFromGetterOrField(getter, DynamoDBIndexHashKey.class);
if ( indexHashKey != null ) {
attributeName = indexHashKey.attributeName();
if ( attributeName != null && attributeName.length() > 0 )
return cacheAttributeName(getter, attributeName);
}
DynamoDBRangeKey rangeKey = ReflectionUtils.getAnnotationFromGetterOrField(getter, DynamoDBRangeKey.class);
if ( rangeKey != null ) {
attributeName = rangeKey.attributeName();
if ( attributeName != null && attributeName.length() > 0 )
return cacheAttributeName(getter, attributeName);
}
DynamoDBIndexRangeKey indexRangeKey = ReflectionUtils.getAnnotationFromGetterOrField(getter, DynamoDBIndexRangeKey.class);
if ( indexRangeKey != null ) {
attributeName = indexRangeKey.attributeName();
if ( attributeName != null && attributeName.length() > 0 )
return cacheAttributeName(getter, attributeName);
}
DynamoDBAttribute attribute = ReflectionUtils.getAnnotationFromGetterOrField(getter, DynamoDBAttribute.class);
if ( attribute != null ) {
attributeName = attribute.attributeName();
if ( attributeName != null && attributeName.length() > 0 )
return cacheAttributeName(getter, attributeName);
}
DynamoDBVersionAttribute version = ReflectionUtils.getAnnotationFromGetterOrField(getter, DynamoDBVersionAttribute.class);
if ( version != null ) {
attributeName = version.attributeName();
if ( attributeName != null && attributeName.length() > 0 )
return cacheAttributeName(getter, attributeName);
}
// Default to the camel-cased field name of the getter method, inferred
// according to the Java naming convention.
attributeName = ReflectionUtils.getFieldNameByGetter(getter, true);
return cacheAttributeName(getter, attributeName);
}
private String cacheAttributeName(Method getter, String attributeName) {
writeLockAttrName.lock();
try {
attributeNameCache.put(getter, attributeName);
} finally {
writeLockAttrName.unlock();
}
return attributeName;
}
/**
* Returns the setter corresponding to the getter given, or null if no such
* setter exists.
*/
Method getSetter(Method getter) {
synchronized (setterCache) {
if ( !setterCache.containsKey(getter) ) {
String fieldName = ReflectionUtils.getFieldNameByGetter(getter, false);
String setterName = "set" + fieldName;
Method setter = null;
try {
setter = getter.getDeclaringClass().getMethod(setterName, getter.getReturnType());
} catch ( NoSuchMethodException e ) {
throw new DynamoDBMappingException("Expected a public, one-argument method called " + setterName
+ " on class " + getter.getDeclaringClass(), e);
} catch ( SecurityException e ) {
throw new DynamoDBMappingException("No access to public, one-argument method called " + setterName
+ " on class " + getter.getDeclaringClass(), e);
}
setterCache.put(getter, setter);
}
return setterCache.get(getter);
}
}
/**
* Returns a marshaller that knows how to provide an AttributeValue for the
* getter method given. Also increments the value of the getterReturnResult
* given.
*/
ArgumentMarshaller getVersionedArgumentMarshaller(final Method getter, Object getterReturnResult) {
synchronized (versionArgumentMarshallerCache) {
if ( !versionArgumentMarshallerCache.containsKey(getter) ) {
ArgumentMarshaller marshaller = null;
final Class> returnType = getter.getReturnType();
if ( BigInteger.class.isAssignableFrom(returnType) ) {
marshaller = new ArgumentMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
if ( obj == null )
obj = BigInteger.ZERO;
Object newValue = ((BigInteger) obj).add(BigInteger.ONE);
return getArgumentMarshaller(getter).marshall(newValue);
}
};
} else if ( Integer.class.isAssignableFrom(returnType) ) {
marshaller = new ArgumentMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
if ( obj == null )
obj = new Integer(0);
Object newValue = ((Integer) obj).intValue() + 1;
return getArgumentMarshaller(getter).marshall(newValue);
}
};
} else if ( Byte.class.isAssignableFrom(returnType) ) {
marshaller = new ArgumentMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
if ( obj == null )
obj = new Byte((byte) 0);
Object newValue = (byte) ((((Byte) obj).byteValue() + 1) % Byte.MAX_VALUE);
return getArgumentMarshaller(getter).marshall(newValue);
}
};
} else if ( Long.class.isAssignableFrom(returnType) ) {
marshaller = new ArgumentMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
if ( obj == null )
obj = new Long(0);
Object newValue = ((Long) obj).longValue() + 1L;
return getArgumentMarshaller(getter).marshall(newValue);
}
};
} else {
throw new DynamoDBMappingException("Unsupported parameter type for "
+ DynamoDBVersionAttribute.class + ": " + returnType + ". Must be a whole-number type.");
}
versionArgumentMarshallerCache.put(getter, marshaller);
}
return versionArgumentMarshallerCache.get(getter);
}
}
/**
* Returns a marshaller for the auto-generated key returned by the getter given.
*/
ArgumentMarshaller getAutoGeneratedKeyArgumentMarshaller(final Method getter) {
synchronized (keyArgumentMarshallerCache) {
if ( !keyArgumentMarshallerCache.containsKey(getter) ) {
ArgumentMarshaller marshaller = null;
Class> returnType = getter.getReturnType();
if ( String.class.isAssignableFrom(returnType) ) {
marshaller = new ArgumentMarshaller() {
@Override
public AttributeValue marshall(Object obj) {
String newValue = UUID.randomUUID().toString();
return getArgumentMarshaller(getter).marshall(newValue);
}
};
} else {
throw new DynamoDBMappingException("Unsupported type for " + getter + ": " + returnType
+ ". Only Strings are supported when auto-generating keys.");
}
keyArgumentMarshallerCache.put(getter, marshaller);
}
return keyArgumentMarshallerCache.get(getter);
}
}
/**
* Returns whether the method given is an annotated, no-args getter of a
* version attribute.
*/
boolean isVersionAttributeGetter(Method getter) {
synchronized (versionAttributeGetterCache) {
if ( !versionAttributeGetterCache.containsKey(getter) ) {
versionAttributeGetterCache.put(
getter,
getter.getName().startsWith("get") && getter.getParameterTypes().length == 0
&& ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBVersionAttribute.class));
}
return versionAttributeGetterCache.get(getter);
}
}
/**
* Returns whether the method given is an assignable key getter.
*/
boolean isAssignableKey(Method getter) {
synchronized (autoGeneratedKeyGetterCache) {
if ( !autoGeneratedKeyGetterCache.containsKey(getter) ) {
autoGeneratedKeyGetterCache.put(
getter,
ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBAutoGeneratedKey.class)
&& ( ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBHashKey.class) ||
ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBRangeKey.class)));
}
return autoGeneratedKeyGetterCache.get(getter);
}
}
/**
* Returns the name of the primary hash key.
*/
String getPrimaryHashKeyName(Class> clazz) {
return getAttributeName(getPrimaryHashKeyGetter(clazz));
}
/**
* Returns the name of the primary range key, or null if the table does not
* one.
*/
String getPrimaryRangeKeyName(Class> clazz) {
Method primaryRangeKeyGetter = getPrimaryHashKeyGetter(clazz);
return primaryRangeKeyGetter == null ?
null
:
getAttributeName(getPrimaryRangeKeyGetter(clazz));
}
/**
* Returns true if and only if the specified class has declared a
* primary range key.
*/
boolean hasPrimaryRangeKey(Class> clazz) {
return getPrimaryRangeKeyGetter(clazz) != null;
}
}