Tip: Split Packages and Visibility

In honor of the Olympics, I figure a tip about something involving splits is in order:

nastia beam split2 300x250 Tip: Split Packages and Visibility

Unfortunately, I’m not a good gymnast so I can’t teach you about the types of splits in the picture above, however, I can help you in the OSGi world. In an OSGi-based system, there are cases when you want to do some heavy refactoring but run the risk of breaking downstream clients. For example, let’s say you have plug-in A, it exports the com.company.util package. Now let’s pretend this package contains utility methods for math functions and logging. Now let’s say you have a request to break apart the math and logging code into separate plug-ins (B and C) but don’t want to break downstream clients that depend on you. How can we do this? There’s two steps, re-exporting bundles and splitting packages.

Re-exporting Bundles

The first step to ensure we don’t break downstream clients is to re-export the two new bundles B and C within A. In the Require-Bundle header, we would add the ‘visibility:=reexport’ attribute to B and C within bundle A’s MANIFEST.MF file.

Bundle-SymbolicName: a;
Require-Bundle: b;visibility:=reexport, c;visibility:=reexport

This re-exporting of dependencies allow downstream clients to still depend on ‘A’ and get the math and logging code that reside in new plug-ins. This also allows clients to depend on the math or logging code, depending on their needs, so everyone wins. For more information about the visibility attribute, check out the OSGi specification 3.13.1 section.

So this takes care of all of our Require-Bundle clients… what about the guys that use the Import-Package header?

Split Packages

So, what happens now to people that use to Import-Package: com.company.util… if you know OSGi well… they will just get wired to one of the producers of this package, instead of the desired result of including both bundles B and C which export that same package. How do we fix this problem so the OSGi resolver is aware that these packages should really be merged? The answer is split packages. This is what the MANIFEST.MF would look like for B and C:

Bundle-SymbolicName: b
Export-Package: com.company.util;b=split;mandatory:=b

Bundle-SymbolicName: c
Export-Package: com.company.util;c=split;mandatory:=c

Now the resolver is aware that when you import the ‘com.company.util’ package via a Require-Bundle dependency, you should be wired to B and C if you want the com.company.util package. However, split packages can be dangerous because they are open ended… how are you really sure that you’re really getting what was intended in a system? The OSGi specification talks about these dangers in section 3.13.2

For those reading this far… some of this may sound familiar to you as this is exactly what Eclipse did with its org.eclipse.core.runtime bundle. If you look at the org.eclipse.core.runtime bundle and the org.eclipse.equinox.common bundle, you’ll see the use of split packages and bundle re-exporting as a way to maintain backwards compatibility.

In the end, this shows the complexity of not breaking downstream clients when working in a modular system. There are always little things you have to be aware of when you refactor your code in a modular system like OSGi. Now it’s time to get back to the Olympics icon wink Tip: Split Packages and Visibility

4 Responses to “Tip: Split Packages and Visibility”

  1. Parag says:

    Chris,

    Thanks for responding my mail regarding “Import-Package vs. Require-Bundle”.

    Your answer and this blog helps but requires some more clarification.

    Let me put an example to describe the problem with the use of Import-Package.

    Bundle “A” is using JDom Document class (also some other plug-in projects are using JDom). Hence, we have converted JDom JARs to OSGi bundles (using bnd), and the JDom bundles are declaring to export all packages for use by the outside world.

    Our target platform is Eclipse 3.3 which has org.eclipse.mylyn.web.core_2.3.0.v20080225-2200.jar bundle. The org.eclipse.mylyn.web.core_2.3.0 bundle has jdom-1.0.jar in its runtime classpath and exports jdom packages.

    So, now two different bundles (our own version of jdom_1.0.0.jar and org.eclipse.mylyn.web.core_2.3.0.v20080225-2200.jar) exports the “org.jdom” package.

    Now at runtime, when Bundle “A” is attempting to load the JDom Document class, its being loaded by the classloader of “org.eclipse.mylyn.web.core” bundle, while the Bundle “A” is expecting it to be loaded from our own version of jdom_1.0.0 bundle.

    This results in ClassCastException, if Bundle “A” has added the jdom_1.0.0 to required bundle list, since the Document class is loaded by different bundle classloader.

    Now time to ask some questions :)
    – How can I specify to get a specific class from a specific bundle instead of getting it from some other bundle? e.g. if some other bundle is exporting “org.jdom” package which has “Document” class but either a different implementation or has version mismatch, then my app will have runtime failure.
    – What if the exported package by other bundle has no class file that my bundle is looking for, then it may result in class not found error e.g. some Bundle “X” is exporting the package org.eclipse.core.resources, which has no Resource classes. Now if Bundle “Y” has specified “org.eclipse.core.resources” in import-package header, and attempts to refer the IResource class, this will result in NoClassDefFound error.

    How can we avoid these problems?

    Thanks,
    Parag

  2. Eugen Labun says:

    Parag,
    > – How can I specify to get a specific class from a specific bundle instead of getting it from some other bundle?

    Using Import-Package you can specify the bundle-symbolic-name attribute for that purpose, e.g.:

    Import-Package: com.mycompany.jdom; bundle-symbolic-name=MyJDom;

    (assuming com.mycompany.jdom package is in MyJDom bundle). The bundle-symbolic-name attribute must not be specified in the exporting bundle (i.e. MyJDom), it will be implicitly provided to importer by OSGi framework.

    See OSGi specs, 3.6.8 Porvider Selection, for details.

  3. Eugen Labun says:

    Chris,

    thank you for this post. It has brought me to reading OSGi specs :)

    Some addtions / clarifications aka $0.02. Please correct me if anything is wrong.

    1. Package com.company.util is split in both cases (in section “Re-exporting Bundles” and in “Split Packages”).

    2. The recipe in section “Split Packages” will effectively break the downstream clients if they use Import-Package header.

    To depend on a specific split of the original package they must now specify an additinal attribute in their import definitions: “b=split” for depending on the package from the bundle ‘b’ or “c=split” for the opposite from bundle ‘c’, e.g.:

    Import-Package: com.company.util; b=split
    (The package will be imported from the bundle ‘b’, wich specifies the same attribute/value pair.)

    3. The literal value “split” in the samples above has nothing to do with split packages. It can be anything else.

    4. Both parts of com.company.util package will not be merged if clients use Import-Package header. There is no way to do it at all with Import-Package header!

    The only way to merge split package is the use of Require-Bundle header.

    What do we understand under “merge”? The search for the required class will be continued in other parts of the package, i.e. in other bundles, what happens in case of the Require-Bundle headers.

    In case of Import-Package header, the importer will be wired to the one and only package-exporter bundle while resolving the importing bundle. If the required class is not found in that bundle, it will not be searched in other bundles exporting the same package.

  4. Thanks Eugen for answering Parag’s question :)

    I will take your points and update the post, I agree that me leaving out the part of merging split packages using Require-Bundle is confusing. Thanks for your feedback again Eugen!

4 responses so far

Written by . Published in Categories: Planet Eclipse

Author:
Published:
Aug 22nd, 2008