Chapter 2. An OSGi framework primer – OSGi in Depth

Chapter 2. An OSGi framework primer

 

This chapter covers

  • Creating and sharing OSGi bundles
  • Importing and exporting bundle packages
  • Running bundles in an OSGi framework
  • Defining and retrieving OSGi services
  • Understanding the OSGi service registry

 

In this chapter you’ll expand your knowledge of the OSGi framework. I’ll describe two of the most important concepts related to OSGi: bundles and services. Bundles and services are the cornerstone of OSGi, and we’ll keep revisiting these two concepts throughout this book.

We’ll also cover the OSGi service registry and several less common, albeit still essential, APIs of the framework, such as the event listener interfaces.

As tradition dictates, to learn the basics, we’ll use an OSGi-powered hello world application. This will allow us to demonstrate the OSGi concepts in the scope of a single simplistic application. It’ll also allow us to compare the OSGi technology to other programming environments that, without doubt, are part of your background. Let’s start by creating a simple OSGi module.

2.1. Modules and information hiding

The unit of modularization in OSGi is called a bundle. Bundles allow us to enforce the principles of information hiding. As was brilliantly stated by D. Parnas as early as 1972, information hiding helps us achieve the following benefits:

  • Changeability
  • Comprehensibility
  • Independent development

Through the use of bundles, and by applying the principles of information hiding, we’re better able to cope with the complexity of large systems.

This all seems complicated and abstract, but a bundle is easily defined in OSGi by adhering to the following two simple steps:

  • Package the module as a JAR file.
  • Include a manifest file with the mandatory manifest header Bundle-SymbolicName. This header provides an identifier for the bundle.

As defined by Java’s manifest format specification, a JAR manifest file exists within the JAR file with the name of META-INF/MANIFEST.MF.

Here’s a sample manifest file:

Manifest-Version: 1.0
Bundle-SymbolicName: helloworld

The Bundle-SymbolicName header specifies a name of helloworld for this bundle. Strictly speaking, that’s all you need to create a bundle. But as a best practice, you should also specify the version of the OSGi specification being used. The manifest header Bundle-ManifestVersion, which defaults to 1, is used for this purpose. In this book, we use OSGi Platform Version 4.2, which maps to

Bundle-ManifestVersion: 2

In summary, a bundle encapsulates a collection of Java classes that are highly cohesive. This means we’re achieving information hiding by using the technique of encapsulation.

The next step is to create some useful piece of code in the bundle that can be reused. Let’s code a simple Printer class that outputs to standard out, à la hello world.

package manning.osgi.helloworld;

public class Printer {

    public void print(String message) {
        System.out.println(message);
    }

}

Don’t worry about the triviality of the code being used; our goal for the time being is to implement a hello world application using OSGi.

Let’s share this code with other Java clients. Create a PrinterClient class that imports the Printer class and invokes it:

package manning.osgi.helloworld.client;

import manning.osgi.helloworld.Printer;

public class PrinterClient {

    public void printMyMessage() {
        new Printer().print("Hello World");
    }
}

Figure 2.1 demonstrates this interaction.

Figure 2.1. The PrinterClient class invokes the method print in the Printer class.

 

Note

This book attempts to be goal oriented. Rather than presenting the technology for the technology’s sake, the discussion of a topic is driven by first introducing a problem to be solved. I hope this makes the book more enjoyable to read and more useful.

 

We’ve created the simplest possible bundle. Can OSGi help us improve anything in this simple interaction between the Printer and the PrinterClient classes? Yes. After all, we haven’t achieved any form of information hiding so far. Consider the case where you want to prohibit the usage of PrinterClient by any other class. Applications can reuse the Printer class, but the client application code can’t be reused. Another way of looking at this is that you’d like to “hide” the PrinterClient module. This is a reasonable requirement; you wouldn’t want someone else printing your unique “Hello World” message, would you? How do you go about fulfilling this requirement in OSGi?

2.1.1. Establishing a formal import/export contract

First, you should keep the Printer and the PrinterClient classes in separate bundles. This is just good engineering, because it’s likely that these classes are provided by different developers and should be decoupled.

Next, you’d like a mechanism whereby you can formally specify the dependencies between bundles. Using this mechanism, you can grant permission for bundles to use the Printer class and conversely restrict bundles from using the PrinterClient class, as illustrated in figure 2.2.

Figure 2.2. Other classes are restricted from invoking the printMyMessage method in the PrinterClient class.

In OSGi, you accomplish this by having the provider bundle export the Java packages that are meant to be shared, and then have the consumer bundle import the Java packages that it needs.

To export Java packages, a bundle uses the manifest header Export-Package:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: helloworld
Export-Package: manning.osgi.helloworld

In this case, the bundle helloworld is exporting all of its public classes located in the package manning.osgi.helloworld.

To import a Java package, a bundle uses the manifest header Import-Package:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: manning.osgi.client
Import-Package: manning.osgi.helloworld

The bundle helloworld.client is importing all public classes in the package manning.osgi.helloworld.

As should be clear, we have two bundles, helloworld and helloworld.client, as shown in figure 2.3. The bundle hello-world includes the Printer class. The bundle helloworld.client includes the PrinterClient class, which makes use of the Printer class.

Figure 2.3. The bundle helloworld includes the Printer class. The bundle helloworld.client includes the PrinterClient class.

Have we solved our initial problem? That is, have we prohibited other classes from using the PrinterClient class? Yes, by not exporting the package manning.osgi.helloworld.client, we make sure that no other class outside the helloworld.client bundle can use it.

Couldn’t we have simply made PrinterClient private? Not really; we need to keep in mind that PrinterClient can’t be completely inaccessible because it needs to be invoked or bootstrapped at some time. For example, it may need to be invoked by the application’s main method.

What if we moved the main method to the PrinterClient class itself and kept its other methods private? If we did this, we’d be coupling together some application logic code with the JVM’s bootstrap method, which isn’t a good practice; for example, it would prevent us from creating JUnit test cases to test the PrinterClient class because its constructor would be private. In addition, we’d be leaving the main method open for access.

What if we made the PrinterClient package protected and included both the main method and the JUnit test cases in the same package? That might work, if we’re willing to always have a single package for our application, which clearly isn’t a good practice either. Of course, we could also cheat by using our package name when creating our classes.

The conclusion is that even though we may be able to devise some clever way of achieving similar results, OSGi provides a simple, elegant, and standard approach to the issue. OSGi allows us to scale, both in terms of code complexity and in terms of working with larger teams. Furthermore, OSGi achieves this through a metadata-driven approach. OSGi doesn’t impose changes on either the Printer or PrinterClient classes; only the manifest files are impacted.

By having separate bundles for the provider and the consumer of the code and explicitly establishing their interdependencies, we’ve increased the modularity of our hello world application.

Next, we should run and test our hello world OSGi application. But before we do that, we need to code how the bundles get bootstrapped.

2.1.2. Activating a bundle

How do you control the start of a regular Java SE application? As touched on in the previous section, you have to implement the main native method:

package manning.osgi.helloworld.client;

public class PrinterClientMain {

    public static void main(String args[]) {
        new PrinterClient().printMyMessage();
    }
}

The JVM invokes the main method, which takes care of starting the application in JSE.

