
com.aerospike.mapper.tools.ClassCacheEntry Maven / Gradle / Ivy
package com.aerospike.mapper.tools;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils;
import com.aerospike.client.AerospikeException;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
import com.aerospike.client.Value;
import com.aerospike.client.cdt.MapOrder;
import com.aerospike.client.policy.BatchPolicy;
import com.aerospike.client.policy.Policy;
import com.aerospike.client.policy.QueryPolicy;
import com.aerospike.client.policy.ScanPolicy;
import com.aerospike.client.policy.WritePolicy;
import com.aerospike.mapper.annotations.AerospikeBin;
import com.aerospike.mapper.annotations.AerospikeConstructor;
import com.aerospike.mapper.annotations.AerospikeExclude;
import com.aerospike.mapper.annotations.AerospikeGetter;
import com.aerospike.mapper.annotations.AerospikeKey;
import com.aerospike.mapper.annotations.AerospikeOrdinal;
import com.aerospike.mapper.annotations.AerospikeRecord;
import com.aerospike.mapper.annotations.AerospikeSetter;
import com.aerospike.mapper.annotations.ParamFrom;
import com.aerospike.mapper.exceptions.NotAnnotatedClass;
import com.aerospike.mapper.tools.configuration.BinConfig;
import com.aerospike.mapper.tools.configuration.ClassConfig;
import com.aerospike.mapper.tools.configuration.KeyConfig;
import com.aerospike.mapper.tools.utils.ParserUtils;
import com.aerospike.mapper.tools.utils.TypeUtils;
import com.aerospike.mapper.tools.utils.TypeUtils.AnnotatedType;
public class ClassCacheEntry {
public static final String VERSION_PREFIX = "@V";
public static final String TYPE_PREFIX = "@T:";
public static final String TYPE_NAME = ".type";
private String namespace;
private String setName;
private Integer ttl = null;
private boolean mapAll = true;
private Boolean sendKey = null;
private Boolean durableDelete = null;
private int version = 1;
private final Class clazz;
private ValueType key;
private String keyName = null;
private boolean keyAsBin = true;
private final TreeMap values = new TreeMap<>();
private ClassCacheEntry> superClazz;
private int binCount;
private final IBaseAeroMapper mapper;
private Map ordinals = null;
private Set fieldsWithOrdinals = null;
private final ClassConfig classConfig;
private final Policy readPolicy;
private final WritePolicy writePolicy;
private final BatchPolicy batchPolicy;
private final QueryPolicy queryPolicy;
private final ScanPolicy scanPolicy;
private String[] constructorParamBins;
private Object[] constructorParamDefaults;
private Constructor constructor;
private final ClassConfig config;
private String factoryMethod;
private String factoryClass;
private enum FactoryMethodType {
NO_PARAMS,
CLASS,
MAP,
CLASS_MAP
}
private Method factoryConstructorMethod;
private FactoryMethodType factoryConstructorType;
/**
* When there are subclasses, we need to store the type information to be able to re-create an instance of the same type. As the
* class name can be verbose, we provide the ability to set a string representing the class name. This string must be unique for all classes.
*/
private String shortenedClassName;
private boolean isChildClass = false;
private volatile boolean constructed;
// package visibility only.
ClassCacheEntry(@NotNull Class clazz, IBaseAeroMapper mapper, ClassConfig config, boolean requireRecord,
@NotNull Policy readPolicy, @NotNull WritePolicy writePolicy,
@NotNull BatchPolicy batchPolicy, @NotNull QueryPolicy queryPolicy,
@NotNull ScanPolicy scanPolicy) {
this.clazz = clazz;
this.mapper = mapper;
this.classConfig = config;
this.readPolicy = readPolicy;
this.writePolicy = writePolicy;
this.batchPolicy = batchPolicy;
this.scanPolicy = scanPolicy;
this.queryPolicy = queryPolicy;
AerospikeRecord recordDescription = clazz.getAnnotation(AerospikeRecord.class);
if (requireRecord && recordDescription == null && config == null) {
throw new NotAnnotatedClass(String.format("Class %s is not augmented by the @AerospikeRecord annotation",
clazz.getName()));
} else if (recordDescription != null) {
this.setPropertiesFromAerospikeRecord(recordDescription);
}
this.config = config;
}
public ClassCacheEntry construct() {
if (config != null) {
config.validate();
this.overrideSettings(config);
}
if (this.namespace == null || this.namespace.isEmpty()) {
List aerospikeInterfaceRecords = this.loadAerospikeRecordsFromInterfaces(this.clazz);
for (int i = 0; (this.namespace == null || this.namespace.isEmpty()) && i < aerospikeInterfaceRecords.size(); i++) {
this.setPropertiesFromAerospikeRecord(aerospikeInterfaceRecords.get(i));
}
}
this.loadFieldsFromClass();
this.loadPropertiesFromClass();
this.superClazz = ClassCache.getInstance().loadClass(this.clazz.getSuperclass(), this.mapper, !this.mapAll);
this.binCount = this.values.size() + (superClazz != null ? superClazz.binCount : 0);
this.formOrdinalsFromValues();
Method factoryConstructorMethod = findConstructorFactoryMethod();
if (!this.clazz.isInterface()) {
if (factoryConstructorMethod == null) {
this.findConstructor();
} else {
this.setConstructorFactoryMethod(factoryConstructorMethod);
}
}
if (StringUtils.isBlank(this.shortenedClassName)) {
this.shortenedClassName = clazz.getSimpleName();
}
ClassCache.getInstance().setStoredName(this, this.shortenedClassName);
this.checkRecordSettingsAgainstSuperClasses();
constructed = true;
return this;
}
public boolean isNotConstructed() {
return !constructed;
}
public Policy getReadPolicy() {
return readPolicy;
}
public WritePolicy getWritePolicy() {
return writePolicy;
}
public BatchPolicy getBatchPolicy() {
return batchPolicy;
}
public QueryPolicy getQueryPolicy() {
return queryPolicy;
}
public ScanPolicy getScanPolicy() {
return scanPolicy;
}
public Class> getUnderlyingClass() {
return this.clazz;
}
public ClassConfig getClassConfig() {
return this.classConfig;
}
public String getShortenedClassName() {
return this.shortenedClassName;
}
private void overrideSettings(ClassConfig config) {
if (!StringUtils.isBlank(config.getNamespace())) {
this.namespace = config.getNamespace();
}
if (!StringUtils.isBlank(config.getSet())) {
this.setName = config.getSet();
}
if (config.getTtl() != null) {
this.ttl = config.getTtl();
}
if (config.getVersion() != null) {
this.version = config.getVersion();
}
if (config.getDurableDelete() != null) {
this.durableDelete = config.getDurableDelete();
}
if (config.getMapAll() != null) {
this.mapAll = config.getMapAll();
}
if (config.getSendKey() != null) {
this.sendKey = config.getSendKey();
}
if (config.getShortName() != null) {
this.shortenedClassName = config.getShortName();
}
if (config.getFactoryMethod() != null) {
this.factoryMethod = config.getFactoryMethod();
}
if (config.getFactoryClass() != null) {
this.factoryClass = config.getFactoryClass();
}
}
public boolean isChildClass() {
return isChildClass;
}
private List loadAerospikeRecordsFromInterfaces(Class> clazz) {
List results = new ArrayList<>();
Class>[] interfaces = clazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
Class> thisInterface = interfaces[i];
AerospikeRecord[] aerospikeRecords = thisInterface.getAnnotationsByType(AerospikeRecord.class);
for (int j = 0; j < aerospikeRecords.length; j++) {
results.add(aerospikeRecords[j]);
}
results.addAll(loadAerospikeRecordsFromInterfaces(thisInterface));
}
return results;
}
private void setPropertiesFromAerospikeRecord(AerospikeRecord recordDescription) {
this.namespace = ParserUtils.getInstance().get(recordDescription.namespace());
this.setName = ParserUtils.getInstance().get(recordDescription.set());
this.ttl = recordDescription.ttl();
this.mapAll = recordDescription.mapAll();
this.version = recordDescription.version();
this.sendKey = recordDescription.sendKey();
this.durableDelete = recordDescription.durableDelete();
this.shortenedClassName = recordDescription.shortName();
this.factoryClass = recordDescription.factoryClass();
this.factoryMethod = recordDescription.factoryMethod();
}
private void checkRecordSettingsAgainstSuperClasses() {
if (!StringUtils.isBlank(this.namespace) && !StringUtils.isBlank(this.setName)) {
// This class defines its own namespace + set, it is only a child class if its closest named superclass is the same as ours.
this.isChildClass = false;
ClassCacheEntry> thisEntry = this.superClazz;
while (thisEntry != null) {
if ((!StringUtils.isBlank(thisEntry.getNamespace())) && (!StringUtils.isBlank(thisEntry.getSetName()))) {
if (this.namespace.equals(thisEntry.getNamespace()) && this.setName.equals(thisEntry.getSetName())) {
this.isChildClass = true;
}
break;
}
thisEntry = thisEntry.superClazz;
}
} else {
// Otherwise this is a child class, find the set and namespace from the closest highest class
this.isChildClass = true;
ClassCacheEntry> thisEntry = this.superClazz;
while (thisEntry != null) {
if ((!StringUtils.isBlank(thisEntry.getNamespace())) && (!StringUtils.isBlank(thisEntry.getSetName()))) {
this.namespace = thisEntry.getNamespace();
this.setName = thisEntry.getSetName();
break;
}
thisEntry = thisEntry.superClazz;
}
}
ClassCacheEntry> thisEntry = this.superClazz;
while (thisEntry != null) {
if (this.durableDelete == null && thisEntry.getDurableDelete() != null) {
this.durableDelete = thisEntry.getDurableDelete();
}
if (this.ttl == null && thisEntry.getTtl() != null) {
this.ttl = thisEntry.getTtl();
}
if (this.sendKey == null && thisEntry.getSendKey() != null) {
this.sendKey = thisEntry.getSendKey();
}
if (this.key == null && thisEntry.key != null) {
this.key = thisEntry.key;
}
thisEntry = thisEntry.superClazz;
}
}
private BinConfig getBinFromName(String name) {
if (this.classConfig == null || this.classConfig.getBins() == null) {
return null;
}
for (BinConfig thisBin : this.classConfig.getBins()) {
if (thisBin.getDerivedName().equals(name)) {
return thisBin;
}
}
return null;
}
private BinConfig getBinFromField(Field field) {
if (this.classConfig == null || this.classConfig.getBins() == null) {
return null;
}
for (BinConfig thisBin : this.classConfig.getBins()) {
if (thisBin.getField() != null && thisBin.getField().equals(field.getName())) {
return thisBin;
}
}
return null;
}
private BinConfig getBinFromGetter(String name) {
if (this.classConfig == null || this.classConfig.getBins() == null) {
return null;
}
for (BinConfig thisBin : this.classConfig.getBins()) {
if (thisBin.getGetter() != null && thisBin.getGetter().equals(name)) {
return thisBin;
}
}
return null;
}
private BinConfig getBinFromSetter(String name) {
if (this.classConfig == null || this.classConfig.getBins() == null) {
return null;
}
for (BinConfig thisBin : this.classConfig.getBins()) {
if (thisBin.getSetter() != null && thisBin.getSetter().equals(name)) {
return thisBin;
}
}
return null;
}
private void formOrdinalsFromValues() {
for (String thisValueName : this.values.keySet()) {
ValueType thisValue = this.values.get(thisValueName);
BinConfig binConfig = getBinFromName(thisValueName);
Integer ordinal = binConfig == null ? null : binConfig.getOrdinal();
if (ordinal == null) {
for (Annotation thisAnnotation : thisValue.getAnnotations()) {
if (thisAnnotation instanceof AerospikeOrdinal) {
ordinal = ((AerospikeOrdinal) thisAnnotation).value();
break;
}
}
}
if (ordinal != null) {
if (ordinals == null) {
ordinals = new HashMap<>();
fieldsWithOrdinals = new HashSet<>();
}
if (ordinals.containsKey(ordinal)) {
throw new AerospikeException(String.format("Class %s has multiple values with the ordinal of %d",
clazz.getSimpleName(), ordinal));
}
ordinals.put(ordinal, thisValueName);
fieldsWithOrdinals.add(thisValueName);
}
}
if (ordinals != null) {
// The ordinals need to be valued from 1..
for (int i = 1; i <= ordinals.size(); i++) {
if (!ordinals.containsKey(i)) {
throw new AerospikeException(String.format("Class %s has %d values specifying ordinals." +
" These should be 1..%d, but %d is missing",
clazz.getSimpleName(), ordinals.size(), ordinals.size(), i));
}
}
}
}
private boolean validateFactoryMethod(Method method) {
if ((method.getModifiers() & Modifier.STATIC) == Modifier.STATIC && this.factoryMethod.equals(method.getName())) {
Parameter[] params = method.getParameters();
if (params.length == 0) {
return true;
}
if (params.length == 1 && ((Class.class.isAssignableFrom(params[0].getType())) || Map.class.isAssignableFrom(params[0].getType()))) {
return true;
}
if (params.length == 2 && Class.class.isAssignableFrom(params[0].getType()) && Map.class.isAssignableFrom(params[1].getType())) {
return true;
}
}
return false;
}
private Method findConstructorFactoryMethod() {
if (!StringUtils.isBlank(this.factoryClass) || !StringUtils.isBlank(this.factoryMethod)) {
// Both must be specified
if (StringUtils.isBlank(this.factoryClass)) {
throw new AerospikeException(String.format("Missing factoryClass definition when factoryMethod is specified on class %s",
clazz.getSimpleName()));
}
if (StringUtils.isBlank(this.factoryClass)) {
throw new AerospikeException(String.format("Missing factoryMethod definition when factoryClass is specified on class %s",
clazz.getSimpleName()));
}
// Load the class and check for the method
try {
Class> factoryClazzType = Class.forName(this.factoryClass);
Method foundMethod = null;
for (Method method : factoryClazzType.getDeclaredMethods()) {
if (validateFactoryMethod(method)) {
if (foundMethod != null) {
throw new AerospikeException(String.format("Factory Class %s defines at least 2 valid " +
"factory methods (%s, %s) as a factory for class %s",
this.factoryClass, foundMethod, method, this.clazz.getSimpleName()));
}
foundMethod = method;
}
}
if (foundMethod == null) {
throw new AerospikeException(String.format("Class %s specified a factory class of %s and a factory" +
" method of %s, but no valid method with that name exists on the class. A valid" +
" method must be static, can take no parameters, a single Class parameter, a single" +
" Map parameter, or a Class and a Map parameter, and must return an object which is" +
" either an ancestor, descendant or equal to %s",
clazz.getSimpleName(), this.factoryClass, this.factoryMethod, clazz.getSimpleName()));
}
return foundMethod;
} catch (ClassNotFoundException cnfe) {
throw new AerospikeException(String.format("Factory class %s for class %s cannot be loaded",
this.factoryClass, clazz.getSimpleName()));
}
}
return null;
}
/**
* Set up the details of the constructor factory method. The method must be returned from the
* findConstructorFactoryMethod
above to ensure it is valid.
*
* @param method The factory method to set.
*/
private void setConstructorFactoryMethod(Method method) {
this.factoryConstructorMethod = method;
this.factoryConstructorMethod.setAccessible(true);
if (method.getParameterCount() == 0) {
this.factoryConstructorType = FactoryMethodType.NO_PARAMS;
} else if (method.getParameterCount() == 2) {
this.factoryConstructorType = FactoryMethodType.CLASS_MAP;
} else if (Class.class.isAssignableFrom(method.getParameters()[0].getType())) {
this.factoryConstructorType = FactoryMethodType.CLASS;
} else {
this.factoryConstructorType = FactoryMethodType.MAP;
}
}
@SuppressWarnings("unchecked")
private void findConstructor() {
Constructor>[] constructors = clazz.getDeclaredConstructors();
if (constructors.length == 0) {
throw new AerospikeException(String.format("Class %s has no constructors and hence cannot be mapped to Aerospike",
clazz.getSimpleName()));
}
Constructor> desiredConstructor = null;
Constructor> noArgConstructor = null;
if (constructors.length == 1) {
desiredConstructor = constructors[0];
} else {
for (Constructor> thisConstructor : constructors) {
if (thisConstructor.getParameters().length == 0) {
noArgConstructor = thisConstructor;
}
AerospikeConstructor aerospikeConstructor = thisConstructor.getAnnotation(AerospikeConstructor.class);
if (aerospikeConstructor != null) {
if (desiredConstructor != null) {
throw new AerospikeException(String.format("Class %s" +
" has multiple constructors annotated with @AerospikeConstructor." +
" Only one constructor can be so annotated.", clazz.getSimpleName()));
} else {
desiredConstructor = thisConstructor;
}
}
}
}
if (desiredConstructor == null && noArgConstructor != null) {
constructorParamBins = new String[0];
desiredConstructor = noArgConstructor;
}
if (desiredConstructor == null) {
throw new AerospikeException(String.format("Class %s has neither a no-arg constructor, " +
"nor a constructor annotated with @AerospikeConstructor so cannot be mapped to Aerospike.",
clazz.getSimpleName()));
}
Parameter[] params = desiredConstructor.getParameters();
this.constructorParamBins = new String[params.length];
this.constructorParamDefaults = new Object[params.length];
Map allValues = new HashMap<>();
ClassCacheEntry> current = this;
while (current != null) {
allValues.putAll(current.values);
current = current.superClazz;
}
int count = 0;
// Parameters can be either specified by their name (which requires the use of the javac -parameters flag),
// or through an @ParamFrom annotation.
for (Parameter thisParam : params) {
count++;
boolean isFromAnnotation = false;
String binName = thisParam.getName();
ParamFrom parameterDetails = thisParam.getAnnotation(ParamFrom.class);
if (parameterDetails != null) {
binName = parameterDetails.value();
isFromAnnotation = true;
}
// Validate that we have such a value
if (!allValues.containsKey(binName)) {
String valueList = String.join(",", values.keySet());
String message = String.format("Class %s has a preferred constructor of %s. However, parameter %d is " +
"mapped to bin \"%s\" %s which is not one of the values on the class, which are: %s%s",
clazz.getSimpleName(), desiredConstructor, count, binName,
isFromAnnotation ? "via the @ParamFrom annotation" : "via the argument name",
valueList,
(!isFromAnnotation && binName.startsWith("arg")) ? ". Did you forget to specify '-parameters' to javac when building?" : "");
throw new AerospikeException(message);
}
Class> type = thisParam.getType();
if (!type.isAssignableFrom(allValues.get(binName).getType())) {
throw new AerospikeException(String.format("Class %s has a preferred constructor of" +
" %s. However, parameter %s" +
" is of type %s but assigned from bin \"%s\" of type %s." +
" These types are incompatible.",
clazz.getSimpleName(), desiredConstructor, count, type, binName, values.get(binName).getType()));
}
constructorParamBins[count - 1] = binName;
constructorParamDefaults[count - 1] = PrimitiveDefaults.getDefaultValue(thisParam.getType());
}
this.constructor = (Constructor) desiredConstructor;
this.constructor.setAccessible(true);
}
private PropertyDefinition getOrCreateProperty(String name, Map properties) {
PropertyDefinition thisProperty = properties.get(name);
if (thisProperty == null) {
thisProperty = new PropertyDefinition(name, mapper);
properties.put(name, thisProperty);
}
return thisProperty;
}
private void loadPropertiesFromClass() {
Map properties = new HashMap<>();
PropertyDefinition keyProperty = null;
KeyConfig keyConfig = config != null ? config.getKey() : null;
for (Method thisMethod : clazz.getDeclaredMethods()) {
String methodName = thisMethod.getName();
BinConfig getterConfig = getBinFromGetter(methodName);
BinConfig setterConfig = getBinFromSetter(methodName);
boolean isKey = false;
boolean isKeyViaConfig = keyConfig != null && (keyConfig.isGetter(methodName) || keyConfig.isSetter(methodName));
if (thisMethod.isAnnotationPresent(AerospikeKey.class) || isKeyViaConfig) {
if (keyProperty == null) {
keyProperty = new PropertyDefinition("_key_", mapper);
}
if (isKeyViaConfig) {
if (keyConfig.isGetter(methodName)) {
keyProperty.setGetter(thisMethod);
} else {
keyProperty.setSetter(thisMethod);
}
} else {
AerospikeKey key = thisMethod.getAnnotation(AerospikeKey.class);
if (key.setter()) {
keyProperty.setSetter(thisMethod);
} else {
keyProperty.setGetter(thisMethod);
}
}
isKey = true;
}
if (thisMethod.isAnnotationPresent(AerospikeGetter.class) || getterConfig != null) {
String getterName = (getterConfig != null) ? getterConfig.getName() : thisMethod.getAnnotation(AerospikeGetter.class).name();
String name = ParserUtils.getInstance().get(ParserUtils.getInstance().get(getterName));
PropertyDefinition thisProperty = getOrCreateProperty(name, properties);
thisProperty.setGetter(thisMethod);
if (isKey) {
keyName = name;
}
}
if (thisMethod.isAnnotationPresent(AerospikeSetter.class) || setterConfig != null) {
String setterName = (setterConfig != null) ? setterConfig.getName() : thisMethod.getAnnotation(AerospikeSetter.class).name();
String name = ParserUtils.getInstance().get(ParserUtils.getInstance().get(setterName));
PropertyDefinition thisProperty = getOrCreateProperty(name, properties);
thisProperty.setSetter(thisMethod);
}
}
if (keyProperty != null) {
keyProperty.validate(clazz.getName(), config, true);
if (key != null) {
throw new AerospikeException(String.format("Class %s cannot have more than one key", clazz.getName()));
}
AnnotatedType annotatedType = new AnnotatedType(config, keyProperty.getGetter());
TypeMapper typeMapper = TypeUtils.getMapper(keyProperty.getType(), annotatedType, this.mapper);
this.key = new ValueType.MethodValue(keyProperty, typeMapper, annotatedType);
}
for (String thisPropertyName : properties.keySet()) {
PropertyDefinition thisProperty = properties.get(thisPropertyName);
thisProperty.validate(clazz.getName(), config, false);
if (this.values.get(thisPropertyName) != null) {
throw new AerospikeException(String.format("Class %s cannot define the mapped name %s more than once",
clazz.getName(), thisPropertyName));
}
AnnotatedType annotatedType = new AnnotatedType(config, thisProperty.getGetter());
TypeMapper typeMapper = TypeUtils.getMapper(thisProperty.getType(), annotatedType, this.mapper);
ValueType value = new ValueType.MethodValue(thisProperty, typeMapper, annotatedType);
values.put(thisPropertyName, value);
}
}
private void loadFieldsFromClass() {
KeyConfig keyConfig = config != null ? config.getKey() : null;
String keyField = keyConfig == null ? null : keyConfig.getField();
for (Field thisField : this.clazz.getDeclaredFields()) {
boolean isKey = false;
BinConfig thisBin = getBinFromField(thisField);
if (Modifier.isFinal(thisField.getModifiers()) && Modifier.isStatic(thisField.getModifiers())) {
// We cannot map static final fields
continue;
}
if (thisField.isAnnotationPresent(AerospikeKey.class) || (!StringUtils.isBlank(keyField) && keyField.equals(thisField.getName()))) {
if (thisField.isAnnotationPresent(AerospikeExclude.class) || (thisBin != null && thisBin.isExclude() != null && thisBin.isExclude())) {
throw new AerospikeException(String.format("Class %s cannot have a field which is both a key and excluded.",
clazz.getName()));
}
if (key != null) {
throw new AerospikeException(String.format("Class %s cannot have more than one key",
clazz.getName()));
}
AerospikeKey keyAnnotation = thisField.getAnnotation(AerospikeKey.class);
boolean storeAsBin = keyAnnotation == null || keyAnnotation.storeAsBin();
if (keyConfig != null && keyConfig.getStoreAsBin() != null) {
storeAsBin = keyConfig.getStoreAsBin();
}
if (!storeAsBin && (this.sendKey == null || !this.sendKey)) {
throw new AerospikeException(String.format("Class %s attempts to store primary key information" +
" inside the aerospike key, but sendKey is not true at the record level", clazz.getName()));
}
AnnotatedType annotatedType = new AnnotatedType(config, thisField);
TypeMapper typeMapper = TypeUtils.getMapper(thisField.getType(), annotatedType, this.mapper);
this.key = new ValueType.FieldValue(thisField, typeMapper, annotatedType);
this.keyAsBin = storeAsBin;
isKey = true;
}
if (thisField.isAnnotationPresent(AerospikeExclude.class) || (thisBin != null && thisBin.isExclude() != null && thisBin.isExclude())) {
// This field should be excluded from being stored in the database. Even keys must be stored
continue;
}
if (this.mapAll || thisField.isAnnotationPresent(AerospikeBin.class) || thisBin != null) {
// This field needs to be mapped
AerospikeBin bin = thisField.getAnnotation(AerospikeBin.class);
String binName = bin == null ? null : ParserUtils.getInstance().get(bin.name());
if (thisBin != null && !StringUtils.isBlank(thisBin.getDerivedName())) {
binName = thisBin.getDerivedName();
}
String name;
if (StringUtils.isBlank(binName)) {
name = thisField.getName();
} else {
name = binName;
}
if (isKey) {
this.keyName = name;
}
if (this.values.get(name) != null) {
throw new AerospikeException(String.format("Class %s cannot define the mapped name %s more than once",
clazz.getName(), name));
}
if ((bin != null && bin.useAccessors()) || (thisBin != null && thisBin.getUseAccessors() != null && thisBin.getUseAccessors())) {
validateAccessorsForField(name, thisField);
} else {
thisField.setAccessible(true);
AnnotatedType annotatedType = new AnnotatedType(config, thisField);
TypeMapper typeMapper = TypeUtils.getMapper(thisField.getType(), annotatedType, this.mapper);
ValueType valueType = new ValueType.FieldValue(thisField, typeMapper, annotatedType);
values.put(name, valueType);
}
}
}
}
private Method findMethodWithNameAndParams(String name, Class>... params) {
try {
Method method = this.clazz.getDeclaredMethod(name, params);
// TODO: Should this ascend the inheritance hierarchy using getMethod on superclasses?
return method;
} catch (NoSuchMethodException nsme) {
return null;
}
}
private void validateAccessorsForField(String binName, Field thisField) {
String fieldName = thisField.getName();
String methodNameBase = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
String getterName = "get" + methodNameBase;
String setterName = "set" + methodNameBase;
Method getter = findMethodWithNameAndParams(getterName);
if (getter == null) {
throw new AerospikeException(String.format(
"Expected to find getter for field %s on class %s due to it being configured to useAccessors, but no method with the signature \"%s %s()\" was found",
fieldName,
this.clazz.getSimpleName(),
thisField.getType().getSimpleName(),
getterName));
}
Method setter = findMethodWithNameAndParams(setterName, thisField.getType());
if (setter == null) {
setter = findMethodWithNameAndParams(setterName, thisField.getType(), Key.class);
}
if (setter == null) {
setter = findMethodWithNameAndParams(setterName, thisField.getType(), Value.class);
}
if (setter == null) {
throw new AerospikeException(String.format(
"Expected to find setter for field %s on class %s due to it being configured to useAccessors, but no method with the name \"%s\" was found",
fieldName,
this.clazz.getSimpleName(),
setterName));
}
AnnotatedType annotatedType = new AnnotatedType(config, thisField);
TypeMapper typeMapper = TypeUtils.getMapper(thisField.getType(), annotatedType, this.mapper);
PropertyDefinition property = new PropertyDefinition(binName, mapper);
property.setGetter(getter);
property.setSetter(setter);
property.validate(clazz.getName(), config, false);
ValueType value = new ValueType.MethodValue(property, typeMapper, annotatedType);
values.put(binName, value);
}
public Object translateKeyToAerospikeKey(Object key) {
return this.key.getTypeMapper().toAerospikeFormat(key);
}
private Object _getKey(Object object) throws ReflectiveOperationException {
if (this.key != null) {
return this.translateKeyToAerospikeKey(this.key.get(object));
} else if (superClazz != null) {
return this.superClazz._getKey(object);
}
return null;
}
public Object getKey(Object object) {
try {
Object key = this._getKey(object);
if (key == null) {
throw new AerospikeException(String.format("Null key from annotated object of class %s." +
" Did you forget an @AerospikeKey annotation?", this.clazz.getSimpleName()));
}
return key;
} catch (ReflectiveOperationException re) {
throw new AerospikeException(re);
}
}
private void _setKey(Object object, Object value) throws ReflectiveOperationException {
if (this.key != null) {
this.key.set(object, this.key.getTypeMapper().fromAerospikeFormat(value));
} else if (superClazz != null) {
this.superClazz._setKey(object, value);
}
}
public void setKey(Object object, Object value) {
try {
this._setKey(object, value);
} catch (ReflectiveOperationException re) {
throw new AerospikeException(re);
}
}
public String getNamespace() {
return namespace;
}
public String getSetName() {
return setName;
}
public Integer getTtl() {
if (ttl == null || ttl == Integer.MIN_VALUE) {
return null;
}
return ttl;
}
public Boolean getSendKey() {
return sendKey;
}
public Boolean getDurableDelete() {
return durableDelete;
}
private boolean contains(String[] names, String thisName) {
if (names == null || names.length == 0) {
return true;
}
if (thisName == null) {
return false;
}
for (String aName : names) {
if (thisName.equals(aName)) {
return true;
}
}
return false;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public Bin[] getBins(Object instance, boolean allowNullBins, String[] binNames) {
try {
Bin[] bins = new Bin[this.binCount];
int index = 0;
ClassCacheEntry thisClass = this;
while (thisClass != null) {
Set keys = thisClass.values.keySet();
for (String name : keys) {
if (name.equals(thisClass.keyName) && !thisClass.keyAsBin) {
// Do not explicitly write the key to the bin
continue;
}
if (contains(binNames, name)) {
ValueType value = (ValueType) thisClass.values.get(name);
Object javaValue = value.get(instance);
Object aerospikeValue = value.getTypeMapper().toAerospikeFormat(javaValue);
if (aerospikeValue != null || allowNullBins) {
if (aerospikeValue instanceof TreeMap, ?>) {
TreeMap, ?> treeMap = (TreeMap, ?>) aerospikeValue;
bins[index++] = new Bin(name, new ArrayList(treeMap.entrySet()), MapOrder.KEY_ORDERED);
} else {
bins[index++] = new Bin(name, Value.get(aerospikeValue));
}
}
}
}
thisClass = thisClass.superClazz;
}
if (index != this.binCount) {
bins = Arrays.copyOf(bins, index);
}
return bins;
} catch (ReflectiveOperationException ref) {
throw new AerospikeException(ref);
}
}
public Map getMap(Object instance, boolean needsType) {
try {
Map results = new HashMap<>();
ClassCacheEntry> thisClass = this;
if (needsType) {
results.put(TYPE_NAME, this.getShortenedClassName());
}
while (thisClass != null) {
for (String name : thisClass.values.keySet()) {
ValueType value = thisClass.values.get(name);
Object javaValue = value.get(instance);
Object aerospikeValue = value.getTypeMapper().toAerospikeFormat(javaValue);
results.put(name, aerospikeValue);
}
thisClass = thisClass.superClazz;
}
return results;
} catch (ReflectiveOperationException ref) {
throw new AerospikeException(ref);
}
}
private void addDataFromValueName(String name, Object instance, ClassCacheEntry> thisClass, List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy