Adding Coherence Indexes Using Annotations

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.

About these ads
This entry was posted in Examples and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s