| Tackling Incompatibility Problems of using 3rd Party Components with OSGi |
|
|
| Written by Valery Abu-Eid | |
|
When we say “a component is incompatible with OSGi” we generally mean that the component is unusable from the OSGi Environment – Yes, it's the equivalent of saying: “I can't get the component working inside my OSGi-based application!”. No matter how often you face this problem with different components and how complex or time consuming it can be you will always end up finding that the cause of the incompatibility was one or both of the following problems:
Next two sections discuss these problems and different solutions for them. OSGi Incompliant Class LibrariesAs was said before: An OSGi incompliant class library is a JAR file that doesn't contain OSGi headers in it's manifest – I will call these libraries: Non-bundled libraries. A bundle inside an OSGi Environment need to identify itself, expose the classes and other resources it contains and consume classes and other resources exposed by other bundles, for that the bundle describes this information using OSGi Headers in its manifest file. The problem with non-bundled libraries is clear: They don't describe this information. Since OSGi has no support for non-bundled libraries, all the approaches that aim to solve this problem don't differentiate in whether they generate the needed OSGi headers for the non-bundled library or not, but rather when these headers are generated and how. As you know, you can't add headers to JAR file's manifest but you can create a new JAR file which has the same contents of the original one except for the manifest which would have the additional headers. I will call the process of generating an OSGi compatible JAR file from an OSGi-incompatible one Bundling. Bundling approaches/solutions can be broke into 3 categories based on their automation level:
I will discuss the three kind of solutions for bundling class libraries and a different approach in which developers rely on 3rd party vendors for providing them with bundled versions of the class libraries they use. Manual BundlingIf you have a very small library (a JAR file that contains very few packages) and you don't want to spend time on learning a new tool or approach to bundle it, then it might be suitable for you to add the OSGi headers manually in the manifest file, though in real world applications you will find many libraries that need to be bundled, so it's not practical to bundle them yourself. If you decide to do so, then you need to take into account the following:
As said before, this can only be suitable when you have one or two bundles with very few packages, but when you use 3rd party libraries, the libraries themselves will mostly have dependencies on other non-bundled libraries. Semi-Automatic BundlingBest known tool that falls in this category is Bnd. Although Bnd usually used for creating bundles, it can also be used for bundling non-bundled libraries (wrapping – as it's called in Bnd). The wrap command accepts a JAR file or a directory that contains JAR files as an input and returns a bundled JAR file which contains generated OSGi headers that ideally should work. Following are the good and the bad things about the wrapping functionality of Bnd. The Good:
The Bad:
Automatic BundlingBundle Generators generate bundles from non-bundled libraries at load time or runtime. They need nothing from the developer other than specifying the libraries that need to be bundled. Bundle Generators can be divided into two categories: Simple and Smart Bundle Generators. Simple Bundle Generators are usually implemented with the intention of bundling components that consist of a single class library (a single JAR file), for that case they work fine, but there are no guarantee that they would work with more complex components (components that consist of more than one class library). They almost contain no logic at all and usually fit within a single Java class. This post contains an example of developing a simple Bundle Generator. Smart Bundles Generators on the other hand are intended for complex non-osgi components. They take into account that a component can consist of multiple libraries, package versioning, the existence of library's packages in application's classpath and other problems related to running a non-osgi component in an OSGi Environment. I never noticed the difference between Bundle Generators unless I developed a bundle generator for DA-Launcher. I started with a simple Bundle Generator like the ones I've seen in other projects and every thing seemed fine during tests – Every thing seemed fine during tests due the fact that I was testing a “Hello World!” bundle all the way. But when I wanted real world non-osgi components get to work on DA-Launcher using the Bundle Generator I fell into countless problems. At the end I realized that I've spent time on the development of the working version of the Bundle Generator 15-20 times more than I've spent on the simple one and that there is a clear difference between an implementation of a Bundle Generator that works for components which consist of a single library and an implementation that works for components which consist of several libraries. It's worth mentioning that during the course, I was able to run in DA-Launcher more than 9 major non-osgi components that contained more than 130 class libraries – Actually, after running a little more than 100 libraries I reached to the point where I couldn't face new problems with bundles wiring, at that moment I was sure that the Bundle Generator works fine enough to be released. Below are the good and the bad things about Bundle Generators in general and the Bundle Generator of DA-Launcher specifically. The Good:
The Bad:
Relying on 3rd party vendors for Bundling Class LibrariesThe only company that provides such a service is SpringSource with it's SpringSource Bundle Repository. The Spring Source Repository contains bundled versions of many components. Below are the Good and the Bad things about SpringSource Repository. The Good:
The Bad:
Unexpected Behavior of non-osgi ComponentsUnfortunately, even if you managed to have non-osgi component's libraries bundled and wired correctly you might still have some problems concerning the behavior of the component. Theoretically it can be any problem caused by the differences between OSGi and non-osgi environments, but on practice you will find that most of the problems, if not all of them, are class or resource loading exceptions. The cause of the problem is that many components like JAX-WS, JAXB, Toplink, etc. load some of the classes they need and classes provided by the user from the context class loader of the thread they run in. For instance, when Toplink tries to load an entity class described in the persistence.xml file it will look for it in the context class loader of the thread where it runs. Below are the most common indicators of a class/resource loading problem:
The most suitable solution for this problem would be to set thread's context class loader to a class loader that loads classes and resources from the OSGi Environment before invoking classes provided by an OSGi-incompliant component that will probably load some resources – For the sake of simplicity I usually set the class loader in the start method of my Bundle Activator. While you can always develop your own class loader, generally it's not recommended since you will mostly fall into many unexpected problems along the way – If you find yourself in a position where you need to develop your own, it's better to extend an existing one rather than beginning from scratch. The The ClassLoading-Utils project provides a bundle that defines a class loader which loads classes and resources from the bundles of the OSGi environment. This class loader is optimized and is used by all of the projects and examples at DynamicJava.org that run OSGi-incompatible components – In other words it proved to work with more than 200 non-bundled class libraries. The ClassLoading-Utils bundle is very lightweight (by having the minimal amount of classes and no dependencies on other bundles), it weighs less than 20 kilobytes. This example contains an application that uses JAXB, if you look at the source code you will find how the class loader was set to tackle this problem. A similar example for JPA can be downloaded from here.SummaryThe process of tackling incompatibility problems when using an OSGi-incompatible component in an OSGi Environment involves two steps:
Bundling component's class libraries mostly will be done either by a Bundle Generator that will bundle the libraries at runtime or a tool that the developer will use to bundle the libraries before running the application. Fixing class/resource loading issues for OSGi-incompatible components can be solved by making a class loader which loads classes and resources from the OSGi environment as the context class loader of the thread that invokes the component. |