Secrets of The Felix Bundle Plug-in Macros Revealed

I was recently dealing with some client concerns regarding package import scopes in their Maven-built artifact using the Felix Bundle Plug-in. The client was curious about strategies for dealing with package import version ranges as well as how to manage the creation of these ranges within their Maven builds.  This article discusses some strategies for managing package import versions using the Felix Bundle Plug-in and the versioning related macros.

Before we begin, this article assumes that you have a working knowledge of OSGi bundles and a basic understanding of how to use the Felix Bundle Plug-in with a Maven build.  For more information on OSGi and the Felix Bundle Plug-in, refer to the links below.

The Default Plug-in Behavior

The simplest strategy that comes to mind is to just let the bundle plug-in loose on your build and let it choose the version range on each package import. The bundle plug-in’s default behavior is to create a range from a dependency’s current version, truncated to only the major and minor version, up to but not including the next major version number. For example, if your project depends on package x.y.z from another OSGi bundle with the package export version 1.2.3.4, the resulting import will use the version range from 1.2 up to, but not including, 2. The import will look like this:

x.y.z; version="[1.2,2)"

Manually Specifying Versions

The next level of control over the package imports in your bundle is to use Maven properties in your POM to define version numbers and to use token replacement in the configuration of the Felix Bundle Plug-in.  You can also just hard-code the literals in the bundle plug-in, but this requires a little more attention to detail when performing POM maintenance. This approach allows you to manually set a variable representing the version of a given library and to reuse it a number of times across multiple configurations of the Felix Bundle Plug-in. This approach looks something like the following POM snippet.

<project xmlns="http://maven.apache.org/POM/4.0.0">
  …
  <properties>
    <camel.version>2.6.0-fuse-01-09</camel.version>
  </properties>
  …
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>2.3.4</version>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Import-Package>
              org.apache.camel*; version="${camel.version}",
              *
            </Import-Package>
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>
  …
</project>

This POM snippet instructs the Felix Bundle Plug-in to import all Camel related packages with a strict version requirement on the version represented by the value of the camel.version property in the POM, 2.6.0-fuse-01-09 in this example. All other packages will be imported using the default range chosen by the Felix Bundle Plug-in.

This approach quickly becomes tedious to maintain if you want to retain ranges on your package imports as you would need to define properties for both the upper and lower bounds of every import as well as keep all those properties properly sorted in your POM.

Using the BND Macros

The BND tool, the underlying utility that the Felix Bundle Plug-in is built on, provides a number of predefined macros for working with version numbers on package imports. The most common macros, are @, version, and range. These macros represent the version of the package as supplied by your maven resolved dependencies, a configurable mask applied to a version number, and a range built using a mask applied to a version number, respectively. While these macro capabilities sound great, the BND tool documentation isn’t always up to date nor does the Felix Bundle Plug-in describe the use of these macros in much detail. The following examples should help to clarify the behavior of the Felix Bundle Plug-in (version 2.3.4) and the use these macros in the context of the plug-in as it isn’t always obvious and there are a couple gotchas. Before I continue, I’d like to thank Fintan Bolton, tech writer extraordinaire from FuseSource. Fintan helped to put some of the pieces in place and I am happy to say that the following information already appears in, or will appear shortly, in the FuseSource documentation on Apache ServiceMix and OSGi.

Caveat Emptor

The First Gotcha

Maven uses the ${} pattern for its own token replacement mechanism. While the BND tool also allows the ${} pattern to be used for its macros, keep in mind that Maven will process the plug-in configuration first and happily replace your ${@} macro usage with its own value for the @ property.
So this in your POM:

org.slf4j;version="${@}

Results in this in your bundle manifest:

org.slf4j;version=null

Suitable substitutes for ${} in the Felix Bundle Plug-in are: $() and $<>. Note that when using <> as your macro delimiters, you either need to escape < with &lt; or wrap the contents of the element with CDATA as shown below.  The CDATA approach is far more manageable over the long term as it is much easier to read and maintain.

