Using Maven to Bundle Non-OSGi Third-Party Libraries

Using Maven to Bundle Non-OSGi Third-Party Libraries

One of the delights of modern software development is the rich availability of third-party libraries. Maven has been at the forefront on this, with a nearly endless supply of components at The Central Repository.

Unfortunately, without special adaptations, many of the these libraries cannot used directly in an OSGi platform like Eclipse.  OSGi mandates specific information within the distribution JAR-files.  Although some libraries, like Gauva, include this data but there are many that do not.  

DepAn, as an Eclipse RCP application, uses the OSGi model for plug-in components.  This provides excellent control for libraries that are packaged with OSGi metadata. However, of the 8 third-party libraries used by DepAn,  6 of them are as simple JAR-files without any of the requisite metadata. This is only the start. Regardless of the technical challenges, easy integration of third-party graph analysis packages is an essential requirement for DepAn.

With the Maven bundle <packaging> plugin, it is straightforward to encapsulate a third-party JAR-file for OSGi use.  Although this is a repackaging of the JAR-file, it does not modify the distribution JAR-file in any way.  The distribution JAR-file is embedded in an outer wrapper. The wrapper describes the OSGi access rules and places the original distribution JAR-file on the wrapper JAR-file’s classpath.  Since the Eclipse OSGi implementation can access these embedded JAR-files, the capabilities of the encapsulated third party library are readily available.

Once these third-party libraries are packaged up, dependent modules can use them through the standard Maven process of including the wrapping artifact in the module’s dependencies:

You simply define the version in your build’s parent:

  <properties>
     <depan.jung.version>0.0.1-SNAPSHOT</depan.jung.version>
   </properties>
  <dependencyManagement>
   <dependencies>
      <dependency>
        <groupId>com.pnambic.depan</groupId>
        <artifactId>depan-jung-library</artifactId>
        <version>${jung.version}</version>
      </dependency>

   </dependencies>
  </dependencyManagement>

And declare the dependency in each dependent module:

  <dependencies>
    <dependency>
      <groupId>com.pnambic.depan</groupId>
      <artifactId>depan-jung-library</artifactId>
    </dependency>
  </dependencies>

Presto-Buildo.  The third-party libraries are available to other components in the OSGi framework without changes to the underlying distribution files.  There are some configuration tricks to enable this, and these are covered in the following section.

Bundling  JUNG libraries for OSGi usage

To expand on this example, DepAn relies on the JUNG libraries for several graph layout strategies.  This third party actually consists of several JAR-files from UC Irvine and it in turn relies on numerous other third-party libraries  The entire collection of JUNG JAR-files and their dependencies are packaged as as single OSGi compliant bundle.

The Maven Bundle Plugin reference provides lots of detail, and some of the more advanced feature can be really useful.  Most <instruction> elements get translated to OSGi MANIFEST.MF sections, but some provide guidance about the packaging process.

Define the Third-Party Library Dependencies

The Maven file to build the OSGi-compliant bundle is fairly straightforward.  Since the bundle components are specific to the product, I identify the version numbers of the included third-party libraries in the POM for the wrapper bundle.

  <properties>
    <collections-generic.version>4.01</collections-generic.version>
    <jung.version>2.0.1</jung.version>
 </properties>

The released wrapper bundle artifact should also be numbered as 2.0.1 (<jung.version>), but that has to be arranged separately.

The dependencies element lists all of the Maven artifact dependencies for JUNG.

  <dependencies>
   <dependency>
     <groupId>net.sourceforge.collections</groupId>
     <artifactId>collections-generic</artifactId>
     <version>${collections-generic.version}</version>
   </dependency>
   <dependency>
     <groupId>net.sf.jung</groupId>
     <artifactId>jung2</artifactId>
     <version>${jung.version}</version>
     <type>pom</type>
   </dependency>
   <dependency>
     <groupId>net.sf.jung</groupId>
     <artifactId>jung-algorithms</artifactId>
     <version>${jung.version}</version>
   </dependency>
   <dependency>
     <groupId>net.sf.jung</groupId>
     <artifactId>jung-api</artifactId>
     <version>${jung.version}</version>
   </dependency>
   <dependency>
     <groupId>net.sf.jung</groupId>
     <artifactId>jung-graph-impl</artifactId>
     <version>${jung.version}</version>
   </dependency>
 </dependencies>

The precise list of dependencies will depend on the third-party library that you are trying to integration.  MavenCentral often has good dependency information, but some trial and error may be required before the full set of deployment components can be identified.

Configuring the Bundle Plugin

The next step is to configure the Maven bundle plugin to put the correct data in the target JAR-file’s META_INF/MANIFEST.MF.

The <Embed-Dependency> element does most of the work.  This causes the output JAR-file to include the JAR-files from the dependencies, and it adds each of the embedded JAR-files to the bundle’s class path.

The other part that requires attention is the <Export-Package> element.  This should be as small a set of package names as possible.  For example, in DepAn’s OSGi wrapper for JOGL, only 6 of the top-level API packages are exposed.  For JUNG, only a subset of all the packages are available to dependent modules.

  <build>
   <plugins>
     <plugin>
       <groupId>org.apache.felix</groupId>
       <artifactId>maven-bundle-plugin</artifactId>
       <version>${bundle.version}</version>
       <extensions>true</extensions>
       <configuration>
         <instructions>
           <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
           <Bundle-Vendor>Pnambic Computing</Bundle-Vendor>
           <Embed-Dependency>*</Embed-Dependency>
           <Export-Package>
             edu.uci.ics.jung.algorithms.importance,
             edu.uci.ics.jung.algorithms.layout,
             edu.uci.ics.jung.algorithms.util,
             edu.uci.ics.jung.graph,
             org.apache.commons.collections15,
             org.apache.commons.collections15.map
           </Export-Package>
           <_nouses>true</_nouses>
           <_removeheaders>
             Embed-Dependency,
             Embed-Artifacts,
             Import-Package,
             Private-Package
           </_removeheaders>
         </instructions>
       </configuration>
     </plugin>
   </plugins>
 </build>

Most configuration for the bundle <packaging> type is dependent on the third-party library that is being bundled, and is properly associated with the bundling pom.xml file itself.  The only item that is configured in the parent module is the version of the bundling plugin to use:

    <bundle.version>2.5.3</bundle.version>

Guidelines, Advice, and Alternatives

I recommend leaving the wrapper bundle’s version number as -SNAPSHOT until you refine the <Export-Package> list.  Once that is as small as possible for your application, it might as well be released with the version number of the underlying third-party library ${jung.version}.

In strict release regimens, teams might be consider adding their own customization and sequencing suffixes (e.g. -blix-201511a).

Leave a comment