/*
* Copyright 2013 the original author or authors.
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.socialsignin.spring.data.dynamodb.repository.support;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.MethodCallback;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMarshaller;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMarshalling;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBVersionAttribute;
/**
* @author Michael Lavelle
*/
public class DynamoDBEntityMetadataSupport<T, ID extends Serializable> implements DynamoDBHashKeyExtractingEntityMetadata<T> {
private final Class<T> domainType;
private boolean hasRangeKey;
private String hashKeyPropertyName;
private List<String> globalIndexHashKeyPropertyNames;
private List<String> globalIndexRangeKeyPropertyNames;
private String dynamoDBTableName;
private Map<String, String[]> globalSecondaryIndexNames = new HashMap<String, String[]>();
@Override
public String getDynamoDBTableName() {
return dynamoDBTableName;
}
/**
* Creates a new {@link DynamoDBEntityMetadataSupport} for the given domain type.
*
* @param domainType
* must not be {@literal null}.
*/
public DynamoDBEntityMetadataSupport(final Class<T> domainType) {
Assert.notNull(domainType, "Domain type must not be null!");
this.domainType = domainType;
DynamoDBTable table = this.domainType.getAnnotation(DynamoDBTable.class);
Assert.notNull(table, "Domain type must by annotated with DynamoDBTable!");
this.dynamoDBTableName = table.tableName();
this.globalSecondaryIndexNames = new HashMap<String, String[]>();
this.globalIndexHashKeyPropertyNames = new ArrayList<String>();
this.globalIndexRangeKeyPropertyNames = new ArrayList<String>();
ReflectionUtils.doWithMethods(domainType, new MethodCallback() {
@Override
public void doWith(Method method) {
if (method.getAnnotation(DynamoDBHashKey.class) != null) {
hashKeyPropertyName = getPropertyNameForAccessorMethod(method);
}
if (method.getAnnotation(DynamoDBRangeKey.class) != null) {
hasRangeKey = true;
}
DynamoDBIndexRangeKey dynamoDBRangeKeyAnnotation = method.getAnnotation(DynamoDBIndexRangeKey.class);
DynamoDBIndexHashKey dynamoDBHashKeyAnnotation = method.getAnnotation(DynamoDBIndexHashKey.class);
if (dynamoDBRangeKeyAnnotation != null) {
addGlobalSecondaryIndexNames(method, dynamoDBRangeKeyAnnotation);
}
if (dynamoDBHashKeyAnnotation != null) {
addGlobalSecondaryIndexNames(method, dynamoDBHashKeyAnnotation);
}
}
});
ReflectionUtils.doWithFields(domainType, new FieldCallback() {
@Override
public void doWith(Field field) {
if (field.getAnnotation(DynamoDBHashKey.class) != null) {
hashKeyPropertyName = getPropertyNameForField(field);
}
if (field.getAnnotation(DynamoDBRangeKey.class) != null) {
hasRangeKey = true;
}
DynamoDBIndexRangeKey dynamoDBRangeKeyAnnotation = field.getAnnotation(DynamoDBIndexRangeKey.class);
DynamoDBIndexHashKey dynamoDBHashKeyAnnotation = field.getAnnotation(DynamoDBIndexHashKey.class);
if (dynamoDBRangeKeyAnnotation != null) {
addGlobalSecondaryIndexNames(field, dynamoDBRangeKeyAnnotation);
}
if (dynamoDBHashKeyAnnotation != null) {
addGlobalSecondaryIndexNames(field, dynamoDBHashKeyAnnotation);
}
}
});
Assert.notNull(hashKeyPropertyName, "Unable to find hash key field or getter method on " + domainType + "!");
}
public DynamoDBEntityInformation<T, ID> getEntityInformation() {
if (hasRangeKey) {
DynamoDBHashAndRangeKeyExtractingEntityMetadataImpl<T, ID> metadata = new DynamoDBHashAndRangeKeyExtractingEntityMetadataImpl<T, ID>(
domainType);
return new DynamoDBIdIsHashAndRangeKeyEntityInformationImpl<T, ID>(domainType, metadata);
} else {
return new DynamoDBIdIsHashKeyEntityInformationImpl<T, ID>(domainType, this);
}
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.core.EntityMetadata#getJavaType()
*/
@Override
public Class<T> getJavaType() {
return domainType;
}
@Override
public boolean isHashKeyProperty(String propertyName) {
return hashKeyPropertyName.equals(propertyName);
}
protected boolean isFieldAnnotatedWith(final String propertyName, final Class<? extends Annotation> annotation) {
Field field = findField(propertyName);
return field != null && field.getAnnotation(annotation) != null;
}
private String toGetMethodName(String propertyName) {
String methodName = propertyName.substring(0, 1).toUpperCase();
if (propertyName.length() > 1) {
methodName = methodName + propertyName.substring(1);
}
return "get" + methodName;
}
protected String toSetterMethodNameFromAccessorMethod(Method method) {
String accessorMethodName = method.getName();
if (accessorMethodName.startsWith("get")) {
return "set" + accessorMethodName.substring(3);
} else if (accessorMethodName.startsWith("is")) {
return "is" + accessorMethodName.substring(2);
}
return null;
}
private String toIsMethodName(String propertyName) {
String methodName = propertyName.substring(0, 1).toUpperCase();
if (propertyName.length() > 1) {
methodName = methodName + propertyName.substring(1);
}
return "is" + methodName;
}
private Method findMethod(String propertyName) {
Method method = ReflectionUtils.findMethod(domainType, toGetMethodName(propertyName));
if (method == null) {
method = ReflectionUtils.findMethod(domainType, toIsMethodName(propertyName));
}
return method;
}
private Field findField(String propertyName) {
return ReflectionUtils.findField(domainType, propertyName);
}
public String getOverriddenAttributeName(Method method) {
if (method != null) {
if (method.getAnnotation(DynamoDBAttribute.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBAttribute.class).attributeName())) {
return method.getAnnotation(DynamoDBAttribute.class).attributeName();
}
if (method.getAnnotation(DynamoDBHashKey.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBHashKey.class).attributeName())) {
return method.getAnnotation(DynamoDBHashKey.class).attributeName();
}
if (method.getAnnotation(DynamoDBRangeKey.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBRangeKey.class).attributeName())) {
return method.getAnnotation(DynamoDBRangeKey.class).attributeName();
}
if (method.getAnnotation(DynamoDBIndexRangeKey.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBIndexRangeKey.class).attributeName())) {
return method.getAnnotation(DynamoDBIndexRangeKey.class).attributeName();
}
if (method.getAnnotation(DynamoDBIndexHashKey.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBIndexHashKey.class).attributeName())) {
return method.getAnnotation(DynamoDBIndexHashKey.class).attributeName();
}
if (method.getAnnotation(DynamoDBVersionAttribute.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBVersionAttribute.class).attributeName())) {
return method.getAnnotation(DynamoDBVersionAttribute.class).attributeName();
}
}
return null;
}
@Override
public String getOverriddenAttributeName(final String propertyName) {
Method method = findMethod(propertyName);
if (method != null) {
if (method.getAnnotation(DynamoDBAttribute.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBAttribute.class).attributeName())) {
return method.getAnnotation(DynamoDBAttribute.class).attributeName();
}
if (method.getAnnotation(DynamoDBHashKey.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBHashKey.class).attributeName())) {
return method.getAnnotation(DynamoDBHashKey.class).attributeName();
}
if (method.getAnnotation(DynamoDBRangeKey.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBRangeKey.class).attributeName())) {
return method.getAnnotation(DynamoDBRangeKey.class).attributeName();
}
if (method.getAnnotation(DynamoDBIndexRangeKey.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBIndexRangeKey.class).attributeName())) {
return method.getAnnotation(DynamoDBIndexRangeKey.class).attributeName();
}
if (method.getAnnotation(DynamoDBIndexHashKey.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBIndexHashKey.class).attributeName())) {
return method.getAnnotation(DynamoDBIndexHashKey.class).attributeName();
}
if (method.getAnnotation(DynamoDBVersionAttribute.class) != null
&& StringUtils.isNotEmpty(method.getAnnotation(DynamoDBVersionAttribute.class).attributeName())) {
return method.getAnnotation(DynamoDBVersionAttribute.class).attributeName();
}
}
Field field = findField(propertyName);
if (field != null) {
if (field.getAnnotation(DynamoDBAttribute.class) != null
&& StringUtils.isNotEmpty(field.getAnnotation(DynamoDBAttribute.class).attributeName())) {
return field.getAnnotation(DynamoDBAttribute.class).attributeName();
}
if (field.getAnnotation(DynamoDBHashKey.class) != null
&& StringUtils.isNotEmpty(field.getAnnotation(DynamoDBHashKey.class).attributeName())) {
return field.getAnnotation(DynamoDBHashKey.class).attributeName();
}
if (field.getAnnotation(DynamoDBRangeKey.class) != null
&& StringUtils.isNotEmpty(field.getAnnotation(DynamoDBRangeKey.class).attributeName())) {
return field.getAnnotation(DynamoDBRangeKey.class).attributeName();
}
if (field.getAnnotation(DynamoDBIndexRangeKey.class) != null
&& StringUtils.isNotEmpty(field.getAnnotation(DynamoDBIndexRangeKey.class).attributeName())) {
return field.getAnnotation(DynamoDBIndexRangeKey.class).attributeName();
}
if (field.getAnnotation(DynamoDBIndexHashKey.class) != null
&& StringUtils.isNotEmpty(field.getAnnotation(DynamoDBIndexHashKey.class).attributeName())) {
return field.getAnnotation(DynamoDBIndexHashKey.class).attributeName();
}
if (field.getAnnotation(DynamoDBVersionAttribute.class) != null
&& StringUtils.isNotEmpty(field.getAnnotation(DynamoDBVersionAttribute.class).attributeName())) {
return field.getAnnotation(DynamoDBVersionAttribute.class).attributeName();
}
}
return null;
}
@Override
public DynamoDBMarshaller<?> getMarshallerForProperty(final String propertyName) {
Method method = findMethod(propertyName);
if (method != null && method.getAnnotation(DynamoDBMarshalling.class) != null) {
try {
return method.getAnnotation(DynamoDBMarshalling.class).marshallerClass().newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return null;
}
protected String getPropertyNameForAccessorMethod(Method method) {
String methodName = method.getName();
String propertyName = null;
if (methodName.startsWith("get")) {
propertyName = methodName.substring(3);
} else if (methodName.startsWith("is")) {
propertyName = methodName.substring(2);
}
Assert.notNull(propertyName, "Hash or range key annotated accessor methods must start with 'get' or 'is'");
String firstLetter = propertyName.substring(0, 1);
String remainder = propertyName.substring(1);
return firstLetter.toLowerCase() + remainder;
}
protected String getPropertyNameForField(Field field) {
return field.getName();
}
@Override
public String getHashKeyPropertyName() {
return hashKeyPropertyName;
}
private void addGlobalSecondaryIndexNames(Method method, DynamoDBIndexRangeKey dynamoDBIndexRangeKey) {
if (dynamoDBIndexRangeKey.globalSecondaryIndexNames() != null
&& dynamoDBIndexRangeKey.globalSecondaryIndexNames().length > 0) {
String propertyName = getPropertyNameForAccessorMethod(method);
globalSecondaryIndexNames.put(propertyName, method.getAnnotation(DynamoDBIndexRangeKey.class)
.globalSecondaryIndexNames());
globalIndexRangeKeyPropertyNames.add(propertyName);
}
if (dynamoDBIndexRangeKey.globalSecondaryIndexName() != null
&& dynamoDBIndexRangeKey.globalSecondaryIndexName().trim().length() > 0) {
String propertyName = getPropertyNameForAccessorMethod(method);
globalSecondaryIndexNames.put(propertyName,
new String[] { method.getAnnotation(DynamoDBIndexRangeKey.class).globalSecondaryIndexName() });
globalIndexRangeKeyPropertyNames.add(propertyName);
}
}
private void addGlobalSecondaryIndexNames(Field field, DynamoDBIndexRangeKey dynamoDBIndexRangeKey) {
if (dynamoDBIndexRangeKey.globalSecondaryIndexNames() != null
&& dynamoDBIndexRangeKey.globalSecondaryIndexNames().length > 0) {
String propertyName = getPropertyNameForField(field);
globalSecondaryIndexNames.put(propertyName, field.getAnnotation(DynamoDBIndexRangeKey.class)
.globalSecondaryIndexNames());
globalIndexRangeKeyPropertyNames.add(propertyName);
}
if (dynamoDBIndexRangeKey.globalSecondaryIndexName() != null
&& dynamoDBIndexRangeKey.globalSecondaryIndexName().trim().length() > 0) {
String propertyName = getPropertyNameForField(field);
globalSecondaryIndexNames.put(propertyName,
new String[] { field.getAnnotation(DynamoDBIndexRangeKey.class).globalSecondaryIndexName() });
globalIndexRangeKeyPropertyNames.add(propertyName);
}
}
private void addGlobalSecondaryIndexNames(Method method, DynamoDBIndexHashKey dynamoDBIndexHashKey) {
if (dynamoDBIndexHashKey.globalSecondaryIndexNames() != null
&& dynamoDBIndexHashKey.globalSecondaryIndexNames().length > 0) {
String propertyName = getPropertyNameForAccessorMethod(method);
globalSecondaryIndexNames.put(propertyName, method.getAnnotation(DynamoDBIndexHashKey.class)
.globalSecondaryIndexNames());
globalIndexHashKeyPropertyNames.add(propertyName);
}
if (dynamoDBIndexHashKey.globalSecondaryIndexName() != null
&& dynamoDBIndexHashKey.globalSecondaryIndexName().trim().length() > 0) {
String propertyName = getPropertyNameForAccessorMethod(method);
globalSecondaryIndexNames.put(propertyName,
new String[] { method.getAnnotation(DynamoDBIndexHashKey.class).globalSecondaryIndexName() });
globalIndexHashKeyPropertyNames.add(propertyName);
}
}
private void addGlobalSecondaryIndexNames(Field field, DynamoDBIndexHashKey dynamoDBIndexHashKey) {
if (dynamoDBIndexHashKey.globalSecondaryIndexNames() != null
&& dynamoDBIndexHashKey.globalSecondaryIndexNames().length > 0) {
String propertyName = getPropertyNameForField(field);
globalSecondaryIndexNames.put(propertyName, field.getAnnotation(DynamoDBIndexHashKey.class)
.globalSecondaryIndexNames());
globalIndexHashKeyPropertyNames.add(propertyName);
}
if (dynamoDBIndexHashKey.globalSecondaryIndexName() != null
&& dynamoDBIndexHashKey.globalSecondaryIndexName().trim().length() > 0) {
String propertyName = getPropertyNameForField(field);
globalSecondaryIndexNames.put(propertyName,
new String[] { field.getAnnotation(DynamoDBIndexHashKey.class).globalSecondaryIndexName() });
globalIndexHashKeyPropertyNames.add(propertyName);
}
}
@Override
public Map<String, String[]> getGlobalSecondaryIndexNamesByPropertyName() {
return globalSecondaryIndexNames;
}
@Override
public boolean isGlobalIndexHashKeyProperty(String propertyName) {
return globalIndexHashKeyPropertyNames.contains(propertyName);
}
@Override
public boolean isGlobalIndexRangeKeyProperty(String propertyName) {
return globalIndexRangeKeyPropertyNames.contains(propertyName);
}
}