<Import-Package><![CDATA[
  *;version="$<@>"]]>
</Import-Package>

The Second Gotcha

The plug-in and BND are not looking out for dependencies that are a plain JAR. For example, the JSR 303 API JAR in the Maven repo is not an OSGi bundle. If you attempt to use one of the macros on any package from this JAR, you will get the literal text of the macro instead of a version number in your bundle manifest.
This in your POM:

javax.validation;version="$(@)"

Results in this in your bundle manifest:

javax.validation;version="${@}"

The Third Gotcha

The plug-in and BND are not looking out for dependencies that do not define a version on their exported packages. For example, WSS4J 1.5.8 is packaged as an OSGi bundle, but its package exports do not define a version number in the bundle manifest. A reference to a package from WSS4J using one of the three macros discussed in this article will also result in the literal text of the macro instead of a version number in your bundle manifest.
This in your POM:

org.apache.ws.security;version="$(@)"

Results in this in your bundle manifest:

org.apache.ws.security;version="${@}"

The @ Macro

This macro will inject the version of a package as it is provided in a Maven defined dependency in the build classpath. In the following POM, the source code leverages packages from SLF4J.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
  …
  <dependencies>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.5.8</version>
    </dependency>
  </dependencies>
  …
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>2.3.4</version>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Import-Package>
              *;version="$(@)"
            </Import-Package>
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

The resulting bundle manifest will contain the following:

Import-Package: org.slf4j;version="1.5.8"

The Version Macro

This macro will inject the version of a package, either defined by the version as it is provided in a Maven defined dependency or as supplied as a literal, after it has had a mask applied to it. The mask allows you to easily alter a package version declaratively during the build process. The “=” mask indicates to leave that portion of the version alone, the “+” mask indicates to increment that portion of the version, and the “-“ filter indicates to decrement that portion of the version.

For example if package x.y.z has the version 1.2.3.4, the mask ==== would result in the version 1.2.3.4, the mask =+ would result in the version 1.3, and the mask =- would result in the version 1.1.

The syntax for this macro in version 2.3.4 of the Felix Bundle Plug-in is a little different than described in the BND documentation. The syntax is version;MASK[;LITERAL]. Where MASK is a combination of mask characters and ;LITERAL represents an optional literal version number string. If the literal version number string is omitted, an implicit @ macro is used. The following package import is a valid example of the version macro in 2.3.4.

org.slf4j*;version="[$(version;==),$(version;+;1.5.8))"

This configuration indicates to construct a version range with the lower bound containing only the major and minor version number of the version defined in the SL4J dependency resolved by Maven and the upper bound containing only the major version number, incremented by one, of the literal version string 1.5.8. The literal value can also be supplied by Maven token replacement. So the line from the above example becomes:

org.slf4j*;version="[$(version;==),$(version;+;${slf4j.version}))"

While one can use the version macro to define a version rage for package imports, the range macro makes this task even simpler in many cases.

The Range Macro

The range macro works much like the version macro by applying a mask to a version number; however, this macro also allows you to inject the inclusive/exclusive wrapper characters of the OSGi version range syntax. The following POM snippet illustrates how to create a version range for the SLF4J packages used throughout this article. The module source code contains imports for packages supplied by SLF4J.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
  …
  <dependencies>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.5.8</version>
    </dependency>
  </dependencies>
  …
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>2.3.4</version>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Import-Package><![CDATA[
              org.slf4j*;version="$<range;[==,=+)>"
              ]]>
            </Import-Package>
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

The resulting bundle manifest will contain the following:

Import-Package: org.slf4j;version="[1.5,1.6)"

In this case, the implicit @ macro was used to determine the version number to apply the masks to. Just like the version macro, you can also specify a version number as a string literal or using a Maven property and token replacement.

Note also that the < and > characters were chosen as the separators in this case. This is because the ) character used to close the version range will confuse the parser if the ( and ) characters are used to demarcate the range macro.