What’s the equivalent of a main method in OSGi? The BundleActivator class.

In OSGi, an application is made of potentially several bundles, so you need a way of individually starting the bundles. You can start a bundle by including a Bundle-Activator class in the bundle’s JAR file. You have to follow these steps:

1.  Create a class that implements the interface org.osgi.framework.Bundle-Activator.

2.  Provide an empty constructor that can be instantiated with Class.newInstance().

3.  Add the manifest header Bundle-Activator to the bundle’s manifest file, as shown in the following listing. The value of this header must be the fully qualified class name of the class defined in step 1.

Listing 2.1. BundleActivator for helloworld.client bundle

A bundle activator not only allows you to control the starting of the bundle but also allows you to control the stopping of the bundle. In the stop(BundleContext) method, you can release allocated resources and perform other general cleanups. We’ll discuss the BundleContext class in detail in chapter 4. Note that, conversely, the method start(BundleContext) can be used to acquire resources, as you’ll see in several examples throughout this book.

Finally, you must not forget to update the manifest file to include the Bundle-Activator header:

Manifest-Version: 1.0
Bundle-SymbolicName: helloworld.client
Import-Package: manning.osgi.helloworld
Bundle-Activator: manning.osgi.helloworld.client.PrinterClientActivator

The Bundle-Activator header references the class name of a class that implements the BundleActivator interface within the bundle’s JAR file.

Now you have everything you need to run the OSGi-powered hello world application.

2.2. Running and testing OSGi

To run a standard Java application, you have to execute the JVM program with the application’s JAR files in the JVM’s CLASSPATH. For example, to run our hello world example, you could archive the classes Printer, PrinterClient, and PrinterMain in a helloworld.jar and then execute the JVM with the following configuration:

java –jar helloworld.jar manning.osgi.helloworld.client.PrinterClientMain

To run an OSGi bundle, you have to first run an OSGi framework and then install the bundle into the framework. Which OSGi framework should we use to test our OSGi-powered hello world application?

We’ll use the Apache Felix product, which is an open source implementation of the OSGi framework. You can download Felix from http://felix.apache.org/site/index.html.

2.2.1. Apache Felix, the open source OSGi framework

As a first step, install Felix in your machine. For the purpose of the examples in this book, we’ll be using Felix 3.0.6.

Open a shell, and change directory to Felix’s installation home. You can run Felix with the following command:

felix-framework-3.0.6$ java -jar bin/felix.jar
____________________________

Welcome to Apache Felix Gogo

g!

 

Conventions

Before we continue, here are some assumptions and conventions you should know:

  • We’ll assume that the Java program is in your shell’s path. This is generally the case.
  • We’ll be using JDK 1.5.0_16 to run the examples.
  • We’ll be using UNIX commands, as executed in a bash shell, as the convention. Thanks to Java, we’re mostly isolated from the operating system, except for a few file-related commands.
  • If you’re using Windows, you may have to replace the forward slash (/) with a backward slash (\) in file-related commands. As an example, this command,
    java -jar bin/felix.jar should be changed to the following in a Windows environment:
    java -jar bin\felix.jar

 

You’ve started the OSGi framework. When Felix starts, it presents its own text-based shell, called Gogo. Gogo uses g! as its prompt.

 

Warning

There’s no standard for the shell commands for OSGi. Therefore, some of the commands used in this book, such as install, start, and uninstall, may be subject to change.

 

You can use Felix’s shell to install the bundles into the framework and perform other bundle- and framework-related tasks. For a list of all the commands, type help at the prompt.

2.2.2. Building OSGi bundles

As you saw in section 2.1, a bundle is built as a standard JAR file, whose manifest file contains OSGi-related entries. Hence, you can use your Java build tool of choice. In particular, Maven has added support for packaging OSGi bundles and even generating a MANIFEST.MF file with the appropriate OSGi header entries.

But to keep things simple and most importantly transparent, we’ll use Ant to compile and build the helloworld bundle. First, let’s start by defining an Ant macro, as shown in the following listing.

Listing 2.2. Bundle-up ANT macro

The bundle-up macro takes one argument: the name of the bundle . In addition, it references the osgi.install.dir property, which should point to the location of the OSGi framework installation directory . It then assumes that the bundle’s source files and manifest file are positioned using the Maven convention:

${basedir}/
    modules/
        ${name}/
            src/main/java/
            src/main/resources/META-INF/

${basedir} points to the directory that invokes the bundle-up Ant macro, and ${name} maps to the attribute name used in the macro. In the case of the helloworld bundle, first make sure all sources are lined up using this convention:

build.xml
modules/
    helloworld/
        src/main/java/
                   manning/osgi/helloworld/Printer.java
        src/main/resources/
                   META-INF/
                       MANIFEST.MF

Then invoke bundle-up as follows:

<bundle-up name="helloworld" />

Keep in mind that you need to set the osgi.install.dir property to point to the directory where you’ve installed Felix, therefore ensuring that the bin/felix.jar file is in the class path. The output bundle file helloworld.jar, which is used in the next section, is placed in this directory:

${basedir}/dist

2.2.3. Installing bundles into Felix

Next, you need to install the helloworld and helloworld.client bundles into the framework.

We have two bundles, or JAR files: helloworld.jar and helloworld.client.jar. The archived content of the helloworld.jar bundle is

META-INF/MANIFEST.MF
manning/osgi/helloworld/Printer.class

And for the helloworld.client.jar, it’s

META-INF/MANIFEST.MF
manning/osgi/helloworld/client/PrinterClient.class
manning/osgi/helloworld/client/PrinterClientActivator.class

Copy helloworld.jar and helloworld.client.jar to the Felix installation to make it easier to reference the files. Create a dist directory underneath the install directory, and place the JAR files there.

 

Note

Don’t place the hello world bundles in the bundle directory, because this directory is by default configured as Felix’s autodeploy directory, a feature that we’ll talk about in later chapters.

 

Now, you can install these bundles with the install command typed in the Felix shell:

g! felix:install file:dist/helloworld.jar
Bundle ID: 5
g! felix:install file:dist/helloworld.client.jar
Bundle ID: 6

Each time you install a bundle, Felix provides a unique bundle ID, which is needed later by the other commands.

 

Note

The Gogo shell commands, such as felix:install, can be specified without the prefix (that is, felix, obr, gogo) if they’re unique. But generally it’s good to keep the prefix because it helps with backward compatibility for future versions of Felix.

 

Did you expect to see “Hello World” printed? We’re almost there.

The installation of a bundle copies the binary image of the bundle, that is, its JAR file content, into the framework. It does not, however, actually start the bundle.

2.2.4. Starting the bundles in Felix

To start a bundle, you must use the start command in the Felix shell. As expected, the start command triggers the BundleActivator.start() callback. Let’s try it out:

g! felix:start 4
g! felix:start 5

Note that you must use the ID returned by the install command.

You should get a stack trace and still no “Hello World” message, which probably isn’t what you expected. If you go down the stack, you’ll eventually notice the following message:

Caused by: java.lang.NoClassDefFoundError: org/osgi/framework/BundleActivator

The class PrinterClientActivator inherits from BundleActivator, which is in the package org.osgi.framework, so you must add this package to the Import-Package header in the bundle’s manifest file:

Manifest-Version: 1.0
Bundle-SymbolicName: helloworld.client
Import-Package: manning.osgi.helloworld,
 org.osgi.framework
Bundle-Activator: manning.osgi.helloworld.client.PrinterClientActivator

 

Tip

You must explicitly import the packages of all classes being used. A bundle must even explicitly import the OSGi framework’s own APIs, such as the org.osgi.framework package. As you’ll see later, this can be quite tricky to get right when dealing with classes that are loaded using reflection.

 

Update the MANIFEST.MF file, re-archive helloworld.client.jar, and copy it to the bundle directory in the Felix installation. Then uninstall the offending bundle from Felix, reinstall the fixed one, and start it over again.

g! felix:uninstall 5
g! felix:install file:dist/helloworld.client.jar
Bundle ID: 6
g! felix:start 6
Hello World

Congratulations, you got your hard-earned “Hello World” message!

Looking back at section 2.2, have we actually solved the problem of restricting access to the PrinterClient class? There’s no way of being completely sure without testing it. Let’s create another bundle, helloworld.client2.jar, as shown in the following listing.

Listing 2.3. PrinterClientActivator2 tries to invoke non-exported PrinterClient

In the PrinterClientActivator2 class, we try to invoke the PrinterClient class . We’ll even add manning.osgi.helloworld.client to the Import-Package header in the bundle’s manifest file:

Manifest-Version: 1.0
Bundle-SymbolicName: helloworld.client2
Import-Package: manning.osgi.helloworld.client,
org.osgi.framework
Bundle-Activator: manning.osgi.helloworld.client2.PrinterClientActivator2

The helloworld.client2 JAR has only two files: the PrinterClientActivator2 class and the MANIFEST.MF file.

You’re now ready. Copy the helloworld.client2.jar file to Felix’s bundle directory, install and start the bundle in Felix, and see what happens:

g! felix:install file:dist/helloworld.client2.jar
Bundle ID: 7
g! felix:start 7
org.osgi.framework.BundleException: Unresolved constraint in bundle 7: package; (package=manning.osgi.helloworld.client)

The helloworld.client2 bundle isn’t able to import the package manning.osgi.helloworld.client. You’ll get an exception when you try to use a class whose package hasn’t been exported, even though you try to import it. Thus, we can validate that the code is indeed restricted.

In chapter 4, we’ll look in detail at the process that OSGi goes through to match the imported packages with exported packages, which in the OSGi nomenclature is known as resolving package constraints.

2.2.5. Can we cheat using reflection?

Before we move on, there’s another test worth pursuing. Could we cheat the OSGi framework by trying to use the PrinterClient class through reflection? There are cases in other frameworks, including application servers, where you can create “backdoors” through reflection. Is this the case for the OSGi framework?

Let’s test and see. In the following listing, the PrinterClientIntrospector-Activator class uses reflection to access the PrinterClient class.

Listing 2.4. Using reflection to attempt to invoke a non-exported package
package manning.osgi.helloworld.introspector;

import java.lang.reflect.Method;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class PrinterIntrospectorActivator implements BundleActivator {

    public void start(BundleContext context) throws Exception {
        Class<?> printerClientClass =
            Class.forName("manning.osgi.helloworld.client.PrinterClient");
        Object printerClientInstance =
            printerClientClass.newInstance();
        Method printMethod =
            printerClientClass.getMethod("printMyMessage");
        printMethod.invoke(printerClientInstance);
     }

    public void stop(BundleContext context) throws Exception {
        // NOP
     }
}

The helloworld.introspector bundle includes the PrinterClientIntrospector-Activator class. As you’ve learned, you must also not forget to import the package manning.osgi.helloworld.client in the bundle’s manifest file:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: helloworld.introspector
Import-Package: manning.osgi.helloworld.client,
org.osgi.framework
Bundle-Activator: manning.osgi.helloworld.introspector.PrinterIntrospectorActivator

Install the bundle in Felix and start it. Again, as desired, you’ll get a BundleException:

g! felix:install file:dist/helloworld.introspector.jar
Bundle ID: 19
g! felix:start 19
org.osgi.framework.BundleException: Unresolved constraint in bundle 19: package; (package=manning.osgi.helloworld.client)

We’ll be using the OSGi framework to build enterprise-grade solutions, so we must make sure it’s robust.

2.2.6. Eclipse Equinox

Eclipse Equinox is another open source OSGi framework implementation. Although we’re generally using Felix in this book, any OSGi framework implementation will do. One reason for opting for Equinox would be if you need to interact with or leverage Eclipse IDE plug-ins in some form.

You can download the Equinox OSGi framework implementation from the following URL: http://download.eclipse.org/equinox/. You can choose to download just the Equinox JAR file, which at the time of writing is named org.eclipse.osgi_3.6.2.R36 x_v20110210.jar, or you can download the full SDK, containing the Equinox JAR file and several other infrastructure bundles.

You can start Equinox by typing the following command in the current directory where the Equinox JAR file exists:

java -jar org.eclipse.osgi_3.6.2.R36x_v20110210.jar –console

Equinox provides you with this prompt:

osgi>

You can type help to get a list of all console commands, which should look similar to what you’ve used so far with Felix. For example, the following commands install our helloworld bundles and start them as expected:

osgi> install file:helloworld.jar
Bundle id is 1

osgi> install file:helloworld.client.jar
Bundle id is 2

osgi> start 1 2
Hello World

When needed, I’ll point out the differences between Felix and Equinox in upcoming chapters.

2.3. Coping with changes to a module

Consider the scenario where you’ve decided that always printing “Hello World” to the standard output is a bit inflexible; instead, you want to allow the user of the Printer class to specify the location where the message should be printed. In addition, you’ve noticed a mistake in your current interface; the interface is named print, but it actually prints and adds a new line, so a more suitable name would have been println. Changing the interface of a module is a common scenario, which we’ll explore next.

2.3.1. Changing a bundle’s interface

Following up with our example, let’s say you want to replace the existing interface, public void print(String message), with public void println(PrintStream stream, String message).

This presents a problem. If you do this, you’ll have to modify and recompile all client code that uses the Printer class, which may be unacceptable. For example, if you’re on a production system, you can’t just stop the system. Or, quite frequently, you may not own the client code and hence have no way of changing it.

Here are two valid reasons for changing the interface:

  • The interface name is wrong and you want to correct it.
  • The interface is inflexible and you want to add parameters to it.

But because of a series of constraints, such as backward compatibility, you can’t simply fix the existing code. What’s the solution?

One solution is to create a new class, say the Printer2 class, which has the new interface, and keep the old one untouched. That’s an option that is commonly employed today, but it has plenty of problems:

  • It isn’t clear to the user which class to use. Should the user look for Printer3, Printer4, ...? What if you had called it NewPrinter; would you call the next one NewerPrinter? In other words, there’s no clear convention on how to name a class to represent versioning.
  • Encoding the version into the class name binds the client to a specific version. What if the client wanted to always use the latest version? This wouldn’t be possible.

Next, you’ll learn how OSGi addresses this problem.

2.3.2. Versioning bundles

OSGi provides native support for bundle versioning. With OSGi, a bundle can specify a version for each package being exported, and conversely a bundle can specify version ranges when importing packages. OSGi defines a version as a value of four parts: major, minor, micro, and qualifier, as shown in figure 2.4.

Figure 2.4. OSGi defines a version as a value of four parts: major, minor, micro, and qualifier. The first three take numeric values. The last part takes alphanumeric digits, or _, or -. By default, zero is used for the first three parts.

For now, let’s not worry about this. You just need to know that by default 0.0.0 is used, and that any time a part isn’t specified, it takes the value 0, or an empty string in the case of the qualifier. For example, version 1.1 is equal to and is actually converted to 1.1.0. The full definition of versions and version ranges is provided in chapter 4.

Let’s look at how to do this with the helloworld bundle and the Printer class.

First, we’ll create a new version of the helloworld bundle. We’ll do this by creating a new bundle JAR file, which we’ll name helloworld_1.1.0.jar. Note that the bundle JAR filename has no implications at runtime and doesn’t need to have a version number. Bundle helloworld_1.1.0.jar has the same bundle symbolic name of helloworld as bundle helloworld.jar. But, in addition, the bundle helloworld_1.1.0.jar must use the manifest header Bundle-Version. In our case, we’ll set Bundle-Version with the value 1.1. In reality, it’s the conjunction of the Bundle-Version header and the Bundle-SymbolicName header that uniquely identifies a bundle. If we had tried to install helloworld_1.1.0.jar without using the Bundle-Version header, we’d have gotten a BundleException informing us that our bundle isn’t unique:

org.osgi.framework.BundleException: Bundle symbolic name and version are not unique: helloworld:0.0.0

Bundle-Version takes care of versioning the bundle, but it doesn’t version the packages being exported by the bundle. In other words, we still need to version the package being exported so that we don’t create a non-backward change to the Printer class.

To do this, append the tag version to each exported package listed in the Export-Package header. The MANFIEST.MF file for version 1.1 of helloworld is as follows:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: helloworld
Bundle-Version: 1.1
Export-Package: manning.osgi.helloworld;version=1.1

We keep the same bundle symbolic name of helloworld, but we change the bundle version to 1.1 and also export the package manning.osgi.helloworld with version 1.1. Note, as explained previously, that we don’t necessarily need to use the same version of the bundle for the packages being exported.

What would have happened if we hadn’t versioned the exported package manning.osgi.helloworld in the bundle helloworld_1.1.0.jar? It would have meant that both helloworld.jar and helloworld_1.1.0.jar are exporting version 0.0.0 of the package manning.osgi.helloworld. OSGi calls this a split package, and it’s decidedly not something you want to do. We’ll investigate this matter further in chapter 4.

 

Tip

Do not export the same package (of the same version) in more than one bundle.

 

Next, we need to implement the new version of the Printer class and include it in the bundle helloworld_1.1.0.jar:

package manning.osgi.helloworld;

import java.io.PrintStream;

public class Printer {

    public void println(PrintStream stream, String message) {
        stream.println(message);
    }
}

Now we have two hello world bundles: helloworld.jar, which is technically version 0.0.0, and helloworld_1.1.0.jar, which contains the new interface for the Printer class.

In hindsight, we should have named helloworld.jar as helloworld_1.0.0.jar, and we should have set its Bundle-Version manifest header with the value 1.0.0. It’s considered good practice to name the bundle JAR file with the following pattern: bundle-SymbolicName + _ + bundleVersion + .jar, as shown in figure 2.5. As you’ve noticed, that convention is used in this book.

Figure 2.5. The convention is to name a bundle’s JAR file using the bundle’s symbolic name, appended with _ and the bundle’s version.

We’ve finished the provider side of the equation. On the client side, let’s create a new client class that makes use of version 1.1 of the Printer class. We’ll name it the PrinterAnotherClient class:

package manning.osgi.helloworld.anotherclient;

import manning.osgi.helloworld.Printer;

public class PrinterAnotherClient {

    public void printAnotherMessage() {
        new Printer().println(System.out, "Another Hello World");
    }
}

This class needn’t be a new version of the PrinterClient class. In practice, it may be just as common that the first user of a new version is a new client rather than an existing client that changes working code to use the new version. To our advantage, there’s no binding of a version number in the Java code.

We’ll create a new bundle JAR file called helloworld.anotherclient_1.0.0.jar, which contains the PrinterAnotherClient class. Bundle helloworld.anotherclient must import helloworld version 1.1 in its Import-Package manifest header:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: manning.osgi.anotherclient
Bundle-Version: 1.0.0
Import-Package: manning.osgi.helloworld;version="[1.1,1.1.9]",
  org.osgi.framework
Bundle-Activator: manning.osgi.helloworld.anotherclient.PrinterAnotherClientActivator

Note how we use a version range for the Import-Package manifest header. In this case, we’re specifying that the package to be imported must be of version 1.1.0 (inclusive) up to version 1.1.9 (inclusive).

 

Tip

When specifying version ranges in the Import-Package manifest header, you must enclose the version range in double quotes. The reason is that version ranges include a comma, which is used to delimit the packages being imported.

 

Archive hellworld_1.1.0.jar and helloworld.anotherclient_1.0.0.jar, and copy them to the bundle directory in Felix. Make sure Felix is running. You’re now ready to run the bundles.

Running Multiple Versions of a Bundle

Before you install the new bundles, let’s double-check which bundles you’ve installed and started so far.

In the Felix shell prompt, type the command lb (list bundles):

g! felix:lb
START LEVEL 1
   ID|State      |Level|Name
    0|Active     |    0|System Bundle (3.0.6)
    1|Active     |    1|Apache Felix Bundle Repository (1.6.2)
    2|Active     |    1|Apache Felix Gogo Command (0.6.1)
    3|Active     |    1|Apache Felix Gogo Runtime (0.6.1)
    4|Active     |    1|Apache Felix Gogo Shell (0.6.1)
    5|Active     |    1|helloworld (0.0.0)
    6|Active     |    1|helloworld.client (0.0.0)
    7|Installed  |    1|helloworld.client2 (0.0.0)

You should see a list of all installed bundles, which should include our first version of helloworld, and two helloworld client bundles, namely helloworld.client and helloworld.client2. The latter has the state of “Installed” instead of “Active” as the other bundles have. If you remember in section 2.2.4, the bundle hellworld.client2 attempted to import a package that wasn’t being exported and therefore failed to start. We’ll study bundle states and actions, namely install, resolve, start, stop, and uninstall, in chapter 4.

 

Equinox

In Equinox, you can get a list of all bundles by typing the following command:

osgi> ss
Framework is launched.
id  State       Bundle
0   ACTIVE      org.eclipse.osgi_3.6.2.R36x_v20110210

 

Even if you shut down and restart Felix, the list of installed bundles remains the same. Let’s do some cleanup. We obviously don’t need helloworld.client2, so let’s uninstall it.

g! felix:uninstall 7

Now, let’s install and start version 1.1 of helloworld—the helloworld_1.1.0.jar bundle JAR. (By the way, don’t worry if the bundle ID associated with your bundle is different from what’s being shown in the code.)

