Chapter 4. In-depth look at bundles and services – OSGi in Depth

Chapter 4. In-depth look at bundles and services

 

This chapter covers

  • Keeping bundle packages private to decrease exposure
  • Making bundle packages optional to improve flexibility
  • The wiring API
  • Understanding class loading in a bundle
  • Advanced use of service filtering
  • Service prioritization
  • Service factories
  • Unit testing and integration testing for robustness

 

In chapter 3, we developed our first OSGi-based application employing two important OSGi features: bundles and services. As you’ve seen, bundles allow you to modularize your code, which lends to better reuse of code. Likewise, services improve the decoupling between the bundles, which yields a more resilient and flexible system architecture.

In this chapter we’ll take an in-depth look into several patterns and techniques that will help us develop better decoupled, flexible, and extensible bundles and services for our OSGi applications. We’ll start our discussion by looking into how to minimize the dependencies between the bundles we create and use.

4.1. Restricting a bundle’s export contract

In object-oriented programming, you learn that you should keep the signature of a class as restrictive as possible. This is done, for example, by first annotating a method of a class as private, and if that’s not possible, making it protected, and only if absolutely necessary making it public.

The same guideline applies to bundles. To begin with, make your bundle as restrictive as possible by not exporting any of its packages. If that’s not possible, the second step is to check to see if you can embed the dependency within the bundle itself; these are known as private packages.

4.1.1. Keeping packages private

If only a single bundle—the keyword being single—needs to import a class, you can embed the class, or library, in the consumer bundle itself and keep it private, rather than creating a new bundle that exports this class. This way the bundle is still able to use the class, but the class doesn’t need to be exported and thus made visible to any other bundle. Such a class is part of the bundle’s class path and is named the bundle’s private package.

For example, consider a bundle B1 that wants to use classes within the JAR file mylegacy.jar. First, archive the legacy JAR file within the bundle JAR file. The general convention is to place the legacy JAR under a lib directory. Thus, the contents of the bundle JAR file are

META-INF/MANIFEST.MF
lib/mylegacy.jar
manning/osgi/MyClass.class

Second, you need to use the manifest header Bundle-Classpath to specify the internal class path of the bundle. By default, it points to the dot (.), but in our case we need to add the lib directory to it:

Bundle-ClassPath: .,lib

In this example, the bundle’s class path comprises all resources (for example, classes, JAR files) at the root of the bundle’s JAR and within its lib directory.

 

Tip

You can load a bundle resource from its class path by using the method Bundle.getResource(String resourceName).

 

Next, we’ll consider the case where other bundles need to access the package.

4.1.2. Excluding classes from an exported package

If more than one bundle needs to use a class, then the proper solution is indeed to create a bundle and export it. But you don’t need to export the full package where the class resides; instead, you can pick and choose which classes are to be exported from a package. This is done using the include and exclude parameters of the Export-Package manifest header.

By default, the include parameter is set to *, meaning that all classes of the package are included, and the exclude parameter is set to an empty string, meaning that no class is excluded. Irrespective of which one is defined first, a class is exported only if it’s part of the include list and not part of the exclude list.

For example, if the package being exported has several classes, and you’d like to export only a single class, say class APublicClass, then the best approach is to use the include parameter:

Export-Package: manning.osgi.test; include:=APublicClass

Conversely, if you’d like to export all classes except for one, then it’s easier to use the exclude parameter:

Export-Package: manning.osgi.test; exclude:=APrivateClass

You can list several packages in the include and exclude parameters by using the comma character (,). For example, consider the following entry:

Export-Package: manning.osgi.test; include:="Foo*, Bar"; exclude:=FooImpl

As illustrated in figure 4.1, the exclude parameter takes precedence, hence the class FooImpl is excluded in spite of being included by the include parameter. The class BarImpl is also not included because it isn’t part of the include parameter.

Figure 4.1. Inclusion and exclusion of classes in an exported package

 

Export-Package syntax

Anytime a parameter, such as include, exclude, version, or uses for an OSGi entry (Export-Package), uses a comma (,) to enumerate its component values, you’ll need to specify this parameter in double quotes. This is necessary because the clauses of an OSGi entry already use commas to separate their values.

Consider the following OSGi entry:

Export-Package: manning.osgi.foo; include="Foo1, Foo2",
manning.osgi.bar; version="[1,2]"

In this example, note how a comma (,) is used to separate both the items Foo1 and Foo2 and the manning.osgi.bar item, hence the former need to be in quotes.

Note that the package specification (manning.osgi.foo) is separated from the include, exclude, version, and uses parameters with a semi-colon (;).

 

4.1.3. Avoiding split packages

As you’ve learned, the Import-Package and Export-Package manifest headers work (intuitively) at the granularity of Java packages. The Require-Bundle manifest header is another mechanism for establishing code dependency, but in this case it’s at the granularity of a bundle. A bundle that specifies Require-Bundle is able to see all the exported packages of the required bundle. In addition, the requiring bundle can even be configured to transitively inherit all of the imported packages of the required bundle.

For example, consider a bundle B2 that exports packages p and q and imports package r from bundle B3. Now, consider a bundle B1 with the following manifest:

Bundle-SymbolicName: B1
Require-Bundle: B2; visibility:=reexport

Notably, bundle B1 has no Import-Package header, only a Require-Bundle package with the visibility parameter set to reexport. As a result, bundle B1 is able to see packages p, q, and r, as shown in figure 4.2. If the visibility parameter is removed or set to private, then B1 sees only packages p and q.

Figure 4.2. Bundle B1 specifies bundle B2 as a required bundle. B1 imports B2’s exported packages and B2’s imported packages from bundle B3.

What did we gain by using the Require-Bundle header? We avoided having to explicitly list three separate packages to be imported. This is a good thing, right?

Well, not really. There’s a host of problems created because of Require-Bundle. The most serious issue is that when you use Require-Bundle, a package may be split between the requiring and the required bundles, as shown in figure 4.3.

Figure 4.3. Split package caused by Require-Bundle

This means that the same class could exist in both locations, which is very confusing, makes the ordering of Require-Bundle significant, and hinders the runtime performance of the OSGi framework.

 

Note

Split packages can only happen when the Require-Bundle header is used. None of the other mechanisms, such as fragment bundles, which you’ll see later, can cause split packages.

 

Another problem is that the requiring bundle is coupled with the symbolic name of the required bundle, which leads to a poor contract. The required bundle can be refactored and its packages changed, but this incompatibility doesn’t show in the contract. There are several other problems as well, such as class shadowing and unexpected signature changes.

What’s the solution? The solution is simple: don’t use the Require-Bundle header. You can restrict yourself to using only the Import-Package and Export-Package headers and still accomplish the same goals but without any of these mishaps. I’ve implemented a fair share of bundles and systems, and to this date I haven’t had to use Require-Bundle, so avoid it!

Now that you understand how to keep a bundle’s signature to a minimum, the next issue that the developer frequently runs into is dealing with Java reflection as a mechanism of creating generic code. We’ll tackle this issue in the next section.

4.2. Expanding a bundle’s export contract

We seldom work in isolation when developing new applications; it’s common that we need to integrate and communicate with legacy systems. One way of achieving this is by keeping the solution open and generic. In Java, this sometimes translates to the use of reflection.

Let’s consider the example of a federated database system. A federated database allows an application to write a single query that, in a transparent manner, may scope several distributed sources, some of which may be other databases.

