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;
}
}