g! felix:install file:dist/helloworld_1.1.0.jar
Bundle ID: 10
g! felix:start 10
g! felix:lb
START LEVEL 1
   ID|State      |Level|Name
    0|Active     |    0|System Bundle (3.0.6)
    1|Active     |    1|Apache Felix Bundle Repository (1.6.2)
    2|Active     |    1|Apache Felix Gogo Command (0.6.1)
    3|Active     |    1|Apache Felix Gogo Runtime (0.6.1)
    4|Active     |    1|Apache Felix Gogo Shell (0.6.1)
    5|Active     |    1|helloworld (0.0.0)
    6|Active     |    1|helloworld.client (1.0.0)
    7|Active     |    1|helloworld (1.1.0)

The bundle named helloworld (1.1.0) represents version 1.1 of the bundle hello-world.

In spite of having started a new non-backward-compatible version of the Printer class, you’ll notice that this didn’t break the running helloworld.client bundle. If you want to double-check, stop and restart the helloworld.client bundle, and it should still print the “Hello World” message:

g! felix:stop 6
g! felix:start 6
Hello World

Finally, install and start bundle helloworld.anotherclient:

g! felix:install file:dist/helloworld.anotherclient_1.0.0.jar
Bundle ID: 11
g! felix:start 11
Another Hello World

If we hadn’t imported version 1.1 of manning.osgi.helloworld, we’d have gotten the following exception when we tried to start the bundle helloworld.anotherclient:

java.lang.NoSuchMethodError: manning.osgi.helloworld.Printer.println(Ljava/io/PrintStream; Ljava/lang/String;)V

There you have it. We’re able to run two versions of the same application concurrently within the same JVM. Regardless of it being a simple hello world application, this isn’t to be taken lightly and generally isn’t supported by most application frameworks.

Testing Bundle Versioning

Let’s make matters more interesting by quickly creating a version 1.2.0 of helloworld:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: helloworld
Bundle-Version: 1.2
Export-Package: manning.osgi.helloworld;version=1.2

Version 1.2 adds a Date parameter to the println method in the Printer class:

package manning.osgi.helloworld;
import java.io.PrintStream;
import java.util.Date;

public class Printer {

    public void println(PrintStream stream, String message,
Date dateOfMessage) {
        stream.println(dateOfMessage.toString() + ':' + message);
    }
}

As usual, archive helloworld_1.2.0.jar, and then install and start it in Felix. You’ll get no exceptions. If you stop and restart the helloworld.client bundle, you’ll again get the correct “Hello World” message:

g! felix:stop 6
g! felix:start 6
Hello World

Isn’t this a bit strange? After all, you installed an incompatible version of the Printer class. Try uninstalling and then reinstalling the helloworld.client bundle. As you’ll see when you reinstall the bundle, the OSGi framework raises a NoSuchMethodError exception:

java.lang.NoSuchMethodError:
manning.osgi.helloworld.Printer.println(Ljava/lang/String;)V

The bundle helloworld.client was unable to find the method Printer.println (String message), which corresponds to version 0.0.0 of the bundle helloworld. First of all, why was it that we got different behavior when we reinstalled a bundle as opposed to when we restarted the bundle? The reason is that the OSGi framework won’t attempt to change the packages being imported by a bundle after the packages have already been selected, even if you install new versions of these packages or stop and restart the importing bundle. If you need to renegotiate the packages being imported, a process called resolve, you must reinstall the bundle. To be precise, you could also update the bundle, but we’ll examine bundle updates in chapter 4.

Now that you understand when the packages get resolved, and you’ve taken the correct measures to have a clean sheet, let’s try to make sense of the versioning model:

  • Bundle helloworld version 0.0.0 exports manning.osgi.helloworld version 0.0.0.
  • Bundle helloworld version 1.1.0 exports manning.osgi.helloworld version 1.1.0.
  • Bundle helloworld version 1.2.0 exports manning.osgi.helloworld version 1.2.0.
  • Bundle helloworld.client doesn’t specify a version when importing package manning.osgi.helloworld, which means that it’s specifying the version range starting at 0.0.0 and up to infinity, that is, [0.0.0,∞).
  • Bundle helloworld.anotherclient specifies the version range [1.1.0,1.1.9] when importing manning.osgi.helloworld, which maps to the version range starting at 1.1.0 and going up to 1.1.9.

The OSGi framework resolves helloworld.client to version 1.2.0 of package manning.osgi.helloworld, and it resolves helloworld.anotherclient to version 1.1.0 of package manning.osgi.helloworld, as shown in figure 2.6. No bundle is bound to version 0.0.0 of package manning.osgi.helloworld.

Figure 2.6. Bundle helloworld.client resolves to package helloworld version 1.2.0; bundle helloworld.anotherclient resolves to package helloworld version 1.1.0.

The bundle helloworld.client specifies a version range of [0.0.0,∞), which potentially means versions 0.0.0, 1.1.0, and 1.2.0 of manning.osgi.helloworld. But the highest available package version of 1.2.0 is the one being selected. The bundle helloworld.anotherclient is also bound to the highest available package version that is allowed by the constraint of its version range of 1.1.0 to 1.1.9, which in this case is package version 1.1.0.

 

Tip

The OSGi framework resolves an imported package to the highest available package version that meets its constraints.

 

This seemingly simple but somewhat counterintuitive rule is important. You could argue that a better policy would be to select the lowest available package version, because it would avoid incompatibility problems caused by new installed bundle versions, such as those we ran into at the beginning of this section. But as you’ve seen, this isn’t such a good idea. The OSGi framework correctly resolves the packages of a bundle only once per installation (or, more precisely, per update), so new installed bundles won’t break existing bundles that have already been installed. In addition, if a bundle does need to be sure that it always resolves to a single specific version, it can always use a range that includes only that version, such as version="[1.0.0,1.0.0]".

2.3.3. Changing a module’s implementation

Previously, we ran into a scenario where we needed to change the interface of a bundle. Life isn’t always so dramatic, and most commonly you may need to change the implementation of a bundle and not its exposed interface.

Let’s look again at our first version of the Printer class, from section 2.1. We’d like to change it, as shown in the following listing, so that each printed message gets time-stamped with the current time.

Listing 2.5. The Printer service implementation
package manning.osgi.helloworld;

import java.text.DateFormat;
import java.util.Date;

public class Printer {

    private static final String SEPARATOR = ": ";

    public void print(String message) {
        System.out.println(getCurrentTime() + SEPARATOR + message);
    }

    private String getCurrentTime() {
        return DateFormat.getTimeInstance(DateFormat.LONG).
format(new Date());
    }
}

You know the drill. Create a new version of bundle helloworld, and then as usual provision it in Felix. Here’s where it starts getting interesting. When would the existing client bundles pick up this new version? For bundle helloworld.client to be able to use version 1.3 of bundle helloworld, which includes the timestamp in the printed message, you’ll have to uninstall and reinstall the helloworld.client bundle, as we proved in section 2.3.2.

Doesn’t it feel wrong that you need to reinstall bundle helloworld.client in this case? After all, you’re just changing the implementation of a method in an imported class, and not its interface. You don’t need to recompile the client code, so why do you need to reinstall it?

The problem is that the contract, as it stands with the current solution between the helloworld.client bundle and the helloworld bundle, is the Printer class in its entirety.

