/*
* Copyright (c) 2012-2014 Savoir 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://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 com.savoirtech.hecate.cql3;
import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.Row;
import com.google.common.collect.Lists;
import com.savoirtech.hecate.cql3.annotations.Id;
import com.savoirtech.hecate.cql3.annotations.IdColumn;
import com.savoirtech.hecate.cql3.annotations.Table;
import com.savoirtech.hecate.cql3.dao.abstracts.GenericCqlDao;
import com.savoirtech.hecate.cql3.dao.abstracts.GenericPojoGraphDao;
import com.savoirtech.hecate.cql3.exception.HecateException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ReflectionUtils {
//----------------------------------------------------------------------------------------------------------------------
// Fields
//----------------------------------------------------------------------------------------------------------------------
public static final TypeVariable<Class<List>> LIST_ELEMENT_TYPE_VAR = List.class.getTypeParameters()[0];
public static final TypeVariable<Class<Set>> SET_ELEMENT_TYPE_VAR = Set.class.getTypeParameters()[0];
public static final TypeVariable<Class<Map>> MAP_KEY_TYPE_VAR = Map.class.getTypeParameters()[0];
public static final TypeVariable<Class<Map>> MAP_VALUE_TYPE_VAR = Map.class.getTypeParameters()[1];
private final static Logger LOGGER = LoggerFactory.getLogger(ReflectionUtils.class);
private static final String IS_PREFIX = "is";
private static final String GET_PREFIX = "get";
private static final String SET_PREFIX = "set";
//----------------------------------------------------------------------------------------------------------------------
// Static Methods
//----------------------------------------------------------------------------------------------------------------------
private static void collectFields(Class<?> type, List<Field> fields) {
final Field[] declaredFields = type.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (isPersistable(declaredField)) {
fields.add(declaredField);
}
}
if (type.getSuperclass() != null) {
collectFields(type.getSuperclass(), fields);
}
}
public static <K> K extractFieldValue(String fieldName, Field fieldType, Row row) {
return null;
}
public static String[] fieldNames(Class mappingClazz) {
List<String> fields = new ArrayList<>();
for (Field field : getFieldsUpTo(mappingClazz, null)) {
fields.add(field.getName());
}
return fields.toArray(new String[fields.size()]);
}
public static <T> Object[] fieldValues(T pojo) {
List vals = new ArrayList();
for (Field field : getFieldsUpTo(pojo.getClass(), null)) {
try {
field.setAccessible(true);
Object value = field.get(pojo);
vals.add(value);
}
catch (IllegalAccessException e) {
LOGGER.error("Could not access field " + e);
}
}
return vals.toArray(new Object[vals.size()]);
}
private static Method findDeclaredMethod(Class<?> c, String name, Class<?>... parameterTypes) {
try {
return c.getDeclaredMethod(name, parameterTypes);
}
catch (NoSuchMethodException e) {
if (c.getSuperclass() != null) {
return findDeclaredMethod(c.getSuperclass(), name, parameterTypes);
}
return null;
}
}
private static <T> Class<?> findTypeVariable(Type type, Class<T> declaringClass, TypeVariable<Class<T>> target) {
final Map<TypeVariable<?>, Type> arguments = TypeUtils.getTypeArguments(type, declaringClass);
for (Map.Entry<TypeVariable<?>, Type> entry : arguments.entrySet()) {
if (target.equals(entry.getKey())) {
return TypeUtils.getRawType(entry.getValue(), type);
}
}
return null;
}
private static String getClassName(Object target) {
return target == null ? "null" : target.getClass().getCanonicalName();
}
public static Field getFieldType(String id) {
return null;
}
public static Object getFieldValue(Field field, Object target) {
try {
LOGGER.debug("Getting field {} value from object {} (type={})...", field.getName(), target, getClassName(target));
return FieldUtils.readField(field, target, true);
}
catch (IllegalAccessException e) {
throw new HecateException(String.format("Unable to read field %s value from object of type %s.", field.getName(), getClassName(target)), e);
}
}
public static List<Field> getFields(Class<?> pojoType) {
List<Field> fields = new LinkedList<>();
collectFields(pojoType, fields);
return fields;
}
public static Iterable<Field> getFieldsUpTo(Class<?> startClass, Class<?> exclusiveParent) {
List<Field> currentClassFields = Lists.newArrayList(startClass.getDeclaredFields());
Class<?> parentClass = startClass.getSuperclass();
if (parentClass != null && (exclusiveParent == null || !(parentClass.equals(exclusiveParent)))) {
List<Field> parentClassFields = (List<Field>) getFieldsUpTo(parentClass, exclusiveParent);
currentClassFields.addAll(parentClassFields);
}
return currentClassFields;
}
public static <K> String getIdName(Class clazz) {
for (Field fied : getFieldsUpTo(clazz, null)) {
if (fied.isAnnotationPresent(IdColumn.class) || fied.isAnnotationPresent(Id.class)) {
return fied.getName();
}
}
return null;
}
public static Class getIdType(Class instanceClazz) {
for (Field fied : getFieldsUpTo(instanceClazz, null)) {
if (fied.isAnnotationPresent(IdColumn.class) || fied.isAnnotationPresent(Id.class)) {
return fied.getType();
}
}
return null;
}
public static Method getReadMethod(Class<?> pojoType, PropertyDescriptor descriptor) {
Method method = descriptor.getReadMethod();
if (method == null) {
Method candidate = findDeclaredMethod(pojoType, getReadMethodName(descriptor));
if (candidate != null && descriptor.getPropertyType().equals(candidate.getReturnType())) {
method = candidate;
}
}
return method;
}
private static String getReadMethodName(PropertyDescriptor descriptor) {
final String prefix = Boolean.TYPE.equals(descriptor.getPropertyType()) ? IS_PREFIX : GET_PREFIX;
return prefix + propertySuffix(descriptor);
}
@SuppressWarnings("unchecked")
public static List<Class<?>> getSupertypes(Class<?> type) {
List<Class<?>> supertypes = new LinkedList<>();
supertypes.add(type.getSuperclass());
Collections.addAll(supertypes, type.getInterfaces());
return supertypes;
}
public static Method getWriteMethod(Class<?> pojoType, PropertyDescriptor descriptor) {
Method method = descriptor.getWriteMethod();
if (method == null) {
Method candidate = findDeclaredMethod(pojoType, getWriteMethodName(descriptor), descriptor.getPropertyType());
if (candidate != null && Void.TYPE.equals(candidate.getReturnType())) {
method = candidate;
}
}
return method;
}
private static String getWriteMethodName(PropertyDescriptor descriptor) {
return SET_PREFIX + propertySuffix(descriptor);
}
public static <P> P instantiate(Class<P> type) {
try {
return type.newInstance();
}
catch (InstantiationException e) {
throw new HecateException(String.format("Unable to instantiate object of type %s.", type.getName()), e);
}
catch (IllegalAccessException e) {
throw new HecateException(String.format("Default constructor not accessible for class %s.", type.getName()), e);
}
}
public static Object invoke(Object target, Method method, Object... params) {
try {
Validate.notNull(method).setAccessible(true);
return method.invoke(target, params);
}
catch (IllegalAccessException e) {
throw new HecateException(String.format("Unable to invoke method %s on object of type %s.", method.getName(), getClassName(target)), e);
}
catch (InvocationTargetException e) {
throw new HecateException(String.format("Method %s threw exception when invoked on an object of type %s.", method.getName(), getClassName(target)));
}
}
public static boolean isInstantiable(Class<?> type) {
return type != null && ConstructorUtils.getAccessibleConstructor(type) != null;
}
public static boolean isPersistable(Field field) {
final int mods = field.getModifiers();
return !(Modifier.isFinal(mods) ||
Modifier.isTransient(mods) ||
Modifier.isStatic(mods));
}
public static Class<?> listElementType(Type type) {
return findTypeVariable(type, List.class, LIST_ELEMENT_TYPE_VAR);
}
public static Class<?> mapKeyType(Type type) {
return findTypeVariable(type, Map.class, MAP_KEY_TYPE_VAR);
}
public static Class<?> mapValueType(Type type) {
return findTypeVariable(type, Map.class, MAP_VALUE_TYPE_VAR);
}
@SuppressWarnings("unchecked")
static <T> T[] newArray(Class<T> type, int length) {
return (T[]) Array.newInstance(type, length);
}
public static <T> Object[] pojofieldValues(T pojo) {
List vals = new ArrayList();
for (Field field : getFieldsUpTo(pojo.getClass(), null)) {
try {
field.setAccessible(true);
//TODO - If this is another object, a collection or a Dictionary
//we need to inspect this and generate the appropriate values.
//We also need to iterate over each field and create new table inserts.
Object value = field.get(pojo);
vals.add(value);
}
catch (IllegalAccessException e) {
LOGGER.error("Could not access field " + e);
}
}
return vals.toArray(new Object[vals.size()]);
}
public static <T> void populate(T clz, Row row) {
for (ColumnDefinitions.Definition cf : row.getColumnDefinitions()) {
LOGGER.debug("Column " + cf.getType().asJavaClass());
List<String> fields = Arrays.asList(fieldNames(clz.getClass()));
try {
for (String fname : fields) {
if (fname.equalsIgnoreCase(cf.getName())) {
Field field = clz.getClass().getDeclaredField(fname);
field.setAccessible(true);
field.set(clz, FieldMapper.getJavaObject(cf.getType().getName().name(), cf.getName(), row));
}
}
}
catch (NoSuchFieldException e) {
LOGGER.error("Trying to access a field that doesn't exist " + e);
}
catch (IllegalAccessException e) {
LOGGER.error("Access problem " + e);
}
}
}
public static <T> void populateGraph(T clz, Row row, GenericCqlDao dao) throws HecateException {
for (ColumnDefinitions.Definition cf : row.getColumnDefinitions()) {
LOGGER.debug("Column " + cf.getType().asJavaClass());
List<String> fields = Arrays.asList(fieldNames(clz.getClass()));
try {
boolean fieldAdded = false;
for (String fname : fields) {
if (fname.equalsIgnoreCase(cf.getName())) {
Field field = clz.getClass().getDeclaredField(fname);
field.setAccessible(true);
LOGGER.debug("Adding in a " + field.toGenericString());
if (FieldMapper.getRawCassandraType(field) == null) {
LOGGER.debug("Looking up " + field.getGenericType() + " with key " + FieldMapper.getJavaObject(
cf.getType().getName().name(), cf.getName(), row) + " from " + dao.getKeySpace() + "." + tableName(field));
Object id = FieldMapper.getJavaObject(cf.getType().getName().name(), cf.getName(), row);
if (id != null) {
Object o = ((GenericPojoGraphDao) dao).findChildRow(id, field.getType(), dao.getKeySpace(), tableName(field));
field.set(clz, o);
}
fieldAdded = true;
}
if ("list<blob>".equals(FieldMapper.getRawCassandraType(field))) {
LOGGER.debug("Looking up " + field.getGenericType() + " with key " + FieldMapper.getJavaObject(
cf.getType().getName().name(), cf.getName(), row) + " from " + dao.getKeySpace() + "." + tableName(field));
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Class entityClazz = typeToClass(pt.getActualTypeArguments()[0], field.getDeclaringClass().getClassLoader());
List idlist = (List) FieldMapper.getJavaObject(cf.getType().getName().name(), cf.getName(), row);
List entities = new ArrayList();
for (Object id : idlist) {
if (id != null) {
LOGGER.debug("Find list item " + id + " from " + dao.getKeySpace() + "." + tableName(field));
Object o = ((GenericPojoGraphDao) dao).findChildRow(id, entityClazz, dao.getKeySpace(), tableName(field));
LOGGER.debug("Found entity " + o);
entities.add(o);
}
}
field.set(clz, entities);
fieldAdded = true;
}
}
if ("set<blob>".equals(FieldMapper.getRawCassandraType(field))) {
LOGGER.debug("Looking up " + field.getGenericType() + " with key " + FieldMapper.getJavaObject(
cf.getType().getName().name(), cf.getName(), row) + " from " + dao.getKeySpace() + "." + tableName(field));
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Class entityClazz = typeToClass(pt.getActualTypeArguments()[0], field.getDeclaringClass().getClassLoader());
Set idlist = (Set) FieldMapper.getJavaObject(cf.getType().getName().name(), cf.getName(), row);
Set entities = new HashSet();
for (Object id : idlist) {
if (id != null) {
LOGGER.debug("Find set item " + id + " from " + dao.getKeySpace() + "." + tableName(field));
Object o = ((GenericPojoGraphDao) dao).findChildRow(id, entityClazz, dao.getKeySpace(), tableName(field));
LOGGER.debug("Found entity " + o);
entities.add(o);
}
}
field.set(clz, entities);
fieldAdded = true;
}
}
if (FieldMapper.getRawCassandraType(field) != null && FieldMapper.getRawCassandraType(field).toLowerCase().startsWith("map<")
&& FieldMapper.getRawCassandraType(field).toLowerCase().contains(",blob>")) {
LOGGER.debug("Looking up " + field.getGenericType() + " with key " + FieldMapper.getJavaObject(
cf.getType().getName().name(), cf.getName(), row) + " from " + dao.getKeySpace() + "." + tableName(field));
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Class entityClazz = typeToClass(pt.getActualTypeArguments()[1], field.getDeclaringClass().getClassLoader());
Map idlist = (Map) FieldMapper.getJavaObject(cf.getType().getName().name(), cf.getName(), row);
Map entities = new HashMap();
for (Object ento : idlist.entrySet()) {
Map.Entry ent = (Map.Entry) ento;
Object id = ent.getKey();
if (id != null) {
LOGGER.debug("Find map item " + id + "=>" + ent.getValue() + " from " + dao.getKeySpace() + "." + tableName(
field));
Object o = ((GenericPojoGraphDao) dao).findChildRow(ent.getValue(), entityClazz, dao.getKeySpace(), tableName(
field));
LOGGER.debug("Found entity " + o);
entities.put(id, o);
}
}
field.set(clz, entities);
fieldAdded = true;
}
}
if (!fieldAdded) {
field.set(clz, FieldMapper.getJavaObject(cf.getType().getName().name(), cf.getName(), row));
}
}
}
}
catch (NoSuchFieldException e) {
LOGGER.error("Trying to access a field that doesn't exist " + e);
}
catch (IllegalAccessException e) {
LOGGER.error("Access problem " + e);
}
catch (ClassNotFoundException e) {
LOGGER.error("Class not found " + e);
}
}
}
private static String propertySuffix(PropertyDescriptor descriptor) {
return StringUtils.capitalize(descriptor.getName());
}
public static Class<?> setElementType(Type type) {
return findTypeVariable(type, Set.class, SET_ELEMENT_TYPE_VAR);
}
public static void setFieldValue(Field field, Object target, Object fieldValue) {
try {
LOGGER.debug("Setting field {} to value {} on object {} (type={})...", field.getName(), fieldValue, target, getClassName(target));
FieldUtils.writeField(field, target, fieldValue, true);
}
catch (IllegalAccessException e) {
throw new HecateException(String.format("Unable to write field %s value on object of type %s.", field.getName(), getClassName(target)), e);
}
}
public static String tableName(Field field) {
Table annot = field.getAnnotation(Table.class);
if (annot != null && StringUtils.isNotEmpty(annot.name())) {
return annot.name();
}
return field.getName();
}
public static Class typeToClass(Type type, ClassLoader cl) throws ClassNotFoundException {
return cl.loadClass(type.toString().split(" ")[1]);
}
public static <T> Map<Class, Set<DataDescriptor>> valuesForClasses(Map<Class, Set<DataDescriptor>> values, String originalTableName,
T pojo) throws HecateException {
List vals = new ArrayList();
DataDescriptor dataDescriptor = new DataDescriptor();
dataDescriptor.tableName = originalTableName;
for (Field field : getFieldsUpTo(pojo.getClass(), null)) {
try {
field.setAccessible(true);
String csType = FieldMapper.getRawCassandraType(field);
boolean fieldProcessed = false;
if (csType == null) {
LOGGER.debug("Encountered an Object, we need to convert this object to a new insert value.");
Object fieldVal = field.get(pojo);
String id = ReflectionUtils.getIdName(field.getType());
dataDescriptor.tableName = tableName(field);
if (fieldVal != null) {
for (Field nestedF : getFieldsUpTo(fieldVal.getClass(), null)) {
nestedF.setAccessible(true);
if (id == null) {
throw new HecateException("Id field not found on object " + fieldVal);
}
if (id.equals(nestedF.getName())) {
vals.add(nestedF.get(fieldVal));
fieldProcessed = true;
valuesForClasses(values, dataDescriptor.getTableName(), fieldVal);
}
}
} else {
vals.add(null);
fieldProcessed = true;
}
}
if ("list<blob>".equalsIgnoreCase(csType)) {
LOGGER.debug("Encountered a List, checking generic type");
dataDescriptor.tableName = tableName(field);
List fieldVal = (List) field.get(pojo);
List rawList = new ArrayList();
LOGGER.debug("List " + fieldVal);
for (Object o : fieldVal) {
LOGGER.debug("Object class " + o.getClass());
String id = ReflectionUtils.getIdName(o.getClass());
for (Field nestedF : getFieldsUpTo(o.getClass(), null)) {
LOGGER.debug("Parsing list item " + nestedF);
nestedF.setAccessible(true);
if (id == null) {
throw new HecateException("Id field not found on list item " + fieldVal);
}
if (id.equals(nestedF.getName())) {
LOGGER.debug("Field to use " + field);
rawList.add(nestedF.get(o));
valuesForClasses(values, dataDescriptor.getTableName(), o);
}
}
}
vals.add(rawList);
fieldProcessed = true;
}
if ("set<blob>".equalsIgnoreCase(csType)) {
LOGGER.debug("Encountered a Set, checking generic type");
dataDescriptor.tableName = tableName(field);
Set fieldVal = (Set) field.get(pojo);
Set rawSet = new HashSet();
for (Object o : fieldVal) {
LOGGER.debug("Object class " + o.getClass());
String id = ReflectionUtils.getIdName(o.getClass());
for (Field nestedF : getFieldsUpTo(o.getClass(), null)) {
nestedF.setAccessible(true);
if (id == null) {
throw new HecateException("Id field not found on set item " + fieldVal);
}
if (id.equals(nestedF.getName())) {
rawSet.add(nestedF.get(o));
valuesForClasses(values, dataDescriptor.getTableName(), o);
}
}
}
vals.add(rawSet);
fieldProcessed = true;
}
if (csType != null && csType.toLowerCase().contains("map<") && csType.toLowerCase().contains(",blob>")) {
LOGGER.debug("Encountered a Map, checking generic type");
dataDescriptor.tableName = tableName(field);
Map fieldVal = (Map) field.get(pojo);
Map rawMap = new HashMap();
if (fieldVal != null) {
for (Object en : fieldVal.entrySet()) {
Map.Entry o = (Map.Entry) en;
LOGGER.debug("Object class " + o.getValue());
String id = ReflectionUtils.getIdName(o.getValue().getClass());
if (id == null) {
throw new HecateException("Id field not found on set item " + fieldVal);
}
for (Field nestedF : getFieldsUpTo(o.getValue().getClass(), null)) {
nestedF.setAccessible(true);
if (id.equals(nestedF.getName())) {
rawMap.put(o.getKey(), nestedF.get(o.getValue()));
valuesForClasses(values, dataDescriptor.getTableName(), o.getValue());
}
}
}
}
vals.add(rawMap);
fieldProcessed = true;
}
if (!fieldProcessed) {
Object value = field.get(pojo);
vals.add(value);
}
}
catch (IllegalAccessException e) {
LOGGER.error("Could not access field " + e);
}
}
dataDescriptor.values = vals.toArray(new Object[vals.size()]);
if (values.containsKey(pojo.getClass()) && values.get(pojo.getClass()) == null || !values.containsKey(pojo.getClass())) {
values.put(pojo.getClass(), new HashSet<DataDescriptor>());
}
values.get(pojo.getClass()).add(dataDescriptor);
for (Map.Entry<Class, Set<ReflectionUtils.DataDescriptor>> entry : values.entrySet()) {
for (DataDescriptor descriptor : entry.getValue()) {
LOGGER.debug(dataDescriptor.getTableName() + "=>" + Arrays.asList(descriptor.getValues()));
}
}
return values;
}
//----------------------------------------------------------------------------------------------------------------------
// Inner Classes
//----------------------------------------------------------------------------------------------------------------------
public static class DataDescriptor {
String tableName;
String id;
Object[] values;
public Object[] getValues() {
return values;
}
public String getTableName() {
return tableName;
}
@Override
public String toString() {
return "DataDescriptor{" +
"tableName='" + tableName + '\'' +
", id='" + id + '\'' +
", values=" + Arrays.toString(values) +
'}';
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void setValues(Object[] values) {
this.values = values;
}
}
}