One way of implementing this is to break the query into separate clauses that are individually forwarded to the different sources. For example, consider the following SQL query:

SELECT *
FROM Sale sale, Customer customer
WHERE sale.customer = customer.name
    AND sale.price > 100 AND customer.location = "CA"

This query finds all sale transactions that are higher than $100 and where the customer resides in California (CA). Now, consider that the Sale table is located in a Derby database running on one machine, and the Customer table is located in a MySQL database running on another machine. The goal is to have a client issue a single query to a central database, which is responsible for breaking it up, dispatching to the proper remote servers, and putting their responses back together into a single return to the client, as shown in figure 4.4.

Figure 4.4. The client issues a query to a federated database, which breaks it into separate clauses to be executed remotely in a Derby database and a MySQL database.

A federated database could take the following approach to servicing the query in the example. First, it retrieves all sale records from the remote Derby instance whose price is higher than $100. Second, it retrieves all customers residing in CA from the remote MySQL instance, and finally it locally joins together these records brought from the distributed sources.

If the source is another database, as it is in this example, then the federated database can communicate with the source database using JDBC.

 

Note

JDBC is a standard Java API for communicating with databases. If you haven’t used JDBC yet, don’t despair; we’ll cover JDBC in detail in chapter 7.

 

To load a JDBC driver manager, the common approach is to use reflection. For example, here’s the command to load the Apache Derby JDBC driver:

Class.forName("org.apache.derby.jdbc.EmbeddedDriver");

Let’s say you’d like to implement a federated database so that you can dynamically load new database sources into the federated database without having to shut it down. In other words, these are the requirements:

  • You must be able to dynamically install database drivers.
  • You must be able to dynamically start (for example, load) the drivers using some generic mechanism, such as reflection.

The first issue is easily addressed by using OSGi to dynamically install bundles. But can OSGi handle the second issue? The problem is that OSGi’s import and export package specification is static. At compilation time, the federated database OSGi bundle doesn’t know in advance which JDBC drivers need to be loaded; hence the bundle can’t be authored with the proper Import-Package manifest header that’s needed so that Class.forName would work. For example, to load the Derby driver, you need the following import to be present in the federated database bundle’s MANIFEST file:

Import-Package: org.apache.derby.jdbc

Does OSGi have a solution to this problem? Yes, through the use of dynamic imports.

4.2.1. Dynamic imports

The OSGi framework defines the manifest header DynamicImport-Package, which can be used to specify Java packages that can be dynamically searched at runtime to load Java classes. In our case, we should specify the following manifest header:

DynamicImport-Package: org.apache.derby.jdbc

The OSGi framework uses DynamicImport-Package as a last resort. OSGi first attempts to resolve a class through the normal means of wiring Import-Packages to Export-Packages. Only if the class isn’t found when needed will the OSGi framework search the packages specified in the DynamicImport-Package header. If the class is then found, its location is fixed and can’t be changed.

 

Note

You can use * when specifying dynamic imports, which makes the command very powerful. For example, DynamicImport-Package:org.apache.* imports all classes under the package org.apache.

 

DynamicImport-Package is convenient and flexible, so why shouldn’t you always use it? First, if you only use DynamicImport-Package, then you’d be by all practical means not really taking advantage of OSGi to achieve modularization. You’d lose the power of the contract established by Import-Package and Export-Package. Second, because DynamicImport-Package is searched when a class is needed, rather than when the bundle is resolved, there’s a higher performance cost and less-predictable behavior at runtime.

4.2.2. Optional packages

Consider a variation of the previous scenario; instead of arbitrarily loading any database source, let’s say that the federated database system supports only a set of well-known database sources, which may or may not be present at runtime. This is a more likely scenario, because to make sure that the federated database really works, it has to be tested with the drivers, so it’s unlikely that it would support an unknown and thus untested driver. But this list of supported drivers could be extensive, numbering in the dozens, so it’s also likely that in a particular instance of the environment, only a subset of these would be present.

The OSGi framework has a different and somewhat better solution for this case. In OSGi, you can specify a package that’s being imported as optional. This means that the framework will attempt to wire this class during the resolve process, as the bundle is being installed, but it won’t consider it an error if the package isn’t found. If some bundle is exporting the package, then it will be imported normally; if no exported package is found, then the import of the package is ignored.

This can be done in the following manner in our case:

Import-Package: org.apache.derby.jdbc;resolution:=optional

What happens in the case where the package isn’t imported, and the bundle still tries to use it? As expected, the bundle will get a NoClassDefFoundError or a ClassNotFoundException. This means that anytime you define a package as optional, you have to be prepared to handle these situations. In the case of our federated database system, we could simply ignore those drivers that aren’t found at runtime by doing the following:

try {
    Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
} catch (ClassNotFoundException e) {
    // log
}

Let’s take a step back and consider the overall solution we have so far, as shown in figure 4.5. We, the framework developers, are providing a federated database bundle, which through either optional packages or dynamic imports can generically load different drivers. The database vendors, such as Derby or MySQL, provide their own drivers, which are wrapped as OSGi bundles and export their respective driver manager classes. The remaining role is that of the federated database client, who has to develop an OSGi application that uses the federated database bundle, for example, by issuing the SQL query that’s to be distributed.

Figure 4.5. The client provides an application bundle, the framework developer provides a federated database bundle, and the database vendors provide the driver bundles.

If the database vendor has to provide a new bundle containing the driver, couldn’t the vendor also somehow extend the behavior of the federated database bundle in such a way that it’s able to see the driver? Yes, OSGi allows this to be done using fragment bundles.

4.2.3. Fragment bundles

Fragment bundles are degenerated bundles that need to attach to a host bundle in order to function. A fragment bundle doesn’t work by itself; it logically merges with its host bundle as if both bundles were a single bundle and, most important, with a single class loader.

For example, we could have each driver vendor provide a fragment bundle that attaches to the federated database bundle and in doing so augments the federated database bundle’s internal class path, as shown in figure 4.6. Therefore, the federated database bundle would have visibility to the driver’s class and would be able to do a Class.forName() without having to import its package.

Figure 4.6. Database vendors provide driver bundle fragments, which attach to the federated database bundle.

 

Warning

At runtime, the OSGi framework first searches the class path of the host bundle and only then searches that of its fragment bundles, in ascending order of bundle IDs.

 

To do this, each vendor fragment bundle must add the following manifest header that references the federated database bundle:

Fragment-Host: manning.osgi.federated-database

In this case, we assume that the symbolic name of the federated database bundle is manning.osgi.federated-database. Also, we assume that the driver manager class is part of the content of the driver’s bundle.

 

Warning

A fragment bundle can’t specify a bundle activator class.

 

At face value, fragment bundles would seem like the best approach we’ve investigated so far for solving our problem of implementing a generic bundle, but they also have their problems and limitations. Fragment bundles must be installed before their host bundle; in other words, a host bundle after it’s resolved ignores any other fragment bundles. In our example, this means that we wouldn’t be able to dynamically load new drivers after the system is started. The other drawback is that fragment bundles create a tight coupling with their host bundles. Again, citing our example, the driver fragment bundle and the federated database bundle share an implicit contract that a driver manager class is contained within the fragment.