To break this contract, the client bundle needs to reestablish the packages and classes it imports, a process known as resolving package dependencies. As you’ll see in detail in chapter 4, package resolving happens after the bundle has been installed but before the bundle has been commanded to start, with a few exceptions, such as when dealing with dynamic imports. What this means is that when you start and stop a bundle, it doesn’t change its resolved packages, as shown in figure 2.7. Therefore, new versions of packages or class implementation changes won’t be picked up.

Figure 2.7. Starting and stopping a bundle changes the state of the bundle between active and resolved, which means that the bundle’s package won’t be resolved again during this process.

 

Tip

Starting and stopping a bundle won’t change the list of classes that a bundle has access to.

 

You can easily test this. Start Felix with a clean sheet. You can do this by uninstalling all the bundles you’ve installed so far. Create helloworld_1.3.0.jar with the new print method implementation you saw in the beginning of this section. The MANFIEST.MF file for bundle helloworld_1.3.0.jar is as follows:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: helloworld
Bundle-Version: 1.3
Export-Package: manning.osgi.helloworld;version=1.3.0

Here are the steps:

1.  Install the bundles helloworld.jar and helloworld.client.jar. Start them. As expected, you’ll get your “Hello World” message.

2.  Install helloworld_1.3.0.jar. Stop and restart helloworld.client.jar. You’ll notice that again the “Hello World” message without the timestamp is printed, which indicates that the helloworld.client bundle is still using the first version of the helloworld bundle, even though a more recent version is available.

3.  Try shutting down and restarting Felix. The bundle helloworld.client still prints the old message. The OSGi framework persists the state of the bundle and doesn’t change it even during a shutdown.

Tip

The OSGi framework persists the state of the bundles when the framework is shut down and restarted without changes. But during a restart, the OSGi framework may remove old packages that have been uninstalled, a process called refresh.

4.  Uninstall and reinstall helloworld.client.jar. Start it, and this time you’ll get a message with the timestamp, something similar to “08:03:00 AM PST: Hello World.”

OSGi does provide another option for dealing with this problem. You can tell OSGi to update a bundle. In this case, OSGI stops the bundle, changes the bundle state to installed, and refreshes the resolved packages, in the process picking up new versions and implementations. Although you’re not uninstalling a bundle, it’s almost as if you were. You’re essentially reinstalling a bundle from already-read bits. We’ll discuss update and refresh in chapter 4.

This isn’t optimal; could we do better?

Yes, the heart of the problem is that we’re coupling two bundles on a class that contains implementation code. What we should be doing is sharing an interface between the bundles, therefore keeping the interface’s implementation hidden within the providing bundle. To do this right, we rely on OSGi’s service layer, as you’ll see next.

2.4. Services and loose coupling

Our objective is to be able to change the implementation of a class, such as the Printer class, without having to reinstall the bundles, such as the helloworld.client bundle, that use this class. To accomplish this, we need to define the Printer class as a service.

With the popularity of Service Oriented Architecture (SOA), the term service has become somewhat overused. In OSGi, a service has three parts:

  • An interface —An OSGi service interface is a conjunction of Java class (java.lang.Class) names. These Java classes don’t have to extend or implement any particular OSGi API, or follow any particular conventions, such as that of JavaBeans.
    Tip

    Remember that java.lang.Class represents classes, interfaces, enums, and annotations.


  • An implementation —An OSGi service implementation is a Java object. This Java object must be an instance of all the classes whose names were used to define the service interface.
  • Service properties —A map of key-value pairs that can be used to provide metadata to the service is optional.

 

Technology-agnostic services

One of the advantages of OSGi is that it doesn’t dictate that the service objects implement any technology-specific interfaces. This technology-agnostic behavior is a good quality of a framework. It allows user code to move freely from one framework to another.

 

The OSGi service registry manages the OSGi services. The OSGi service registry has two main roles:

  • It allows services to be registered and deregistered by the service provider bundle.
  • It allows services to be retrieved and released by the service requestor bundle.

In these roles, the OSGi service registry acts as the mediator, isolating the provider from the requestor. The service provider bundle owns the service interface, service properties, and service implementation. The service requestor bundle uses the service interface and service properties, but it can’t access the service implementation. A diagram of the OSGi service registry is shown in figure 2.8.

Figure 2.8. The service provider bundle registers the service interface, service properties, and service implementation into the OSGi service registry. The service requestor bundle retrieves the service interface and the service properties from the OSGi service registry.

Returning to our example, what do we need to do to change the Printer class into a Printer service?

2.4.1. The Printer service

First, we must create a service interface for the Printer service. OSGi makes this a simple task. We’ll just create a Java interface for the Printer class:

package manning.osgi.helloworld;

public interface Printer {

    public void print(String message);

}

The Printer service interface is defined by a Java interface with a single println method. No implementation is needed to define the service interface. Technically, we could use a Java class rather than a Java interface to define the OSGi service interface. In this case, the service implementation would extend the Java class instead of implementing the Java interface. But using a Java interface is cleaner, is easier to understand, and keeps coupling between the service provider and the service requestor to a minimum.

Next, we need a Java object to serve as the service implementation. Version 1.3 of the Printer class is almost good enough; we just need to change it so that it implements the service interface, as shown here.

Listing 2.6. Upgraded Printer service implementation
package manning.osgi.helloworld.impl;

import java.text.DateFormat;
import java.util.Date;

import manning.osgi.helloworld.Printer;

public class PrinterImpl implements Printer {

    private static final String SEPARATOR = ": ";

    public void print(String message) {
        System.out.println(getCurrentTime() + SEPARATOR + message);
    }

    private String getCurrentTime() {
        return DateFormat.getTimeInstance(DateFormat.LONG).
format(new Date());
    }
}

First, the service implementation must implement (or extend) the service interface. Second, it’s important to use a different Java package for the implementation than what is used for the interface. The package used by the interface needs to be exported, whereas we don’t want to export the package used by the implementation. Otherwise, we might not be able to change the implementation easily without reinstalling the clients.

 

Tip

Use different packages for the service interface and service implementation, and export the package for the service interface. Don’t export the package for the service implementation.

 

Those are all the changes needed to make the Printer class into an OSGi service. Observe how we didn’t have to change the class to use any of the OSGi framework APIs; we just had to refactor it to adhere to software engineering practices that would have made sense regardless of OSGi.

Now that we have a service, we must register it in the OSGi service registry.

2.4.2. Registering a service in the OSGi service registry

We register a service into the OSGi service registry by using the BundleContext class provided in the BundleActivator, as shown in the following listing. We should register the Printer service when the helloworld bundle is started and unregister the service when the bundle is stopped.

Listing 2.7. Registering the Printer service in the OSGi service registry

You can archive the Printer interface, the Printer implementation, and the PrinterActivator classes into version 1.4.0 of the helloworld bundle:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: helloworld
Bundle-Version: 1.4
Export-Package: manning.osgi.helloworld;version=1.4.0
Import-Package: org.osgi.framework
Bundle-Activator: manning.osgi.helloworld.impl.PrinterActivator

In the start method, you instantiate the service by specifying its interface and implementation , and you register it in the service registry using the bundleContext.registerService() method. This method returns a serviceRegistration object, which represents the registered service.

Next, you use the serviceRegistration object in the stop() method to unregister the service . This is done by invoking the ServiceRegistration.unregister() method.

 

Note

