killbill-uncached
Changes
beatrix/src/test/resources/log4j.xml 36(+36 -0)
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();
+ }
+
+ }
+
+
+
+}
+
beatrix/src/test/resources/log4j.xml 36(+36 -0)
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>