killbill-memoizeit

Details

diff --git a/api/src/main/java/com/ning/billing/osgi/api/OSGIUserApi.java b/api/src/main/java/com/ning/billing/osgi/api/OSGIUserApi.java
index edde588..b0bdfed 100644
--- a/api/src/main/java/com/ning/billing/osgi/api/OSGIUserApi.java
+++ b/api/src/main/java/com/ning/billing/osgi/api/OSGIUserApi.java
@@ -17,6 +17,4 @@
 package com.ning.billing.osgi.api;
 
 public interface OSGIUserApi {
-
-    public <S> S getService(final String serviceClassName) throws LiveTrackerException;
 }
diff --git a/osgi/src/main/java/com/ning/billing/osgi/api/DefaultOSGIUserApi.java b/osgi/src/main/java/com/ning/billing/osgi/api/DefaultOSGIUserApi.java
index 7f28a9c..a034ab9 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/api/DefaultOSGIUserApi.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/api/DefaultOSGIUserApi.java
@@ -16,21 +16,6 @@
 
 package com.ning.billing.osgi.api;
 
-import javax.inject.Inject;
-
-import com.ning.billing.osgi.LiveTracker;
-
 public class DefaultOSGIUserApi implements OSGIUserApi {
 
-    private final LiveTracker liveTracker;
-
-    @Inject
-    public DefaultOSGIUserApi(LiveTracker liveTracker) {
-        this.liveTracker = liveTracker;
-    }
-
-    @Override
-    public <S> S getService(final String serviceClassName) throws LiveTrackerException {
-        return liveTracker.getRegisteredOSGIService(serviceClassName );
-    }
 }
diff --git a/osgi/src/main/java/com/ning/billing/osgi/ContextClassLoaderHelper.java b/osgi/src/main/java/com/ning/billing/osgi/ContextClassLoaderHelper.java
new file mode 100644
index 0000000..3e4660b
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/ContextClassLoaderHelper.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2010-2013 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.
+ */
+
+package com.ning.billing.osgi;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class ContextClassLoaderHelper {
+
+
+    /*
+      http://impalablog.blogspot.com/2008/10/using-threads-context-class-loader-in.html:
+
+      "Many existing java libraries are designed to run inside a container (J2EE container, Applet container etc).
+      Such containers explicitly define execution boundaries between the various components running within the container.
+      The container controls the execution boundaries and knows when a boundary is being crossed from one component to the next.
+
+      This level of boundary control allows a container to switch the context of a thread when a component boundary is crossed.
+      Typically when a container detects a context switch it will set the context class loader on the thread to a class loader associated with the component which is being entered.
+      When the component is exited then the container will switch the context class loader back to the previous context class loader.
+
+      The OSGi Framework specification does not define what the context class loader should be set to and does not define when it should be switched.
+      Part of the problem is the Framework is not always aware of when a component boundary is crossed."
+
+      => So our current implementation is to proxy all calls from Killbill to OSGI registered services, and set/unset classloader before/after entering the call
+
+    */
+
+    public static <T> T getWrappedServiceWithCorrectContextClassLoader(final T service) {
+
+        final Class<T> serviceClass = (Class<T>) service.getClass();
+        final List<Class> allServiceInterfaces = getAllInterfaces(serviceClass);
+        final Class[] serviceClassInterfaces = allServiceInterfaces.toArray(new Class[allServiceInterfaces.size()]);
+
+        final InvocationHandler handler = new InvocationHandler() {
+            @Override
+            public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+                final ClassLoader initialContextClassLoader = Thread.currentThread().getContextClassLoader();
+                try {
+                    Thread.currentThread().setContextClassLoader(serviceClass.getClassLoader());
+                    return method.invoke(service, args);
+                } catch (InvocationTargetException e) {
+                    if (e.getCause() != null) {
+                        throw e.getCause();
+                    } else {
+                        throw new RuntimeException(e);
+                    }
+                } finally {
+                    Thread.currentThread().setContextClassLoader(initialContextClassLoader);
+                }
+            }
+        };
+        final T wrappedService = (T) Proxy.newProxyInstance(serviceClass.getClassLoader(),
+                                                            serviceClassInterfaces,
+                                                            handler);
+        return wrappedService;
+    }
+
+
+    // From apache-commons
+    private static List getAllInterfaces(Class cls) {
+        if (cls == null) {
+            return null;
+        }
+        List list = new ArrayList();
+        while (cls != null) {
+            Class[] interfaces = cls.getInterfaces();
+            for (int i = 0; i < interfaces.length; i++) {
+                if (list.contains(interfaces[i]) == false) {
+                    list.add(interfaces[i]);
+                }
+                List superInterfaces = getAllInterfaces(interfaces[i]);
+                for (Iterator it = superInterfaces.iterator(); it.hasNext(); ) {
+                    Class intface = (Class) it.next();
+                    if (list.contains(intface) == false) {
+                        list.add(intface);
+                    }
+                }
+            }
+            cls = cls.getSuperclass();
+        }
+        return list;
+    }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java b/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java
index ce9b51e..1728d7c 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java
@@ -27,7 +27,6 @@ import com.ning.billing.osgi.DefaultOSGIKillbill;
 import com.ning.billing.osgi.DefaultOSGIService;
 import com.ning.billing.osgi.KillbillActivator;
 import com.ning.billing.osgi.KillbillEventObservable;