The OSGi framework automatically unregisters all services registered by a bundle when the bundle in question is stopped. Strictly speaking, it’s unnecessary to explicitly invoke unregister() as we’ve done previously. But for completeness’ sake, you generally include the unregistration of a service to highlight its proper usage and role. Furthermore, if you pass along the ownership of your serviceReference to another bundle, it then becomes that bundle’s responsibility to unregister the service. Therefore, to avoid having to worry about who owns a service reference, it’s better to always unregister it when it’s no longer needed.

 

We’ve finished with the provider side of the Printer service. Next we need to work on the client side, which is more commonly referenced as the service requestor side, of the Printer service.

Let’s start with the PrinterClient class. Instead of printing a single message, let’s print a message every couple of seconds, as shown in the following listing. Printing multiple messages should make it easier for you to see the dynamic behavior of the OSGi services.

Listing 2.8. RunnablePrinterClient class

Again, you can observe that the RunnablePrinterClient class doesn’t import any OSGi framework API. The only interface it depends on is the Printer service interface. In particular, it doesn’t depend on the Printer service implementation anywhere.

The last piece to this puzzle is the PrinterClientActivator class. The main purpose of this class is to look up the Printer service in the OSGi service registry, which it then sets into the RunnablePrinterClient.

2.4.3. Looking up a service from the OSGi service registry

The lookup of a service is done through BundleContext, using the getServiceReference method, which takes a service interface as the key for the lookup. The getServiceReference method returns a service reference instead of the actual service object.

 

Warning

The method getServiceReference() returns null if no reference is found for the requested service.

 

The reason for this indirection is that the service reference includes the service properties. These properties can provide additional metadata to help you determine if the service you’ve requested is the correct one before fully retrieving it from the registry.

If you do decide to go on, you can get to the service object by using the method BundleContext.getService(ServiceReference). This method returns an object that can be safely cast to the Java class defined as the service interface.

 

Tip

A service retrieved from the OSGi service registry can be safely cast to its service interface.

 

 

Generics

Why weren’t generics used to implement the service layer in OSGi version 4.2? The simple answer is that the OSGi Alliance didn’t want to impose usage of JDK 5.0 and above. This has since been relaxed in version 4.3, and it avoids the cast of the service objects returned from BundleContext.getService(ServiceReference).

But because OSGi version 4.3 is barely out at the time of writing, I’ve generally used version 4.2 throughout the book.

 

As part of retrieving a service from the service registry, a client must also release the when the service is no longer needed or when the service is no longer available. The release of a service is done through BundleContext, using the method ungetService, which takes the service reference that we retrieved previously.

You need to release the service when your client bundle stops. In addition, you should register a service listener with the OSGi framework, so that it can inform you when the service you’re using is no longer available, in which case you should also release it. We’ll discuss service listeners in following chapters.

 

Note

Strictly speaking, the OSGi framework will also unget a service for you when it’s unregistered, even if you forget to do it. But by doing it yourself, you’re likely improving memory management and most importantly allowing service factories to work properly, a subject that we’ll discuss in chapter 4.

 

There are a lot of details and caveats related to dealing with services. These will be dealt with in detail in chapter 4. You’ll also learn a way of using services by using a declarative language instead of Java, which will greatly simplify things. In the following listing, we stick to the basics.

Listing 2.9. BundleActivator for Printer service requestor

We have self-contained all of the OSGi-related usage in the PrinterClientActivator class. This is a good design pattern. If you ever need to move to a different framework, you just need to replace this class.

 

Tip

Avoid using OSGI framework APIs in the classes that implement the application logic. Keep them contained in a single location, the Bundle-Activator class being a good choice.

 

The filter syntax used in the registration of the service event listener in is based on LDAP (Lightweight Directory Access Protocol) and is used extensively by the OSGi framework. We’ll study it in detail in chapter 4. For the purpose of this chapter, it suffices to know that it selects only events related to the Printer service.

By now, you’ve surely noticed that your BundleActivator class is invoked on a thread provided by the OSGi framework. In other words, you don’t have to spawn any thread or implement java.lang.Runnable to be called back when the bundle starts or stops. But if that’s the case, why did we spawn a different thread to execute the logic of the RunnablePrinterClient class in ? Couldn’t we just have called the Printer service in a loop in the start method of the PrinterClientActivator class?

No, we couldn’t have. The OSGi framework invokes all of the BundleActivators sequentially and dictates that the BundleActivators must return quickly in their implementation of the start and stop methods. If we were to hold on to the start method, not only would our bundle not activate, but we could likely be holding on to other bundles waiting to be activated.

 

Tip

A BundleActivator implementation must return quickly in the implementation of its start and stop methods. As you’ll learn in later examples, spawning new threads can accomplish this.

 

To finalize, let’s archive PrinterClientActivator and the RunnablePrinterClient classes in version 1.1.0 of the helloworld.client bundle:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: helloworld.client
Bundle-Version: 1.1.0
Import-Package: manning.osgi.helloworld;version=1.4.0,
 org.osgi.framework
Bundle-Activator: manning.osgi.helloworld.client.PrinterClientActivator

Figure 2.9 shows the interaction among the Printer service, its implementation, its client, and the activators.

Figure 2.9. The PrinterActivator class registers the Printer and the PrinterImpl classes in the OSGi registry. The PrinterClientActivator class retrieves the Printer service and provides it to the RunnablePrinterClient, which invokes the Printer methods.

Now we’re finished with both the service provider and the service requestor, and we’re ready to test.

2.4.4. Running OSGi service-oriented applications

Starting fresh, let’s install and then start the bundles helloworld_1.4.0 and helloworld.client_1.1.0 in Felix.

The first test is to stop the bundle helloworld. When stopped, helloworld unregisters the Printer service. The OSGi framework then sends an unregistering service event to the bundle helloworld.client, which should then stop using the Printer service:

g! felix:install file:dist/helloworld_1.4.0.jar
Bundle ID: 63
g! felix:start 63
g! felix:install file:dist/helloworld.client_1.1.0.jar
Bundle ID: 64
g! felix:start 64
g! 11:06:38 PM PST: Hello...
11:06:40 PM PST: Hello...
g! felix:stop 64
g! 11:06:42 PM PST: Hello...
11:06:44 PM PST: Hello...

If you try this out, you’ll notice that nothing happens. You’ll still see the “Hello...” messages being printed out. Let’s uninstall the helloworld.client bundle. This surely will stop the messages from being printed:

g! felix:uninstall 64
g! 11:06:46 PM PST: Hello...
11:06:48 PM PST: Hello...

Guess again; nothing changed. OK, so let’s give it a final try. Let’s use the refresh command; this worked the last time, so it must work now.

g! felix:update
g! 11:06:50 PM PST: Hello...
11:06:52 PM PST: Hello...

The problem is a simple but crucial one. We forgot to stop the RunnablePrinterClient thread when the Printer service was unregistered. We’ll do so now in listing 2.10.

 

Tip

Remember to release all resources when a bundle is stopped. This is generally done in the implementation of the BundleActivator.stop method.

 

Listing 2.10. BundleActivator must release resources when stopped

 

Tip

Thread management is tricky in Java and particularly hard to get right when dealing with frameworks. In this example, no matter what we did, the JVM and OSGi weren’t able to stop a running thread. The only predictable way to stop a running thread in Java is to let its run method return.

 