Nonetheless, fragment bundles are well suited for cases when the fragment complements a well-established aspect of the host bundles, and their assembly can be done ahead of time. Localization is the perfect case. For example, you could place the localization files for English in one fragment, for French in another fragment, and so on. Then you could install the internationalized host bundle and the localized fragment bundles all together, and the host bundle would be able to see all the localization files and select the proper one to use. In this scenario, it’s unlikely that you’d need to support a new localization without being able to redeploy the host bundle or restart the platform.

 

Augmenting a bundle’s manifest

A fragment bundle can not only augment the internal class path of its host bundle but also extend its manifest header entries. For example, the fragment bundle can specify an Import-Package header, which is associated with the host bundle, thus increasing the number of packages being imported.

 

The use of reflection to dynamically load features is a common pattern for the development of enterprise applications. We’ve investigated three approaches for accomplishing this in OSGi. These are summarized in table 4.1.

Table 4.1. Different approaches to supporting generic bundles in OSGi

Approach

When

Advantage

Disadvantage

Dynamic imports After resolve, at runtime Most dynamic Decreases modularity, predictability
Optional packages During package resolve Similar to standard Import-Package Optional packages must be known at design time
Fragment bundles During package resolve of host bundle Allows host bundle’s class path to be augmented with new resources Coupling of fragment and host bundles

Reflection is a great tool, but it comes with a price: applications that use reflection have a greater chance of causing class-loading-related exceptions, like the ClassNotFoundException. Later on in this chapter, we’ll take a look at several approaches for avoiding this type of problem. But before we do that, let’s take a final look at the concept of a bundle’s exported and imported packages.

4.3. Packages as requirements and capabilities

As you’ve seen, a bundle’s exported packages define the bundle’s modularization signature; another way of looking at this is to consider the exported packages as the capabilities of a bundle and the imported packages as the requirements of a bundle.

 

Sneak Preview

The wiring API is new in version 4.3, and at the time of this writing there were no existing framework implementations for it. Nonetheless, this is a central change in the specification, so we’ll look at the basic concepts here.

 

Version 4.3 of the OSGi specification takes this approach and provides a generic mechanism—the org.osgi.framework.wiring API—for specifying a bundle’s capabilities and requirements. This mechanism is then used to represent the relationship of a bundle’s exported and imported packages. Let’s try to understand this API by looking at simple example. Consider a provider bundle and requirer bundle, each respectively defining the following MANIFEST files:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: provider
Bundle-Version: 1.0.0
Export-Package: manning.osgi.mypackage;version=1.0.0

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: requirer
Bundle-Version: 1.0.0
Import-Package: manning.osgi.mypackage;version=1.0.0

In this case, the provider bundle is declaring its capability of being a provider of the manning.osgi.mypackage package. This capability declaration can be introspected using the BundleRevision API as follows.

Listing 4.1. Retrieving a bundle’s declared capabilities

Version 4.3 of the specification introduces a Bundle.adapt() method , which allows you to adapt a bundle object to a BundleRevision. The BundleRevision object represents the latest revision of the bundle and contains its declared metadata, such as its capabilities. The capabilities are grouped into namespaces. In particular, you can retrieve the export package capability by specifying the PACKAGE_NAMESPACE . Finally, a capability is organized as a set of attributes and directives, corresponding to the package name and version attributes and the uses and resolution directives.

Next, let’s retrieve the declared requirements for the requirer bundle:

Bundle bundle =
    bundleContext.getBundle();

BundleRevision revision =
    bundle.adapt(BundleRevision.class);

List<BundleRequirement> requirements =
    revision.getDeclaredRequirements(BundleRevision.PACKAGE_NAMESPACE);

for (BundleRequirement requirement : requirements) {
    Map<String, Object> attributes = requirement.getAttributes();
    System.out.println("Package name = " +
        attributes.get("osgi.wiring.package"));
    System.out.println("Package version = " + attributes.get("version"));
}

In this case, the only difference is that you retrieve a list of requirements.

The OSGi framework defines not only the package namespace but also a bundle namespace to manage bundle-related metadata, such as a bundle’s symbolic name, and a host namespace, which takes care of fragments. But most important, it also allows you to define your own namespace to manage user-defined resources within a bundle. For example, in the federated database scenario, you could create a schema namespace, which an application bundle would use to indicate the table schema it either defines or depends on. Later, in subsequent chapters, we’ll explore this further and look at an example where you define your own namespace to manage the dependencies between remote bundles.

One other noteworthy feature of the wiring API is that it also allows you to introspect the runtime live association between the requirements and capabilities. For example, should you install both the provider and requirer bundles, during their resolve process a wire would be created between these two bundles, indicating that the provider’s declared capability has been resolved and therefore wired to the requirer’s declared requirements. The following listing illustrates how to navigate this relationship.

Listing 4.2. Navigating a bundle’s wire

Starting from the provider bundle, you retrieve its latest revision and then retrieve its bundle wiring . Bundle wiring holds the runtime live wires that connect to other bundle wirings. Using the wiring, you retrieve all the provided wires for the package namespace . Then you traverse the wire by retrieving the requirer endpoint , which can be used to retrieve the requirer bundle.

The wiring API is powerful. As you’ve seen, it provides a BundleRevision object for the current bundle. Furthermore, the framework also keeps a BundleRevision object for each previous revision of a bundle that hasn’t been refreshed yet, that is, for revisions that are waiting to be removed. This information allows a user to consider among other things the cost of a bundle update.

Now that you understand how to declare and use a bundle’s package, let’s look at how to avoid runtime problems because of class loading.

4.4. Avoiding the dreaded class-hell problem

The OSGi framework achieves much of its power through its class-loading architecture, which is more complex than the usual class loading of a standard Java application. Thus, it should come as no surprise that class-loading problems, popularly known as class hell, can be more prevalent. In the next few sections, we’ll go through some of the possible problems you may run into and see how they can be addressed.

4.4.1. Don’t forget to import the package!

The first problem you’re likely to run into is a ClassNotFoundException as a result of having forgotten to import its package using the Import-Package header. This is the most common problem you’ll run into in OSGi. The good news is that it’s easily spotted and corrected. But can you avoid it? Yes, you could automate the generation of the MANIFEST file of a bundle. The tool would have to introspect the Java code within the bundle, checking for all the import class statements. Then, the tool would need to generate an Import-Package for each package whose class is being imported.

This is what Peter Kriens does with his popular bnd tool, which can be found at http://www.aqute.biz/Code/Bnd. You can use his tool, which has several interesting configurations, or create your own custom tool for the job.

4.4.2. Keeping class space consistency

Being able to create a system that relies on multiple bundles is beneficial, because it means that you’re maximizing reuse. Yet, there’s one negative side effect: it increases the likelihood of creating class space inconsistency between the bundles. Class space inconsistency has the annoying attribute of causing seemly random class-cast exceptions at runtime. To understand the reason for this, we must first look into the class-loading architecture in Java.

In Java, a class isn’t really defined by its full class name, such as frameworks.Test-Class, as intuitively expected, but rather by the combination of its full class name and the ClassLoader instance that loaded the class.

What this means is that class A loaded by class loader L1, henceforth identified as L1.A, and the same class loaded by class loader L2 are considered different classes at runtime by the JVM.

Object instanceOfClassA =
Class.forName("A", true, firstClassLoader);
Object anotherInstanceOfClassA =
Class.forName("A", true, secondClassLoader);
assert !instanceOfClassA.getClass().equals(
anotherInstanceOfClassA.getClass());

