Embedding OSGi
Contents
Embedding OSGi inside a host application
There are many reasons to embed OSGi inside a host application:
- the host application may have an extension mechanism that offers plugins no classloader protection from other extensions. This means that version problems can crop up between different extensions.
- you have an existing OSGi implementation (e.g. Protege 4) which you want to run in a non-OSGi application setting.
There are several examples of OSGi showing up on the web. In particular, the Apache and Equinox groups have teamed up to devise a scheme that works with tomcat. Inside the OSGi environment, bundles see the standard OSGi servlet services which are transparently mapped to the underlying tomcat implementation. In response to a potential Protege developer who was having serious classloader problems I implemented the following demonstration of how a plugin for a host application can be implemented inside a embedded OSGi environment. The demonstration can be checked out with svn and run by executing ant run.
In the HostApplication project, I simulated the host application (Host.java). This application finds and initializes a plugin (PluginImpl.java). I did not simulate the plugin mechanism inside the host application; the call to initialize the plugin is a simple call to a member of the PluginImpl class. The PluginImpl class then immediately starts up OSGi and the work of the plugin is then done in the OSGi environment. The steps taken by this small example are as follows:
- the host application starts (Host.main()).
- the host application finds and initializes the host plugin. For now this is simulated with a simple proceedure call but a real host application would find the plugin by some means such as reading a plugin directory, loading jar files from that directory and then loading and initializing the classes from that directory.
- as part of its initialization, the host plugin starts the OSGi environment which then initializes the plugin bundle (org.example.bundle). The plugin bundle then demonstrates that it can make calls to the host application evironment and the conflicted library.
There are two main points that this example is intended to demonstrate. First, the OSGi bundle is able to invoke host services. To do this, when the host application plugin is initialized it saves a static copy of itself; the plugin is a singleton. This singleton becomes a reference point for the host application services that are needed in the OSGi environment. When the OSGi bundle Activator is started by the OSGi implementation it can find the plugin by using the static PluginImpl.getInstance() method and then access and call host application services through that reference.
Second, my example included a class loader conflict. There is a library (conflict.jar) used by the host application. The plugin for the host application needs to use the same routines from the library but needs to use a different version of the library (conflict-v2.jar). OSGi has many very flexible mechanisms to ensure that this class loader isolation takes place. I will describe three different ways of isolating these libraries here.
First and most simple, the OSGi environment can be simply configured not to expose certain java packages supplied in the host application environment. The classes loaded by the host application environment's class loader are introduced to the OSGi environment through the system bundle. The set of packages are visible to the system bundle can be controlled by a configuration parameter called
org.osgi.framework.system.packages
If you examine this declaration in the OSGi configuration file (look at the end of the fourth line) you will see that the conflicting library is explicitly included:
org.osgi.framework.system.packages=...,org.example.conflict;version="1.0.0"
Note the version information, this will be important later. If this entry is removed then OSGi environment will not be able to load this class from the host application class loaders. This method is probably the simplest approach for most people.
Second, bundles in the OSGi framework can control where and how they load their classes. For example , the manifest of one of the bundles running in the OSGi environment explicitly imports the library definitions in conflict. Note the first line from that manifest below:
Import-Package: org.example.conflict;version="2.0.0", org.example.host, org.example.plugin, org.osgi.framework, org.osgi.service.packageadmin Bundle-ClassPath: .
If this import is removed and the relevant conflicting package is loaded through the Bundle-ClassPath, the bundle still will not import the host applications conflicting class.
Finally, and this is the technique that the example uses, the import can be controlled by using versions. The bundle states that it wants to import version 2.0.0 of the conflicting package. The version of this package from the host application is marked as version 1.0.0 and is therefore not sufficient. This means that the bundle will look elsewhere to find the desired version of the package. The advantage of this mechanism is that it allows other bundles in the same OSGi environment to use the host applications version of the conflicting library.
This example has been designed so that it should be easy to tweak the parameters described above and see how the result is changed. Note that if there is an error, a log file will appear in the
staged/osgi/configuration
directory. Other than the presence of this log file there will be no indication of an error. In particular if you start a run and nothing happens after OSGi is initialized then OSGi probably got into some trouble.
Variations
Note that when making changes it is easy to revert back to the original version with the svn command
svn revert -R .
Baseline
Running
ant run
on a freshly checked out copy gives this result
[java] -------------------- Starting Host Application -------------------- [java] Host is calling library with conflict [java] host version of the conflicting library called [java] ----------------- Starting Host Application Plugin ----------------- [java] Host Application Plugin starts up an OSGi environment [java] -------------------- OSGi Initialized -------------------- [java] Demonstrating that OSGi can call the host services [java] host service called [java] Demonstrating that OSGi can call library routines with conflict [java] OSGi version of the conflicting library called [java] Bundle loading the org.example.conflict.Util class is [java] org.example.conflict_2.0.0 [2] [java] -------------------- OSGi Shutting down --------------------
This output shows that the OSGi plugin bundle executes a different copy of the conflict library that is used by the host application. In addition the plugin bundle was able to determine that the source of the library was the org.example.conflict bundle which supplies version 2.0.0 of the conflict library.
Removing the Import Version Constraint
At the time I originally wrote this note I did not know what would happen if the version=2.0.0 attribute is removed. Specifically suppose that we change the import line
Import-Package: org.example.conflict;version="2.0.0",
in the plugin bundle manifest as follows
Import-Package: org.example.conflict,
In this case there are two libraries that the OSGi environment can choose to link to. It turns that the preferred library is deterministically defined by the OSGi specification. It states that if the import resolution is ambiguous then the following three constraints are considered in decreasing order of priority (and the following is quoted from the specification):
- A resolved exporter must be preferred over an unresolved exporter.
- An exporter with a higher version is preferred over an exporter with a lower version.
- An exporter with a lower bundle ID is preferred over a bundle with a higher ID.
Thus the 2.0.0 version of the library will still win over the library from the host environment because it has the higher version id.
Altering the imported version of the library
Now suppose that we change the import line
Import-Package: org.example.conflict;version="2.0.0",
in the plugin bundle manifest as follows
Import-Package: org.example.conflict;version="[1.0.0,2.0.0)",
This says that the plugin bundle is not compatible with version 2.0 of the conflict library and instead desires to use version 1.0 of the conflict library. The results of a run are now different:
[java] -------------------- Starting Host Application -------------------- [java] Host is calling library with conflict [java] host version of the conflicting library called [java] ----------------- Starting Host Application Plugin ----------------- [java] Host Application Plugin starts up an OSGi environment [java] -------------------- OSGi Initialized -------------------- [java] Demonstrating that OSGi can call the host services [java] host service called [java] Demonstrating that OSGi can call library routines with conflict [java] host version of the conflicting library called [java] Bundle loading the org.example.conflict.Util class is [java] org.eclipse.osgi_3.5.0.v20090520 [0] [java] -------------------- OSGi Shutting down --------------------
Now the plugin bundle gets the same version of the conflict library as used by the host application. In addition we see that the plugin bundle gets its copy of the conlict library from the system bundle, org.eclipse.osgi (bundle 0).
Removing the host application library fron OSGi
Now, in addition to making the change described above to the version of the conflict library imported by the plugin bundle, suppose that we also change the OSGi configuration file to not pass the conflicting package to the OSGi environment. This is the very last part of the
org.osgi.framework.system.packages
line. Now we get the following output:
[java] -------------------- Starting Host Application -------------------- [java] Host is calling library with conflict [java] host version of the conflicting library called [java] ----------------- Starting Host Application Plugin ----------------- [java] Host Application Plugin starts up an OSGi environment
and it takes a while for the OSGi environment to quit. If we then look for logs in the staged/osgi/configuration directory we see the following information:
org.osgi.framework.BundleException: The bundle could not be resolved. Reason: Missing Constraint: Import-Package: org.example.conflict; version="[1.0.0,2.0.0)"
This happens because the OSGi environment could not find a version of the conflicting library which is an earlier version than 2.0.0. Version 2.0.0 of the conflicted library is available and exposed by the bundle org.example.conflict but this does not satisfy the plugin bundles requirement.
Using the buundle classpath
First I revert everything back to the baseline
[tredmond@Andromeda embedded]$ svn revert -R . Reverted 'staged/osgi/configuration/config.ini' Reverted 'PluginBundle/META-INF/MANIFEST.MF' [tredmond@Andromeda embedded]$
and then I remove the import of the conflict library and modify the bundles classpath:
Import-Package: org.example.host, org.example.plugin, org.osgi.framework, org.osgi.service.packageadmin Bundle-ClassPath: .,lib/conflict-v2.jar
Now the output of the run is very similar to the baseline:
[java] -------------------- Starting Host Application -------------------- [java] Host is calling library with conflict [java] host version of the conflicting library called [java] ----------------- Starting Host Application Plugin ----------------- [java] Host Application Plugin starts up an OSGi environment [java] -------------------- OSGi Initialized -------------------- [java] Demonstrating that OSGi can call the host services [java] host service called [java] Demonstrating that OSGi can call library routines with conflict [java] OSGi version of the conflicting library called [java] Bundle loading the org.example.conflict.Util class is [java] org.example.bundle_1.0.0 [1] [java] -------------------- OSGi Shutting down --------------------
Note however the lines at the end of this run. Instead of finding the conflict library from the org.example.conflict bundle the plugin bundle finds the conflict library from its own classpath.