Options for embedding libraries

October 8, 2024

When building a Jahia OSGi module, very quickly the need to integrate third party libraries comes. To integrate these into your project there are different possibilities, which we will now present:

  • Find an existing OSGi bundle you can simply deploy and use as a dependency
  • Embed them inside your bundle (either as a JAR inside a JAR or by inlining them). Not optimal in terms of re-use but quite nice in terms of isolation and easy to do. Be careful with Embed-Transitive though!
  • Transform a non-OSGi library into an OSGi compliant bundles (multiple options, from dynamic transforming to new projects)
  • Deploy them at the framework level, exposing them to the whole OSGi runtime. This is not recommended unless you have a very specific reason to do so and know what you are doing. In any case, you should always specify the version of all the packages you are exporting.

We will now go into more details of each option, in order from most to less recommended.

Finding OSGi bundles

Finding ready-made OSGI bundles can be a bit of a challenge for the OSGI newcomer. So we will quickly give a few tips on where to find good bundles.

The Apache software foundation has made quite a large effort to update most of its projects to now offer either separate OSGi bundle releases, or simply to add OSGi metadata to existing release JARs. So, for example the Apache Commons project JARs are almost all OSGi bundles.

The Apache ServiceMix project also maintains “ports” of existing common libraries as OSGi bundles, and of course encourages other to contribute to the already quite large collection of ported libraries. You can find the projects and the releases here: http://servicemix.apache.org/developers/source/bundles-source.html

