Montag, 27. September 2010

OSGi, Equinox, and missing package versions in the JRE

Coming from Equinox p2 and the awkward workaround with the "a.jre" magic IU (the installable unit that claims to provide the JRE packages), I had a closer look in how the classes from the Java runtime environment (JRE) are loaded in OSGi.

It starts off really easy: java.* packages are always obtained from the boot class loader.

But then, the OSGi specification (section 3.8, "Runtime Class Loading") gets more interesting: For other packages in the JRE, e.g. javax.xml, bundles need to specify a package import in order to be able to use these packages. This was surprising to me because I had used javax.* packages in bundles without declaring the corresponding package imports. Through tests I could verify that this is in fact possible in an Equinox runtime. But obviously the Equinox guys did not accidentally deviate from the OSGi specification, but chose to do so for compatibility reasons: If a class could not be found by any class loader (as specified by OSGi), Equinox asks the boot class loader as last resort. This compatibility option can be disabled by setting the property osgi.compatibility.bootdelegation to false. Nevertheless, for OSGi-compliance, bundles shall declare imports for all used non-java.* packages.

This led me to the question, how imports of JRE packages are resolved. I learned from the OSGi specification that packages from the parent class loader are exported by the system bundle. The list of exported packages is computed by the OSGi framework (unless that list is explicitly specified via the property org.osgi.framework.system.packages). In Equinox, there is a static list of packages for each execution environment. These lists can be found as *.profile files in the root of the org.eclipse.osgi bundle. So if Equinox is running in a Java 1.6 JRE, the system bundle will, amongst others, export the javax.xml package, and a bundle with an "Import-Package: javax.xml" manifest header can load the class javax.xml.XMLConstants via the system bundle from the boot class loader.

I would have stopped exploring here, if I had not come across a bundle that declares an import of a JRE package with version constraint: "Import-Package: javax.xml.stream;version="[1.0.1, 2.0.0)"". Unfortunately, this import will not be resolved against the system bundle because in Equinox all JRE packages are exported in version 0.0.0 only. Version 0.0.0 is definitely wrong, but so would be 1.6.0 (think of the packages org.w3c.dom or javax.xml.ws included in JavaSE-1.6). I can understand that Equinox refuses to do Oracle's homework and to add version information for the packages from the Java runtime. It's quite sad that Sun has never come around to apply their own package versioning mechanism to the JRE packages: java.lang.Package.getPackage("javax.xml") returns null in a 1.6 Sun JDK. This leaves us OSGi users in quite a mess: In order to use JRE packages from a bundle, we shall declare imports for them, but we may not place any (reasonable) version constraints on them.

But what if someone else's bundle does a versioned import of a package that is (nowadays) part of the JRE? I could think of several options:
  • Just install a bundle that exports the package in a properly declared version (and hope that the duplicate classes never cross their path...).
  • Alternatively, it should also be possible to make the system bundle export JRE packages with a correct version (given that you know the correct version...), by setting the (standard OSGi) property org.osgi.framework.system.packages.extra or by installing a fragment to the system bundle that just adds a package export.
When I had tried out the first option, I suspected an undesired side-effect: Having installed the bundle that exports java.xml.stream to satisfy the versioned import, will any unversioned package imports (as I recommended them above) also get resolved against the dedicated bundle instead of the system bundle? The answer would be yes according to the OSGi standard - packages with higher versions are preferred in the resolving process - but Equinox chose to deviate from the standard: By default, Equinox matches package imports to the system bundle if the version constraints allow it. (This behaviour can be controlled with the property osgi.resolver.preferSystemPackages.) Therefore unversioned package imports of JRE packages in fact work as expected - they give access to the classes of the parent class loader - despite the fact that the JRE packages are not properly versioned.

Keine Kommentare:

Kommentar veröffentlichen