All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.spikeify.ClassMapper Maven / Gradle / Ivy

There is a newer version: 0.2.35
Show newest version
package com.spikeify;

import com.aerospike.client.Key;
import com.aerospike.client.async.AsyncClient;
import com.aerospike.client.async.IAsyncClient;
import com.spikeify.annotations.Namespace;
import com.spikeify.annotations.SetName;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("unchecked")
public class ClassMapper {

	private final Map mappers;
	private final Map> ldtMappers;

	private final Class type;
	private final String classSetName;
	private final String classNamespace;

	private final FieldMapper generationFieldMapper;
	private final FieldMapper expirationFieldMapper;
	private final FieldMapper namespaceFieldMapper;
	private final FieldMapper setNameFieldMapper;
	private final FieldMapper userKeyFieldMapper;
	private final FieldMapper anyPropertyMapper;

	public ClassMapper(Class clazz) {
		this.type = clazz;

		// parse @Namespace class annotation
		Namespace namespaceClassAnnotation = clazz.getAnnotation(Namespace.class);
		classNamespace = namespaceClassAnnotation != null ? namespaceClassAnnotation.value() : null;

		// parse @SetName class annotation
		SetName setNameAnnotation = clazz.getAnnotation(SetName.class);
		classSetName = setNameAnnotation != null ? setNameAnnotation.value() : null;

		Map fieldMappers = MapperUtils.getFieldMappers(clazz);
//		fieldMappers.putAll(MapperUtils.getJsonMappers(clazz));
		mappers = fieldMappers;

		ldtMappers = MapperUtils.getLDTClasses(clazz);

		generationFieldMapper = MapperUtils.getGenerationFieldMapper(clazz);
		expirationFieldMapper = MapperUtils.getExpirationFieldMapper(clazz);
		namespaceFieldMapper = MapperUtils.getNamespaceFieldMapper(clazz);
		setNameFieldMapper = MapperUtils.getSetNameFieldMapper(clazz);
		userKeyFieldMapper = MapperUtils.getUserKeyFieldMapper(clazz);
		anyPropertyMapper = MapperUtils.getAnyFieldMapper(clazz);
	}

	public Class getType() {
		return type;
	}

	public ObjectMetadata getRequiredMetadata(Object target, String defaultNamespace) {
		Class type = target.getClass();
		ObjectMetadata metadata = new ObjectMetadata();

		// acquire UserKey
		if (userKeyFieldMapper == null) {
			throw new SpikeifyError("Class " + type.getName() + " is missing a field with @UserKey annotation.");
		}
		Object userKeyObj = userKeyFieldMapper.getPropertyValue(target);
		if (userKeyObj == null) {
			throw new SpikeifyError("Field with @UserKey annotation cannot be null" +
					" Field " + type.getName() + "$" + userKeyFieldMapper.field.getName() + " type is " + userKeyFieldMapper.field.getType().getName() + ", value: null");
		} else if (userKeyObj instanceof String) {
			metadata.userKeyString = (String) userKeyObj;
			if (metadata.userKeyString.isEmpty()) {
				throw new SpikeifyError("Field with @UserKey annotation and with type of String cannot be empty" +
						" Field " + type.getName() + "$" + userKeyFieldMapper.field.getName() + " type is " + userKeyFieldMapper.field.getType().getName() + ", value: ''");

			}
		} else if (userKeyObj instanceof Long) {
			metadata.userKeyLong = (Long) userKeyObj;
		} else {
			throw new SpikeifyError("@UserKey annotation can only be used on fields of type: String, Long or long." +
					" Field " + type.getName() + "$" + userKeyFieldMapper.field.getName() + " type is " + userKeyFieldMapper.field.getType().getName());
		}

		// acquire Namespace in the following order
		// 1. use @Namespace on a field or
		// 2. use @Namespace on class or
		// 3. use default namespace
		String fieldNamespace = namespaceFieldMapper != null ? namespaceFieldMapper.getPropertyValue(target) : null;
		metadata.namespace = fieldNamespace != null ? fieldNamespace :
				(classNamespace != null ? classNamespace : defaultNamespace);
		// namespace still not available
		if (metadata.namespace == null) {
			throw new SpikeifyError("Error: namespace could not be inferred from class/field annotations, " +
					"for class " + type.getName() +
					", nor is default namespace available.");
		}

		// acquire @SetName in the following order
		// 1. use @SetName on a field or
		// 2. use @SetName on class or
		// 3. Use Class simple name
		String fieldSetName = setNameFieldMapper != null ? setNameFieldMapper.getPropertyValue(target) : null;
		metadata.setName = fieldSetName != null ? fieldSetName :
				(classSetName != null ? classSetName : type.getSimpleName());

		// acquire @Expires
		metadata.expires = expirationFieldMapper != null ? expirationFieldMapper.getPropertyValue(target) : null;

		// acquire @Generation
		metadata.generation = generationFieldMapper != null ? generationFieldMapper.getPropertyValue(target) : null;

		return metadata;
	}

	/**
	 * @return returns set name according to class type or setName annotation
	 */
	public String getSetName() {

		return classSetName != null ? classSetName : type.getSimpleName();
	}

	public String getNamespace() {
		return classNamespace;
	}

	public Map getProperties(TYPE object) {

		Map props = new HashMap<>(mappers.size());
		for (FieldMapper fieldMapper : mappers.values()) {
			Object propertyValue = fieldMapper.getPropertyValue(object);
			props.put(fieldMapper.binName, propertyValue);
		}

		// find unmapped properties
		if (anyPropertyMapper != null) {
			Map unmappedProperties = (Map) anyPropertyMapper.getPropertyValue(object);
			for (String propName : unmappedProperties.keySet()) {
				props.put(propName, unmappedProperties.get(propName));
			}
		}

		return props;
	}

