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>