When developing applications with Coherence, sometimes you may know that you will always need an index on a particular method in a POJO. It would be nice to be able to annotate the POJO and then be sure your index would always be added on execution of bootstrap code. I’ve created a little demo that shows this in action.
In Coherence, you use the following type of syntax to programmatically add an index.
// get a handle to your cache
NamedCache nc = CacheFactory.getCache("Person");
// add an index on the getName method and make it ordered by natural ordering
nc.addIndex(new ReflectionExtractor("getName"),true,null);
Normally you use bootstrap code and have a series of the above statements to add all your indexes. I’m my example below you can just annotate the POJO’s and then run an “IndexHelper”, to automatically index your caches. This would then allow you to set a number of default indexes you always want to add. Perhaps code is easier or more flexible, but it is just a method of ensuring you don’t miss an index you wanted!
First we need an annotation. I’ve called mine “CoherenceIndex”. This allows you to define a type of extractor, if it is ordered and a comparator.
// CoherenceIndex.java
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target(METHOD)
@Retention(RUNTIME)
public @interface CoherenceIndex {
Class indexType() default com.tangosol.util.extractor.ReflectionExtractor.class;
boolean ordered() default false;
Class comparator() default NullComparator.class;
}
I have added a NullComparator as a default, so we can tell when we don’t have one. (I’m sure it could be done a different way!)
Next, I create a POJO (Person.java) with some attributes and create the getters, setters, etc. I can then annotate them as such:
// Just get methods for brevity
// standard ReflectionExtractor index
@CoherenceIndex
public String getName() {
return name;
}
// customer extractor
@CoherenceIndex(indexType = com.tangosol.util.extractor.ReflectionExtractor.class, ordered = true)
public String getAddress() {
return address;
}
The index on getAddress() above is just for the standard ReflectionExtractor, but showing that you can add your own custom extractor.
Now comes the crux of this, the IndexHelper. This is what you run against your individual caches to add the indexes. I’ve also implemented a remove index function as well.
// IndexHelper.java
import com.tangosol.net.NamedCache;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Comparator;
/*
* This helper class will add or remove indexes that are annotated with the CoherenceIndex annotation on
* top of methods of a class
* E.g. The following will add a standard ReflectionExtractor to the getName method.
* you can specify ordered and n comparator
*
* @CoherenceIndex
* public String getName() {
* return name;
* }
*
* Or to add your custom index
* @CoherenceIndex(indexType = "com.oracle.demos.indexhelper.demos.CaseInsensitiveReflectionExtractor",
* ordered = true)
*/
public class IndexHelper {
public IndexHelper() {
}
public static boolean debug = false;
public static void removeIndexes(Class clazz, NamedCache cache) throws NoSuchMethodException,
ClassNotFoundException,
InstantiationException,
IllegalAccessException,
InvocationTargetException {
performIndexOperation(true, clazz, cache);
}
public static void addIndexes(Class clazz, NamedCache cache) throws NoSuchMethodException,
ClassNotFoundException,
InstantiationException,
IllegalAccessException,
InvocationTargetException {
performIndexOperation(false, clazz, cache);
}
public static void performIndexOperation(boolean remove,
Class clazz,
NamedCache cache) throws NoSuchMethodException,
ClassNotFoundException,
InstantiationException,
IllegalAccessException,
InvocationTargetException {
for (Method m : clazz.getMethods()) {
// look for the annotations for the method
for (Annotation a : m.getDeclaredAnnotations()) {
if (a instanceof CoherenceIndex) {
Class className = ((CoherenceIndex)a).indexType();
boolean ordered = ((CoherenceIndex)a).ordered();
Class comparatorClass = ((CoherenceIndex)a).comparator();
Comparator comparator = null;
// get the constructor that takes the method name
Constructor c = className.getConstructor(new Class[] { String.class });
Object[] args = new Object[] { m.getName() };
ValueExtractor index = (ValueExtractor)c.newInstance(args);
if (!remove) { // don't need comparator when we remove
// if the comparator is not null then instantiate it
comparator = (Comparator)comparatorClass.newInstance();
if (comparator instanceof NullComparator)
comparator = null;
}
if (remove)
cache.removeIndex(index);
else
cache.addIndex(index, ordered, comparator);
if (debug)
System.out.printf(( (remove) ? "Remove" : "Add") +
" index [%s] on method [%s] = [%s] \n", className,
m.getName(), index);
} else {
// ignore annotation
}
}
}
}
public static void setDebug(boolean debug) {
IndexHelper.debug = debug;
}
public static boolean isDebug() {
return debug;
}
}
Now we have all this, we can just run our test code to ensure that the adding and removing of indexes works. Below is a main method for running this.
public static void main(String[] args)
throws NoSuchMethodException,
ClassNotFoundException,
InstantiationException,
IllegalAccessException,
InvocationTargetException {
long start,end;
Set keys;
boolean isIndexed = true;
int MAX = 20000;
CacheFactory.ensureCluster();
NamedCache cache = CacheFactory.getCache("person");
Map buffer = new HashMap();
if (cache.size() != MAX) {
System.out.print("Inserting data...");
for (int i = 1; i<= MAX; i ++ ) {
Person p = new Person(i,"Person number " + i,"Address number " + i);
buffer.put(p.getId(), p);
}
cache.putAll(buffer);
buffer.clear();
System.out.println(" done");
}
IndexHelper.setDebug(false);
// Call the helper to add indexes marked by annotations
IndexHelper.addIndexes(Person.class, cache);
// first time will be with index, second time without
for (int i = 0; i < 2 ; i++ ) {
// utilize getName index - Using default ReflectionExtractor
start = System.currentTimeMillis();
keys = cache.keySet(new EqualsFilter("getName","Person number 500"));
end = System.currentTimeMillis();
System.out.println("\nTime to search for person with getName and isIndexed " +
isIndexed + " " + (end - start) + " ms");
start = System.currentTimeMillis();
keys = cache.keySet(new EqualsFilter("getAddress","Address number 345"));
end = System.currentTimeMillis();
System.out.println("Time to search for person with getAddress and isIndexed " +
isIndexed + " " + (end - start) + " ms");
// now remove indexes
IndexHelper.removeIndexes(Person.class, cache);
isIndexed = false;
}
}
After running this, the follow is displayed, showing the effect of adding indexes.
Inserting data... done Time to search for person with getName and isIndexed true 18 ms Time to search for person with getAddress and isIndexed true 7 ms Time to search for person with getName and isIndexed false 337 ms Time to search for person with getAddress and isIndexed false 249 ms
Thanks to one of my customers, Damian Murphy, who was the the inspiration for this and Steve Button for helping with some of the annotation syntax.
PS. I have not included the ability to use PofExtractors, but I’m sure my annotation could easily be modified to do this. Maybe for another day.