	public FieldMapper getFieldMapper(String fieldName) {
		return mappers.get(fieldName);
	}

	public void setFieldValues(TYPE object, Map properties) {

		// create a copy
		Map mappedProps = new HashMap<>(properties);

		for (FieldMapper fieldMapper : mappers.values()) {
			Object prop = mappedProps.get(fieldMapper.binName);
			mappedProps.remove(fieldMapper.binName);
			if (prop != null) {
				fieldMapper.setFieldValue(object, prop);
			}
		}

		// at this point mappedProps should only contain unmapped properties
		if (anyPropertyMapper != null) {
			anyPropertyMapper.setFieldValue(object, mappedProps);
		}
	}


	public void setBigDatatypeFields(Object object, IAsyncClient client, Key key) {

		if (!(client instanceof AsyncClient)) {
			return;  // only real client can be used, mocks do not support LDTs
		}

		for (Map.Entry> entry : ldtMappers.entrySet()) {
			Field field = null;
			try {
				field = object.getClass().getDeclaredField(entry.getKey()); // to see all fields not just public ones
			} catch (NoSuchFieldException e) {
				// should not happen
				throw new SpikeifyError("Field '" + entry.getKey() + "' on class " + object.getClass() + " not found!");
			}

			try {
				field.setAccessible(true); // to allow setting private fields
				BigDatatypeWrapper wrapper = (BigDatatypeWrapper) field.get(object);
				if (wrapper == null || !wrapper.isInitialized()) {
					wrapper = (new NoArgClassConstructor()).construct(entry.getValue());
					wrapper.init(client, key, MapperUtils.getBinName(field), field);
				}
				field.set(object, wrapper);
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Translates bin names/values into field names/values.
	 *
	 * @param properties map of field properties
	 * @return mapped properties
	 */
	public Map getFieldValues(Map properties) {

		Map fieldValues = new HashMap<>();

		for (FieldMapper fieldMapper : mappers.values()) {
			if (properties.containsKey(fieldMapper.binName)) {
				Object propValue = properties.get(fieldMapper.binName);
				fieldValues.put(fieldMapper.field.getName(), fieldMapper.getFieldValue(propValue));
			}
		}

		return fieldValues;
	}


	public void setMetaFieldValues(Object object, String namespace, String setName, int generation, int recordExpiration) {

		if (generationFieldMapper != null) {
			generationFieldMapper.setFieldValue(object, generation);
		}
		if (expirationFieldMapper != null) {
			expirationFieldMapper.setFieldValue(object, ExpirationUtils.getExpirationMillisAbs(recordExpiration));
		}
		if (namespaceFieldMapper != null) {
			namespaceFieldMapper.setFieldValue(object, namespace);
		}
		if (setNameFieldMapper != null) {
			setNameFieldMapper.setFieldValue(object, setName);
		}
	}

	public Integer getRecordExpiration(TYPE object) {
		if (expirationFieldMapper == null) {
			return null;
		}
		return ExpirationUtils.getRecordExpiration(expirationFieldMapper.getPropertyValue(object));
	}

	/**
	 * Gets a value of the field marked with @Generation
	 *
	 * @param object Object.
	 * @return null if no @Generation marked field exists, 0 if this is a new object, otherwise an integer value
	 */
	public Integer getGeneration(TYPE object) {
		if (generationFieldMapper == null) {
			return null;
		}
		Integer generation = generationFieldMapper.getPropertyValue(object);
		return generation == null ? 0 : generation;  // default generation value for new objects is 0
	}

	public void setUserKey(TYPE object, String userKey) {
		if (userKeyFieldMapper != null) {
			if (!String.class.isAssignableFrom(userKeyFieldMapper.field.getType())) {
				throw new SpikeifyError("Key type mismatch: @UserKey field '" +
						userKeyFieldMapper.field.getDeclaringClass().getName() + "#" + userKeyFieldMapper.field.getName() +
						"' has type '" + userKeyFieldMapper.field.getType() + "', while key has type 'String'."
				);
			}
			userKeyFieldMapper.setFieldValue(object, userKey);
		}
	}

	public void setUserKey(TYPE object, Long userKey) {
		if (userKeyFieldMapper != null) {
			if (!long.class.isAssignableFrom(userKeyFieldMapper.field.getType()) && // UserKey could be a long but given "userKey" will always be Long
					!Long.class.isAssignableFrom(userKeyFieldMapper.field.getType())) {
				throw new SpikeifyError("Key type mismatch: @UserKey field '" +
						userKeyFieldMapper.field.getDeclaringClass().getName() + "#" + userKeyFieldMapper.field.getName() +
						"' has type '" + userKeyFieldMapper.field.getType() + "', while key has type 'Long'."
				);
			}
			userKeyFieldMapper.setFieldValue(object, userKey);
		}
	}


	public String getBinName(String fieldName) {
		FieldMapper fieldMapper = mappers.get(fieldName);
		return MapperUtils.getBinName(fieldMapper.field);
	}

	public void checkKeyType(Key key) {
		try {
			userKeyFieldMapper.converter.fromProperty(key.userKey.getObject());
		} catch (ClassCastException e) {
			throw new SpikeifyError("Mismatched key type: provided " + key.userKey.getObject().getClass().getName() +
					" key can not be mapped to " + userKeyFieldMapper.field.getType() + " ("
					+ userKeyFieldMapper.field.getDeclaringClass().getName() + "." + userKeyFieldMapper.field.getName() + ")");
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy