Richard Hall started yelling at us telling that we had "done a terrible job of explaining the importance of the collaboration model in OSGi and how importing AND exporting the same package fits into it..." Fortunately he included himself. Anyway, I am afraid he is right.

If you carefully read the specification you will find that all the details are there, succinctly described. However, the specifications are a lot to digest and many details are not that relevant for the bundle programmers, just for framework implementers.

So what is Richard talking about?

An OSGi bundle is a JAR file that contains classes and resources. Meta data in the manifest header describes which packages are exported and which packages are imported. Not exported packages are private to the bundle; other bundles can not import them. This is a simple model that a (clever) child can understand. First time users therefore clearly separate their packages into exported, private, and imported.

Ok? Well, no.

The problem is that the OSGi specifications allow multiple exporters for the same package. In this case, the OSGi framework selects one of the exported packages to be the actual importer for a bundle and ignores the other export definitions.

Before OSGi R4 the framework would automatically turn an export clause into an import if not selected to export. However, the R4 expert group felt that it was more flexible to allow a developer to only export a package but never import it. Export only guarantees the bundle developer that he would always see his own classes and never import those classes from other bundles. If he is not selected as the exporter, he happily loads those classes from its own JAR. I have never felt the need for this feature but I am sure someone will tell now comment how he cannot live without this export only option. Since R4, bundles must specifically import their exported packages if they desire substitutability of those packages.

For example, lets assume we have two bundles A and B, both exporting package q and a third bundle importing package q:

Bundle-SymbolicName: A Export-Package: q

Bundle-SymbolicName: B Export-Package: q

Bundle-SymbolicName: C Import-Package: q

The framework must now pick an exporter, lets say it picks bundle A. Therefore, the import C.q is bound to A.q.

So what is wrong with that? Well, because B does not import package q, the framework must divide your installed bundles in different class spaces. A class space is a consistent set of classes. In the previous example we have divided the world; bundle B lives in a separate class space than bundle A and C. This is not a problem as long as they do not have to communicate. Bundle B is perfectly happy to use its own variant of package q from its own JAR file. Bundles A and C happily share theversion from A.

However, problems occur when B wants to communicate with bundle A or C: it no longer can. If A would create a service from package q then this service is bound to A's class space. C also belongs to this class space so if bundle C would get this service the object would be compatible with the class that it is bound to. However, if bundle B would get this service its definition of the class would come from another class loader than the service object's class loader (A). The result would be a very nasty class loader exception. Nasty because the assigned object has actually the same class name as the class it is assigned to, the only difference is the class loader. You can stare surprisingly long at a line like:

X x = (X) ctxt.getService(ref)

When the object you get back from the getService method is actually implementing X! The first time it happens to you, you start doubting the compiler, the VM, and in the end your sanity.

The OSGi framework therefore keeps services from bundles that live in different class spaces separately. This means that if you list the services in the service registry with the getServiceReferences method, you can only see the services that are compatible with your bundle's class space. There is another method, getAllServiceReferences, that does not filter the services. However when you use that method you are on your own with respect to ClassCastException.

The solution is fairly simple, just import all the packages you export. If you use my bnd tool (which is included in the osgi plugin for maven then you do not have to worry because exported packages are automatically imported. This R3 like model is a very sane default in my opinion.

Importing and exporting enables substitutability. Maybe in this example it does not sound as a big deal. However, not providing it erodes the roots of the OSGi model. The OSGi specifications create quite a bit of overhead to enable sharing. Bundles that only export packages are obviously not good citizens in this model because they are unwilling to use the packages from other bundles. Using export only bundles quickly creates a lot of standalone bundles that can no longer collaborate, voiding a significant use of the OSGi specifications.

5 comments:

Nice description Peter. When I was starting with OSGi it was not so obvious that this is why you would import your own exported packages from the , indeed, couple of lines you can find in the specs.It may be a good addition if you can make a follow up to give some hints about exporting/importing packages that contains resources (other then classes). This can be quite tricky, I would say, with regards to loading a resource via the class loader, as for example your configuration file being substituted.

Hi, I'm a newbie to OSGI, and was wondering why we need to import all packages we set as export-packages in the bundle. As per your explanation what we try to achieve here is having a single/common class space for a particular version of the package?

With this importing practice, how can we customize a package, export it and use it in the bundle itself? Will versioning of the package suffice this requirement?