We’re almost there. Fix the code and reinstall helloworld.client_1.1.0.jar in Felix. Start both helloworld_1.4.0.jar and helloworld.client_1.1.0.jar. Stop helloworld, and helloworld.client also ceases to print the “Hello...” message. Start helloworld, and helloworld.client resumes printing the message.

All seems fine. Let’s get back to our original problem. What we want is to change the implementation of the Printer service and have the Printer service client bundle pick up the new implementation without us having to do anything, such as refreshing or reinstalling the client bundle. Let’s see if we’re able to solve the problem. Uninstall helloworld. Change the SEPARATOR field of the PrinterImpl class:

private static final String SEPARATOR = "-> ";

Reinstall and start the helloworld bundle:

g! felix:install file:dist/helloworld_1.4.0.jar
Bundle ID: 70
g! felix:start 70
g! 11:06:54 PM PST: Hello...

And the problem is still there. But in this case, if you refresh the helloworld.client bundle, which is something you won’t like to do, you’ll get the right message.

The issue is that the PrinterImpl class is located in the same bundle as the Printer service interface. When you uninstall the PrinterImpl class, you’re also uninstalling the Printer class, which is used in the contract with the helloworld.client bundle. Because the packages can’t be changed without refreshing the bundle, what happens is that the OSGi framework keeps around an old version of the helloworld bundle associated with the helloworld.client bundle and will only pick up the new version of helloworld when the packages are resolved again. This behavior is vendor specific, which makes it an even bigger problem.

What you need to do to avoid all the confusion is to move the PrinterImpl class to another bundle separate from the bundle that includes the service interface. You do this by creating a third bundle named helloworld.impl_1.4.0.jar, which includes the PrinterImpl and PrinterActivator classes, as shown in figure 2.10.

Figure 2.10. Bundle helloworld version 1.4 includes the Printer class. Bundle helloworld.impl version 1.4 includes the PrinterActivator and PrinterImpl classes. Bundle helloworld.client version 1.1 includes the PrinterClientActivator and RunnablePrinterClient classes.

The bundle helloworld.impl needs to import the package manning.osgi.helloworld, but it doesn’t need to export any packages. The MANIFEST.MF file for helloworld.impl_1.4.0.jar is shown here:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: helloworld.impl
Bundle-Version: 1.4
Import-Package: manning.osgi.helloworld;version=1.4.0,org.osgi.framework
Bundle-Activator: manning.osgi.helloworld.impl.PrinterActivator

In the helloworld_1.4.0.jar bundle, we keep only the Printer class, and we no longer need to import the org.osgi.framework package:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: helloworld
Bundle-Version: 1.4
Export-Package: manning.osgi.helloworld;version=1.4.0

Let’s retest:

g! felix:lb
START LEVEL 1
   ID|State      |Level|Name
    0|Active     |    0|System Bundle (3.0.6)
    1|Active     |    1|Apache Felix Bundle Repository (1.6.2)
    2|Active     |    1|Apache Felix Gogo Command (0.6.1)
    3|Active     |    1|Apache Felix Gogo Runtime (0.6.1)
    4|Active     |    1|Apache Felix Gogo Shell (0.6.1)
g! felix:install file:dist/helloworld_1.4.0.jar
Bundle ID: 63
g! felix:start 63
g! felix:install file:dist/helloworld.impl_1.4.0.jar
Bundle ID: 64
g! felix:start 64
g! felix:install file:dist/helloworld.client_1.1.0.jar
Bundle ID: 65
g! felix:start 65
g! 11:06:38 PM PST: Hello...
11:06:40 PM PST: Hello...
g! felix:uninstall 64
g! felix:install file:dist/helloworld.impl_1.4.0.jar
Bundle ID: 66
g! felix:start 66
g! 11:06:42 PM PST-> Hello...
11:06:44 PM PST-> Hello...

Install bundle helloworld version 1.4, which contains the Printer service interface. Install bundle helloworld.impl version 1.4, which contains the Printer service implementation. Install bundle helloworld.client version 1.1, which contains the client that retrieves the Printer service and prints the “Hello ...” messages. Next, uninstall helloworld.impl and install a new implementation for the Printer service, replacing the SEPARATOR constant : with ->. The helloworld.client continues to function normally, but now it uses the new Printer service implementation. Congratulations, you changed the implementation of a running application!

This simple hello world application allowed us to investigate several important features from OSGi, ranging from versioning, to package restriction and accessibility, to service management. In the next section, we’ll discuss the role of these features in the overall architecture of the OSGi framework.

2.5. The OSGi layered architecture

The OSGi framework is implemented using a layered architecture, depicted in figure 2.11.

Figure 2.11. OSGi’s layered architecture is composed of four layers: the execution environment, the module layer, the lifecycle layer, and the service layer. In addition, all layers make use of security.

The bottom layer is the execution environment. Even though the OSGi framework is running on top of a JVM, the JVM itself can have different versions and profiles. For instance, there’s a Java Micro Edition of the JVM, which is named Java ME; there’s also a Java Standard Edition and a Java Enterprise Edition. On top of that, each edition may have several versions, such as Java SE 1.5 and 1.6. Because the developer can be using bundles provided by different vendors, it’s important that each bundle be annotated with the execution environment it supports, which is done though the manifest header Bundle-RequiredExecutionEnvironment. This allows the developer to select only bundles that are known to execute on the environment that the developer is using. For instance, if you’re running the OSGi framework on top of Java 1.3, then you don’t want to install a bundle that uses enumerations. Unfortunately, in practice, it’s seldom the case that people take the time to annotate the bundle’s execution environment.

The module layer takes care of OSGi’s modularity. The module layer manages the import and export of packages, through the use of a class loader delegation model, which is explained in chapter 4.

The lifecycle layer allows the developer to install, uninstall, start, stop, and update the bundles. This layer manages the bundle’s lifecycle.

The last layer is the service layer, which allows the user to register and retrieve service objects through the use of the OSGi service registry. All layers are secured by an extension to Java security architecture.

From an architectural perspective, we’ve touched on all of the main features of the OSGi framework. In the next chapter, we’ll look further into the details of the OSGi framework, but with an eye to techniques and OSGi features that are important for building complex full-fledged applications.

2.6. Summary

Bundles and services are the cornerstone of OSGi. Bundles help us achieve the principles of information hiding, whereas services allow us to create loosely coupled modules.

Bundles are Java modules. A bundle is a regular JAR file that contains OSGi-related manifest header entries. The Bundle-SymbolicName and Bundle-Version headers uniquely define a bundle.

Bundles are used to improve the modularity of an application. They do so by allowing the developer to control access to code. The manifest headers Import-Package and Export-Package are used for this purpose.

In addition, bundles can be versioned. Versioning a bundle allows the developer to change interfaces without impacting existing clients. Developers can version bundles by specifying a version tag in the packages referenced in the Import-Package and Export-Package manifest headers.

Services are regular Java objects that are managed individually by the OSGi service layer. A service has a service interface, a service implementation, and service properties.

Developers can use services to further decouple bundles that provide code to bundles that consume code. Using services, the developer can dynamically change a service implementation without impacting the bundles that use the service.

In the next chapter, we’ll employ what you’ve learned so far and author a full-fledged application using OSGi.