Thus, if you try to cast L1.A to L2.A or vice versa, you’ll get a cast exception. This can be quite tricky to debug, because it wasn’t caused by an application logic error or by a mistake in some algorithm, but rather it’s an infrastructure error.

The good news is that in a standard Java application you’re unlikely to run into this type of class cast exception because by default the class-loader architecture is simple. There’s a system class loader, which loads the JRE classes, and it has a child class loader, which is used to load the application classes, as shown in figure 4.7. The child class loader inherits all classes from its parent class loader, avoiding the problem of the same class being loaded by different loaders.

Figure 4.7. Hierarchical class-loader architecture for standard Java applications

Things are less simple in OSGi. As you’ve seen, each OSGi bundle has a unique visibility of the classes it can use, which is determined by the Import-Package specifications in its MANIFEST.MF file. This scoping of classes can be achieved by each bundle having its own class loader, which delegates to the class loader of the bundle whose packages it’s importing. Because each bundle may import packages from different bundles, which may also then import from other bundles, the class-loading delegation mechanism forms a network (graph) of connections. In other words, whereas a regular Java application has a hierarchical class-loader architecture, OSGi uses a network-based class-loader architecture, as shown in figure 4.8.

Figure 4.8. Delegation network class-loader architecture for OSGi

Summing it up, there’s an abundance of class loaders in OSGi, which means that the likelihood of a class being loaded by different class loaders has greatly increased.

 

Note

The OSGi delegation network class-loader architecture even allows for cycles. This cyclic behavior surfaced a race condition in several JVM implementations in JSE 6.0, which is addressed in the next release of JSE.

 

You may ask, “What if I just avoid casting altogether? Would this solve the problem?” Unfortunately, the story gets a bit more complicated. When a bundle B1 imports a class MyClass, the bundle needs to know the complete signature of the class, which includes the types it extends and the types of the parameters of its methods, as shown in figure 4.9.

Figure 4.9. Bundle child_1.0.0 imports class Parent from bundle parent_1.0.0, and bundle client_1.0.0 imports class Child from bundle child_1.0.0.

 

Tip

This is another reason why it’s so important to export interfaces instead of classes. An interface has a much smaller “surface area” for dependencies.

 

For example, if bundle client_1.0.0 imports class Child, which extends class Parent, then bundle client_1.0.0 must not only load class Child but also load class Parent. Well, class Parent is similar to any other class, and we need to find the bundle that’s exporting it.

So far, so good. Now consider that bundle child_1.0.0 is exporting class Child and importing class Parent from bundle parent_1.0.0. You’ve learned that each bundle has a different class loader, so this means that bundle child_1.0.0 uses Parent from class loader parent_1.0.0. Here’s where we get into trouble. Assume that bundle parent_2.0.0 exports a different version of class Parent. Also assume that bundle client_1.0.0, in addition to importing the class Child from bundle child_1.0.0, is also importing Parent from bundle parent_2.0.0. In this scenario, bundle client_1.0.0 is using a different class loader for class Parent. When bundle client_1.0.0 gets hold of the class Child, it will also see the class Parent owned by the class loader of parent_1.0.0, which it may try to cast to Parent as if it were owned by the class loader of parent_2.0.0, and, alas, will find it incompatible! See figure 4.10.

Figure 4.10. Bundle client_1.0.0 gets the same class Parent from two different bundles and thus likely causes an exception.

Let’s take a closer look at the source code and MANIFEST files for this scenario. First, let’s consider version 1 of the Parent class and its corresponding MANIFEST file, as shown in the following listing.

Listing 4.3. Parent class version 1 and its MANIFEST
package manning.osgi.parent;

public class Parent {

    public Parent() {
        System.out.println("Parent (v1)");
    }
}
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: parent
Bundle-Version: 1.0.0
Export-Package: manning.osgi.parent;version=1.0.0

The class is simple enough; its main purpose is to allow us to understand OSGi’s class-loading implications. In the next listing is version 2 of this same class and its MANIFEST file.

Listing 4.4. Parent class version 2 and its MANIFEST
package manning.osgi.parent;

public class Parent {

    public Parent() {
        System.out.println("Parent (v2)");
    }

    public void newMethod() {
        System.out.println("new method");
    }
}


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

Now that you’ve seen the parent classes, let’s look at the Child and Client classes and their MANIFEST files, as shown in the following listings.

Listing 4.5. Child class and its MANIFEST

Notably, this version of the Child class imports version 1 of the Parent class . The next listing shows the second version of the Client class.

Listing 4.6. Client class and its MANIFEST

Version 2 of the Client class imports version 2 of the Parent class . There is a clear indication that something is amiss, because you’re invoking the method newMethod , which you know isn’t present in the class Child. Nonetheless, compilation will go through successfully because it assumes you’re using version 2 of Parent.

When you install and start these bundles in Felix, you’ll get the following exception:

java.lang.VerifyError: (class: frameworks/client/ClientBundleActivator,
  method: start signature: (Lorg/osgi/framework/BundleContext;)V)
 Incompatible object argument for function call

To say the least, this can be brutal. This is a simple example and the error message wasn’t too enigmatic, but how would you be able to handle the case when there are dozens or hundreds of bundles? It would be hard to find the closure of their dependencies.

Fortunately, OSGi has a simple solution to this problem. The idea is to specify which packages are being used by the package that’s being exported and thus group them together. This is done with the uses parameter. In this case, only the child bundle needs to be corrected:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: child

Bundle-Version: 1.0.0
Import-Package: manning.osgi.parent;version="[1,1]"
Export-Package: manning.osgi.child;version=1.0.0;uses:= manning.osgi.parent

 

Tip

Note how the uses fragment is followed by := instead of just = as the version parameter.

 

Reinstall this bundle, refresh, and restart the client bundle. Now you’ll get a different error message:

org.osgi.framework.BundleException: Unable to resolve due to constraint
violation.

This looks a little better. The OSGi framework is telling you that the client bundle can’t be resolved because it has a constraint that can’t be fulfilled. In other words, the child bundle, through the uses parameter, is stating that if a bundle imports its exported package of manning.osgi.child, this bundle must also import version 1 of the package manning.osgi.parent. The client bundle doesn’t fulfill this constraint, hence the OSGi framework exception.

Ultimately, to make this example work, you either have to change the client bundle to import and use version 1 of the package manning.osgi.parent or change the child bundle to import version 2 of this same package.

 

Method implementation

As you’ve seen, when you import a class, you also need to import all classes referenced by any public signatures of the imported class. Why is it that you don’t need to import the classes used in the implementation of the methods of the imported class?

The reason you don’t is that when you instantiate class B within a method of class A, it’s equivalent to loading class B using the class loader of class A, so the importing bundle doesn’t need to use its own class loader for the task.

class A {
    void method() {
        new B();
    }
}

is equivalent to

class A {
    void method() {
    try {
        A.class.getClassLoader().
            loadClass(B.class.getName());
    } catch (ClassNotFoundException e) {
            // ...
        }
    }
}

 

4.4.3. Package export race condition

This last problem isn’t related to class loading but rather it’s a race condition between bundles, which can be just as painful to debug.

Consider a bundle that initializes some static field during its startup—when the framework invokes the bundle’s activator start method. Furthermore, the class that provides access to the static field is being exported through an Export-Package header.

A bundle that imports this class through an Import-Package header and accesses the static field may get an uninitialized value. The reason for this is that the Export-Package takes effect immediately after the bundle is resolved but before the bundle has been started.

