I recently found myself in the situation where I needed to provide an easy way for a client to install a set of OSGi bundles and their dependencies. Karaf Features allow you to name a collection of bundles and other Karaf Features for easy installation using a single command. If you’ve never heard of a Karaf Feature, refer to the Karaf documentation for a good tutorial. By combining a feature descriptor with the PAX URL Wrap Protocol and Maven Protocol, you can provide your users with a simple deployment mechanism to support provisioning existing bundles as well as creating bundles from non-OSGi ready JARs at deployment time. While the Spring EBR and ServiceMix both maintain a collection of non-OSGi JARs that have been converted to OSGi bundles, sometimes you need to convert a JAR that is not in one of these repositories to an OSGi bundle, or your users cannot access one of these repositories.
In this situation, you can either create the bundle from the JAR and distribute this bundle to your end users or you can couple PAX URL, Karaf Features, and Maven together to provide a small and easy to distribute installation descriptor. In my case, I need a small and self-contained Zip package for distribution. Creating bundles from the needed third-party JARs and distributing them in the Zip package would unnecessarily bloat the distribution package. This blog post discusses how to combine the tools mentioned above to script the creation of OSGi bundles during installation of a Karaf Feature.
The Karaf Feature Descriptor
The code fragment below illustrates a simple feature descriptor that installs a user created bundle as well as an existing OSGi bundle from a Maven repository.
<features> <feature name="myFeature" version="1.0"> <bundle>mvn:valeri.blog.examples/karaf-wrap/1.0</bundle> <bundle>mvn:commons-codec/commons-codec/1.4</bundle> </feature> </features>
The URLs in each bundle element are PAX URL Maven Protocol URLs. These URLs trigger the provisioning of the Maven artifact into the OSGi framework.
The following code fragment illustrates a feature descriptor that leverages the Wrap Protocol from PAX URL to provision a non-OSGi-ready artifact.
<features> <feature name="myFeature" version="1.0"> <bundle>mvn:valeri.blog.examples/karaf-wrap/1.0</bundle> <bundle>mvn:commons-codec/commons-codec/1.4</bundle> <bundle>wrap:mvn:commons-beanutils/commons-beanutils-core/1.8.3</bundle> </feature> </features>
All is well and simple so far, but what if you need to customize the wrapping by providing specific configuration options. No problem, the PAX wrap capability uses the bnd tool under the covers and the bnd tool allows you to customize the generated bundle. For complex configuration options, the bnd tool uses .bnd files. Luckily, PAX URL allows you to specify the location of such a file in the Wrap URL. For example, the Wrap URL for Commons BeanUtils Core with a custom .bnd file would be formatted as follows.
The above URL is great, if you know where the .bnd file will be located on the file system. In my case, I am distributing a Zip file containing binary artifacts and Maven metadata. There is no way to know where this file will be unzipped to on a user’s file system. Fortunately, the location for the .bnd file can also be a Maven URL. The following Wrap URL specifies the Maven artifact to convert to an OSGi bundle as well as the Maven artifact that describes the configuration for the bnd tool.
While the above URL looks complex, it simply states that the .bnd file is a Maven artifact with the following properties:
- GroupId: valeri.blog.examples
- ArtifactId: karaf-wrap
- Version: 1.0
- Type: bnd
- Classifier: commons-beanutils-core-1.8.3
By using a URL like the one above, you can avoid needing to have advanced knowledge of the location of the .bnd file and you can completely automate the conversion of the non-OSGi-ready artifact without needing to distribute the artifact yourself. The next section will describe how to configure Maven to create and deploy the needed Karaf Feature descriptor and .bnd file.
Configuring Maven to Deploy Karaf Feature Descriptors and .bnd Files
The easiest way to attach these artifacts to your Maven build is to use the Build Helper Plug-in. This Maven plug-in allows you to work with the Maven build process as well as to attach arbitrary artifacts to the Maven build. The following POM fragment adds the feature descriptor and the .bnd file to the Maven build process using the Build Helper Plug-in’s attach-artifact goal.
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <executions> <execution> <id>attach-artifacts</id> <phase>package</phase> <goals> <goal>attach-artifact</goal> </goals> <configuration> <artifacts> <artifact> <file>target/features/features.xml</file> <type>xml</type> <classifier>features</classifier> </artifact> <artifact> <file>target/bnd/commons-beanutils-core-1.8.3.bnd</file> <type>bnd</type> <classifier>commons-beanutils-core-1.8.3</classifier> </artifact> </artifacts> </configuration> </execution> </executions> </plugin>
In the above code fragment, the attached artifacts are retrieved from the target folder of the build. While it is possible to attach static artifacts that live in the source tree, it is often convenient to use filtering of tokens in these files to avoid needing to update version numbers between releases. In this example, the .bnd files and feature descriptor are copied and filtered into the target/bnd and target/features folders, respectively. By default, Maven copies resources into the target/classes folder. In order to keep these files out of the bundle and off the classpath when creating the feature descriptor as part of the bundle module, two additional executions of the Maven Resources Plug-in must be configured. The following POM fragment illustrates the configuration of these executions.
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-features</id> <phase>generate-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>target/features</outputDirectory> <resources> <resource> <directory>src/main/features</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> <execution> <id>copy-bnd</id> <phase>generate-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>target/bnd</outputDirectory> <resources> <resource> <directory>src/main/bnd</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin>
The above configuration, coupled with other configuration in the project POM will deploy the needed artifacts to the Maven repository. You can look at the deployed artifacts that were created by the above configuration by browsing to https://davidvaleri.googlecode.com/svn/repo/valeri/blog/examples/karaf-wrap/1.0/.
To see the complete example configuration and source files, you can browse the example tag in SVN at https://davidvaleri.googlecode.com/svn/projects/examples/karaf-wrap/tags/karaf-wrap-1.0. You can also use this URL to checkout the example code and build it yourself. To build from the source, you will need Maven 2.2.1, Java 1.5/1.6, and access to the Internet to retrieve the needed dependencies from the Maven repositories.
Putting it All Together and Deploying The Feature
Once the artifacts are deployed to a Maven repository, either your local repository or a repository on the network, you can provision them into OSGi using Karaf Features. The easiest way to work with Karaf Features and the other OSGi tools used above is to get an OSGi container that already comes with the needed tools installed. Apache ServiceMix provides these tools out of the box. These instructions will use the supported FUSE distribution available at http://fusesource.com. If you don’t use ServiceMix, you will need to install and configure Karaf Features, PAX URL, and Spring DM in your OSGi container to execute this example. Use this link to download the Zip installer. Simply extract the Zip file and execute the servicemix script in the bin directory. The extracted location of the Zip file will be referred to as $SERVICEMIX_HOME in these instructions. After you execute the script, you will be presented with a shell environment used in the following instructions.
- Add the example Maven repository to the PAX URL configuration by opening $SERVICEMIX_HOME/etc/org.ops4j.pax.url.mvn.cfg in a text editor.
- Edit the org.ops4j.pax.url.mvn.repositories property by appending , http://davidvaleri.googlecode.com/svn/repo/ to the last line of the property value.
- Save your changes to this file.
- In the shell environment, type features:addUrl mvn:valeri.blog.examples/karaf-wrap/1.0/xml/features and press enter to add the feature to the list of known features. Karaf Features is now aware of the feature.
- In the shell environment, type features:list | grep myFeature and press enter. The information for the newly added feature will display.
- In the shell environment, type features:install myFeature and press enter.
- In the shell environment, type osgi:list and press enter. A list of installed OSGi bundles displays. The last three entries in the list are the bundles installed by the new feature. Your output should look like the text below.
[ 207] [Active ] [ ] [Started] [ 60] David Valeri's... [ 208] [Active ] [ ] [ ] [ 60] Commons Codec (1.4) [ 209] [Active ] [ ] [ ] [ 60] Commons BeanUtils...
- Open $SERVICEMIX_HOME/data/log/servicemix.log and observe the log output generated by the installed feature. The log should contain output that resembles the following.
-------------------------------------------- 23:08:14,937 | INFO | raf_wrap.Example | Example | og.examples.karaf_... -------------------------------------------- Protocol: http Host: davidvaleri.wordpress.com Path: --------------------------------------------
While this is a fairly trivial example using only a single JAR, this approach can help you to migrate legacy JARs to OSGi without needing to maintain binaries for each converted JAR or build each converted JAR from source. Avoiding the need to pass these converted JARs out to my clients in the Zip means that the Zip need only contain my application binaries which are not available in a public repository.