One of the useful features of OSGi is the ability to have multiple versions of the same Java classes deployed in a single JVM at the same time. This is helpful if you have multiple applications deployed that are dependent on different versions of third-party framework, or your own utility classes.
You can therefore use newer versions of a framework in your new applications, without having to regression test the existing ones.
This article will cover some of the basics of versioning in OSGi, to hopefully help you avoid a few of the common pitfalls.
The example was written for and tested on the Apache Karaf OSGi container that is part of Red Hat JBoss Fuse, but the principles apply to other OSGi containers. The example consists of projects written using the Red Hat JBoss Developer Studio (a supported and extended version of Eclipse), but can be used in any IDE. As only small changes are required, even a simple text editor will be sufficient. The article assumes some knowledge of Maven, and that you have it set up on your machine. It also assumes that you have Red Hat JBoss Fuse on Karaf installed. The article has been tested with the latest versions of both (currently 3.3.9 and 6.3.0 respectively), although it should work with older versions too (in particular versions of Fuse back to 6.1.0). If you don’t yet have Fuse installed, it is available here: http://developers.redhat.com/products/fuse/download/. Maven can be found here: https://maven.apache.org/download.cgi.
The sample application contains a test project (actually a Camel route which fires every second), plus a few different versions of a utility class in separate projects. The test project calls the utility class, which simply prints out its version number.
If you’re not familiar with Camel it’s a simple framework for system integration. For more details see the Camel website: http://camel.apache.org/what-is-camel.html.
Download the source code for the sample application from here, and unpack it to a suitable directory. Build version 1.0.0 of the utility, and also the test application, from the directory where you unpacked the code (the
-f switch builds the project in the specified sub-directory):
mvn clean install -f util-1.0.0
mvn clean install -f test
Now, from the Fuse console, deploy both the utility and the test application into your Fuse container (the
-s switch starts the bundle when deployment is complete):
osgi:install -s mvn:com.tier2consulting.blog/osgi-util/1.0.0
osgi:install -s mvn:com.tier2consulting.blog/osgi-test/1.0.0-SNAPSHOT
Make a note of the bundle ID that the container reports after the deployment of the test application.
Tail the logs using
log:tail. You should see a message every second indicating that version 1.0.0 of the utility is being used. So it works, but it’s not very interesting. This kind if thing could have been achieved with regular Java JARs. However, let’s assume there is now a new version of the utility available. Build and deploy the new version:
mvn clean install -f util-2.0.0
osgi:install -s mvn:com.tier2consulting.blog/osgi-util/2.0.0
There are now two versions of the same utility class deployed, something that can’t be done with an ordinary Java classpath. But how can the test application use the new version? Simple: just change the dependency in the test application’s POM file. Edit the
pom.xml file in the test project, and change the
util.version property to be
2.0.0. This property is used by the
osgi-util dependency to determine which version to use. Rebuild the test application, then redeploy it using the bundle ID noted above:
mvn clean install -f test
If you didn’t note the bundle ID previously, it’s the first column of the output from the following command:
osgi:list | grep "Fuse OSGi Test".
Tail the logs again, and you should now see version 2.0.0 of the utility class being output.
That makes sense, but how did it work?
An OSGi bundle is basically a Java JAR with some descriptor information in the
META-INF/MANIFEST.MF file. Here’s the manifest file taken from the test project:
1 Manifest-Version: 1.0 2 Bnd-LastModified: 1479066189197 3 Build-Jdk: 1.8.0_40 4 Built-By: ian 5 Bundle-ManifestVersion: 2 6 Bundle-Name: Fuse OSGi Test 7 Bundle-SymbolicName: com.tier2consulting.blog.osgi-test 8 Bundle-Version: 1.0.0.SNAPSHOT 9 Created-By: Apache Maven Bundle Plugin 10 Export-Package: com.tier2consulting.blog;uses:="org.apache.camel, 11 com.tier2consulting.blog.util,org.apache.camel.builder,org.apache.camel.model" 12 ;version="1.0.0.SNAPSHOT" 13 Import-Package: com.tier2consulting.blog.util;version="[2.0,3)",org.apache.camel;version="[2.15,3)", 14 org.apache.camel.builder;version="[2.15,3)",org.apache.camel.model;version="[2.15,3)",org.osgi.service.blueprint;version="[1.0.0,2.0.0)" 15 Tool: Bnd-1.50.0
Don’t panic, this file is generated by the
maven-bundle-plugin in the POM. This plugin uses the version numbers of the project and its dependencies from the POM to generate the required values in the manifest file.
On line 8 is the
Bundle-Version element that indicates this bundle has a version of 1.0.0.SNAPSHOT (more on SNAPSHOTs later). This is a semantic version. In short, in consists of three or more parts: the major, minor, and patch (sometimes refereed to as micro) versions, plus optional additional labels. In the example the major version is 1, and the minor and patch versions are both 0. These three elements should be incremented under specific circumstances. The patch version is incremented when code is patched, for example to fix a bug. The minor version is incremented (and the patch version reset to 0) when functionality is added but it is still backwardly compatible with the previous version. The major version is incremented (and the minor and patch versions are reset to 0) when major changes are made to the code that break backwards compatibility with previous versions. Full details about semantic versioning can be found here: http://semver.org/.
On line 10 is the
Export-Package element. This details the packages that this bundle exports. Other bundles can use this to determine if a particular bundle satisfies its dependency requirements.
On line 13 is the
Import-Package element, which lists the packages this bundle depends on. The first entry indicates that this bundle depends on the com.tier2consulting.blog.util package. If you take a look at the
META-INF/MANIFEST.MF file for one of the util bundles you will see that it exports this package. What about the version, though? What does
The square bracket means “inclusive”, and the round bracket means “exclusive”. So
[2.0,3) means “from version 2.0 up to, but not including, version 3”. This is the default behaviour of the Maven bundle plugin; it specifies a version range from the minor version up to, but not including, the next major version.
Try building and deploying version 2.1.0 of the utility bundle. The test application will need to be refreshed using it’s bundle ID to allow it to re-check its dependencies.
mvn clean install -f util-2.1.0
osgi:install -s mvn:com.tier2consulting.blog/osgi-util/2.1.0
The utility will now output version 2.1.0. Because semantic versioning dictates that minor and patch versions are backwardly compatible, the dependency on version 2.0.0 of the utility bundle can be satisfied by any bundle with a major version of 2, as they should always be backwardly compatible. So, although the POM in the test application says that it has a dependency on version 2.0.0 of the util bundle, it’s now happily using version 2.1.0, because semantic versioning says that version 2.1.0 is backwardly compatible with version 2.0.0. The container will basically use the latest available version that satisfies the dependency. It’s therefore vitally important that your code adheres to the semantic versioning rules so that the OSGi container resolves your dependencies correctly.
What happens if you want to have a dependency on a specific version though?
Overriding Default Behaviour
The Maven bundle plugin can be given specific directions on how to generate the manifest file. In this example we will tell it what specific packages to import. In the test project’s POM file, change the
util.version property to
2.0.1, and uncomment the configuration section in the bundle plugin (lines 50 to 57). The
Import-Package element allows the entry with the same name in the manifest file to be overridden. Notice the syntax of the version number. It basically says: “use version 2.0.1 to version 2.0.1 inclusive”, i.e. use version 2.0.1. The asterisk after this just indicates that the plugin should use all the other default package imports.
Note that the
util.version property placeholder could have been used here to remove the need to retype version numbers, but the explicit values make the syntax more obvious in this example.
Build and deploy version 2.0.1 of the utility bundle, and rebuild and redeploy the test project:
mvn clean install -f util-2.0.1
osgi:install -s mvn:com.tier2consulting.blog/osgi-util/2.0.1
mvn clean install -f test
The utility class should now be printing out version 2.0.1, despite the fact that a later bundle version (2.1.0) is available. Of course you can also explicitly declare a version range, for example
[2.0.1,2.1) (any version from 2.0.1 up to, but not including, 2.1).
Note about Snapshots
–SNAPSHOT suffix has a special meaning: it allows code to be changed and redeployed without changing the version number. This is useful during development when redeployment is common. Once development has been completed the suffix is removed, and then the final code can be released, for example to a test environment. Other suffixes may be used, for example to indicate release candidates etc.
If a deployment has been made to an environment without the
–SNAPSHOT suffix, then a change to that deployment will require a change to the version number. So non-snapshot deployments can’t be updated; the version number must be changed and the new version deployed.
So, version number aren’t random! They have specific meanings, and the OSGi container will treat them differently according to the values of the major, minor, and patch values. Hopefully this will help you use them effectively.