BT

Modular Java: Static Modularity

Posted by Alex Blewitt on Oct 24, 2009 |

Modularity is an important aspect of large Java systems. Build scripts and projects are often split up into modules in order to improve the build, but this is rarely taken into account at runtime.

In the second of the Modular Java series, we'll cover static modularity. We'll describe how to create bundles, how to install them into an OSGi engine and how to set up (versioned) dependencies between bundles. In the next episode, we'll look at dynamic modularity and show how bundles can react to other bundles coming and going.

As covered last time in Modular Java: What Is It?, Java has always had packages as a unit of modularity at development time, and JARs as a unit of modularity at deployment time. However, although build tools like Maven guarantee a certain combination of packages and JARs at compile time, these dependencies may be inconsistent with the runtime classpath. To solve this problem, modules can declare their dependency requirements so that, at runtime, they can be checked prior to execution.

OSGi is a runtime dynamic module system for Java. Specifications describe the behaviour of how the OSGi runtime works; the current release is OSGi R4.2 (as covered by InfoQ previously).

An OSGi module (also known as a bundle) is just a simple JAR file, with additional information in the archive's MANIFEST.MF. At a minimum, each bundle's manifest must contain:

  • Bundle-ManifestVersion: must be 2 for OSGi R4 bundles (otherwise defaults to 1 for OSGi R3 bundles)
  • Bundle-SymbolicName: textual identifier of the bundle, often in reverse domain name format such as com.infoq and frequently corresponds to the packages contained therein
  • Bundle-Version: a version of the form major.minor.micro.qualifier where the first three elements are numeric (defaulting to 0) and qualifier is textual (defaulting to the empty string)

Creating a bundle

The simplest bundle contains purely a manifest file as follows:

Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.infoq.minimal
Bundle-Version: 1.0.0

That wouldn't be tremendously exciting to create, however, so let's create one with an activator. This is an OSGi-specific piece of code which is invoked when the bundle is started, like a per-bundle main method.

package com.infoq;
import org.osgi.framework.*;
public class ExampleActivator implements BundleActivator {
  public void start(BundleContext context) {
    System.out.println("Started");
  }
  public void stop(BundleContext context) {
    System.out.println("Stopped");
  }
}

In order for OSGi to know which class is the activator, we need to add two extra items to the manifest:

Bundle-Activator: com.infoq.ExampleActivator
Import-Package: org.osgi.framework

Bundle-Activator declares the class to instantiate and invoke the start() method when the bundle is started; similarly, the stop() method is called when the bundle is stopped.

What about the Import-Package? Each bundle needs to define its dependencies in the manifest, in order to determine at runtime whether all the required code is available. In this case, the ExampleActivator depends on BundleContext from the org.osgi.framework package; if we don't declare that dependency in the manifest, we'll get a NoClassDefFoundError at runtime.

Downloading an OSGi engine

To compile and test our bundle, we'll need an OSGi engine. There are several open-source engines available for OSGi R4.2, listed below. It is also possible to download the Reference API to compile against (which guards against using any platform-specific features); however, you still need an OSGi engine to run it. Here are the options:

Equinox
License Eclipse Public License
Documentation http://www.eclipse.org/equinox/
Download org.eclipse.osgi_3.5.0.v20090520.jar
Notes

The org.eclipse.osgi bundle contains the framework, the runtime and a shell as an all-in-one. This is the only 'single JAR' solution (and therefore in some ways the easiest to get started with). It's also the longest named file; tab completion (or renaming to equinox.jar) will make that problem go away.

To get a console, supply java -jar org.eclipse.osgi_3.5.0.v20090520.jar -console on the command line.

Framework org.eclipse.osgi_3.5.0.v20090520.jar
Felix
License Apache License
Documentation http://felix.apache.org/
Download Felix Framework Distribution 2.0.0
Notes Seen as the most strictly compliant of the OSGi engines, it's also used in GlassFish and many other open-source products. You need to run java -jar bin/felix.jar instead of java -jar felix.jar since it looks for a path bundles to auto start from the current directory.
Framework bin/felix.jar
Knopflerfish
License Knopflerfish License (BSD-esque)
Documentation http://www.knopflerfish.org/
Download knopflerfish_fullbin_osgi_2.3.3.jar
Notes This JAR is a self-extracting zip file; you have to run with java -jar initially to unpack. Don't download the 'bin_osgi' alternative as this fails to start.
Framework knopflerfish.org/osgi/framework.jar

Whilst there are also smaller OSGi R3 runtimes available (like Concierge) that target embedded devices, this series will focus on OSGi R4.

Compiling and running a bundle

Having got your framework.jar, compiling the example above is a case of adding the OSGi framework to the classpath, and then packaging it up into a JAR:

