/* * Copyright (c) 2011-2014 Jeppetto and Jonathan Thompson * * 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.iternine.jeppetto.dao.dynamodb.expression; import com.amazonaws.services.dynamodbv2.model.Projection; import com.amazonaws.services.dynamodbv2.model.ProjectionType; import org.iternine.jeppetto.dao.annotation.Transient; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class ProjectionExpressionBuilder extends ExpressionBuilder { //------------------------------------------------------------- // Constants //------------------------------------------------------------- private static final Set<Class<?>> DIRECTLY_PROJECTED_TYPES = new HashSet<Class<?>>() {{ add(Date.class); add(boolean.class); add(char.class); add(byte.class); add(short.class); add(int.class); add(long.class); add(float.class); add(double.class); add(Number.class); add(BigDecimal.class); add(BigInteger.class); add(String.class); add(Boolean.class); add(Character.class); add(Byte.class); add(Short.class); add(Integer.class); add(Long.class); add(Float.class); add(Double.class); }}; private static final String EXPRESSION_ATTRIBUTE_NAME_PREFIX = "#p"; //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- private final Set<String> nonKeyAttributes; private final String expression; //------------------------------------------------------------- // Constructors //------------------------------------------------------------- public ProjectionExpressionBuilder(Class<?> entityClass, String hashKeyField, String rangeKeyField, String optimisticLockField) { super(false); this.nonKeyAttributes = collectFields(entityClass, "", new HashSet<String>()); nonKeyAttributes.remove(hashKeyField); nonKeyAttributes.remove(rangeKeyField); if (optimisticLockField != null) { // Add the optimistic lock field explicitly since it may not be exposed in the entity directly. nonKeyAttributes.add(optimisticLockField); } StringBuilder expressionStringBuilder = new StringBuilder(getExpressionAttributeName(hashKeyField)); if (rangeKeyField != null) { expressionStringBuilder.append(", ").append(getExpressionAttributeName(rangeKeyField)); } for (String nonKeyAttribute : nonKeyAttributes) { expressionStringBuilder.append(", ").append(nonKeyAttribute); } this.expression = expressionStringBuilder.toString(); } //------------------------------------------------------------- // Implementation - ExpressionBuilder //------------------------------------------------------------- @Override public boolean hasExpression() { return expression.length() > 0; } @Override public String getExpression() { // NB: nonKeyAttributes no longer used once the expression is fetched. Could make variable non-final and set to null here. return expression; } @Override public String getExpressionAttributeValuePrefix() { throw new RuntimeException("Should not be called"); } @Override public String getExpressionAttributeNamePrefix() { return EXPRESSION_ATTRIBUTE_NAME_PREFIX; } //------------------------------------------------------------- // Methods - Public //------------------------------------------------------------- public Boolean isCoveredBy(Projection projection) { switch (ProjectionType.valueOf(projection.getProjectionType())) { case ALL: return Boolean.TRUE; case KEYS_ONLY: return nonKeyAttributes.isEmpty(); case INCLUDE: return nonKeyAttributes.containsAll(projection.getNonKeyAttributes()); } return Boolean.FALSE; } //------------------------------------------------------------- // Methods - Private //------------------------------------------------------------- private Set<String> collectFields(Class clazz, String fieldPrefix, Set<String> fields) { Map<String, Class> fieldsAndClasses = getFieldsAndClasses(clazz, fieldPrefix); for (Map.Entry<String, Class> entry : fieldsAndClasses.entrySet()) { String field = entry.getKey(); Class fieldClass = entry.getValue(); if (DIRECTLY_PROJECTED_TYPES.contains(fieldClass) || Collection.class.isAssignableFrom(fieldClass)) { fields.add(field); } else { collectFields(fieldClass, field + ".", fields); } } return fields; } private Map<String, Class> getFieldsAndClasses(Class clazz, String fieldPrefix) { Map<String, Class> fieldMap = new HashMap<String, Class>(); List<Method> methods = new ArrayList<Method>(); Collections.addAll(methods, clazz.getDeclaredMethods()); Collections.addAll(methods, clazz.getMethods()); for (Method method : methods) { if (method.getDeclaringClass().equals(Object.class) || method.getReturnType().equals(void.class) || Modifier.isFinal(method.getModifiers()) || Modifier.isAbstract(method.getModifiers()) || method.getParameterTypes().length != 0 || method.getAnnotation(Transient.class) != null) { continue; } String methodName = method.getName(); String upperCaseFieldName; if (methodName.startsWith("get")) { upperCaseFieldName = methodName.substring(3); } else if (methodName.startsWith("is")) { upperCaseFieldName = methodName.substring(2); } else { continue; } try { //noinspection unchecked clazz.getMethod("set".concat(upperCaseFieldName), method.getReturnType()); } catch (NoSuchMethodException e) { continue; } String field = upperCaseFieldName.substring(0, 1).toLowerCase().concat(upperCaseFieldName.substring(1)); fieldMap.put(fieldPrefix + getExpressionAttributeName(field), method.getReturnType()); } return fieldMap; } }