Thursday

The Class Casting Exception in OSGi Environments

In previous posts, i have deal with the OSGi approach on building services, either the general approach or  using iPOJO. These are ok, if one is willing to implement a startup of services bundles or some project from the start, or to develop a solution with a nature of separation or independency on existing IT assets. Unfortunately, this is rarely the case. Usually, there is either the need to consume some objects from existing applications or to just the other way around,  to consume objects created in the OSGi environment. And real life, demands both. The first issue is normally easy to deal with. Actually, this is the way you should go on. Having in mind the S.O.A. enabling that your project, solution,architecture, code etc, should comply with,  there is an issue in the horizon.

Demystifying the Issue one level down

Consider the common condition: You 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).

The above condition will provide us a problem issue: When 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.

The reason for getting this exception, is that any 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.

Getting around

If 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.Here comes another key resource place for the OSGi’s: DynamicJava. You can overcome the above exception with the help of  API-Bridge for instance, which provides such capabilities. Actually, this API 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. Lets see an example that clarifies how API-Bridge functions.

API-Bridge

Below is a code snippet that loads the same class using two different class loaders and then bridges them using the API-Bridge provided by DynamicJava resources.

   1: public void testApiBridge() throws Exception {  


   2:      /// Class A which implements interface AInterface is loaded by two   


   3:      /// different class loaders so they are treated as different examples  


   4:      Class aClass1 = getClassLoader1().loadClass("org.test.A");  


   5:      Class aClass2 = getClassLoader2().loadClass("org.test.A");  


   6:      Class aInterface1 = getClassLoader1().loadClass("org.test.AInterface");  


   7:      Class aInterface2 = getClassLoader2().loadClass("org.test.AInterface");  


   8:        


   9:      /// Objects of AImplementation class loaded from Class Loader 1 are not  


  10:      /// assignable from objects loaded from Class Loader 2.  


  11:      assertFalse(aInterface1.isAssignableFrom(aInterface2));  


  12:        


  13:      Object a2 = aClass2.newInstance();  


  14:        


  15:      /// The object is not castable since it's an instance of a class  


  16:      /// that was loaded using a different class loader.  


  17:      assertFalse(aInterface1.isInstance(a2));  


  18:        


  19:      /// We create an API Bridge that bridges classes (their fields,  


  20:      /// method parameters, etc.) of the "org.test" package to  


  21:      /// the class loader 1.  


  22:      ApiBridge apiBridge = ApiBridge.getApiBridge(getClassLoader1(), "org.test");  


  23:        


  24:      Object bridgedA2 = apiBridge.bridge(a2);  


  25:      /// The bridged a2 object is now castable  


  26:      assertTrue(aInterface1.isInstance(bridgedA2));  


  27:  }  






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


An 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





   1: public class DelegatorServlet extends HttpServlet {  


   2:       


   3:     @Override  


   4:     protected void doGet(HttpServletRequest request, HttpServletResponse response)  


   5:             throws ServletException, IOException {  


   6:         BundleContext bundleContext = getBundleContext();  


   7:         if (bundleContext != null) {  


   8:             ServiceReference servletServiceRef = bundleContext.getServiceReference(  


   9:                     Servlet.class.getName());  


  10:             if (servletServiceRef != null) {  


  11:                 /// We can't write a line like:  


  12:                 /// Servlet servletObject =  


  13:                 ///     (Servlet)bundleContext.getService(servletServiceRef);  


  14:                 /// because a class casting exception will be thrown since the  


  15:                 /// Servlet class in this code is loaded by Tomcat class loader   


  16:                 /// while the retrieved Servlet by the OSGi Class Loader.  


  17:                 Object servletObject = bundleContext.getService(servletServiceRef);  


  18:                   


  19:                 ApiBridge apiBridge = ApiBridge.getApiBridge(  


  20:                         Thread.currentThread().getContextClassLoader(),  


  21:                        "javax.servlet", "javax.servlet.http");  


  22:                 Servlet servlet = (Servlet)apiBridge.bridge(servletObject);  


  23:                 /// Note that method parameters will be bridged too by API Bridge,  


  24:                 /// so they could be passed to the OSGi Environment.  


  25:                 servlet.service(request, response);  


  26:             } else {  


  27:                 printHtml("Error: A Servlet OSGi Service was not found.",  


  28:                         response);  


  29:             }  


  30:         } else {  


  31:             printHtml("Error: Bundle Context was not found in the ServletContext.",  


  32:                     response);  


  33:         }  


  34:     }  


  35:       


  36:     protected BundleContext getBundleContext() {  


  37:         /// Here we retrieve the Bundle Context of the OSGi-based application  


  38:     }  


  39:       


  40:     /// ... Other supportive code  


  41:       


  42: }