‘OSGi Demystified’ is a series of articles addressing common OSGi issues in CICS. We offer insight into OSGi, discuss best practices, and provide setup and configuration advice. This is the second in the series of section 2, to see the previous section discussing CICS bundles, Liberty bundles, and OSGi services, here’s the link to the first in the section. In this section of articles we’ll address a number of common Java and OSGi class loader problems you might encounter in CICS OSGi and Liberty JVM servers. This follows on from OSGi Demystified: 2.2 – The mystery explained with boot delegation. To catch up on this section, follow the link.
- Definitions for a class path and a class loader.
- The problems around
- The Four Scopes of OSGi resolution.
Let’s take a moment to remind ourselves of some essential definitions.
A class path is a list of files, typically JARs on the file-system where a class loader will look for classes. A class loader uses its class path to read the classes from the file-system and returns instances of classes to those parts of the application that require them.
As mentioned earlier, a
ClassNotFoundException is most likely a result of your code actively loading a class using
Class.forName(). To rub salt in the wound it probably worked outside of OSGi. That can easily be explained, because when everything is on one big class path, and presuming the necessary JARs exist on the class path, the class will be found.
So what is the problem with Class.forName() in OSGi?
Recall that OSGi is a component based system allowing multiple versions of a package to exist within the same runtime. Each component is a bundle. Each bundle has its own class loader. Each bundle’s class loader loads only the classes contained in that bundle. By allowing different versions of a class to be loaded by different bundles, multiple versions of the same class can be available in the runtime. That’s something which cannot easily be achieved using Java’s standard parent-first delegation model.
In contrast to the one big class path approach, OSGi has a peer network of class loaders. External packages required by your bundle are declared as dependencies (i.e. Import-Package) with a package/version combination. When the OSGi framework resolves your bundle, it determines the best fit and ‘wires’ your bundle to those bundles providing the dependencies. Thus OSGi becomes a web of bundle class loaders that delegate to each other. If a class is not on your bundle’s class loader, then wiring is used to ask another bundle to load the class on your behalf and return a class instance.
Therein lies a problem.
Class.forName() can be used to dynamically load arbitrary classes at runtime. An example of this might be applications that load the IBM Data Server Driver for JDBC and SQLJ. Although no longer a requirement for Db2, the technique is still prevalent. Tooling isn’t able to spot dependencies hidden in your source-code in this way. So unless you know in advance what the dynamically loaded packages will be, and can specify those dependencies in your MANIFEST.MF and ensure a provider bundle exports the relevant package, then the resolution process (wiring) will not occur. To put it simply, if the class you want to load is from a package outside of your OSGi bundle, and you are using ‘back-door’ class loading techniques, it is up to you declare the dependency!
More often than not, failing to declare dependencies on classes that are dynamically loaded causes
ClassNotFoundExceptions. If you want to understand more about dynamic class loading, how to workaround the problems it introduces, or better still how to avoid it entirely, sit tight for section 3 of OSGi Demystified focussing on dynamic class loading.
Before we move on, I ought to mention there are other, less common, causes of a
ClassNotFoundException. Taking one particular scenario; a dependency is specified, the package exists and it is exported by another bundle. So what’s up? Well, it is feasible that the specific class from the declared package is not actually contained within the bundle. Granted, one might consider this a development error, but OSGi is effectively tricked into resolving your bundle against a package that is a banana short of a bunch. You don’t need us to tell you not to point that gun at your feet.
The Magic Wand of OSGi resolution
(Spoiler. There isn’t one). There may not be a magic wand, be we do have a substitute technique to help you resolve class loading problems. We like to call it the “Four Scopes of OSGi resolution” coined here for the first (and probably last!) time to help fix all those failures of the Class Not Found genus.
It’s a pretty simple concept. Progressively larger scopes are used to perform dependency resolution, until all classes are found. A Liberty JVM server is a little more involved. Java EE applications are packaged into WAR, EJB or EBA files loaded through isolated OSGi sub-systems where additional class loading is provided through shared libraries – so for this article we’ll stick to the pure OSGi picture.
Your bundle is the first 1 scope, anything located directly within your bundle can be loaded without further ado. If your bundle includes or wraps other JARs, just remember to put those JARs on the Bundle-ClassPath to get at their contents. If you want to make the packages from those JARs available to other bundles you can do so with an Export-Package statement.
But before you do. Hold up. There is a curve-ball. If a package within your bundle (or on your Bundle-ClassPath) is also explicitly Imported by your bundle, the Imported package will take precedence. That’s mildly perplexing – until someone explains to you that the exact same classes loaded from a different class loader are not considered the same to Java (read actively incompatible). So to reduce the likelihood of the same class being loaded by your bundle and by another bundle, an Imported package takes precedence over your internal package. The result is that class loading is delegated to the same class loader (bundle) and everyone gets the same ball. Technically this is known as ensuring class space consistency.
The second 2 scope, as we’ve just discussed, expands to include all the other bundles installed into the OSGi framework. If a class isn’t in your bundle maybe it is found from another? For that you need to declare an Import-Package dependency. Naturally a bundle must also exist which provides that class and exposes the package with a corresponding Export-Package statement.
Still not found? Then the third 3 scope encompasses all bundles AND the inner lining of the OSGi framework, more technically known as the system bundle or bundle zero. The system bundle provides all the dependencies defined on OSGi’s system packages parameter. To your bundle, it is just another bundle. Frameworks such as Equinox (used by CICS and Liberty) will have a default list of packages that typically include javax.* packages, and org.w3c.* packages. It is OSGi’s way of exposing classes from the external boot class hierarchy to bundles within the framework without resorting to the undesirable parent-first delegation that spoils our version party. CICS allows you to extend this list of packages if necessary using the following syntax in your JVM profile.
# Extend the System Packages list to expose the following boot class-loader packages through the OSGi system bundle -Dorg.osgi.framework.system.packages.extra=com.ibm.xylem.types, com.ibm.xylem, com.ibm.xtq
The fourth 4 scope includes packages outside of the OSGi framework that aren’t exposed by the system bundle but are exposed through boot delegation. Boot delegated packages should be considered internal to Java, like sun.* and com.sun.*. They are not considered suitable for replacement implementations. Boot delegation is the worm-hole leading straight to the outermost scope with no stops for sight-seeing and no chance to change your mind. Again CICS allows you to specify your own list of boot delegated packages, but modify it as a last resort – there are very few situations where this is the right choice.
By Ivan Hargreaves, Mark Cocker and Abigail Bettle-Shaffer.
Read the next in the series discussing boot compatibility and some final words on Import-Package statements.