-import com.ning.billing.osgi.LiveTracker;
 import com.ning.billing.osgi.PureOSGIBundleFinder;
 import com.ning.billing.osgi.api.DefaultOSGIUserApi;
 import com.ning.billing.osgi.api.OSGIKillbill;
@@ -76,7 +75,6 @@ public class DefaultOSGIModule extends AbstractModule {
         bind(OSGIService.class).to(DefaultOSGIService.class).asEagerSingleton();
 
         bind(OSGIUserApi.class).to(DefaultOSGIUserApi.class).asEagerSingleton();
-        bind(LiveTracker.class).to(KillbillActivator.class).asEagerSingleton();
         bind(KillbillActivator.class).asEagerSingleton();
         bind(PureOSGIBundleFinder.class).asEagerSingleton();
         bind(PluginFinder.class).asEagerSingleton();
diff --git a/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpService.java b/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpService.java
index 30220f7..b923149 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpService.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpService.java
@@ -28,6 +28,8 @@ import org.osgi.service.http.HttpContext;
 import org.osgi.service.http.HttpService;
 import org.osgi.service.http.NamespaceException;
 
+import com.ning.billing.osgi.ContextClassLoaderHelper;
+
 @Singleton
 public class DefaultHttpService implements HttpService {
 
@@ -40,13 +42,15 @@ public class DefaultHttpService implements HttpService {
 
     @Override
     public void registerServlet(final String alias, final Servlet servlet, final Dictionary initparams, final HttpContext httpContext) throws ServletException, NamespaceException {
+
         if (alias == null) {
             throw new IllegalArgumentException("Invalid alias (null)");
         } else if (servlet == null) {
             throw new IllegalArgumentException("Invalid servlet (null)");
         }
+        final Servlet wrappedServlet = ContextClassLoaderHelper.getWrappedServiceWithCorrectContextClassLoader(servlet);
 
-        servletRouter.registerServiceFromPath(alias, servlet);
+        servletRouter.registerServiceFromPath(alias, wrappedServlet);
     }
 
     @Override
diff --git a/osgi/src/main/java/com/ning/billing/osgi/http/OSGIServlet.java b/osgi/src/main/java/com/ning/billing/osgi/http/OSGIServlet.java
index 1448e58..e4f86fb 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/http/OSGIServlet.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/http/OSGIServlet.java
@@ -79,18 +79,7 @@ public class OSGIServlet extends HttpServlet {
         if (pluginServlet != null) {
             initializeServletIfNeeded(req, pluginServlet);
             final OSGIServletRequestWrapper requestWrapper = new OSGIServletRequestWrapper(req, servletRouter.getPluginPrefixForPath(requestPath));
-
-            //
-            // Before entering into the plugin we want to set context classloader to the Bundle classloader (pluginServlet is set with such a context)
-            // in case some libraries are using contextClassLoader.
-            //
-            final ClassLoader prevContextClassLoader = Thread.currentThread().getContextClassLoader();
-            Thread.currentThread().setContextClassLoader(pluginServlet.getClass().getClassLoader());
-            try {
-                pluginServlet.service(requestWrapper, resp);
-            } finally {
-                Thread.currentThread().setContextClassLoader(prevContextClassLoader);
-            }
+            pluginServlet.service(requestWrapper, resp);
         } else {
             resp.sendError(404);
         }
diff --git a/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java b/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java
index 2c25c32..c2b038c 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java
@@ -16,11 +16,14 @@
 
 package com.ning.billing.osgi;
 
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
 import java.util.Dictionary;
-import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Observable;
 
 import javax.inject.Inject;
@@ -34,11 +37,9 @@ import org.osgi.framework.ServiceEvent;
 import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.http.HttpService;
-import org.osgi.util.tracker.ServiceTracker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.ning.billing.osgi.api.LiveTrackerException;
 import com.ning.billing.osgi.api.OSGIKillbill;
 import com.ning.billing.osgi.api.OSGIPluginProperties;
 import com.ning.billing.osgi.api.OSGIServiceDescriptor;
@@ -49,7 +50,7 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillRegistrar;
 
 import com.google.common.collect.ImmutableList;
 
-public class KillbillActivator implements BundleActivator, ServiceListener, LiveTracker {
+public class KillbillActivator implements BundleActivator, ServiceListener {
 
     // TODO : Is that ok for system bundle to use Killbill Logger or do we need to LoggerService like we do for any other bundle
     private final static Logger logger = LoggerFactory.getLogger(KillbillActivator.class);
@@ -59,8 +60,6 @@ public class KillbillActivator implements BundleActivator, ServiceListener, Live
     private final DataSource dataSource;
     private final KillbillEventObservable observable;
     private final OSGIKillbillRegistrar registrar;
-    private final Map<String, ServiceTracker> liveTrackers;
-
 
     private final List<OSGIServiceRegistration> allRegistrationHandlers;
 
@@ -80,8 +79,6 @@ public class KillbillActivator implements BundleActivator, ServiceListener, Live
         this.observable = observable;
         this.registrar = new OSGIKillbillRegistrar();
         this.allRegistrationHandlers = ImmutableList.<OSGIServiceRegistration>of(servletRouter, paymentProviderPluginRegistry);
-        this.liveTrackers = new HashMap<String, ServiceTracker>();
-
     }
 
     @Override
@@ -107,9 +104,6 @@ public class KillbillActivator implements BundleActivator, ServiceListener, Live
         context.removeServiceListener(this);
         observable.unregister();
         registrar.unregisterAll();
-        for (ServiceTracker tracker : liveTrackers.values()) {
-            tracker.close();
-        }
     }
 
     @Override
@@ -119,11 +113,7 @@ public class KillbillActivator implements BundleActivator, ServiceListener, Live
             return;
         }
 
-
         final ServiceReference serviceReference = event.getServiceReference();
-
-        registerUnregisterLiveTrackers(serviceReference, event.getType());
-
         for (OSGIServiceRegistration cur : allRegistrationHandlers) {
             if (listenForServiceType(serviceReference, event.getType(), cur.getServiceType(), cur)) {
                 break;
@@ -131,61 +121,6 @@ public class KillbillActivator implements BundleActivator, ServiceListener, Live
         }
     }
 
-    @Override
-    public <S> S getRegisteredOSGIService(final String serviceClassName) throws LiveTrackerException {
-        try {
-            ServiceTracker tracker = liveTrackers.get(serviceClassName);
-            if (tracker == null) {
-                throw new LiveTrackerException("No live tracker for service " + serviceClassName);
-            }
-            S result = (S) tracker.getService();
-            if (result == null) {
-                throw new LiveTrackerException("Live tracker found a null service for " + serviceClassName);
-            }
-            return result;
-        } catch (ClassCastException e) {
-            throw new LiveTrackerException("Live tracker got ClassCastException for " + serviceClassName, e);
-        }
-    }
-
-
-    private void registerUnregisterLiveTrackers(final ServiceReference serviceReference, final int eventType) {
-        final Object theServiceObject = context.getService(serviceReference);
-        switch (eventType) {
-            case ServiceEvent.REGISTERED:
-                createLiveTrackerForService(theServiceObject);
-                break;
-            case ServiceEvent.UNREGISTERING:
-                removeLiveTrackerForService(theServiceObject);
-                break;
-            default:
-                break;
-        }
-
-    }
-
-    private void createLiveTrackerForService(final Object theServiceObject) {
-        final String serviceClassName = theServiceObject.getClass().getName();
-        synchronized (liveTrackers) {
-            if (liveTrackers.get(serviceClassName) == null) {
-                final ServiceTracker tracker = new ServiceTracker(context, serviceClassName, null);
-                liveTrackers.put(serviceClassName, tracker);
-                tracker.open();
-            }
-        }
-    }
-
-    private void removeLiveTrackerForService(final Object theServiceObject) {
-        final String serviceClassName = theServiceObject.getClass().getName();
-        synchronized (liveTrackers) {
-            ServiceTracker tracker = liveTrackers.get(serviceClassName);
-            if (tracker != null) {
-                tracker.close();
-                liveTrackers.remove(serviceClassName);
-            }
-        }
-    }
-
     private <T> boolean listenForServiceType(final ServiceReference serviceReference, final int eventType, final Class<T> claz, final OSGIServiceRegistration<T> registration) {
         // Make sure we can retrieve the plugin name
         final String serviceName = (String) serviceReference.getProperty(OSGIPluginProperties.PLUGIN_NAME_PROP);
@@ -206,7 +141,8 @@ public class KillbillActivator implements BundleActivator, ServiceListener, Live
         final OSGIServiceDescriptor desc = new DefaultOSGIServiceDescriptor(serviceReference.getBundle().getSymbolicName(), serviceName, serviceInfo, claz.getName());
         switch (eventType) {
             case ServiceEvent.REGISTERED:
-                registration.registerService(desc, theService);
+                final T wrappedService = ContextClassLoaderHelper.getWrappedServiceWithCorrectContextClassLoader(theService);
+                registration.registerService(desc, wrappedService);
                 break;
             case ServiceEvent.UNREGISTERING:
                 registration.unregisterService(desc.getServiceName());
diff --git a/osgi-bundles/defaultbundles/src/main/assembly/assembly.xml b/osgi-bundles/defaultbundles/src/main/assembly/assembly.xml
index e5cbe02..9ffda42 100644
--- a/osgi-bundles/defaultbundles/src/main/assembly/assembly.xml
+++ b/osgi-bundles/defaultbundles/src/main/assembly/assembly.xml
@@ -1,7 +1,7 @@
 <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
-    <id>jar-with-dependencies</id>
+    <id>tar-with-dependencies</id>
     <formats>
         <format>tar.gz</format>
     </formats>