Sunday, February 28, 2010

Eclipse RCP - Converting Dependencies to OSGi Bundles

Introduction:

Your Eclipse Bundle (plugin) may have many dependencies to third party libraries or to your own API/utility libraries. There are two ways to use them. One is to add them to your bundle classpath or convert them to bundles, add it to target platform and use them as bundle dependency (called as Plug-in Dependency in Eclipse).

(Plugin and Bundle are one and the same. Plugin is Eclipse terminology and Bundle is OSGi terminology.)

Why you need to convert dependencies to bundles?

You already have your third party dependency (log4j, apache commons collections etc.,) as jars and all you need to do is add it to your bundle classpath. This is simple and it works as long as you are developing only one bundle or your bundles do not share common dependencies.

But, in reality, you might be creating a bunch of bundles and they all would use common dependencies. The problem here is in the way OSGi classloading works. Each bundle has it's own classloader and so if you have two bundles both using log4j as jar dependency in their classpath, and when you try to reference log4j in each of the bundles, each would try create their own instances of the log4j classes in their respective classloaders. This might cause classloader constraint violation issues or ClassCastExceptions. If you convert these dependencies to OSGi bundles, then these problems would no longer happen. This is because now each of your dependency (say log4j) has it's own classloader and so whenever your Bundle A or Bundle B want to load log4j class, it will load it from log4j bundle classloader.

What is OSGi bundle anyway?

OSGi bundle is also a regular jar but has OSGi information in it's manifest. For example, this is how commons-lang bundle's manifest looks like. This has OSGi information like bundle name, what packages it exports, what packages it imports etc.,

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: 1.4.2_16 (Apple Computer, Inc.)
Built-By: hen
Build-Jdk: 1.4.2_16
Implementation-Title: Commons Lang
Implementation-Vendor: The Apache Software Foundation
Implementation-Vendor-Id: org.apache
Implementation-Version: 2.4
Specification-Title: Commons Lang
Specification-Vendor: The Apache Software Foundation
Specification-Version: 2.4
X-Compile-Source-JDK: 1.3
X-Compile-Target-JDK: 1.2
Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt
Import-Package: org.apache.commons.lang;version="2.4",
org.apache.commons.lang.builder;version="2.4",
org.apache.commons.lang.enum;version="2.4",
org.apache.commons.lang.enums;version="2.4",
org.apache.commons.lang.exception;version="2.4",
org.apache.commons.lang.math;version="2.4",
org.apache.commons.lang.mutable;version="2.4",
org.apache.commons.lang.text;version="2.4",
org.apache.commons.lang.time;version="2.4"
Bnd-LastModified: 1205638979942
Export-Package: org.apache.commons.lang.math;version="2.4",
org.apache.commons.lang.enums;version="2.4",
org.apache.commons.lang.builder;version="2.4",
org.apache.commons.lang.exception;version="2.4",
org.apache.commons.lang.enum;version="2.4",
org.apache.commons.lang.mutable;version="2.4",
org.apache.commons.lang.text;version="2.4",
org.apache.commons.lang.time;version="2.4",
org.apache.commons.lang;version="2.4"
Bundle-Version: 2.4
Bundle-Description: Commons Lang, a package of Java utility classes
for the classes that are in java.lang's hierarchy, or are considered to be
so standard as to justify existence in java.lang.
Bundle-Name: Commons Lang
Bundle-DocURL: http://commons.apache.org/lang/
Bundle-ManifestVersion: 2
Bundle-Vendor: The Apache Software Foundation
Bundle-SymbolicName: org.apache.commons.lang
Tool: Bnd-0.0.238

How to convert dependencies to bundles?

As OSGi is getting popular, many libraries are already published as bundles. But still, there are many which are not converted already. There are several ways to convert dependencies to bundles.
  1. You can always manually do it by hand i.e take the jar add a manifest with OSGi information in it and re-jar it. But, this is error-prone and also cumbersome if you have many dependencies.
  2. You can use Eclipse File->New->Plugin Development->Plugin from existing jar archives. In the plugin project properties screen, make sure to check "Analyze library contents and add dependencies". This will add appropriate require-bundle entries to the manifest. Also, use "Unzip the jar archives into the project" option. This way is less error-prone, but still cumbersome if you have many dependencies.
  3. There are public repositories which provide OSGi bundles for popular third party libraries. For example, look at Eclipse Orbit or SpringSource Bundle Repository. But still it is hard to find all libraries or the latest versions of the popular libraries.
  4. The best way is to use BND bundle tool. You could use BND directly or use BND maven plugin, which is very powerful and easy to use.
Converting your third party dependency libraries using BND maven plugin:

If you want to convert all of the third dependencies to OSGi bundles, then you could use "bundleall" goal from the maven plugin. I suggest creating a separate bundle generator project and list all the direct dependencies (of all of your Eclipse GUI Bundles) in it's POM. Then, add the build section listed below. See that it uses "bundleall" goal, which is bind to "process-classes" build lifecycle phase.

<dependencies>
<!-- List all top level dependencies. -->
<!-- This will bring other dependencies transitively. -->
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.0.1</version>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes<phase>
<goals>
<goal>bundleall</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Now, when you go to command-line and type "mvn process-classes" (or any build lifecycle phase greater than process-classes. For more info on maven lifecycles and it's phases, refer here), all the OSGi bundles for all dependencies (including transitive) will be generated in the output folder (default is target\classes). You can just copy them from the output folder to your Eclipse target platform or maven repository.

Converting your API libraries using BND maven plugin:

If your API libraries do not change often, then you could use what is suggested above for third party libraries. But, if they are in active development (which might be the case often) and you want your GUI to work against their SNAPSHOTs, then that may not work. Good way to convert your API/utility libraries is to modify the POM's of these libraries or better their parent POM. Look at "Adding OSGi metadata to existing projects without changing the packaging type" section in the BND maven plugin page here. This will make sure that your library is still packaged as jar, but it's manifest would have OSGi information now. So, now your API project will always be automatically published as OSGi bundle to your maven repository.

Automate Copying Bundles to Eclipse target platform:

For third party libraries, I said above that you could either copy them to target platform or maven repo, since they are static and do not change everyday. But, for your own API libraries which change everyday and if you want to work against their SNAPSHOTs, you need to get their OSGi bundles to Eclipse target platform in an automated fashion. For that, I suggest adding POM to your target platform. (By the way, we maintain target platform in subversion) and use "copy-dependencies" goal from maven dependency plugin. Add the following block to your target platform POM.

<properties>
<pluginsDir>./plugins</pluginsDir>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>process-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${pluginsDir}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Now, whenever you do "mvn process-resources", this would pull in latest OSGi bundle dependencies from maven repo and copy to plugins folder in target platform. So, the workflow would be, your CI system would publish your API libraries as OSGi bundles to maven repo and then GUI developers would use this maven command in their target platform, so that their target platform has the latest API bundle dependencies. Also, target platform has to be reloaded/refreshed in your Eclipse IDE using Window->Preferences->Plugin Development->Target Platform. I don't know of a way to automate this part yet.

Conclusion:

We saw the issues with using bundles dependencies as regular jars in bundle classpath and why they need to be converted to OSGi bundles. We looked at what an OSGi bundle is and saw different ways to create an OSGi bundle. Finally, we looked at how to use maven BND plugin to convert third party or your own API/utility libraries to OSGi bundles and how to update your Eclipse target platform automatically.