The lesson here is to be careful when exporting a package and not assume that the bundle activator will be executed before the package is made available to clients. And yes, please avoid static members.

Having looked at several class path–related issues, we’ll look next at OSGi’s class-loading architecture itself.

4.5. Understanding OSGi’s class loading

So far you’ve learned different mechanisms for importing a class in OSGi. A bundle may import the class from another bundle by using the Import-Package header or the DynamicImport-Package header; a bundle may also rely on its own class path (a private package) or use fragments. This process of searching for a class, or more precisely for any resource, as you’ve probably guessed, isn’t a simple one.

First, it’s useful to model the entire searchable space of a bundle as a four-layer architecture, as shown in figure 4.11. At the top level is the boot class loader, which is the parent class loader for all the bundles’ class loaders. Below it are all the packages that are imported from a separate bundle. Next is the bundle’s own internal class path, as specified by the Bundle-Classpath header. And finally comes the class path of any attached fragments.

Figure 4.11. Class space for a bundle in order of precedence, starting at the top

This model provides a rather simple but mostly accurate class space for a bundle, where the top layers have precedence over the lower layers. When a bundle attempts to load a class (or resource), the OSGi framework uses the following algorithm:

1.  If the class is in the java.* package, then the OSGi framework searches for the class definition at the top layer, that is, at the boot class loader. If it isn’t found, then the process fails.

2.  Next, the framework checks to see if the class is in a package that’s being imported using the Import-Package header. If it is, then there must be some bundle that’s exporting this package, in which case the importer and exporter are wired together and the process stops. If no exporter is found, the process fails. If the class isn’t in a package that’s being imported using the Import-Package header, then the framework continues to the next step.

3.  Next, the framework searches for the class in the bundle’s own internal class path.

4.  If the class is not found, the framework searches for the class in the attached fragment bundles in ascending order of bundle IDs.

5.  If the class is still not found, the framework checks to see if a DynamicImport-Package header is defined and it may then attempt to dynamically load the class. If it succeeds, the class is wired to the exporter, and this wiring isn’t changed afterward.

I’ve omitted two details in this algorithm. First, I’ve assumed that the Require-Bundle header isn’t being used. As explained in section 4.2.3, Require-Bundle should be avoided, and you simplify the process by omitting it. Second, I’ve omitted the parent delegation list. In reality, step 1 should also check to see if the class is in a package that’s included in the parent delegation list and should stop if it’s found or continue to step 2 if it’s not found.

 

Parent delegation

Standard Java applications have a hierarchical class-loader architecture, which delegates to the parent class loader first. It’s sometimes necessary in OSGi to explicitly force certain classes to be delegated to the boot (parent) class loader. This can be done using the system property org.osgi.framework.bootdelegation.

For example, the following configuration forces the OSGi framework to always delegate the loading of all sun.* classes to the parent class loader:

org.osgi.framework.bootdelegation=sun.*

 

What happens if a bundle has a package within its class path and also tries to import it using an Import-Package header? Figure 4.7 tells us that Import-Package has precedence; hence, an exporter for the package must be present. If no exporter is found, then the resolve fails, in spite of the class being present in the bundle’s class path. If you consider that the class in the private package has no version, this makes sense, because the importer always needs a versioned package.

Let’s try a harder question: what if a bundle imports and exports the same package? In this case, the resolve process is successful because the bundle ends up importing the class from its own exporter.

By the way, why would someone import and export the same package? Although it may seem counterintuitive, this is a useful pattern in OSGi. This pattern allows the bundle to first try to find a package that’s being provided by some other bundle, and if no exporter is found, it falls back to its own version of the package within its class path.

 

Tip

Generally, it’s a good idea to always import a package that you’re exporting. Although not necessary, because the class is found in the private package, it makes for more flexible bundles.

 

You’ve learned how to develop robust bundles, minimizing their exposure. Next, we’ll look at how to further improve our architecture by making good use of OSGi services.

4.6. Decoupling bundles using services

One of the intrinsic qualities of easy-to-maintain applications is that you can change the implementation of a particular application’s features without impacting the other features and subsystems that together form the application. This is in great part achieved by the correct use of the OSGi service layer. Clients must interact solely through defined service interfaces, thus allowing the application framework to swap and improve service implementations transparently. In chapter 2, we changed the implementation of the printer service without impacting existing clients. In chapter 3, we went through another more elaborate example, where buyers and sellers who participate in auctions are shielded from the details of the auction and auditor policies being used.

Albeit being simple and intuitive, this is the most important pattern to be followed when building applications using OSGi. Strive to keep interactions between bundles strictly through service interfaces, as shown in figure 4.12. Your bundle shouldn’t import packages that contain implementation classes from other bundles. Even more to the point, you should generally import only Java interfaces and not any Java classes, and you should likewise avoid exporting packages that contain anything but Java interfaces.

Figure 4.12. Interaction between bundles, particularly bundles that reside at different layers, should be done through service interfaces. Bundles can provide different service implementations transparently to the client bundle.

As you start using services more frequently, there are other important aspects that you need to learn; namely, how to author advanced service filters, how to prioritize a service, how to uniquely identify a service, and the concept of service factories. Let’s begin by tackling services filters.

4.6.1. Advanced service filtering

If there’s one feature that the OSGi developer must master, it’s the authoring of service reference filters. As you’ve seen, service reference filters allow bundles to find services provided by other bundles through a declarative mechanism, yielding more robust and flexible systems. In section 3.3.1, we skimmed over this. Let’s go over filters now in detail.

Instead of providing a BNF (Backus-Naur Form) to describe the syntax used for specifying filters, we’ll use a series of parse trees, each one describing a useful instance of a filter scenario. Collectively these should allow you to understand fully the “language” of OSGi filters.

An OSGi filter is a collection of operations. An operation is defined by the following three elements: attribute, comparison operator, and value. An attribute is a string representing a service property key. The comparison operators are equals (=), greater than or equals (>=), less than or equals (<=), approximate (~=), and exists (=*). A value is a string representing a service property value.

Comparison Operators

The simplest case of an OSGi service filter is an equality comparison of an attribute to a value, such as "(attribute=value)", as shown in figure 4.13.

Figure 4.13. Parse tree for the filter expression "(attribute=value)"

Attributes aren’t case sensitive and may contain white spaces, but the leading and trailing spaces are stripped during comparison. Conversely, values are case sensitive, and leading and trailing white spaces aren’t stripped.

For example, the following expressions are similar:

" myAttr =myvalue"
"myattr=myvalue"

But that’s not the case of the following expressions, none of which match:

"my attr=myvalue"
"myattr=myvalue"
"myattr= myvalue"

