| Consuming objects created in the OSGi Environment from a non-osgi application |
|
|
| Written by Valery Abu-Eid | |
| Tuesday, 02 December 2008 01:48 | |
ConditionsYou have a non-osgi Java Application in which an OSGi-based application is embedded. You want to consume objects created in the OSGi-based application (an OSGi Service for example). Problem DescriptionWhen you try to consume (invoking methods, assigning to local variables, etc.) the objects created inside the OSGi Environment from the non-osgi application you get Class Casting Exception. CauseAny class loaded inside the OSGi Environment is loaded from a special class loader provided by the OSGi Framework which is different from the class loader used by the non-osgi application. So even if the class of the non-osgi application is exactly the same as the one of the OSGi-based application, the objects will still be treated as of different types since their classes were loaded using different class loaders. SolutionIf you want to perform on the object a single method invocation or two without passing parameters of conflicting types (the classes that loaded from different class loaders) just invoke the method using reflection, if you have a more complex case then use utilities that would allow you to bridge the classes. API-Bridge for instance, provides such capabilities, actually, it was created to address this problem specifically. Bridge objects act as their type is of the target class loader, so when the non-osgi application works with the bridged object it will seem to it that it was loaded from the same class loader. Next section provide an example that clarifies how API-Bridge functions. API-Bridge Code SnippetBelow is a code snippet that loads the same class using two different class loaders and then bridges them using the API-Bridge.
public void testApiBridge() throws Exception {
/// Class A which implements interface AInterface is loaded by two
/// different class loaders so they are treated as different examples
Class aClass1 = getClassLoader1().loadClass("org.test.A");
Class aClass2 = getClassLoader2().loadClass("org.test.A");
Class aInterface1 = getClassLoader1().loadClass("org.test.AInterface");
Class aInterface2 = getClassLoader2().loadClass("org.test.AInterface");
/// Objects of AImplementation class loaded from Class Loader 1 are not
/// assignable from objects loaded from Class Loader 2.
assertFalse(aInterface1.isAssignableFrom(aInterface2));
Object a2 = aClass2.newInstance();
/// The object is not castable since it's an instance of a class
/// that was loaded using a different class loader.
assertFalse(aInterface1.isInstance(a2));
/// We create an API Bridge that bridges classes (their fields,
/// method parameters, etc.) of the "org.test" package to
/// the class loader 1.
ApiBridge apiBridge = ApiBridge.getApiBridge(getClassLoader1(), "org.test");
Object bridgedA2 = apiBridge.bridge(a2);
/// The bridged a2 object is now castable
assertTrue(aInterface1.isInstance(bridgedA2));
}
Although the example doesn't look very clear at first glance, it's important to note that we wrote only 2 lines of code to bridge the API. What we did up there is that we bridged an object and verified the fact is that it's castable to the AInterface of a different class loader. While API-Bridge supports classes when used as implementations, super classes and method parameters, casting is only supported for interfaces.Real World ExampleAn example of this problem is a Java Web Application run in an OSGi-oblivious Servlet Container (Tomcat for example), the Web Application has an OSGi-based application embedded inside of it. The OSGi-based application registers an OSGi Service that implements the javax.servlet.Servlet interface. The Web Application has a Delegator Servlet which delegates requests to the registered OSGi Service. The problem with this application is that the Web Application can't consume the Servlet object since it's loaded by a class loader of the OSGi Environment, the fact that causes ClassCastException to be thrown. To overcome this problem, I used the API-Bridge to bridge the Servlet API. Below is the ServletDelegator class which delegates requests to the OSGi Service and uses the API-Bridge to bridge the Servlet objects.
public class DelegatorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
BundleContext bundleContext = getBundleContext();
if (bundleContext != null) {
ServiceReference servletServiceRef = bundleContext.getServiceReference(
Servlet.class.getName());
if (servletServiceRef != null) {
/// We can't write a line like:
/// Servlet servletObject =
/// (Servlet)bundleContext.getService(servletServiceRef);
/// because a class casting exception will be thrown since the
/// Servlet class in this code is loaded by Tomcat class loader
/// while the retrieved Servlet by the OSGi Class Loader.
Object servletObject = bundleContext.getService(servletServiceRef);
ApiBridge apiBridge = ApiBridge.getApiBridge(
Thread.currentThread().getContextClassLoader(),
"javax.servlet", "javax.servlet.http");
Servlet servlet = (Servlet)apiBridge.bridge(servletObject);
/// Note that method parameters will be bridged too by API Bridge,
/// so they could be passed to the OSGi Environment.
servlet.service(request, response);
} else {
printHtml("Error: A Servlet OSGi Service was not found.",
response);
}
} else {
printHtml("Error: Bundle Context was not found in the ServletContext.",
response);
}
}
protected BundleContext getBundleContext() {
/// Here we retrieve the Bundle Context of the OSGi-based application
}
/// ... Other supportive code
}
The Example Application and its source code are available from the link below. api-bridge-servlet-example.war |