Package com.amazonaws.services.dynamodbv2.datamodeling

Source Code of com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBReflector

/*
* 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.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}
*/
public class DynamoDBReflector {

    /*
     * Several caches for performance. Collectively, they can make this class
     * over twice as fast.
     */
    private final Map<Class<?>, Collection<Method>> getterCache = new HashMap<Class<?>, Collection<Method>>();
    private final Map<Class<?>, Method> primaryHashKeyGetterCache = new HashMap<Class<?>, Method>();
    private final Map<Class<?>, Method> primaryRangeKeyGetterCache = new HashMap<Class<?>, Method>();

    /*
     * All caches keyed by a Method use the getter for a particular mapped
     * property
     */
    private final Map<Method, Method> setterCache = new HashMap<Method, Method>();

    @GuardedBy("readWriteLockAttrName")
    private final Map<Method, String> attributeNameCache = new HashMap<Method, String>();

    private final Map<Method, ArgumentUnmarshaller> argumentUnmarshallerCache = new HashMap<Method, ArgumentUnmarshaller>();
    private final Map<Method, ArgumentMarshaller> argumentMarshallerCache = new HashMap<Method, ArgumentMarshaller>();
    private final Map<Method, ArgumentMarshaller> versionArgumentMarshallerCache = new HashMap<Method, ArgumentMarshaller>();
    private final Map<Method, ArgumentMarshaller> keyArgumentMarshallerCache = new HashMap<Method, ArgumentMarshaller>();
    private final Map<Method, Boolean> versionAttributeGetterCache = new HashMap<Method, Boolean>();
    private final Map<Method, Boolean> autoGeneratedKeyGetterCache = new HashMap<Method, Boolean>();

    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<Method> getRelevantGetters(Class<?> clazz) {
        synchronized (getterCache) {
            if ( !getterCache.containsKey(clazz) ) {
                List<Method> relevantGetters = new LinkedList<Method>();
                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.
     */
    <T> Method getPrimaryRangeKeyGetter(Class<T> 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
     */
    <T> Collection<Method> getPrimaryKeyGetters(Class<T> clazz) {
        List<Method> keyGetters = new LinkedList<Method>();
        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.
     */
    <T> Method getPrimaryHashKeyGetter(Class<T> 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.
     */
    <T> DynamoDBTable getTable(Class<T> 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.
     * <p>
     * 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
     */
    <T> 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 <T> 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<Double> argument = new HashSet<Double>();
                        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<BigDecimal> argument = new HashSet<BigDecimal>();
                        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<BigInteger> argument = new HashSet<BigInteger>();
                        for ( String s : value.getNS() ) {
                            ((Set<BigInteger>) 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<Integer> argument = new HashSet<Integer>();
                        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<Float> argument = new HashSet<Float>();
                        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<Byte> argument = new HashSet<Byte>();
                        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<Long> argument = new HashSet<Long>();
                        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<Short> argument = new HashSet<Short>();
                        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<Boolean> argument = new HashSet<Boolean>();
                        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<Date> argument = new HashSet<Date>();
                        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<Calendar> argument = new HashSet<Calendar>();
                        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<ByteBuffer> argument = new HashSet<ByteBuffer>();
                         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<byte[]> argument = new HashSet<byte[]>();
                     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<String> argument = new HashSet<String>();
                            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> 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<String> timestamps = new LinkedList<String>();
                        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<String> timestamps = new LinkedList<String>();
                        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<String> booleanAttributes = new ArrayList<String>();
                        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<String> attributes = new ArrayList<String>();
                        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<ByteBuffer> attributes = new ArrayList<ByteBuffer>();
                         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<ByteBuffer> attributes = new ArrayList<ByteBuffer>();
                         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<String> attributes = new ArrayList<String>();
                    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;
    }
}
TOP

Related Classes of com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBReflector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.