As you’ve seen, the exact semantic of the comparison operators depends on the type of the value. Here are the rules:

  • The equals (=) operator maps to Object.equal if the service property value is a Java object instance that can be instantiated using a constructor that takes a single String object as its argument. In other words, this is applicable to all primitive type wrappers, such as Integer, Char, and Long. If the value is a collection or an array of objects, then the comparison is true if at least one of the elements of the collection or of the array compares successfully with the rules outlined here. For example, the service property value [a,b,c] representing an array of characters matches with the following service property values:

    "a"

    "b"

    But it doesn’t match with

    "ab"


    Collection matches

    Doesn’t it strike you as odd that the OSGi filter matches even if only one of the values of a collection or array matches, instead of all the values? The reason for this seemingly counterintuitive rule is that this approach is consistent with the handling of OSGi service interfaces. Remember that a service interface is actually an array of Java classes, and that you need to match with only one of them to be able to retrieve the service object and cast it to the matched Java class. This makes even more sense if you consider that a service interface is really a service property whose key is objectClass and whose value is an array of Strings.


  • The exists (=*) operator returns true if the service property key is present in the service reference, so in this case the value and its type are irrelevant.
  • If the value is of type String, or a collection or array of Strings, you can use wildcards to perform substring equality matches. This is done using the * character, and it can be used multiple times in the value. For example, the value *a*b*c matches with the following service property values:
    "aabbcc"
    "1a2b3c"
    But this same value of *a*b*c doesn’t match with the following property values:
    "abc"
    "abbccd"
  • The operators >= and <= use the same rules as the equals operator, except that they map to Comparable.compareTo(). In other words, the value must be a Java object that implements Comparable and can be constructed from a String.
  • The approximate (~=) operator is implementation-specific, but it must at least ignore case and white space differences in the value.
Expression Operators

The OSGi filter operations themselves can be combined with three logical operators: the conjunction (and) operator (&), the disjunction (or) operator (|), and the negation (not) operator (!). The first two operators, contrary to the norm, aren’t exclusively binary and are specified in prefix form, so you can associate any number of operations with them. The last operator is unary_as expected; that is, it modifies a single operation.

The parse tree shown in figure 4.14 describes an OSGi filter that verifies if all the conditions are met.

Figure 4.14. Parse tree for the filter expression "(&(attribute=value)(attribute=value))"

You can associate several operations together using a single expression operator. For example, consider the following Java expression:

if ((attr1 == value1) && (attr2 == value2) && (attr3 == value3)) { ... }

This can be expressed using the following OSGi filter:

(&(attr1=value1)(attr2=value2)(attr3=value3))

Also, the slightly more complicated Java expression,

if ((attr1 == value1) && ((attr2 == value2) || (attr3 == value3))) { ... }

can be expressed using the following OSGi filter:

(&(attr1=value1)(|(attr2=value2)(attr3=value3)))

So there you have it; you’re now an expert on the OSGi filter syntax. Don’t feel bad though if you still prefer the infix style, such as that used in Java, instead of the prefix style used here. You’re not alone.

In the next section, we’ll look at how to distinguish services that share the same interface.

4.6.2. Prioritizing services

Sometimes there’s a need to prioritize which OSGi service is retrieved from the OSGi registry. Consider the scenario where a user wants to check the integrity of messages being exchanged with other parties. A user can do this by fingerprinting, or hashing, the message.

Hash functions used for this purpose have the property that no pair of input messages that differ will have the same hash value. This means that a user can send a message and its hash value to a receiver, which can then apply the same hash function to the received message. The generated hash value will match the received hash value only if the message has not been altered.

We can define our fingerprint service as follows:

public interface FingerprintService {

    byte [] hash(String input);

}

There are several implementations of hash functions; in particular, there are the MD4 and MD5 algorithms, both invented by Rivest. MD4 is quicker, but there are known attacks for it, whereas MD5 is slower but safer. Most users don’t really care about these differences; they just want to get some default implementation, which should probably be MD5. When dealing with security, we always want to be conservative. But other users who may be more educated on the subject would like to pick a particular algorithm. How do you solve this problem in the OSGi framework?

First of all, you should register these two different service implementations, providing some additional service property that highlights their differences. For example, you can define a PERFORMANCE property whose value is set to SLOW and FAST respectively for the MD4 and MD5 service implementations:

Dictionary<String, Object> md4Properties =
    new Hashtable<String, Object>();

md4Properties.put("PERFORMANCE", "FAST");

registration = bundleContext.registerService(
        FingerprintService.class.getName(),
        MD4FingerprintServiceImpl,
        md4Properties
);

A user who wants the FAST fingerprint service indicates so by setting the PERFORMANCE property to FAST when retrieving the service from the OSGi service registry:

String filter =
    "(&(objectClass=" + FingerprintService.class.getName() + ")" +
    "(PERFORMANCE=FAST))";

ServiceReference[] serviceReferences =
    bundleContext.getServiceReferences(null, filter);

But in the typical case, where the user doesn’t care to specify the PERFORMANCE property, which service would you get? You could get either; it’s largely nondeterministic which service the framework would hand back to you.

In this case, you want to avoid this nondeterminism; you’d like the MD5 service implementation to have priority over the MD4 implementation. This can be done through the use of the predefined SERVICE_RANKING property, whose value is of type Integer. When multiple qualifying service implementations exist, the OSGi framework selects the one with the highest SERVICE_RANKING property, as shown in the following listing.

Listing 4.7. Service rankings for the fingerprint service

The MD5 service implementation has a service ranking of 10 , whereas the MD4 service implementation has a ranking of 5 . Thus the OSGi framework selects the MD5 service if no other service property is specified to further differentiate the services.

 

Note

In this example, if you specify any other service property, such as the PERFORMANCE, ALGORITHM, or SECURITY_LEVEL, then the ranking won’t be used because the additional service property alone is able to differentiate between the MD4 and MD5 implementations.

 

Next, let’s look at when and how you can uniquely identify a service, even after the OSGi framework is restarted.

4.6.3. Uniquely identifying services

There are several cases where it’s useful to uniquely identify a service. One such a case is when the service represents some external entity. For example, a service may represent a machine in the network, as shown in figure 4.15. Generally, networked machines are referenced through their TCP/IP addresses. Wouldn’t it be ideal if you could use the IP address as the service identification? By doing so, you’d allow a client bundle to retrieve the service that represents the machine by using its IP address, which is generally a well-known and understood concept. Can you solve this problem of creating identifiers for services in the OSGi framework?

Figure 4.15. A service representing a single external entity

In the previous examples, you may have noticed that all services have a special service property called SERVICE_ID. The OSGi framework sets this service property automatically when a service implementation is registered. The value of the SERVICE_ID property is transient; the framework sets a new value every time the service or the framework is restarted. The only guarantee is that for an instance of the framework, that is, for each launch of the framework, no two services have the same SERVICE_ID.

Could you associate the IP address to the SERVICE_ID property? No, you can’t. Not only is the OSGi framework responsible for assigning a value to the SERVICE_ID property, but it changes at every restart of the framework.

Instead, the OSGi framework defines another built-in property: the SERVICE_PID. You can assign your own value to the SERVICE_PID property, providing it’s kept unique and persistent. This means that you should only register a single service with the same SERVICE_PID, and you must keep using the same value across restarts of this service. Client bundles are then guaranteed that they’re always getting the same service.

 

Bundle identification

The SERVICE_PID property uniquely and durably identifies a service. What’s the equivalent for a bundle?

The most obvious persistent identification for a bundle is the combination of a bundle’s symbolic name (Bundle-SymbolicName) and its bundle version (Bundle-Version). This pair not only is unique within an OSGi framework instance but also will survive restarts of the platform. But it’s too long an identification to be used effectively in day-to-day operations such as installs, updates, and uninstalls. Therefore, there’s also the bundle identification.

The bundle identification (or bundle ID) is the long value returned when you invoke an install command in the Gogo shell. As you recall, this ID can later be used to identify a bundle through its lifecycle. The bundle ID is guaranteed to be unique and likewise is the same if you restart the OSGi framework and don’t change the current bundles installed. In other words, it’s dependent on the order of installation of the bundles. This means that if you uninstall a bundle and reinstall it, the bundle ID will change. This is different from the bundle’s symbolic name, which wouldn’t necessarily change after a reinstall.

