killbill-uncached

Details

diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ClassFinder.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ClassFinder.java
new file mode 100644
index 0000000..156cf57
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ClassFinder.java
@@ -0,0 +1,79 @@
+package com.ning.billing.beatrix.lifecycle;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClassFinder extends ClassList {
+
+    private final Logger log = LoggerFactory.getLogger(ClassFinder.class);
+
+    /*
+    private static final Set<String> PACKAGE_FILTER = new ImmutableSet.Builder<String>()
+																.add("ning.viking.core")
+																.add("ning.viking.clients")
+																.build();
+
+	private static final Class<?> SUBSYSTEMS = Subsystem.class;
+    private static final Class<?> PLATFROM_ACCESSORS = PlatformAccessor.class;
+    private static final Class<?> DATA_FEED = DataFeed.class;
+    private static final Class<?> DATA_ACCESS = DataAccess.class;
+*/
+
+	private final ClassLoader loader;
+	private final Map<String, Set<Class<?>>> map;
+
+	/*
+	private static final Class<?> [] INTERFACES_WE_CARE = {
+		SUBSYSTEMS,
+		PLATFROM_ACCESSORS,
+		DATA_FEED,
+		DATA_ACCESS
+	};
+	*/
+
+	public ClassFinder(ClassLoader loader) {
+		this.loader = loader;
+		this.map = initialize();
+		Iterator<String> it = map.keySet().iterator();
+		while (it.hasNext()) {
+			String key = it.next();
+			log.info("Found classes - " + key + " : " + map.get(key));
+		}
+	}
+
+
+	@SuppressWarnings("unchecked")
+	public List<Class<? extends LifecycleService>> getServices() {
+		List<Class<? extends LifecycleService>> res = new ArrayList<Class<? extends LifecycleService>>();
+		for (Class clz : map.get(LifecycleService.class.getName())) {
+			res.add(clz);
+		}
+		return res;
+	}
+
+
+	private Map<String, Set<Class<?>>> initialize() {
+		try {
+
+		    Set<String> packageFilter = new TreeSet<String>();
+		    packageFilter.add("com.ning.billing.beatrix.lifecycle");
+
+			Set<String> interfaceFilter = new TreeSet<String>();
+			interfaceFilter.add(LifecycleService.class.getName());
+
+			return findClasses(loader, interfaceFilter, packageFilter, null, true);
+		} catch (ClassNotFoundException nfe) {
+			throw new RuntimeException("Failed to initialize ClassFinder", nfe);
+		}
+	}
+
+
+}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ClassList.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ClassList.java
new file mode 100644
index 0000000..45af33a
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ClassList.java
@@ -0,0 +1,232 @@
+package com.ning.billing.beatrix.lifecycle;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarFile;
+
+/**
+ * This abstract class can be used to obtain a list of all classes in a classpath.
+ *
+ * <em>Caveat:</em> When used in environments which utilize multiple class loaders--such as
+ * a J2EE Container like Tomcat--it is important to select the correct classloader
+ * otherwise the classes returned, if any, will be incompatible with those declared
+ * in the code employing this class lister.
+ * to get a reference to your classloader within an instance method use:
+ *  <code>this.getClass().getClassLoader()</code> or
+ *  <code>Thread.currentThread().getContextClassLoader()</code> anywhere else
+ * <p>
+ * @author Kris Dover <krisdover@hotmail.com>
+ * @version 0.2.0
+ * @since   0.1.0
+ */
+public abstract class ClassList {
+
+	/**
+	 * Searches the classpath for all classes matching a specified search criteria,
+	 * returning them in a map keyed with the interfaces they implement or null if they
+	 * have no interfaces. The search criteria can be specified via interface, package
+	 * and jar name filter arguments
+	 * <p>
+	 * @param classLoader       The classloader whose classpath will be traversed
+	 * @param interfaceFilter   A Set of fully qualified interface names to search for
+	 *                          or null to return classes implementing all interfaces
+	 * @param packageFilter     A Set of fully qualified package names to search for or
+	 *                          or null to return classes in all packages
+	 * @param jarFilter         A Set of jar file names to search for or null to return
+	 *                          classes from all jars
+	 * @return A Map of a Set of Classes keyed to their interface names
+	 *
+	 * @throws ClassNotFoundException if the current thread's classloader cannot load
+	 *                                a requested class for any reason
+	 */
+	public static Map<String, Set<Class<?>>> findClasses(ClassLoader classLoader,
+			Set<String> interfaceFilter,
+			Set<String> packageFilter,
+			Set<String> jarFilter,
+			boolean filterAbstract)
+			throws ClassNotFoundException {
+
+		Map<String, Set<Class<?>>> classTable = new HashMap<String, Set<Class<?>>>();
+		Object[] classPaths;
+		try {
+			// get a list of all classpaths
+			classPaths = ((java.net.URLClassLoader) classLoader).getURLs();
+		}
+		catch(ClassCastException cce){
+			// or cast failed; tokenize the system classpath
+			classPaths = System.getProperty("java.class.path", "").split(File.pathSeparator);
+		}
+
+		for(int h = 0; h < classPaths.length; h++){
+			Enumeration<?> files = null;
+			JarFile module = null;
+			// for each classpath ...
+			File classPath = new File( (URL.class).isInstance(classPaths[h]) ?
+					((URL)classPaths[h]).getFile() : classPaths[h].toString() );
+
+
+
+			if (classPath.isDirectory() && jarFilter == null){   // is our classpath a directory and jar filters are not active?
+				//System.out.println("List dir: " + classPath.getAbsolutePath());
+				List<String> dirListing = new ArrayList<String>();
+				// get a recursive listing of this classpath
+				recursivelyListDir(dirListing, classPath, new StringBuffer() );
+				// an enumeration wrapping our list of files
+				files = Collections.enumeration( dirListing );
+			}
+			else if (classPath.getName().endsWith(".jar") ){    // is our classpath a jar?
+				//System.out.println("Inspect file: " + classPath.getAbsolutePath());
+				// skip any jars not list in the filter
+				if( jarFilter != null && !jarFilter.contains( classPath.getName() ) ){
+					continue;
+				}
+                boolean failed = true;
+				try{
+					// if our resource is a jar, instantiate a jarfile using the full path to resource
+					module = new JarFile( classPath );
+                    failed = false;
+				}catch (MalformedURLException mue){
+					throw new ClassNotFoundException("Bad classpath. Error: " + mue.getMessage());
+				}catch (IOException io){
+					//throw new ClassNotFoundException("jar file '" + classPath.getName() + "' could not be instantiate from file path. Error: " + io.getMessage());
+                    System.out.println("ERROR: jar file '" + classPath.getName() + "' could not be instantiate from file path. Error: " + io.getMessage());
+				}
+                if (! failed) {
+				    // get an enumeration of the files in this jar
+				    files = module.entries();
+                }
+			}
+
+			// for each file path in our directory or jar
+			while( files != null && files.hasMoreElements() ){
+				// get each fileName
+				String fileName = files.nextElement().toString();
+
+				// we only want the class files
+				if( fileName.endsWith(".class") ){
+					// convert our full filename to a fully qualified class name
+					String className = fileName.replaceAll("/", ".").substring(0, fileName.length() - 6);
+
+					// debug class list
+					//System.out.println("Class: " + className);
+					// skip any classes in packages not explicitly requested in our package filter
+					if (packageFilter != null) {
+						boolean skip = true;
+						Iterator<String> it = packageFilter.iterator();
+						while (it.hasNext()) {
+							String filter = it.next() + ".";
+							//System.out.println("\tFilter: " + filter + " Class: " + className);
+							if (className.startsWith(filter)) {
+								skip = false;
+								break;
+							}
+						}
+						if (skip) {
+							continue;
+						}
+					}
+					//System.out.println("\tAdd Class: " + className);
+					// get the class for our class name
+					Class<?> theClass = null;
+					try{
+						theClass = Class.forName(className, false, classLoader);
+					}catch(NoClassDefFoundError e){
+						//System.out.println("Skipping class '" + className + "' for reason " + e.getMessage());
+						continue;
+					}
+					// skip interfaces
+					if( theClass.isInterface() ){
+						continue;
+					}
+
+					if (filterAbstract && Modifier.isAbstract(theClass.getModifiers())) {
+						continue;
+					}
+					//then get an array of all the interfaces in our class
+					Class<?> [] classInterfaces = theClass.getInterfaces();
+
+					// for each interface in this class, add both class and interface into the map
+					String interfaceName = null;
+					for (int i = 0; i < classInterfaces.length || (i == 0 && interfaceFilter == null); i++) {
+						if(i < classInterfaces.length){
+							interfaceName = classInterfaces[i].getName();
+							// was this interface requested?
+							if( interfaceFilter != null && !interfaceFilter.contains(interfaceName) ){
+								continue;
+							}
+						}
+						// is this interface already in the map?
+						if( classTable.containsKey( interfaceName ) ){
+							// if so then just add this class to the end of the list of classes implementing this interface
+							classTable.get(interfaceName).add( theClass );
+						}else{
+							// else create a new list initialised with our first class and put the list into the map
+							Set<Class<?>> allClasses = new HashSet<Class<?>>();
+							allClasses.add( theClass );
+							classTable.put(interfaceName, allClasses);
+						}
+					}
+
+				}
+			}
+
+			// close the jar if it was used
+			if(module != null){
+				try{
+					module.close();
+				}catch(IOException ioe){
+					throw new ClassNotFoundException("The module jar file '" + classPath.getName() +
+							"' could not be closed. Error: " + ioe.getMessage());
+				}
+			}
+
+		} // end for loop
+
+		return classTable;
+	} // end method
+
+	/**
+	 * Recursively lists a directory while generating relative paths. This is a helper function for findClasses.
+	 * Note: Uses a StringBuffer to avoid the excessive overhead of multiple String concatentation
+	 *
+	 * @param dirListing     A list variable for storing the directory listing as a list of Strings
+	 * @param dir                 A File for the directory to be listed
+	 * @param relativePath A StringBuffer used for building the relative paths
+	 */
+	private static void recursivelyListDir(List<String> dirListing, File dir, StringBuffer relativePath){
+		int prevLen; // used to undo append operations to the StringBuffer
+
+
+		// if the dir is really a directory
+		if( dir.isDirectory() ){
+			// get a list of the files in this directory
+			File[] files = dir.listFiles();
+			// for each file in the present dir
+			for(int i = 0; i < files.length; i++){
+				// store our original relative path string length
+				prevLen = relativePath.length();
+				// call this function recursively with file list from present
+				// dir and relateveto appended with present dir
+				recursivelyListDir(dirListing, files[i], relativePath.append( prevLen == 0 ? "" : "/" ).append( files[i].getName() ) );
+				//  delete subdirectory previously appended to our relative path
+				relativePath.delete(prevLen, relativePath.length());
+			}
+		}else{
+			// this dir is a file; append it to the relativeto path and add it to the directory listing
+			dirListing.add( relativePath.toString() );
+		}
+	}
+}
\ No newline at end of file
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
new file mode 100644
index 0000000..32e9c42
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
@@ -0,0 +1,128 @@
+package com.ning.billing.beatrix.lifecycle;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.beatrix.lifecycle.LyfecycleHandlerType.LyfecycleLevel;
+
+
+public class Lifecycle {
+
+    private final static Logger log = LoggerFactory.getLogger(Lifecycle.class);
+
+
+    private final class LifecycleHandler {
+        private final Object target;
+        private final Method method;
+
+        public LifecycleHandler(Object target, Method method) {
+            this.target = target;
+            this.method = method;
+        }
+
+        public Object getTarget() {
+            return target;
+        }
+
+        public Method getMethod() {
+            return method;
+        }
+    }
+
+    private final SetMultimap<LyfecycleLevel, LifecycleHandler> handlersByLevel;
+
+    private final Injector injector;
+
+    @Inject
+    public Lifecycle(Injector injector) {
+        this.handlersByLevel = Multimaps.newSetMultimap(new ConcurrentHashMap<LyfecycleLevel, Collection<LifecycleHandler>>(),
+
+                new Supplier<Set<LifecycleHandler>>() {
+                  @Override
+                  public Set<LifecycleHandler> get() {
+                    return new CopyOnWriteArraySet<LifecycleHandler>();
+                  }
+                });
+        this.injector = injector;
+    }
+
+    public void init() {
+        List<? extends LifecycleService> services = findServices();
+        Iterator<? extends LifecycleService> it = services.iterator();
+        while (it.hasNext()) {
+        //for (<? extends LifecycleService> cur : services) {
+            handlersByLevel.putAll(findAllHandlers(it.next()));
+        }
+    }
+
+
+
+    public void fireStages() {
+        for (LyfecycleLevel level : LyfecycleLevel.values()) {
+            log.info("Firing stage {}", level);
+            Set<LifecycleHandler> handlers = handlersByLevel.get(level);
+            for (LifecycleHandler cur : handlers) {
+                log.info("Calling handler {}", cur.getMethod().getName());
+                try {
+                    Method method = cur.getMethod();
+                    Object target = cur.getTarget();
+                    method.invoke(target);
+                } catch (Exception e) {
+                    log.warn("Faikled to invoke lifecycle handler", e);
+                }
+
+            }
+        }
+    }
+
+    private List<? extends LifecycleService> findServices() {
+
+        List<LifecycleService> result = new LinkedList<LifecycleService>();
+
+        ClassFinder classFinder = new ClassFinder(Lifecycle.class.getClassLoader());
+        List<Class<? extends LifecycleService>> services =  classFinder.getServices();
+        for (Class<? extends LifecycleService> cur : services) {
+            log.info("Found service {}", cur);
+            result.add(injector.getInstance(cur));
+        }
+        return result;
+    }
+
+
+    public Multimap<LyfecycleLevel, LifecycleHandler> findAllHandlers(Object service) {
+        Multimap<LyfecycleLevel, LifecycleHandler> methodsInService =
+            HashMultimap.create();
+        Class clazz = service.getClass();
+        for (Method method : clazz.getMethods()) {
+            LyfecycleHandlerType annotation = method.getAnnotation(LyfecycleHandlerType.class);
+
+            if (annotation != null) {
+
+                LyfecycleLevel level = annotation.value();
+                LifecycleHandler handler = new LifecycleHandler(service, method);
+                methodsInService.put(level, handler);
+            }
+        }
+        return methodsInService;
+    }
+
+
+}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycled.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycled.java
new file mode 100644
index 0000000..ad39dad
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycled.java
@@ -0,0 +1,11 @@
+package com.ning.billing.beatrix.lifecycle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Lifecycled {
+}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/LifecycleService.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/LifecycleService.java
new file mode 100644
index 0000000..721c969
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/LifecycleService.java
@@ -0,0 +1,5 @@
+package com.ning.billing.beatrix.lifecycle;
+
+public interface LifecycleService {
+
+}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/LyfecycleHandlerType.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/LyfecycleHandlerType.java
new file mode 100644
index 0000000..b2e375e
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/LyfecycleHandlerType.java
@@ -0,0 +1,24 @@
+package com.ning.billing.beatrix.lifecycle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface LyfecycleHandlerType {
+
+
+    public enum LyfecycleLevel {
+        LOAD_CATALOG,
+        INIT_BUS,
+        REGISTER_EVENTS,
+        START_SERVICE,
+        STOP_SERVICE,
+        UNREGISTER_EVENTS,
+        SHUTDOWN
+    }
+
+    public LyfecycleLevel value();
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
new file mode 100644
index 0000000..269a13b
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
@@ -0,0 +1,83 @@
+package com.ning.billing.beatrix.lifecycle;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.beatrix.lifecycle.LyfecycleHandlerType.LyfecycleLevel;
+import com.ning.billing.entitlement.IEntitlementService;
+
+
+public class TestLifecycle {
+
+    private final static Logger log = LoggerFactory.getLogger(TestLifecycle.class);
+
+    @Lifecycled
+    public static class Service1 implements LifecycleService {
+
+        @LyfecycleHandlerType(LyfecycleLevel.INIT_BUS)
+        public void initBus() {
+            log.info("Service1 : got INIT_BUS");
+        }
+
+        @LyfecycleHandlerType(LyfecycleLevel.START_SERVICE)
+        public void startService() {
+            log.info("Service1 : got START_SERVICE");
+        }
+    }
+
+    @Lifecycled
+    public static class Service2 implements LifecycleService {
+
+        @LyfecycleHandlerType(LyfecycleLevel.LOAD_CATALOG)
+        public void initBus() {
+            log.info("Service1 : got INIT_BUS");
+        }
+    }
+
+
+    private Service1 s1;
+    private Service2 s2;
+
+    private Lifecycle lifecycle;
+
+    @BeforeClass(groups={"fast"})
+    public void setup() {
+        final Injector g = Guice.createInjector(Stage.DEVELOPMENT, new TestLifecycleModule());
+        s1 = g.getInstance(Service1.class);
+        s2 = g.getInstance(Service2.class);
+        lifecycle = g.getInstance(Lifecycle.class);
+        lifecycle.init();
+    }
+
+    @Test
+    public void testLifecycle() {
+
+        lifecycle.fireStages();
+    }
+
+
+    public static class TestLifecycleModule extends AbstractModule {
+
+        @Override
+        protected void configure() {
+            bind(Lifecycle.class).asEagerSingleton();
+            bind(Service1.class).asEagerSingleton();
+            bind(Service2.class).asEagerSingleton();
+        }
+
+    }
+
+
+
+}
+
diff --git a/beatrix/src/test/resources/log4j.xml b/beatrix/src/test/resources/log4j.xml
new file mode 100644
index 0000000..75abc76
--- /dev/null
+++ b/beatrix/src/test/resources/log4j.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2010-2011 Ning, Inc.
+  ~
+  ~ Ning licenses this file to you under the Apache License, version 2.0
+  ~ (the "License"); you may not use this file except in compliance with the
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  ~ License for the specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+        <param name="Target" value="System.out"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%p	%d{ISO8601}	%X{trace}	%t	%c	%m%n"/>
+        </layout>
+    </appender>
+
+
+    <logger name="com.ning.billing.entitlement">
+        <level value="info"/>
+    </logger>
+
+    <root>
+        <priority value="info"/>
+        <appender-ref ref="stdout"/>
+    </root>
+</log4j:configuration>