SpringSource also has a bundle repository here (http://ebr.springsource.com/repository/app/), but be careful before using a JAR from that repository as it is no longer actively maintained and even worse some bundles contain metadata errors! So, if you have a choice between an Apache ServiceMix bundle and a SpringSource EBR bundle, always use the ServiceMix version.

Another possibility is to talk to library authors and either help them or convince them to release OSGi bundles of their projects (the effort is usually minimal, but see section 12.3 on how to meet them half-way).

Embedding non-OSGi libraries

A quick way to integrate non-OSGi libraries with your module is to directly embed them inside the module’s JAR. This technique has tradeoffs though.

Advantages:

  • Good isolation
  • Easy to do
  • Complete control over deployment

Disadvantages:

  • Can lead to duplicate deployment of libraries if multiple modules embed the same library
  • Not the preferred OSGi way of deploying code and dependencies

Despite the disadvantages, especially in projects migrating from older versions of Jahia, embedding the libraries is usually the best way to migrate to OSGi. Removing the embedded library and building more modular modules could then be done in a second phase once everything is up and running in the first phase.

Embedding JARs is done through the usage of the Felix Maven Bundle Plugin configuration. Here is the syntax of the main configuration property:

<Embed-Dependency>dependencies</Embed-Dependency>

where:

  • dependencies ::= clause ( ',' clause ) *
  • clause ::= MATCH ( ';' attr '=' MATCH | ';inline=' inline )
  • attr ::= 'groupId' | 'artifactId' | 'version' | 'scope' | 'type' | 'classifier' | 'optional'
  • inline ::= 'true' | 'false' | PATH ( '|' PATH ) *
  • MATCH ::= <globbed regular expression>
  • PATH ::= <Ant-style path expression>

The plugin uses the <Embed-Dependency> instruction to transform the project dependencies into <Include-Resource> and <Bundle-ClassPath> clauses, which are then appended to the current set of instructions and passed onto BND. If you want the embedded dependencies to be at the start or middle of <Include-Resource> or <Bundle-ClassPath> then you can use {maven-dependencies}, which will automatically expand to the relevant clauses. The MATCH section accepts alternatives, separated by |, and can be negated by using ! at the beginning of the MATCH. Use * to represent zero or more unknown characters and ? to represent zero or one character. You can also use standard Java regexp constructs. There is no need to escape the . character inside MATCH. The first MATCH in a clause will filter against the artifactId. Examples:

<!-- embed all compile and runtime scope dependencies -->
<Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>

<!-- embed any dependencies with artifactId junit and scope runtime -->
<Embed-Dependency>junit;scope=runtime</Embed-Dependency>

<!-- inline all non-pom dependencies, except those with scope runtime -->
<Embed-Dependency>*;scope=!runtime;type=!pom;inline=true</Embed-Dependency>

<!-- embed all compile and runtime scope dependencies, except those with artifactIds in the given list -->
<Embed-Dependency>*;scope=compile|runtime;inline=false;artifactId=!cli|lang|runtime|tidy|jsch</Embed-Dependency>

<!-- inline contents of selected folders from all dependencies -->
<Embed-Dependency>*;inline=images/**|icons/**</Embed-Dependency>

 

Modules based on jahia-module will come with a default configuration. The default value is set in an embed-dependency property : 

<embed-dependency>*; scope=compile|runtime; type=!pom; inline=false</embed-dependency>

Which means that dependencies with runtime or compile scope will be embedded as jars inside your jar. If you don't want your dependency to be embedded, you should use the "provided" scope.

Transforming non-OSGi libraries into bundles

If a library you want to use is not available as an OSGi bundle, it is possible to transform it into an OSGi bundle. This can be done in two ways:

  • Statically using a wrapper project that will generate a new JAR containing the library with proper OSGi metadata -
  • Dynamically by using an OSGi bundle that will perform the wrapping at runtime

We will now go into more detail of the two possibilities, as well as explain the advantages and disadvantages of both methods.

Static transformation

The static transformation of OSGi bundles is simply done by creating a new project that will wrap the existing library, usually using the Felix Maven Bundle plugin’s embedding feature.

Here is an example pom.xml for wrapping the mysql-connector JAR, taken from the SpringSource OSGi example (https://svn.code.sf.net/p/springframework/svn/osgi-repo/trunk/pom.xml). Note that this example is originally two Maven projects, but they were merged here for the sake of simplicity:

<?xml version="1.0" encoding="UTF-8"?>
<project
        xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <parent>
        <artifactId>osgi-repo</artifactId>
        <groupId>org.springframework.osgi</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.springframework.osgi</groupId>
    <artifactId>mysql-connector-java.osgi</artifactId>
    <packaging>bundle</packaging>
    <version>3.1.14-SNAPSHOT</version>
    <name>mysql-connector-java (OSGi version)</name>
    <properties>
        <unpack.version>3.1.14</unpack.version>
        <export.packages>
            com.mysql*;version=${unpack.version},
            org.gjt*;version=${unpack.version}
        </export.packages>
        <import.packages>
            com.mchange*;resolution:=optional,
            org.apache.log4j;resolution:=optional,
            org.jboss*;resolution:=optional,
            javax.naming*;resolution:=optional,
            javax.net*;resolution:=optional,
            *
        </import.packages>
    </properties>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>3.1.14</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- OSGi Felix bundle plugin -->
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>1.2.0</version>
                <configuration>
                    <unpackBundle>${unpack.bundle}</unpackBundle>
                    <obrRepository>NONE</obrRepository>
                    <instructions>
                        <Bundle-Name>${artifactId}</Bundle-Name>
                        <Bundle-SymbolicName>${symbolic.name}</Bundle-SymbolicName>
                        <Bundle-Description>${pom.name}</Bundle-Description>
                        <Import-Package>${import.packages}</Import-Package>
                        <Private-Package>${private.packages}</Private-Package>
                        <Include-Resource>${include.resources}</Include-Resource>
                        <Embed-Dependency>${embed-dep}</Embed-Dependency>
                        <_exportcontents>${export.packages}</_exportcontents>
                        <Implementation-Title>Spring Dynamic Modules Framework</Implementation-Title>
                        <Implementation-Version>${unpack.version}</Implementation-Version>
                        <Implementation-Vendor>Spring Dynamic Modules Framework</Implementation-Vendor>
                        <Implementation-Vendor-Id>org.springframework.osgi</Implementation-Vendor-Id>
                    </instructions>
                </configuration>
                <extensions>true</extensions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>2.4</version>
            </plugin>
        </plugins>
    </build>
</project>

 

Advantages:

  • Full control over the embedding process
  • Proper repackaging of non-OSGi

Disadvantages:

  • Requires more initial work to repackage library

Dynamic transformation

The dynamic transformation is a possibility offered by some OSGi bundles such as the Pax URL project. It is described at the following URL : https://ops4j1.jira.com/wiki/display/paxurl/Wrap+Protocol

This technology makes it possibly to simply deploy a non-OSGI JAR using a specific URL format and the Pax URL project will then internally execute BND to process the JAR and generate on-the-fly an OSGi bundle and deploy it.

Advantages:

  • Easier to quickly wrap a library
  • Pax URL wrap protocol offers some controls over the wrapping process

Disadvantages:

  • There might be some cases where the configuration becomes so large that using URLs is no longer possible (or viable)
  • Some libraries might do strange things that might not be possible to wrap dynamically

Deploy non-OSGi libraries at the framework level (or above)

The last solution for including non-OSGi libraries is to deploy them at the framework level, which means they will be exposed to all the OSGi bundles in the runtime. This solution is quite extreme and should usually be chosen only as a last resort, as it can have many consequences.

What is extremely important if this solution is selected is to make sure that the library is exposed with very precise version numbers, so that bundles still have the possibility to deploy other versions of the same library if they choose to.

For more information on how to perform this, see Configuring a module that extends the system.