Finally, there’s a third option, which is the bundle’s location. The bundle location is a name assigned to a bundle during its installation. It is generally a URL to the bundle’s JAR file, but it could be anything. The bundle location is the identification of choice for operators, because they generally wouldn’t have access to a bundle’s symbolic name and most likely do know the location of the bundle’s JAR file to be installed. The bundle location is unique and persists across OSGi framework restarts.

In summary, these are the three options for identifying a bundle and the roles for which they’re most adequate:

  • Bundle symbolic name, bundle version (used by application developers)
  • Bundle identifier (shorthand for operators)
  • Bundle location (initial handle for operators)

 

In our example, the provider registers the service by setting the SERVICE_PID property:

Dictionary<String, Object> properties =
        new Hashtable<String, Object>();

properties.put(Constants.SERVICE_PID, "10.0.0.1");

registration = bundleContext.registerService(
        NetworkMachine.class.getName(),
        machineImpl,
        properties);

Then the client bundle specifies the IP value of the machine it wants to manage:

String filter =
    "(Constants.SERVICE_PID + "=10.0.0.1)";

ServiceReference[] serviceReferences =
    bundleContext.getServiceReferences(null, filter);

 

SERVICE_PID

Because the SERVICE_PID is enough to identify a service, you don’t even need to specify the service interface in this case. But if you’re going to eventually cast the service object to its service interface, it’s generally a good practice to specify it as the first argument to getServiceReferences():

String filter = "(" + Constants.SERVICE_PID + "=10.0.0.1)";
ServiceReference[] serviceReferences =
    bundleContext.getServiceReferences(
    NetworkMachineService.class.getName(), filter);
    NetworkMachineService service =
    (NetworkMachineService)
    bundleContext.getService(serviceReferences[0]);

 

Why not just define your own service property called MACHINE_IP and use this property instead of the predefined SERVICE_PID? There are two reasons for this. The first is that by defining a fixed property, the framework can make sure that all of its invariants are kept, that is, that no two services have the same SERVICE_PID and that it’s persistent. The framework wouldn’t be able to do this if the property was being defined by the application.

 

Warning

Unfortunately, as of this writing, neither Apache Felix (3.0.2) nor Eclipse Equinox (3.5.0) actually performs any validation on the SERVICE_PID property. In other words, two services could be registered with the same SERVICE_PID and the framework wouldn’t flag it.

 

The second reason is that we’re able to coordinate and link together several services using this one common property. We’ll explain this in detail in chapter 5.

4.6.4. Service factories

So far, through all of our previous examples, the service implementations (service objects) have had a one-to-one association with their services or, more precisely, with their service references, as shown in figure 4.16. In other words, any time a client bundle retrieves a service reference and uses the reference to get to a service, it’s getting the same service implementation object instance, which is the instance used by the providing bundle in the call to registerService().

Figure 4.16. Typically, there’s a one-to-one association between a service reference and its service implementation (object).

 

Note

In this context, a service reference is the logical entity that represents the handle to a service and not the actual Java object instance. There may be several object instances pointing to the same service reference. These are all considered the same service reference.

 

In most cases, this one-to-one mapping makes sense. For example, consider the machine management scenario we used in the previous section. There’s a single entity, represented by its state, being managed; hence you can keep the state within a single object instance. This pattern is commonly referenced as the singleton pattern for object creation.

There are cases, however, when this style doesn’t fit the application logic. For example, consider a Telnet service:

public interface TelnetService {

    void open(InetAddress target, int port);

    void close();

    String send(String text);

}

A Telnet service allows a client to connect to a remote machine and send and receive text commands.

A client bundle that retrieves the Telnet service and uses it to connect to a machine must be kept isolated from a different client bundle that may also be using the Telnet service. Each client bundle needs a separate instance of the Telnet session. In this case, there’s a one-to-many association between a service reference and the service implementation, as shown in figure 4.17. This pattern is commonly referred as the prototype pattern for object creation.

Figure 4.17. One-to-many associations between a service reference and its service implementations can be achieved through the use of a ServiceFactory.

Fortunately, the OSGi framework does support this scenario. To accomplish this, the service implementation must implement the ServiceFactory interface, as shown in the following listing.

Listing 4.8. Telnet service factory

The getService() method is called back by the framework when a client bundle first invokes the method BundleContext.getService() using a service reference whose service object implements the interface ServiceFactory, as shown in figure 4.18.

Figure 4.18. Client bundle invokes BundleContext.getService(), which causes the method ServiceFactory.getService() to be called back by the OSGi framework.

 

Warning

The object returned from the ServiceFactory.getService() method must be an instance of the service interface classes used during registration. In our case, this means that TelnetImpl must implement the interface TelnetService.

 

Likewise, ungetService() is called back when a bundle invokes the method BundleContext.ungetService() for such a service reference.

In our example, the Telnet service factory is quite simple; it creates a new object instance each time a service is needed by a client bundle. By doing so, each Telnet service has a separate object implementation holding its state.

 

Service management

Don’t we need to manage the TelnetImpl object instance somehow? No, in most cases, nothing needs to be done. The TelnetImpl becomes a OSGi service and therefore has its lifecycle managed by the OSGi framework.

Shouldn’t we do something in the ungetService() method? Again, in the typical case, the OSGi framework will take care of managing the service implementation lifecycle, and hence nothing special needs to be done.

 

The registration remains as expected; you keep the TelnetService as the service interface, but you use the TelnetServiceFactory object as the service implementation:

registration = bundleContext.registerService(
    TelnetService.class.getName(),
    new TelnetServiceFactory(), null);

This allows the client bundle to work through the TelnetService interface, and it’s thus shielded from having to know about factories at all:

ServiceReference serviceReference =
    bundleContext.getServiceReference(
    TelnetService.class.getName());

TelnetService telnetService =
   (TelnetService) bundleContext.getService(serviceReference);

Note how the previous code fragment used by a client to retrieve a service doesn’t have any coupling to service factories, in spite of the fact that the service provider is using a service factory.

Service Cache

What happens if the same bundle calls getService() twice on the TelnetService service reference? Interestingly enough, the framework returns the same service implementation used in the first call to getService(). The TelnetServiceFactory isn’t invoked in this case; instead, a cached service implementation is returned. What this means is that a service created using a service factory is associated with the requester (client) bundle.

At first, this may seem strange; shouldn’t a new service be created every time? Why cache it per bundle? The reason for this behavior is that the OSGI framework assumes that if the same bundle tries to get the same service twice, its intent is to use the same exact service. This makes sense in some cases. For example, to avoid keeping references to a stale service, a bundle may decide to get the same service over and over again from the OSGi registry as needed, rather then holding on to it in some field variable.

But there are cases when this doesn’t apply. For example, consider the case where a single bundle wants to open different Telnet sessions. This isn’t possible today, because all the TelnetService services retrieved by this bundle would point to a single TelnetImpl object instance. In my opinion, this behavior should be made configurable, perhaps through the specification of some new predefined service property, so as to follow the same pattern initiated by SERVICE_RANKING and the other predefined properties. In the end, this is a result of overloading the function getService() to both indicate the retrieval of a service as well as the creation of a service when using a ServiceFactory. Therefore, perhaps another option would have been to define a createService() method in the BundleContext.

