com.bol.reflection.ReflectionCache Maven / Gradle / Ivy
package com.bol.reflection;
import com.bol.secure.FieldEncryptedPredicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ReflectionCache {
private static final Logger LOG = LoggerFactory.getLogger(ReflectionCache.class);
private final ConcurrentHashMap, List> reflectionCache = new ConcurrentHashMap<>();
private final FieldEncryptedPredicate fieldEncryptedPredicate;
public ReflectionCache(FieldEncryptedPredicate fieldEncryptedPredicate) {
this.fieldEncryptedPredicate = fieldEncryptedPredicate;
}
// used by CachedEncryptionEventListener to gather metadata of a class and all it fields, recursively.
public List reflectRecursive(Class> objectClass) {
List nodes = reflectionCache.get(objectClass);
if (nodes != null) return nodes;
synchronized (this) {
return buildRecursive(objectClass, new HashMap<>());
}
}
// building is necessary to avoid putting half-processed data in `reflectionCache` (where it would be returned to other threads)
private List buildRecursive(Class> objectClass, HashMap, List> building) {
if (isPrimitive(objectClass)) return Collections.emptyList();
List processed = reflectionCache.get(objectClass);
if (processed != null) return processed;
List processing = building.get(objectClass);
if (processing != null) return processing;
List nodes = new ArrayList<>();
building.put(objectClass, nodes);
ReflectionUtils.doWithFields(objectClass, field -> {
String fieldName = field.getName();
try {
if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) return;
String documentName = parseFieldAnnotation(field, fieldName);
if (fieldEncryptedPredicate.test(field)) {
// direct @Encrypted annotation - crypt the corresponding field of BasicDbObject
nodes.add(new Node(fieldName, documentName, Collections.emptyList(), Node.Type.DIRECT, field));
} else {
Class> fieldType = field.getType();
Type fieldGenericType = field.getGenericType();
if (Collection.class.isAssignableFrom(fieldType)) {
List children = processParameterizedTypes(fieldGenericType, building);
if (!children.isEmpty()) nodes.add(new Node(fieldName, documentName, unwrap(children), Node.Type.LIST, field));
} else if (Map.class.isAssignableFrom(fieldType)) {
List children = processParameterizedTypes(fieldGenericType, building);
if (!children.isEmpty()) nodes.add(new Node(fieldName, documentName, unwrap(children), Node.Type.MAP, field));
} else {
// descending into sub-documents
List children = buildRecursive(fieldType, building);
if (!children.isEmpty()) nodes.add(new Node(fieldName, documentName, children, Node.Type.DOCUMENT, field));
}
}
} catch (Exception e) {
throw new IllegalArgumentException(objectClass.getName() + "." + fieldName, e);
}
});
reflectionCache.put(objectClass, nodes);
return nodes;
}
// used by ReflectionEncryptionEventListener to map a single Document
public List reflectSingle(Class> objectClass) {
return reflectionCache.computeIfAbsent(objectClass, this::buildSingle);
}
// FIXME: this is a slimmed down copy-paste of buildRecursive(); find a way to bring Cached and Reflective listener closer together!
private List buildSingle(Class> objectClass) {
if (isPrimitive(objectClass)) return Collections.emptyList();
List nodes = new ArrayList<>();
ReflectionUtils.doWithFields(objectClass, field -> {
String fieldName = field.getName();
try {
if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) return;
String documentName = parseFieldAnnotation(field, fieldName);
if (fieldEncryptedPredicate.test(field)) {
// direct @Encrypted annotation - crypt the corresponding field of BasicDbObject
nodes.add(new Node(fieldName, documentName, Collections.emptyList(), Node.Type.DIRECT, field));
} else {
Class> fieldType = field.getType();
if (Collection.class.isAssignableFrom(fieldType)) {
nodes.add(new Node(fieldName, documentName, Collections.emptyList(), Node.Type.LIST, field));
} else if (Map.class.isAssignableFrom(fieldType)) {
nodes.add(new Node(fieldName, documentName, Collections.emptyList(), Node.Type.MAP, field));
} else {
nodes.add(new Node(fieldName, documentName, Collections.emptyList(), Node.Type.DOCUMENT, field));
}
}
} catch (Exception e) {
throw new IllegalArgumentException(objectClass.getName() + "." + fieldName, e);
}
});
return nodes;
}
List processParameterizedTypes(Type type, HashMap, List> building) {
if (type instanceof Class) {
List children = buildRecursive((Class>) type, building);
if (!children.isEmpty()) return Collections.singletonList(new Node(null, children, Node.Type.DOCUMENT));
} else if (type instanceof ParameterizedType) {
ParameterizedType subType = (ParameterizedType) type;
Class> rawType = (Class>) subType.getRawType();
if (Collection.class.isAssignableFrom(rawType)) {
List children = processParameterizedTypes(subType.getActualTypeArguments()[0], building);
if (!children.isEmpty()) return Collections.singletonList(new Node(null, children, Node.Type.LIST));
} else if (Map.class.isAssignableFrom(rawType)) {
List children = processParameterizedTypes(subType.getActualTypeArguments()[1], building);
if (!children.isEmpty()) return Collections.singletonList(new Node(null, children, Node.Type.MAP));
} else {
throw new IllegalArgumentException("Unknown reflective raw type class " + rawType);
}
} else {
throw new IllegalArgumentException("Unknown reflective type class " + type.getClass());
}
return Collections.emptyList();
}
static List unwrap(List result) {
if (result.size() != 1) return result;
Node node = result.get(0);
if (node.fieldName != null) return result;
return node.children;
}
/**
* process custom name in @Field annotation
*/
static String parseFieldAnnotation(java.lang.reflect.Field field, String fieldName) {
Field fieldAnnotation = field.getAnnotation(Field.class);
if (fieldAnnotation != null) {
String name = fieldAnnotation.name();
if (!name.isEmpty()) return name;
else {
String value = fieldAnnotation.value();
if (!value.isEmpty()) return value;
}
}
return fieldName;
}
// same as ClassUtils.isPrimitiveOrWrapper(), but also includes String
public static boolean isPrimitive(Class> clazz) {
return clazz.isPrimitive() || primitiveClasses.contains(clazz);
}
private static final Set> primitiveClasses = new HashSet<>(Arrays.asList(
Boolean.class,
Byte.class,
Character.class,
Double.class,
Float.class,
Integer.class,
Long.class,
Short.class,
Void.class,
String.class
));
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy