Christian Posta bio photo

Christian Posta

Field CTO at solo.io, author Istio in Action and Microservices for Java Developers, open-source enthusiast, cloud application development, committer @ Apache, Serverless, Cloud, Integration, Kubernetes, Docker, Istio, Envoy #blogger

Twitter Google+ LinkedIn Github Stackoverflow

In my previous post, I introduced the rules for how bundles get resolved in an OSGI container. In this post, I'd like to demonstrate each rule step using the Apache Karaf OSGI container. Karaf is based on the Apache Felix core, although the Equinox core can be substituted if desired. Karaf is a full-featured OSGI container and is the cornerstone of the Apache ServiceMix integration container. ServiceMix is basically Karaf but specifically tuned for Apache Camel, Apache ActiveMQ and Apache CXF.

This tutorial will require Maven and Karaf. Download maven from the maven website. Download and install karaf as described in the getting started guide on the Karaf website. You will also need the code that goes along with this example. You can get it at my github repo. After getting it, make sure to run 'mvn install' from the top-level project. This will build and install all of the bundles into your local maven repository. Although you can install bundles a couple different ways, using maven is easiest. Note that this sample code is mostly made up of package names without any real Java classes (except where the tutorial specifies).

First thing to do is start up karaf. In a plain distribution there should be no bundles installed. Verify this by doing "osgi:list" at the karaf commandline. Going in order, we will test out the rules from part one of this two-part series:

For every Import-Package package declaration, there must be a corresponding Export-Package with the same package

To test this rule, let's install Bundle A from our sample bundles. Bundle A specifies an Import-Package of "org.apache.foo" package. According to the first rule, this bundle cannot move to the "Resolved" state since there is no corresponding bundle with an "Export-Package" of org.apache.foo.

From the karaf commandline, type "osgi:install mvn:explore-bundle-resolution/bundleA/1.0". This will install the bundleA bundle. Now do a "osgi:list" again. You should see the bundle installed, and under the "State" column, it should say "Installed". Now try "osgi:resolve bundle id" where bundle id is the ID listed from the "osgi:list" command. This will try to resolve all bundle dependencies and put it into the "Resolved" state. It won't resolve, however. Type "osgi:list" again to see the state of the bundle. It's still in "Installed" state even though we asked OSGI to resolve it. Let's find out why. Execute the "osgi:headers bundle id". Under the Import-Package, you should see the package name org.apache.foo listed in a red color. This dependency is missing, so let's add it. Type "osgi:install -s mvn:explore-bundle-resolution/bundleB/1.0". Note the '-s' switch in the command. This tells OSGI to start the bundle once it's installed. Now type the osgi:resolve command again (with the appropriate bundle ID). This will now resolve the bundle.

Import-Package dictates exactly what version (or attribute) it needs, and a corresponding Export-Package with the same attribute must exist

Let's install bundle C: "osgi:install -s mvn:explore-bundle-resolution/bundleC/1.0" List the bundles again, and you'll see that although bundle C depends on org.apache.foo, it specifies an Import-Package with a specific version=1.5. There is no version 1.5 that is resolved, so bundle C will also not resolve. Bundle D happens to export a package org.apache.foo with a version equal to 1.5. Install bundle D the same way we've installed the others, using the -s to start it. Now try to resolve bundle C and it should work ("osgi:resolve bundle id").

Bundles installed first are used to satisfy a dependency when multiple packages with the same version are found

This rule says that if there are multiple packages exported with the same version, OSGI will choose the first-installed bundle to use when trying to resolve bundles that import the package. Continuing on with the previous example where we installed bundle C and D... consider that bundle D exports org.apache.foo;version=1.5. So if we install bundle F that exports the exact same package and version, we should see that bundle C is resolved with the package from bundle D and not bundle F. Let's see.. install bundle F: "osgi:install -s mvn:explore-bundle-resolution/bundleF/1.0". Do an osgi:list and see that both bundle D and F are correctly installed and "Active". This is a cool feature of OSGI: we can have multiple versions of the same package deployed at the same time, (including in this example the exact same version). Now we should uninstall bundle C and re-install it to see which bundle it uses to resolve for its import of org.apache.foo. Try running "osgi:uninstall bundle id" to uninstall bundle C. Now re-install it using the command from above. It should resolve to use bundle D. Use "package:import bundle id" to verify. You can try switching things around to get F to resolve. You may need to use "osgi:refresh" to refresh the OSGI bundles.

Bundles that have already been resolved have a higher precedence that those not resolved

In a way, we have already seen this with the previous rule, but this rule comes into play when hot deploying. This is left as an exercise to the reader as this post is already getting pretty long and I would like to cover the "uses" directive next.

Bundle "uses" directive

The "uses" directive adds one of the last rules and constraints to avoid runtime class-cast exceptions. Please review my previous blog post for details about the "uses" directive. To simulate how the "uses" directive works, we will install bundles G, H, I, and J and notice how the container enforces the "uses" directive.

Bundle G represents a sort of "service" module that client modules can call to "execute" some form of processing and return a result. The result it returns is an object of type BarClass that comes from Bundle H. But if a client makes a call to bundle G, it too must use the BarClass from bundle H or it will result in a class cast exception. In our samples, Bundle I is the client code and Bundle J represents a different version of the BarClass. Install the bundles in any order you like, but my demonstration followed this order: J, H, G, I. Note that the version of org.apache.bar is indeed the 2.0.0 version which comes from bundle H even though bundle H was installed second (contrary to the rule above). This is because bundle G specified the "uses" directive to depend on a specific version of org.apache.bar.