/* * Copyright 2011-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://aws.amazon.com/apache2.0 * * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES * OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and * limitations under the License. */ package com.amazonaws.services.dynamodbv2.datamodeling; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import com.amazonaws.annotation.GuardedBy; import com.amazonaws.annotation.SdkInternalApi; /** * Reflection assistant for {@link DynamoDBMapper} * * @deprecated This class is internal only and should not be used. It will be removed in the next * major version of the SDK. */ @Deprecated @SdkInternalApi 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>(); private final Map<Class<?>, List<Method>> primaryKeyGettersCache = new HashMap<Class<?>, List<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, 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 = findRelevantGetters(clazz); getterCache.put(clazz, relevantGetters); } return getterCache.get(clazz); } } static List<Method> findRelevantGetters(Class<?> clazz) { List<Method> relevantGetters = new LinkedList<Method>(); for ( Method m : clazz.getMethods() ) { if ( isRelevantGetter(m) ) { relevantGetters.add(m); } } return relevantGetters; } /** * 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 static boolean isRelevantGetter(Method m) { return (m.getName().startsWith("get") || m.getName().startsWith("is")) && m.getParameterTypes().length == 0 && ! (m.isBridge() || m.isSynthetic()) && isDocumentType(m.getDeclaringClass()) && !ReflectionUtils.getterOrFieldHasAnnotation(m, DynamoDBIgnore.class); } private static boolean isDocumentType(Class<?> clazz) { return (clazz.getAnnotation(DynamoDBTable.class) != null) || (clazz.getAnnotation(DynamoDBDocument.class) != null); } /** * 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. */ <T> Collection<Method> getPrimaryKeyGetters(Class<T> clazz) { synchronized (primaryKeyGettersCache) { if ( !primaryKeyGettersCache.containsKey(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); } } primaryKeyGettersCache.put(clazz, keyGetters); } return primaryKeyGettersCache.get(clazz); } } /** * 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 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 " + getter.getDeclaringClass(), e); } catch ( SecurityException e ) { throw new DynamoDBMappingException("No access to public, one-argument method called " + setterName + " on " + getter.getDeclaringClass(), e); } setterCache.put(getter, setter); } return setterCache.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) || ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBIndexHashKey.class) || ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBIndexRangeKey.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; } }