This configuration using the ( and ) characters to demarcate the range:

org.slf4j*;version="$(range;[==,=+))"

Results in the following error message during the build:

[INFO] [bundle:bundle {execution: default-bundle}]
[WARNING] Warning building bundle example:bundle-plugin-macros:bundle:1.0 : No translation found for macro: range;[==,=+
[ERROR] Error building bundle example:bundle-plugin-macros:bundle:1.0 : null, for cmd: range, arguments; [range, [==,=+]
[ERROR] Error(s) found in bundle configuration
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Error(s) found in bundle configuration

Conclusion

While the documentation on these macros and the Felix Bundle Plug-in may not provide a massive amount of detail, once you understand the syntax and pitfalls of the macros, they can be combined in a number of ways to provide rich control over package imports in your bundles.

This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

8 Responses to Secrets of The Felix Bundle Plug-in Macros Revealed

  1. Raman Gupta says:

    Hmm, this looks very useful, except that it isn’t working for me with maven-bundle-plugin 2.3.5. For example, I tried this with slf4j:

    <![CDATA[
    org.slf4j;version="[$<version;===;$<@>>,$<version;==+;$<@>>)",
    *
    ]]>

    which should result in version=”[1.6.2,1.6.3)” (weird range for testing macros). I get the error:

    [WARNING] Bundle mybundle:bundle:1.0-SNAPSHOT : No translation found for macro: @
    [WARNING] Bundle mybundle:bundle:1.0-SNAPSHOT : No translation found for macro: version;===;${@}
    [WARNING] Bundle mybundle:bundle:1.0-SNAPSHOT : No translation found for macro: version;==+;${@}
    [ERROR] Bundle mybundle:bundle:1.0-SNAPSHOT : null, for cmd: version, arguments; [version, ===, ${@}]
    [ERROR] Bundle mybundle:bundle:1.0-SNAPSHOT : null, for cmd: version, arguments; [version, ==+, ${@}]

    I have also tried an implicit $ macro i.e.:

    org.slf4j;version=”[$,$)”

    and the error is now:

    [ERROR] Bundle mybundle:bundle:1.0-SNAPSHOT : No version specified for ${version} or ${range} and no implicit version ${@} either, mask====
    [ERROR] Bundle mybundle:bundle:1.0-SNAPSHOT : No version specified for ${version} or ${range} and no implicit version ${@} either, mask===+

  2. Raman Gupta says:

    The comment system munged the data. Here are the two macros:

    https://gist.github.com/1300707

    • David Valeri says:

      I took a look through the source code. It looks like the macro gets sent through the parser a number of times when it gets handed off to the BND tool. BND churns through it a couple times, generating the error message as it goes. The first time through it can’t process it and logs an error, followed by wrapping the the string in nested @ in ${} and then wrapping the macro token in ${}. It looks like it ends up trying again with this string and succeeding; however, the errors are already logged and the Maven Plug-in fails the build.

      It would seem that the @ is causing the above problem while the implicit @ is failing to resolve the package version. It appears that some code in BND properly sets the value of @ before parsing the macro; however, some does not. In this case, the version macro tries to resolve the implicit @ and gets null back and logs an error for this.

      You can work around this by using a literal or Maven token for the version with 2.3.5, but that is a lot of work if you have a big project.

      Alternatively, go back to 2.3.4 and do not include the last optional argument in the macros.

      <![CDATA[
      org.slf4j;version=”$<range;[==,=+)>",
      *
      ]]>

      OR

      <![CDATA[
      org.slf4j*;version=”[$<version;===>,$<version;==+>)",
      *
      ]]>

      both use the implicit version and work in 2.3.4.

  3. Pingback: Understanding the ‘unresolved constraint’, ‘missing resource’ message from Apache Felix | Technocratic Dilemmas

  4. Dirk says:

    Thanks, very useful!
    Do you know a way to exclude special packes from beeing imported?
    In my case, I don’t want to import javax.ejb;version=”[3.1,4)” at all…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s