Tuesday, February 28. 2012
Using BND to Create OSGi Bundles
As you probably know Glassfish v3 uses an OSGi (Open Services Gateway initiative framework) server internally. This standard provides the modularization of applications into smaller bundles, each one is a tightly-coupled, dynamically loadable collection of classes, jars, and configuration files with explicit dependencies. The framework manages terms like bundles, services, modules and so on. So now any jar (bundle) has to be registered in the internal OSGi implementation of Glassfish (or whatever software which uses OSGi). Apache Felix is the most famous implementation and the one that the Glassfish core uses.
Two times in the last year I faced the problem of adding a bundle (a simple jar file with a few custom classes) to an OSGi server. In the framework you cannot just drop the jar file in a specified location, some special headers in the MANIFEST.MF file have to be specified. Those headers (Import-Package, Export-Package,...) basically specify the dependencies of the bundle, which packages from other bundles this one uses, and the packages that the bundle itself exports to be used by other bundles. Right now there are a lot of software distributors which already include, out of the box, the OSGi headers in their libraries (for example check jar files from Apache or RedHat). Nevertheless a lot of libraries still carry common manifest files (no OSGi headers at all).
The process of adding the headers manually can be very very tedious. In the two commented times I needed to add the headers I became almost crazy. But then I realized that there is a tool called BND which automates the task. Understanding the tool has been quite hard to me, so I decided to explain the process here (I hate wasting my time with a thing I did before but I do not remember how I did it).
This entry explains how to add spymemcached and couchbase-client java libraries as OSGi bundles inside Glassfish v3. These libraries are used to access memcached and couchbase servers from Java clients respectively. The couchbase-client 1.0.1 library depends on spymemcached 2.8.0, apache commons-codec, jboss netty and jettison. The commons-codec and netty libraries are already distributed as OSGi bundles (with beautiful headers in the manifest) and jettison is already packed inside glassfish (version 1.1 in ${GLASSFISH_DIR}/glassfish/modules directory). So the problem is reduced to osgify the other two libraries.
The first step is adding the BND plugin inside Eclipse IDE (there are other ways of using the BND tool but I thought this one was the easiest method). I installed the plugin inside Eclipse Indigo following this tutorial.
Starting with spymemcahed library (the easy one cos it does not depend on any external library) the steps to create the OSGi bundle are the following:
Select File → New → Other... And select Bndtools → Wrap JAR as OSGi Bundle Project. Click Next.
Click Add External and select the downloaded spymemcached-2.8.0.jar library inside the project. Next again.
Select all the packages to be exported. Here the packages to be included in the jar file are selected, in this case, all spymemcached packages should be exported. Click Add All and Next.
Click Next and set default options for OSGi project properties (project name will be spymemcached-2.8.0 as the jar file name).
Finish (default options again).
Set 2.8.0 as version and make log4j and springframework packages optional. You can drag the two packages from calculated to customized box and then mark optional check for both. Making packages optional let the bundle to be loaded in absence of these classes. The other possibility would have been adding the external libraries (I will do that in the next example) but I do not want to use log4j or spring in Glassfish. When you set customized imports always add the * (it makes BND tool to add all the rest of calculated imports that you have not customized).
The final project (BND definition file) is as follows:
-buildpath: lib/spymemcached-2.8.0.jar;version=file Export-Package: net.spy.memcached;version=${Bundle-Version},\ net.spy.memcached.auth;version=${Bundle-Version},\ net.spy.memcached.compat;version=${Bundle-Version},\ net.spy.memcached.compat.log;version=${Bundle-Version},\ net.spy.memcached.internal;version=${Bundle-Version},\ net.spy.memcached.ops;version=${Bundle-Version},\ net.spy.memcached.protocol;version=${Bundle-Version},\ net.spy.memcached.protocol.ascii;version=${Bundle-Version},\ net.spy.memcached.protocol.binary;version=${Bundle-Version},\ net.spy.memcached.spring;version=${Bundle-Version},\ net.spy.memcached.tapmessage;version=${Bundle-Version},\ net.spy.memcached.transcoders;version=${Bundle-Version},\ net.spy.memcached.util;version=${Bundle-Version} Bundle-Version: 2.8.0 Import-Package: org.apache.log4j;resolution:=optional,\ org.springframework.beans.factory;resolution:=optional,\ *
The generated jar file spymemcached-2.8.0.jar is the following (please check the MANIFEST.MF attached and the OSGi headers).
The couchbase-client project is a little more complicated cos it depends on five libraries. Now the steps are the following.
Create a new project as before. File → New → Other... Then Bndtools → Wrap JAR as OSGi Bundle Project. Next (same as before).
Now four external jar libraries are added (couchbase-client, commons-codec and netty directly downloaded from their sites and jettison from glassfish3 modules directory). Remember that those libraries have the OSGi headers yet (because they are distributed this way or because they are already in Glassfish). The spymemcached library will be added later cos here BND projects cannot be added. Next.
Now only the libraries from couchbase-client are going to be exported. So only the three packages of the jar are selected (in this case although five libraries are managed by the project only com.couchbase.client packages will be exported inside the jar file). Next.
Again default properties are set for the project. Next (same as before).
Finish (same as before).
Now the previous project (spymemcached-2.8.0) is added to the current one. In the tab Build click the + button in Build Path section. Select the previous project spymemcached-1.8.0 to the left (the last library is added to the project, but this time is another BND project instead of a direct jar file).
In the main Contents tab, set 1.0.1 as the version. Here you see that all calculated import packages are defined with the version (in the previous library the missing packages were set as optional but here all the needed packages have been explicitly defined). In this case no package needs to be specified as optional.
BND calculates the version automatically, [2.8,3) in case spymemcached packages. I really do not know what it really means (I suppose something like any spymemcached version 2.8.x) but if you want to fix the version, you can select any package and customize it (as it was done previously, just dragging it from calculated to customized and setting the version in the box).
Finally the BND definition file is as follows:
-buildpath: lib/couchbase-client-1.0.1.jar;version=file,\ lib/commons-codec-1.6.jar;version=file,\ lib/jettison.jar;version=file,\ lib/netty-3.2.7.Final.jar;version=file,\ spymemcached-2.8.0;version=latest Export-Package: com.couchbase.client;version=${Bundle-Version},\ com.couchbase.client.vbucket;version=${Bundle-Version},\ com.couchbase.client.vbucket.config;version=${Bundle-Version} Bundle-Version: 1.0.1
Again we have a beautiful jar file couchbase-client-1.0.1.jar generated with all the OSGi headers. If you uncompress the file you would see that only exported couchbase-client packages are there (all the rest of packages are only used to calculate and generate the manifest headers).
As you see with BND the arduous task of creating the OSGi headers is much easier. The tool, out of the box, does the job very well. Maybe some qualifiers have to be added to the definitions just to ignore some packages you are not going to use in your deployment (for example spring in my spymemcached library) or to fix some version numbers. This tool creates a jar file with all the exported packages and generates all the headers for the MANIFEST.MF file (the references are automatically calculated examining the imports of the classes). Obviously the source BND definition file can be directly modified for specific adjustments (but, as you see, I am just a newbie). Finally in the case of Glassfish v3 the final bundles have to be located inside ${GLASSFISH_DIR}/glassfish/modules, when the domain is started they are moved to ${DOMAIN_DIR}/osgi-cache/felix directory. Now I have both libraries ready to be used by internal glassfish OSGi. As always things seem so much easier when explained (I swear that I spent quite a long time to understand the tool) but now the important question is... Why am I adding spymemcached and couchbase bundles to glassfish?
Stay tuned for news!
Comments