We’ve looked into several advanced features of OSGi services that help us improve decoupling. Next, you’ll learn how to test your applications.

4.7. Improve robustness by testing your applications

In the previous sections, we went through several issues pertaining to developing OSGi bundles and services. One final issue related to the process of developing OSGi applications remains: how do you test OSGi bundles effectively?

4.7.1. Unit tests

It’s a good idea to start by testing a bundle at the unit level. You want to make sure that the bundle, which generally is defined by a well-established contract or interfaces, works in isolation. For a standard library or module, this is generally done by creating JUnit test cases. The good news is that you can continue using JUnit to test your OSGi bundle. This is so because OSGi avoids imposing any of its technology-specific interfaces on the application. This allows developers to test their classes as they normally would.

Let’s look into an actual example. Recall our auction framework from chapter 3? How could we test the SealedFirstPriceAuction class of the auction.auctioneer.sealed bundle, which is where the most complex application logic resides in the case of the auction application?

Because we were careful to isolate the OSGi-related code to a single class called SealedFirstPriceAuctioneerActivator, you can instantiate the class SealedFirstPriceAuction in isolation within a JUnit Testcase class without having to worry about the OSGi environment, as shown in the following listing.

Listing 4.9. A unit test for the SealedFirstPriceAuction class

In , you instantiate the test subject as a regular Java class, which in fact it is. You create a mock participant , which only counts the number of accepted callbacks that were invoked . Finally, you test the auction by sending a single bid and ask that match, and you assert the results .

To run this test, which you can do through the command line or in some IDE such as Eclipse, you need to make sure the following JARs are in the class path: the JUnit library, the auction bundle, and the OSGi framework API (in our case, felix.jar will do). The latter is needed only because of the SealedFirstPriceAuctioneerActivator class. Remember that our test is a JUnit test case, so the easiest option is to run it using a JUnit runner, which is provided in the JUnit library itself.

What if we hadn’t been so careful and had polluted our application logic with OSGi-related interfaces? Or what if you just wanted to unit test the bundle activator class itself?

In this case, there are two options: you could either run the unit test within the OSGi platform itself, or you could emulate (that is, mock) an OSGi environment for the unit test. Fortunately, the Spring Framework provides an OSGi mock library, making the latter approach an attractive one. Continuing with our example, let’s unit test the SealedFirstPriceAuctioneerActivator class, as shown in the following listing.

Listing 4.10. Unit test for SealedFirstPriceAuctioneerActivator class

In , you create a bundle activator instance outside the OSGi environment. Then you create Spring’s mock bundle context and use it to start the activator , as if the activator was being started by the OSGi platform. Then you validate the activator by making sure that it has registered a service reference for the auction service .

 

The Spring Framework

The Spring Framework is known for the popular dependency-injection library provided by the company SpringSource, among other things. SpringSource has adopted the OSGi platform and is providing several tools, libraries, and its own OSGi application server!

 

You can also run this test in isolation similarly to the previous case, but you must remember to add the Spring OSGi mock library to the class path.

 

Note

Spring OSGi libraries, also called Spring DM, can be found at http://www.springsource.org/osgi.

 

After having unit tested all of our bundles, the next step is to make sure the bundles are able to talk to each other within the OSGi platform itself. This process is called integration testing.

4.7.2. Integration tests

To execute integration tests for our bundles, you’ll need to run the bundles and the tests within an actual OSGi platform. And obviously, because you want to automate the execution of the tests, you’ll want to do this through scripts or the command line. In essence, you need to do the following:

1.  Start the OSGi platform.

2.  Install all the bundles that need to be tested and their dependencies.

3.  Install the test itself; this can be in the form of another bundle.

4.  Trigger the execution of the test by invoking an MBean from a script or by having the test bundle include a bundle activator that starts the execution of the test.

5.  Store the verdict (that is, pass or fail) of the test; writing the result to a file can do this.

6.  Shut down the OSGi platform, and repeat the process if other tests are needed.

Fortunately, Spring DM also provides a facility for helping you doing this. Simply have your test extend the JUnit abstract class AbstractConfigurableBundleCreatorTests. This base class will take care of starting the OSGi platform, creating a test bundle, executing it, and shutting down the platform, as shown in the following listing.

Listing 4.11. Integration test for the auction framework

To test the auction framework, you create a JUnit class IntegrationTestAuction, which extends the Spring DM helper class AbstractConfigurableBundleCreatorTests . This class is run as a bundle, so it has access to a bundleContext. With the bundleContext, you retrieve the Auction service and perform the actual test by placing bids and asks as you did previously, but now the whole interaction is done through the OSGi framework.

You can test the JUnit test case using a JUnit runner. But you need to include several JARs in its class path this time: the JUnit library, the Spring DM libraries and their dependencies, and the OSGi platform libraries, which in our case is felix.jar. You should run the test from the Felix home directory, so that the test helper class is able to start Felix correctly. You also need to tell the helper class that you want to use Felix and not some other OSGi platform, such as Equinox. This is done at . And finally, don’t forget to install all the necessary bundles, such as all of the auction application bundles, in Felix before running the test; otherwise they won’t be found. It still seems a fair amount of work, but it’s simpler than doing all these tasks manually.

4.8. Summary

In this chapter, we explored several advanced OSGi bundle and service features that allow you to develop flexible, extensible, and robust OSGi applications.

The OSGi framework provides several options for minimizing bundle dependencies. You can restrict a resource to be within a private package and thus not visible to any other bundle. You can also choose to export only a subset of the classes residing in a package, thus reducing the number of classes that make up the exported package contract. Finally, you learned that you should avoid creating bundle dependencies using the Bundle-Required header, because it can cause split packages and cause other problems.

We looked into three OSGi features that facilitate the creation of generic bundles. Dynamic imports provide a way out of the explicit import and export package contract, because they allow classes to be searched as needed. Optional packages are regular import packages that aren’t considered an error if they’re not resolved. And finally, fragment bundles are degenerated bundles that attach and merge with a host bundle, possibly extending its class path.

When a bundle loads a resource, this resource is searched through the bundle’s class space, starting at the boot class loader, then at any Import-Package header, next at the bundle’s private package, and finally at any fragment bundles attached to the bundle.

You learned how to specify complex service reference filters. OSGi service filters follow a prefix form, which can be non-intuitive but allows several operations to be grouped together on a single conjunction or disjunction.

You can use the predefined SERVICE_RANKING property to prioritize which service is used if multiple services meet a criterion when being retrieved from the OSGi service registry. This is useful when you need to specify which service among many should be the default one.

The predefined SERVICE_PID property is used to uniquely identify a service. This is useful when a service represents a real-world entity. No two services should be registered with the same SERVICE_PID, and the SERVICE_PID must be persisted; that is, the same value must be used when a service is restarted.

Generally, a service reference maps to a single service implementation object instance. But if each service must have its own state, then multiple service implementation object instances must be created for the same service reference. To do this, a service implementation object must implement the interface ServiceFactory.

The OSGi framework imposes very few vendor interfaces into the application classes, so it’s generally easy to unit test a bundle by treating it as a regular Java class. Spring DM provides several mock classes for the OSGi framework API. Spring DM also allows you to perform integration tests. It has helper classes that start an OSGi platform, install bundles, and allow you to test their integration.

In the next chapter, we’ll look at how to configure these decoupled bundles in a way that’s consistent for the entire application.