javac -cp framework.jar com/infoq/*/*.java

jar cfm example.jar MANIFEST.MF com

Each of the engines has some kind of shell with similar (but not identical) commands. For the purposes of this exercise, we'll just look at how to get the engine up and running, and install and start/stop the bundle.

Once the engine is up and running, you can install the bundle (specified as a file:// URL), and then using the returned numeric bundle id, start and stop it.

  Equinox Felix Knopflerfish
Launch java -jar org.eclipse.osgi_*.jar -console java -jar bin/felix.jar java -jar framework.jar -xargs minimal.xargs
Help help
List ss ps bundles
Install install file:///path/to/example.jar
Start start id
Update update id
Stop stop id
Uninstall uninstall id
Shutdown exit shutdown

Although all shells work in roughly the same way, the subtle differences between commands can be slightly confusing. A couple of harmonised console projects (Pax Shell, Posh) and launchers (Pax Runner) are available; OSGi RFC 132 is an ongoing proposal to try and standardise the command shells. Apache Karaf, which aims to be a distribution that can run on top of Equinox or Felix and provides a harmonised shell as well as other features. Whilst using these for real deployments might be advisable, this series will focus on the vanilla OSGi framework implementations.

If you launch your OSGi framework, you should be able to install the com.infoq.minimal-1.0.0.jar from above (you can also install this straight from the site using the link's address and the install command). Each time you install the bundle, it will print out the numeric ID of that bundle.

Depending on what other bundles are in the system, it's impossible to know what bundle identifier you'll end up with when installing; but you should be able to list the installed bundles using the appropriate command to find out.

Dependencies

So far, we've just got a single bundle. One of the benefits of modularity is that we can decompose our system into a number of smaller modules, and in doing so, reduce the application's complexity. To some extent, this is already done with Java's packages – for example, a common package with a client and server packages is often used in networked applications. The unstated implication is that the client and server packages are independent, and both depend on the common package; but as with Jetty's recent example (where client accidentally ended up depending on server) this isn't always easy to achieve. In fact, some of the successes that OSGi brings to a project is purely the enforced modularity constraints between modules.

Another benefit to modularisation is to separate out the 'public' packages from the non-public ones. Java's compile-time system allows for hidden non-public classes (those visible within a specific package) but does not provide a greater degree of flexibility than that. However, in an OSGi module, you can choose which packages are exported with the implicit fact that non-exported packages are not visible to other modules.

Let's say we're going to develop a function to instantiate URI Templates (as used in Restlet). Since this is likely to be reusable, we want to put this in its own module and have clients who need to use it depend on us. (Normally, bundles aren't quite as fine-grained as this; but it demonstrates the principle.) The function will take a template, like http://www.amazon.{tld}/dp/{isbn}/, and with a Map containing tld=com,isbn=1411609255, we can generate the URL http://www.amazon.com/dp/1411609255/ (One of the reasons to do this is to allow us to change the template if Amazon's URL scheme changes, although Cool URIs don't change.)

In order to provide an easy way to switch between different implementations, we'll provide an interface and a factory. This will also allow us to see how we can hide the implementation away from clients whilst still giving them the functionality. The code (across several source files) will look like:

package com.infoq.templater.api;
import java.util.*;
public interface ITemplater {
  public String template(String uri, Map data);
}
// ---
package com.infoq.templater.api;
import com.infoq.templater.internal.*;
public class TemplaterFactory {
  public static ITemplater getTemplater() {
    return new Templater();
  }
}
// ---
package com.infoq.templater.internal;
import com.infoq.templater.api.*;
import java.util.*;
public class Templater implements ITemplater {
  public String template(String uri, Map data) {
    String[] elements = uri.split("\\{|\\}");
    StringBuffer buf = new StringBuffer();
    for(int i=0;i<elements.length;i++)
        buf.append(i%2 == 0 ? elements[i] : data.get(elements[i]));
    return buf.toString();
  }
}

The implementation is hidden away in the com.infoq.templater.internal package, whilst the public API is in the com.infoq.templater.api package. This will give us the flexibility to change the implementation to a more efficient mechanism later on, if needed. (The internal package name is somewhat conventional, but you can call it what you want.)

To give other bundles access to the public API, we need to export it from the bundle. Our manifest will look like:

Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.infoq.templater
Bundle-Version: 1.0.0
Export-Package: com.infoq.templater.api

Creating a client bundle

We can now create a client that uses the templater. Using the example above, create an activator whose start() method looks like:

package com.infoq.amazon;
import org.osgi.framework.*;
import com.infoq.templater.api.*;
import java.util.*;
public class Client implements BundleActivator {
  public void start(BundleContext context) {
    Map data = new HashMap();
    data.put("tld", "co.uk"); // or "com" or "de" or ...
    data.put("isbn", "1411609255"); // or "1586033115" or ...
    System.out.println( "Starting\n" + 
        TemplaterFactory.getTemplater().template(
        "http://www.amazon.{tld}/dp/{isbn}/", data));
  }
  public void stop(BundleContext context) {
  }
}

We'll need to define in our manifest that we're explicitly importing the templater API, as otherwise our bundle won't compile. We can either specify the dependency using either Import-Package or Require-Bundle. The former allows us to import packages individually; the latter will implicitly import all exported packages from the bundle. (Multiple packages and bundles are comma separated.)

Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.infoq.amazon
Bundle-Version: 1.0.0
Bundle-Activator: com.infoq.amazon.Client
Import-Package: org.osgi.framework
Require-Bundle: com.infoq.templater

Note that in the earlier example, we were already using the Import-Package to import org.osgi.framework. In this case, we are demonstrating the use of Require-Bundle which uses the Bundle-SymbolicName. We could have equally well added it in as Import-Package: org.osgi.framework, com.infoq.templater.api instead.

Regardless of how we declare our dependency on the templater bundle, we only get access to the single exported package com.infoq.templater. Although the client can access the templater via TemplaterFactory.getTemplater(), we can't directly access the class from the internal package. This gives us the flexibility to change implementation in the future without breaking clients.

Testing the system

Any OSGi application is really just a set of bundles. In this case, we need to compile and JAR up the bundles (as previously), start up an OSGi engine, and install the two bundles. Here's what it would look like in Equinox:

java -jar org.eclipse.osgi_* -console
osgi> install file:///tmp/com.infoq.templater-1.0.0.jar
Bundle id is 1
osgi> install file:///tmp/com.infoq.amazon-1.0.0.jar
Bundle id is 2
osgi> start 2
Starting
http://www.amazon.co.uk/dp/1411609255

The Amazon client bundle has started; and when it did, it instantiated the URI template for us with the (admittedly hard-coded) values previously. It then prints out during the startup of the bundle itself to confirm that it worked. Clearly, a real system wouldn't be so inflexible; but the Templater service could be used in any other application (for example, generating links in a web-based application). We'll look at web applications in an OSGi context in the future.

Versioned dependencies

The final point for this instalment is to note that the dependencies that we have at the moment are unversioned; or rather, that we'll be able to use any version. Both bundles as a whole and packages individually can be versioned, and increases in the minor number are used to signify new features (but remaining backwardly compatible). The org.osgi.framework package, for example, was version 1.4.0 in OSGi R4.1 and 1.5.0 in OSGi R4.2. (This, incidentally, is a good reason to keep the bundle version and marketing version as separate concepts, something that the Scala language has yet to learn.)

To declare a dependency on a specific version, we have to express that in the Import-Package or Require-Bundle. For example, we can specify Require-Bundle: com.infoq.templater;bundle-version="1.0.0" to signify that we want a minimum version of 1.0.0 in order to work. Similarly, we could do the same with Import-Package: com.infoq.templater.api;version="1.0.0" – but bear in mind that the version of the package is distinct from the version of the bundle. If you don't specify a version, it defaults to 0.0.0, so unless you have a corresponding Export-Package: com.infoq.templater.api;version="1.0.0" then this import won't be resolved.

It's also possible to specify a version range. For example, the conventional meaning to OSGi version numbers is that an increment of the major number indicates a change in backward compatibility, so we might just want to restrict ourselves to the 1.x range. To do that, we can express a (bundle-)version="[1.0,2.0)" dependency constraint. In this case, the [ means 'inclusive' and ) means 'exclusive', or in other words 'from 1.0 up to but not including 2.0 onwards'. In fact, expressing a dependency constraint of "1.0" is the same as "[1.0,∞)" – in other words, anything bigger than 1.0.

Although it's outside of the scope of this article, it's possible to have multiple versions of a bundle in an OSGi system at once. This might be useful if you have an old client which depends on version 1.0 of your API, as well as having a new client which depends on version 2.0 of your API. As long as the dependencies for each bundle are consistent (in other words, you don't have a bundle which tries to import 1.0 and 2.0 at the same time, directly or indirectly) then the application will run fine. As an exercise for the reader, you can create a version 2.0 of the Templater API to use generics, and then have a client which only depends on version 1.x as well as one that uses version 2.x.

Summary

In this article, we've explored the open-source OSGi engines Equinox, Felix and Knopflerfish, and created a dependent pair of bundles. We've also touched upon versioned dependencies. At the moment, this modularity is static; we haven't explored any of the dynamic nature of OSGi. We'll be looking at that in the next instalment.

Installable bundles (also containing source):

Hello stranger!

You need to Register an InfoQ account or to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Problem in example code by Christopher Brind

Hi Alex,

The first code sample after the heading "Creating a client bundle" seems to be incomplete?

Also, what is in the com.infoq.minimal-1.0.0.jar? This wasn't created during the article.

Cheers,
Chris

Re: Problem in example code by Christopher Brind

Ah, I see where minimal comes from now... right at the beginning.

Re: Problem in example code by Alex Blewitt

Sorry about that - originally the Map was a generic one (with String,String type arguments) but the publishing system ate everything after the opening angled bracket :-) I've removed it now so the rest of the code shows up - but the source code is included in the .jar too. The minimal one, as you note, is really just there for people who are interested in downloading/using it from an OSGi install.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

3 Discuss

Educational Content

General Feedback
Bugs
Advertising
Editorial
InfoQ.com and all content copyright © 2006-2013 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT