GigaSpaceAccessor.java

package org.microspace.specific.gigaspace;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.microspace.annotation.IndexType;
import org.microspace.annotation.SpaceRecord;
import org.microspace.annotation.ThreadId;
import org.microspace.specific.MethodBasedGetter;
import org.microspace.specific.MethodBasedSetter;
import org.microspace.specific.MethodUtils;
import org.microspace.table.column.Accessor;
import org.microspace.table.column.GetSetPair;

/**
 * This  class is only provided as a proof of concept, and to test existing code.
 * <p>
 * In MicroSpaces by default the fields are not indexed and they are ignored during the search.
 * <p>
 * In GigaSpace the fields are identified by getXXX signature. By default all fields are indexed. 
 * Their index type depends on whether the field is Comparable or not.
 * If a field has to be indexed by other method, or taken out from indexing, you have to annotate the getter
 * with @SpaceIndex(type=SpaceIndexType.NONE) BASIC will correspond to our IndexType.HASH and EXTENDED to our IndexType.SORTED. 
 * <p>
 * SpaceId is to indicate the primary key. It can not be auto-generated.
 * SpaceProperty nullValue is used to indicate which values are considered null.
 * <p>
 * In GigaSpaces only the fields are required to be serializable, the space class is not.
 * In MicroSpace library the space class itself must be serializable too.
 * <p>
 * In gigaspaces you must specify SpaceProperty.nullValue for primitive types. In microspace only primitive types can have null value,
 * and they can default to their inherent null value.
 * <p>
 * Reflexion is used so that this library will not depend on gigaspaces.
 * <p>
 * GigaSpaces indexes first n get methods, this indexes all.
 * @author Gaspar Sinai - {@literal gaspar.sinai@microspace.org}
 * @version 2016-06-26
 */
public class GigaSpaceAccessor<T> implements Accessor<T> {
	
	private static final String SPACE_ID_ANNOTATION="com.gigaspaces.annotation.pojo.SpaceId";
	private static final String SPACE_PROPERTY_ANNOTATION="com.gigaspaces.annotation.pojo.SpaceProperty";
	private static final String SPACE_INDEX_ANNOTATION="com.gigaspaces.annotation.pojo.SpaceIndex";
	private static final String INDEX_TYPE_ENUM="com.gigaspaces.metadata.index.SpaceIndexType";
	private static final String SPACE_ROUTING_ANNOTATION="com.gigaspaces.annotation.pojo.SpaceRouting";

	private GetSetPair<T> primaryKeyGetter;
	private GetSetPair<T> threadIdGetter;
	private GetSetPair<T> partitionIdGetter;
	
	private List<GetSetPair<T>> getSetPairs;
	private Map<String, GetSetPair<T>> getSetPairsByName = new HashMap<String, GetSetPair<T>> ();

	private Class<T> targetClass;
	private boolean localRecord;
	private boolean updatableRecord;

	
	public GigaSpaceAccessor (Class<T> targetClass) {
		this.targetClass = targetClass;
		Class<?> partitionIdType = null;
		
		
		Class<? extends Annotation> spaceIdAnnotation = loadAnnotation (SPACE_ID_ANNOTATION);
		Class<? extends Annotation> spacePropertyAnnotation = loadAnnotation (SPACE_PROPERTY_ANNOTATION);
		Class<? extends Annotation> spaceIndexAnnotation = loadAnnotation (SPACE_INDEX_ANNOTATION);
		Class<? extends Enum<?>> indexTypeEnum = loadEnum (INDEX_TYPE_ENUM);
		Class<? extends Annotation> spaceRoutingAnnotation = loadAnnotation (SPACE_ROUTING_ANNOTATION);
		
		GigaSpaceNullValueExtractor extractor = new GigaSpaceNullValueExtractor(spacePropertyAnnotation);
		TreeMap<String, MethodBasedGetter<T>> getters = MethodUtils.scanGetters (targetClass, extractor);
		TreeMap<String, MethodBasedSetter<T>> setters = MethodUtils.scanSetters (targetClass);
		getSetPairs = MethodUtils.generateGetSetPairs (getters, setters);
		for (GetSetPair<T> pair : getSetPairs) {
			getSetPairsByName.put(pair.getName(), pair);
		}		
		for (MethodBasedGetter<T> getter : getters.values()) {
			IndexType indexType = getIndexType (getter.getMethod(), spaceIndexAnnotation, indexTypeEnum);
			if (indexType == null) continue;
			// Nasty way to update an already stored object.
			getter.setIndexType(indexType);
			if (getter.getMethod().getAnnotation(spaceIdAnnotation) != null) {
				if (primaryKeyGetter != null) {
					throw new IllegalArgumentException ("Class " + targetClass + " multiple primary keys.");
				}
				primaryKeyGetter = getSetPairsByName.get(getter.getName());
			}
			ThreadId threadId = getter.getMethod().getAnnotation(ThreadId.class);
            if (threadId != null) {
                if (threadIdGetter != null) {
                    throw new IllegalArgumentException("Class "
                            + targetClass + " has multiple ThreadId.");
                }
                threadIdGetter = getSetPairsByName.get(getter.getName());
            }
            if (spaceRoutingAnnotation != null 
            		&& getter.getMethod().getAnnotation(spaceRoutingAnnotation) != null) {
                if (partitionIdGetter != null) {
                    throw new IllegalArgumentException("Class "
                            + targetClass + " has multiple SpaceRouting.");
                }
                partitionIdGetter = getSetPairsByName.get(getter.getName());
                partitionIdType = getter.getReturnType();
            }
		}
		if (partitionIdType != null && !Integer.class.equals(partitionIdType)) {
			throw new IllegalArgumentException ("PartitionId is not Integer");
		}

		if (primaryKeyGetter == null) {
			throw new IllegalArgumentException ("No primary key defined.");
		}
		localRecord = false;
		updatableRecord = false;
		if (targetClass.isAnnotationPresent(SpaceRecord.class)) {
			SpaceRecord annotation = targetClass.getAnnotation(SpaceRecord.class);
			localRecord = annotation.local();
			updatableRecord = annotation.updatable();
		}

	}
	
	private IndexType getIndexType (Method method, Class<? extends Annotation> spaceIndexClass, Class<? extends Enum<?>> indexTypeClass) {
		Annotation spaceIndex = method.getAnnotation(spaceIndexClass);
		if (spaceIndex == null) {
			if (Comparable.class.isAssignableFrom(method.getReturnType())) {
				return IndexType.SORTED;
			} else {
				return IndexType.HASHED;
			}
		}
		for (Method m : spaceIndexClass.getMethods()) {
			if (m.getName().equals ("type")) {
				if (!indexTypeClass.equals (m.getReturnType())) {
					throw new IllegalArgumentException ("Expected SpaceIndex type to return IndexType.");
				}
				Enum<?> ret = null;
				try {
					ret = (Enum<?>) m.invoke (spaceIndex);
				} catch (Exception ex) {
					throw new IllegalArgumentException ("SpaceIndex.type getter does not work.", ex);
				}
				if (("NONE").equals (ret.name())) return null;
				if (("BASIC").equals (ret.name())) return IndexType.HASHED;
				if (("EXTENDED").equals (ret.name())) return IndexType.SORTED;
				throw new IllegalArgumentException ("Unhandled SpaceIndex " + ret.name());
			}
		}
	throw new IllegalArgumentException ("No SpaceIndex.type defined.");
	}
	
	public GetSetPair<T> getPrimaryKeyGetSetPair() {
		return primaryKeyGetter;
	}
	/**
	 * MicroSpace adds its own ThreadId annotation.
	 */
	public GetSetPair<T> getThreadIdGetSetPair() {
		return threadIdGetter;
	}
	public GetSetPair<T> getPartitionIdGetSetPair() {
		return partitionIdGetter;
	}
	
	@SuppressWarnings("unchecked")
	private static Class<? extends Annotation> loadAnnotation (String name){
		try {
			Class<?> ret = Class.forName (name);
			return (Class<? extends Annotation>) ret;
		} catch (ClassNotFoundException ex0) {
			throw new IllegalArgumentException ("Can not find annotation " + name);
		} catch (ClassCastException ex) {
			throw new IllegalArgumentException ("Annotation expected " + name);
		}
		
	}
	@SuppressWarnings("unchecked")
	private static Class<? extends Enum<?>> loadEnum (String name) {
		try {
			Class<?> ret = Class.forName (name);
			return (Class<? extends Enum<?>>) ret;
		} catch (ClassNotFoundException ex0) {
			throw new IllegalArgumentException ("Can not find annotation " + name);
		} catch (ClassCastException ex) {
			throw new IllegalArgumentException ("Annotation expected " + name);
		}
		
	}

	public List<GetSetPair<T>> getGetSetPairs() {
		return getSetPairs;
	}

	public GetSetPair<T> getGetSetPair (String name) {
		return getSetPairsByName.get(name);
	}
	public Class<T> getTargetClass() {
		return targetClass;
	}

	public T getBlankObject () {
		return MethodUtils.getBlank (targetClass);
	}
	@Override
	public boolean isLocalRecord() {
		return localRecord;
	}
	@Override
	public boolean isUpdatableRecord() {
		return updatableRecord;
	}

	
}