Chapter 12. Coping with the non-OSGi world

This chapter covers

  • How to use existing third-party libraries with OSGi
  • Patterns to avoid in OSGi where you can
  • How to work around some common problems
  • How to avoid doing more work than you need to

We’ve spent the majority of this book talking to you about how amazing OSGi is, and the fact that everybody should be using it. We believe this to be true, as do many others, but not everybody thinks of OSGi when writing their code. If they did, then there wouldn’t be much need for this book! Even though so many people do now think of OSGi, there are plenty of excellent libraries that have been in use for years, long before OSGi was as widely used as it is now.

We don’t expect enterprise OSGi developers to work in isolation from all of the non-OSGi code that exists. In fact, we would think you rather over-zealous if you tried to. Rather than having to give up all those useful libraries, or waiting for them to be packaged as OSGi bundles (which you should still ask for!), it’s important to learn how to work with what’s already out there.

When it comes to using existing code with OSGi, packaging isn’t the only problem; there are a number of other issues.

The most common problem people encounter when trying to use enterprise OSGi for the first time is that they want to make use of their favorite libraries, but don’t have a copy packaged as an OSGi bundle. On hitting this first hurdle, some give up, but there are a number of simple approaches that you can use to overcome this problem.

12.1. Turning normal JARs into OSGi bundles

As we’ve already mentioned, the Java ecosystem is large and contains a huge number of libraries providing all manner of functions. Some of these libraries are widely used; others are specific to certain fields and may only be useful to a handful of developers; but in all cases, they provide something that other people want to use, and don’t want to have to write for themselves. Because these libraries are developed by such a big community, you can’t rely on them always being provided as OSGi bundles. After all, not everyone has been using OSGi for the last decade!

Native libraries

Not all libraries are written in Java and packaged as JARs. Sometimes the right way to perform a particular operation, for example, encryption or vector calculus, is to communicate with some specialist hardware or to use some optimized assembly code. These situations are rare for most Java developers, particularly in the enterprise space.

Thanks to its origins in embedded systems, OSGi has a well-designed integration point for calling native code libraries, but we consider this to be well beyond the scope of enterprise OSGi. If you’re interested in getting native code or other low-level concepts running in OSGi, then we can recommend OSGi in Action (Hall et al., Manning Publications, 2011) as a useful book on the subject.

When your favorite library isn’t packaged as an OSGi bundle, it can’t be used in an OSGi framework, but some effort is usually enough to turn up an OSGi-packaged version.

12.1.1. Finding bundled libraries

It’s always a good idea to reuse code in the form of libraries where you can; it saves effort and tends to reduce the number of bugs in your code. For exactly the same reason, it’s a good idea to try to find a copy of your library that has already been turned into an OSGi bundle. If you do find a bundled copy of your library, then you should definitely use it. It saves time, and is typically much more reliable at producing well-formed bundles than other approaches.

You may think that being told to find a bundled copy of your library is a little silly; after all, the whole point of section 12.1 is that your library isn’t an OSGi bundle. On the other hand, it’s unlikely that you’re the first person to want to use a given library in OSGi, and it’s definitely worth taking advantage of any existing solutions if you can.

Upgrading or Changing Your Library

One of the most reliable, and most frequently overlooked, ways of obtaining your favorite library as an OSGi bundle is to go to the development site for the library and look. You may feel that this advice is rather useless, but most people are surprised by how many library developers have adopted OSGi packaging in the last couple of years. This is another clear sign of OSGi’s increasing popularity throughout the Java community. Given that mature libraries are often released irregularly, it can be easy to miss a release that adds OSGi metadata to the library’s packaging.

If you can find an officially released version of your library packaged as an OSGi bundle, it’s by far and away your best option. Not only do you have a reasonable level of support, but you can also expect some thought to have been put into the packaging. Typically, the only downside that you encounter when moving to an official OSGi packaged version of your library is that you may have to take a newer version. This sometimes leads to API changes, but unless the library has changed beyond recognition, and would require you to significantly rewrite your application, it’s the right way to go.

If you’re already using the latest and greatest version of your library and you know there isn’t an OSGi version you can use, then you should definitely raise a requirement with the library developers. It’s possible that they’re already thinking of adding OSGi packaging, and even if they aren’t, they will be after you’ve asked for it. They may not know how to provide OSGi metadata, and so providing a patch with your suggested OSGi helpers can often speed things up. If you’re using a library that’s no longer actively maintained, or there’s no chance of getting an OSGi-packaged release, then it’s time to get a little more creative in your search.

Most libraries aren’t created in a vacuum; there are few that are the only choice for providing a given function. Some libraries are much more popular than their competition, but that doesn’t mean that the competition isn’t worth considering. You may find that even though the library you want to use isn’t available as an OSGi bundle, one of its competitors is. If it’s possible to switch to another, OSGi-aware, library implementation with relatively little effort, then this is definitely worth considering. A less-invasive option is to take advantage of a number of repositories that repackage popular libraries as OSGi bundles.

Let Someone Else Do the Work

The whole point of using software libraries is to reduce the amount of effort that you have to expend when writing your code. In that spirit, we recommend not moving on to more drastic bundling steps until you’ve exhausted the possibility that someone else has done the work for you. Even if the developers of your library have not yet offered an OSGi bundle distribution, or you absolutely need a particular version of the library for your application, that doesn’t mean that there isn’t an OSGi bundle for you.

The SpringSource Enterprise Bundle Repository

As is apparent from its name, this bundle repository is operated by SpringSource. The Enterprise Bundle Repository contains a large number of bundlized open source projects. These have their metadata generated automatically through bytecode analysis, then packaged in with the code. This means that the EBR is a good place to find commonly used libraries that don’t offer an OSGi bundle distribution. The bundles provided by the EBR are available under a variety of different licenses, the same ones with which the original projects were made available.

Maven Central

Many popular libraries are available on Maven Central as OSGi bundles, even when the library authors haven’t made a bundle packaging available. The JAR and bundle packaging can co-exist on Maven Central because they have different group IDs; usually, the group ID of the bundle is the ID of the project that repackaged it. This makes it harder to find individual bundles, but easier to browse the whole collection.

The Apache ServiceMix and Apache Geronimo projects have both pushed a large number of useful bundles to Maven Central. Therefore, the most common group IDs for repackaged bundles on Maven Central are org/apache/servicemix/bundles/, org/apache/geronimo/bundles, and org/apache/geronimo/specs.

If you’re unlucky enough to find that the library you want to use isn’t available from SpringSource, Apache, or anywhere you’ve managed to search on Google, then it’s time to start thinking about taking matters into your own hands. A couple of options are open to you here, options broadly similar to those used by Apache and SpringSource.

12.1.2. Building your own bundles

When you’ve reached the point where you need to convert your library JAR into a bundle yourself, we’re afraid that things get more difficult for you. Unfortunately, there’s no magic wand to wave, but there’s a little pixie dust we can introduce you to later. We’ll start by looking at how to build your library bundle by hand. This isn’t particularly easy, but it’s important to know how it’s done, even though later we’ll show you some of the automated tools.

Hand-Crafting a Manifest

Although it may seem like a lot of effort, as long as you don’t have more than a couple of simple libraries to convert, building OSGi manifests by hand isn’t always a bad idea. You learn a lot about the library in the process. The first step for building an OSGi bundle from a JAR is simple: you need to extract the JAR’s manifest file. This can be done using a zip utility, or by using the jar command, as follows:

jar xf jarfile.jar META-INF/MANIFEST.MF

Having extracted your manifest file, the first and most important things you need to add are Bundle-ManifestVersion: 2 and a Bundle-SymbolicName for your bundle. While you’re at it, you should also provide a version for your bundle using the Bundle-Version header. At this point you have, theoretically, added enough information to the manifest to turn your library into a valid OSGi bundle. If you remember back to the start of the book, you didn’t even need to specify the bundle version for that, though hopefully you now know why you always should supply a version.

Although there’s now enough metadata to install this bundle into an OSGi framework, it wouldn’t be useful in its current state. To be able to use this library, you’ll have to export some packages from it. Assuming the library is well documented (and at least vaguely well organized), it should be easy to identify its API packages. When these packages have been identified, you should add them to an Export-Package header in the manifest along with their version, which you may have to assume is the same as the version of the library.

If the library you’re packaging is simple or low-level, and only has dependencies on core Java classes, then you’re done at this point. Unfortunately, most libraries do have dependencies on other library code. Some more complex libraries have large numbers of dependencies. Depending on the quality of the documentation for your library, you may be able to find out what other libraries, and importantly what packages from those libraries, it depends on. Failing that, if you have access to the library source you can read through it to find out what packages are needed.

After you’ve added in any necessary Import-Package header entries to your bundle (complete with version ranges derived from the documentation or build dependencies), you’re ready to repackage your manifest into the JAR. This is a trivial operation, but you should not use a standard zip tool to do it. The JAR specification requires that the manifest file is the first entry in a JAR, something that you can’t guarantee unless you use a tool specifically for creating JARs. A good command-line tool to use is the jar command, as follows:

jar ufm jarfile.jar ./META-INF/MANIFEST.MF

If you prefer a build tool to the command line, then there are a variety of other options; for example, in an ANT build, as follows:

<jar destfile="jarfile.jar" manifest="META-INF/MANIFEST.MF"
    update="true">
Signed JARs

In some cases, the library JAR you wish to convert may be signed, or perhaps the license prevents you from modifying the JAR in any way, so that it isn’t possible for you to update the JAR manifest. Don’t worry, all isn’t lost! In this case, you should create your OSGi manifest from scratch providing the same headers, but also adding the header Bundle-ClassPath: jarfile.jar. What you need to do then is to create a new JAR containing your OSGi manifest and also add the original library JAR to the root of the new bundle. Simple!

Note that creating bundles in this way does have one major disadvantage. The bundle can no longer be used as a normal JAR file because it has an internal classpath.

As you’ve seen, building manifests by hand is simple as long as the library JAR isn’t too complex. Sadly, as the library JAR becomes larger and has more dependencies, that situation changes quickly. Identifying the dependencies for a library can be hard, particularly if the library isn’t well modularized (which is likely, given that it isn’t an OSGi bundle). This makes hand-writing manifests of limited practical use in many cases, which is why a number of tools have been developed to smooth the process.

12.1.3. Generating bundles automatically

As we’ve said before, it’s a sad fact that there are many useful Java libraries that aren’t yet available as OSGi bundles. You now know how to turn these JARs into bundles by hand by updating the manifest with the right headers, but it’s not particularly easy. You might be able to work out which packages to export, based on a published API, but how do you know what to import? If it was easy to work out a bundle’s dependencies by looking at it, setting a normal Java classpath wouldn’t be such a nuisance! See figure 12.1.

Figure 12.1. Tools can take a conventional Java JAR and produce sensible OSGi bundles more reliably than converting them by hand.

Rather than suffer through the slow process of converting every library JAR by hand, a few OSGi developers turned their attention to automating the process. Reading through this section, you’ll see that there are a few problems with automatic bundle conversion, but that for the most part it dramatically speeds up the process of building an OSGi bundle. A number of tools are available for you to use, too many for us to describe all of them in detail. We’ll cover the most popular options.

Using Bnd

In section 8.2.1., we first introduced you to bnd as a useful build-time tool for packaging OSGi bundles. One of the most useful functions of bnd has nothing to do with building. Bnd can use the same logic it uses to generate manifests at compile time to generate manifests for existing JARs.

To demonstrate bnd, you’ll need the command line version of bnd itself (http://www.aqute.biz/Bnd/Download) and a simple demonstration JAR. Rather than trying to use a real library, which would probably be too complicated, you’ll use the Fancy Foods superstore chocolate department bundle from back in chapter 3. This may sound odd—after all, the chocolate department is already an OSGi bundle—but remember that a bundle is still, at its heart, a JAR file.

How to run bnd

Bnd is a multipurpose tool that integrates with lots of other build tools and runtimes. As a result, there are dozens of ways that you can use and launch bnd. To keep things simple, you’ll use bnd from the command line, but there are plenty of other options described in bnd’s documentation.

In the simplest case, you can wrap the chocolate department JAR by pointing bnd at it and invoking the wrap command, as follows:

java -jar biz.aQute.bnd.jar wrap
 fancyfoods.department.chocolate_1.0.0.jar

This will create an OSGi bundle called fancyfoods.department.chocolate_1.0.0.bar. If you forget the naming convention and look on your filesystem for a new JAR file, it may take you some time to find the wrapped JAR! When you’ve found the generated bundle, you should see that it contains a manifest that looks something like the following listing.

Listing 12.1. A bnd-generated default manifest
Manifest-Version: 1
Bnd-LastModified: 1309600295603
Bundle-Blueprint: OSGI-INF/blueprint/*.xml
Bundle-ManifestVersion: 2
Bundle-Name: fancyfoods.department.chocolate_1.0.0
Bundle-SymbolicName: fancyfoods.department.chocolate_1.0.0
Bundle-Version: 1.0.0
Created-By: 1.6.0 (IBM Corporation)
Export-Package: fancyfoods.chocolate;uses:="fancyfoods.food,fancyfoods.o
 ffers",OSGI-INF,OSGI-INF.blueprint
Import-Package: fancyfoods.food;resolution:=optional,fancyfoods.offers;r
 esolution:=optional
Tool: Bnd-1.44.0

If you inspect the manifest bnd has generated, you’ll see that bnd has wrapped the chocolate department JAR into a bundle in the least restrictive way possible; every package in the JAR is freely exported, and every imported package is optional. If you look closely, you’ll see that the bundle manifest is even less restrictive than you might have expected. The exports for the bundle are so loose that it even exports its OSGIINF and OSGI-INF/blueprint folders!

Bnd has a good reason for being so unrestrictive. These defaults mean the generated bundle is guaranteed to start in any OSGi environment, but they don’t guarantee that the bundle will work properly! The optional imports and generous exports give you an OSGi bundle with the character of a conventional JAR, where all classes are externally visible and missing dependencies won’t be detected until classes fail to load at runtime.

As you know, you aren’t using OSGi to make things work like they do with normal Java! It’s at this point where hopefully you’ll understand why we covered building OSGi bundles by hand in section 12.1.2, before looking at automated tools. Every tool needs some level of configuration to produce a sensible result, and that configuration needs to be built with at least a vague understanding of the bundling process.

Fortunately, you can easily refine the bundle bnd produces by using a bnd file in the wrapping process. As a starting point, you may want to set a sensible symbolic name and version; you’ll notice that the default symbolic name for your bundle is particularly unpleasant! Next, because you know that neither of your imports is optional, you should make your imports compulsory by adding an Import-Package header. This doesn’t require you to list the imports, because you can use * as a wildcard. As with build-time usage of bnd, any spurious imports can be individually removed using a !<package name> entry. Finally, you should tighten the package exports by overriding the default Export-Package: * used by bnd. In this case, you don’t want to export anything, so leave the entry blank. It’s also a good idea to let bnd know which packages in your bundle have private implementation using the Private-Package entry.

In the general case, you may find that you want to keep some package imports optional because they’re associated with poorly modularized optional code paths. These can be explicitly added to the Import-Package section of the bnd file including the resolution:=optional directive.

When you’ve finished writing your bnd file, you should have something that looks like this:

Bundle-Name: Fancy Foods Chocolate Bundle
Bundle-SymbolicName: fancyfoods.department.chocolate
Bundle-Version: 1.0.0
Private-Package: fancyfoods.chocolate
Export-Package:
Import-Package: *

To use the bnd file, you need to add a -properties <bndFile.bnd> argument when you invoke bnd, as follows:

java -jar /biz.aQute.bnd.jar wrap -properties
  fancyfoods.department.chocolate.bnd
  fancyfoods.department.chocolate_1.0.0.jar

The manifest for the resulting bundle, shown in the following listing, should now look much more like the original, well-modularized manifest for the Fancy Foods chocolate department; the only missing bits of information are version ranges on the package imports.

Listing 12.2. A bnd manifest generated with configuration
Manifest-Version: 1
Bnd-LastModified: 1309602664432
Bundle-Blueprint: OSGI-INF/blueprint/*.xml
Bundle-ManifestVersion: 2
Bundle-Name: Fancy Foods Chocolate Bundle
Bundle-SymbolicName: fancyfoods.department.chocolate
Bundle-Version: 1.0.0
Created-By: 1.6.0 (IBM Corporation)
Import-Package: fancyfoods.food,fancyfoods.offers
Private-Package: fancyfoods.chocolate
Tool: Bnd-1.44.0

Given that the manifest still isn’t entirely right, there’s a small amount of tinkering still to do. Bnd does attempt to fill in the import version range for any package imports that it generates, but in the absence of any information about the exported version of the package, there’s not a lot it can do! Fortunately, bnd does offer a way for you to provide this information by including one or more JARs on the classpath. Extend your command further to add the fancyfoods API JAR to the bnd classpath using the -classpath argument, as follows:

java -jar /biz.aQute.bnd.jar wrap -properties
 fancyfoods.department.chocolate.bnd
 -classpath fancyfoods.api_1.0.0.jar
 fancyfoods.department.chocolate_1.0.0.jar

The resulting manifest file is now nearly as good as it was to start with, as shown in the following listing.

Listing 12.3. A well-generated bnd manifest
Manifest-Version: 1
Bnd-LastModified: 1309685933015
Bundle-Blueprint: OSGI-INF/blueprint/*.xml
Bundle-ManifestVersion: 2
Bundle-Name: Fancy Foods Chocolate Bundle
Bundle-SymbolicName: fancyfoods.department.chocolate
Bundle-Version: 1.0.0
Created-By: 1.6.0 (IBM Corporation)
Import-Package: fancyfoods.food;version="[1.0,2)",fancyfoods.offers;vers
 ion="[1.0,2)"
Private-Package: fancyfoods.chocolate
Tool: Bnd-1.44.0

To the untrained eye the manifest is now done, but if you look more closely you’ll see that the version ranges on the Import-Package statements aren’t right. The code in your bundle doesn’t only consume the packages it imports, it implements API from them as an API provider. This means that a minor version increment would still be considered a breaking change. Bnd doesn’t cope well with this scenario because there isn’t enough information in the Java class files to tell bnd whether the interfaces are implemented by the consumer or the provider. What you can do is alter your configuration file to let bnd know that you provide implementations for the packages you import by using the provide directive with a value of true, as follows:

Bundle-Name: Fancy Foods Chocolate Bundle
Bundle-SymbolicName: fancyfoods.department.chocolate
Bundle-Version: 1.0.0
Private-Package: fancyfoods.chocolate
Export-Package:
Import-Package: *; provide:=true

Running with this updated configuration, you finally get back a correctly modularized manifest, as in the following listing.

Listing 12.4. A completely configured bnd manifest
Manifest-Version: 1
Bnd-LastModified: 1309804592021
Bundle-Blueprint: OSGI-INF/blueprint/*.xml

Bundle-ManifestVersion: 2
Bundle-Name: Fancy Foods Chocolate Bundle
Bundle-SymbolicName: fancyfoods.department.chocolate
Bundle-Version: 1.0.0
Created-By: 1.6.0 (IBM Corporation)
Import-Package: fancyfoods.food;version="[1.0,1.1)",fancyfoods.offers;ve
 rsion="[1.0,1.1)"
Private-Package: fancyfoods.chocolate
Tool: Bnd-1.44.0

We hope that the advantages of a tool like bnd are now obvious; although it certainly isn’t a zero-effort solution, it’s considerably faster than building the manifest by hand. The other big advantage that you get from a tool like bnd is that you don’t need to guess at the packages your bundle should import, or have to debug unpleasant ClassNotFoundException errors on rarely used code paths. Bnd isn’t your only option for packaging JARs as OSGi bundles, so let’s take a look at some of the competition.

Using Bundlor

Bundlor (http://www.springsource.org/bundlor) is an open source tool developed and maintained by SpringSource. Although Bundlor isn’t as widely used in the community as bnd is, it’s the tool used to generate OSGi bundles for open source JARs in the SpringSource Enterprise Bundle Repository. Bundlor is used for the same purposes as bnd, and it’s able to wrap existing JAR files or create manifests for code at build time. As a result, it’s equally good for this example.

Using Bundlor is remarkably similar to using bnd. It also has integration points for Apache Ant and Apache Maven builds, but for this example you’ll continue to use the command line. Getting Bundlor to create a manifest is as simple as issuing the following command (there are Windows and Linux scripts available):

bundlor.bat -i <path to jar>

Issuing this command, you’ll see that the behavior for Bundlor is different from bnd’s (see figure 12.2).

Figure 12.2. The output from Bundlor

As with bnd, Bundlor creates a manifest for the JAR file, but the default output is to System.out. This isn’t the most useful location for wrapping bundles, but it’s easy to get Bundlor to package the manifest back into a JAR file using the -o property to specify an output file, as follows:

bundlor.bat -i ..jarsfancyfoods.department.chocolate_1.0.0.jar
 -o ..outfancyfoods.chocolate.bundled.jar

The manifest file in the bundle created by Bundlor is a little different from the one created by bnd, as shown next.

Listing 12.5. A default manifest from Bundlor
Manifest-Version: 1.0
Bundle-Blueprint: OSGI-INF/blueprint/*.xml
Bundle-SymbolicName: fancyfoods.department.chocolate
Tool: Bundlor 1.0.0.RELEASE
Export-Package: fancyfoods.chocolate;uses:="fancyfoods.food,fancyfoods
 .offers"
Bundle-Version: 1.0.0
Import-Package: fancyfoods.food,fancyfoods.offers
Bundle-ManifestVersion: 2

The default manifest that comes out of Bundlor is stricter than the one that came out of bnd. Note that none of the imports are optional, and that only the package containing code is exported. Other than this, though, there isn’t much difference between the Bundlor output and the first bundle you generated with bnd. Neither has versioning, and neither has any concept of private implementation. Bundlor wouldn’t be of much use if these generated manifests couldn’t be refined in some way, and they can through the use of template manifests.

Template manifests in Bundlor are similar to bnd configuration files. Any headers specified in the template are copied into the generated manifest, and some special header values are used as processing instructions to guide generation. Given that Bundlor does a better job of defaulting the bundle’s symbolic name and version, you can avoid specifying them (though, in general, specifying them is a good idea). What you do need to do is to tell Bundlor about your private packages and the versions of your imports.

In Bundlor, packages can be marked as private using the Excluded-Exports header in the template manifest; in this case you need to exclude the fancyfoods.chocolate package. Adding version ranges to your package imports also requires a modification to the template manifest. In this case, you need to add an Import-Template header. The Import-Template header allows you to provide attributes for selected package imports, using * as a wildcard if necessary. The resulting template manifest should end up looking something like the following listing.

Listing 12.6. A template manifest for use with Bundlor
Excluded-Exports: fancyfoods.chocolate
Import-Template: fancyfoods.*;version="[1.0,1.1)"

After you’ve created your template manifest, you can provide it to Bundlor using the -m option, as in the following command:

bundlor.bat -i ..jarsfancyfoods.department.chocolate_1.0.0.jar
 -m ..	emplatesundlor.template.mf
 -o ..outfancyfoods.chocolate.bundled.jar

Now that you’ve provided configuration, the manifest generated for your JAR looks as good as it did before, as shown in the following listing.

Listing 12.7. A configured, generated manifest from Bundlor
Manifest-Version: 1.0
Bundle-Blueprint: OSGI-INF/blueprint/*.xml
Bundle-SymbolicName: fancyfoods.department.chocolate
Tool: Bundlor 1.0.0.RELEASE
Bundle-Version: 1.0.0
Import-Package: fancyfoods.food;version="[1.0,1.1)",fancyfoods.offers;
 version="[1.0,1.1)"
Bundle-ManifestVersion: 2

You may have noticed that we spent much less time describing and configuring Bundlor than we did bnd. We feel duty bound to tell you that this isn’t a sign that Bundlor is much simpler than bnd. You may remember that you started out with a JAR that was an OSGi bundle. One of the things that Bundlor does is copy across any headers from the old JAR that aren’t updated by the generation code. This means that other than the import and export headers all of the headers in your generated manifest are copied from the original bundle. This even includes the Bundle-ManifestVersion and Bundle-SymbolicName. If you’d started with an emptier JAR manifest, you would have found that rather more configuration was required to get Bundlor working as you want it to.

Having now seen how similar bnd and Bundlor are, it shouldn’t be surprising to find out that other tools, such as Apache Felix’s mangen, work in a similar way. Rather than covering these tools with further examples, we think that armed with the previous examples and relevant documentation, you should have no problem using any manifest generation tool you choose.

Now you know how a standard JAR can be converted into an OSGi bundle, overcoming the significant hurdles that put off many potential users of OSGi. But further issues are associated with using standard JARs and other Java technologies in OSGi. Even though your JAR is now an OSGi bundle, that doesn’t mean that the things the code will try to do are OSGi-friendly!

12.2. Common problems for OSGi-unaware libraries

For many people, the problems with OSGi begin and end with correctly building a bundle manifest. As long as you’re correctly able to identify your dependencies, then typically things work. This is a testament to the simplicity and resilience of the OSGi model. Unfortunately, there are a number of things that can cause huge problems. These issues typically show up in libraries that use their own plug-in model or try to manage their own modularity. In normal Java, it’s perfectly acceptable to do this sort of thing, but in OSGi it competes with the inherent modularity of the framework. This is the cause of many of the problems that libraries have when moved into OSGi for the first time.

Given that you’ve gone to so much trouble getting your library JAR packaged as an OSGi bundle, it would be rather silly not to use it because of the problems that you might encounter, but it would be good to know what some of the common problems are. One of the most common problems you encounter when moving to OSGi comes from the different classloading model that OSGi has, and how it interferes with reflection.

12.2.1. Reflection in OSGi

Reflection is a commonly used pattern in Java that aims to provide a degree of modularity in the code. You may remember that we introduced reflection as a poor modularity implementation all the way back in chapter 1! Given that it has been such a long time since we last covered reflection, it’s worth a quick recap.

Generally the term reflection is used to describe the concept of a computer program looking inside itself, known as introspection, and modifying its behavior or operation as a result. In Java, this sort of reflection falls into two camps:

  • Locating and calling methods on a known class dynamically
  • Locating and loading classes that were not available at compilation time, or configuration files

When Java developers talk about reflection, they’re usually referring to the first option, where methods can be located and invoked without a compilation dependency. The main reason for thinking of this type of reflection first is that the tools used to do it are in the java.lang.reflect package. Fortunately, in OSGi this sort of reflection works reasonably well. If you can get hold of an Object or Class, then all of the standard reflection APIs will work correctly because they come from the base runtime, which is shared by all bundles, regardless of version or modularity statements.

The big problem for method-level reflection comes at the point when you try to invoke the method or to find a particular method by specifying its arguments. If the method only refers to types from the base runtime—for example, it has an int return type and a java.lang.String argument—then there are no problems. If the method has more complex types in its signature, then class space compatibility becomes a factor. When invoking the method you must have the right view of all the arguments and return type (use types loaded by the right classloader) for things to work properly. If you get this wrong, you’ll get a rather confusing ClassCastException, which often appears to be a failure converting a type into itself.

This problem sounds serious, but it isn’t as bad as you might think. It’s comparatively rare for bundles to be able to get hold of classes that they don’t share a class space with, although you may occasionally find that some methods refer to types your bundle hasn’t imported. It’s the second kind of reflection that causes the majority of reflection-related problems in OSGi.

Reflective Classloading

When a Java program dynamically loads a class by name, rather than by referencing a type during normal execution, this is another form of reflection. The program is inspecting its classpath for a particular class that can then be loaded and used to affect its operation. In principle, there’s no problem with this behavior in OSGi as long as you take into account the way OSGi classloading works. If a piece of code uses its own classloader to try to load another class, it will only be successful if that class exists inside the bundle or is imported from another bundle. If the class exists but is packaged somewhere else, then the class will fail to load.

This classloading model isn’t dissimilar to the one from base Java, where any class on a classloader’s classpath can happily be loaded by any other class. The hitch is that in OSGi the classpath is much smaller and more structured than the big, flat classpath in base Java. Problems therefore arise when one class expects to be able to see another, even though it’s in a different bundle (see figure 12.3).

Figure 12.3. Unlike compile-time dependencies (solid lines), reflective dependencies (dashed lines) may not be explicitly declared in bundle manifests. This won’t cause problems for JARs, which have little distinction between internals and externals, but it can cause runtime failures for bundles.

Because the class is loaded dynamically, there’s no way to determine the dependency before runtime, and you can’t know what to add to your manifest. Frequently, the class to be loaded is a private implementation class, and would not be exported anyway. Effectively, what you get is a break in the modularity of your system, which is why it’s such a thorny problem to solve.

This sort of reflection problem isn’t limited to classes. For example, library configuration files are usually packaged as part of applications, and then loaded reflectively by the library. If the classpath isn’t flat, resources may not be visible, causing resource loading to fail (see figure 12.4).

Figure 12.4. If the containing folder of a configuration file, or other resource, isn’t explicitly exported as a package by Bundle B, it won’t be visible in the class space of Bundle A.

Is everything doomed to failure when classes or resources are loaded by reflection? Not at all—all that’s required to make it work is to follow the normal OSGi rules for classloading. Anything that is to be loaded must have its package exported by the owning bundle. This applies to Java code, unsurprisingly, and also to file resources, which may surprise you. For example, if a fancyfoods.config bundle wants to make a fancyfoods/config/properties/ff.properties file available to other bundles, fancyfoods.config must include the following in its manifest (even if fancyfoods/config/properties doesn’t have any Java classes in it!):

Export-Package: fancyfoods.config.properties

Similarly, bundles trying to load the ff.properties file must import the fancyfoods .config.properties pseudo-package. The same is true for any classes they want to load reflectively. What happens if the package isn’t known at compile time? This is where DynamicImport-Package can be useful—more on that in section 12.3.3.

Even if you were extremely careful to export your configuration package from the application bundle and import it into the library, you could still only have one configuration file package wired at a time. If another provider of the package came along, then one or the other of you would end up using the wrong configuration!

Even in the absence of OSGi, these classloading problems are sometimes visible in Java, particularly in Java EE, where part of the runtime might need to load a class from the application. In these cases, Java makes use of something called the thread context ClassLoader.

12.2.2. Using and abusing the thread context ClassLoader

The thread context ClassLoader is used in Java EE as a Get out of Jail Free card. The thread context ClassLoader is set by various Java EE containers, such as an EJB container or a servlet container, before calling into a managed object. This ClassLoader is the same one used by the application, and the same delegation hierarchy. In a Java SE environment, the thread context ClassLoader is initialized to be the same as the classloader of the class that created the thread.

The thread context ClassLoader is used in Java for a specific purpose: when a piece of code on one classloader needs to act on classes or resources visible to another classloader.

Proper Use of the Thread Context ClassLoader

A well-behaved piece of code can use the thread context ClassLoader to access classes or resources only visible to another classloader. One example would be a library or component that lives inside a server runtime, or at EAR scope within a Java EE application. This library will be visible to any module inside the rest of the application, but crucially the module won’t be visible to the library.

If one of the things the library needs to do when it’s accessed is locate some configuration or instantiate one of the module’s classes, then it’s stuck. Because the classloader that loads the library is the parent of the classloader that can load the configuration, visibility is only one-way. To get around problems like this, libraries can use the thread context ClassLoader to find the necessary classes or resources. The thread context ClassLoader will have been set by the server immediately before the invocation of a servlet or EJB, making it available to any libraries called by the servlet or EJB.

This approach may seem a little haphazard, but it’s part of a number of Java EE specifications. For example, if a JMS resource adapter receives an ObjectMessage, then it needs access to the class of the Object to instantiate it. As with a library loading a configuration file, this is problematic. The JMS resource adapter is almost always part of the Java EE server runtime, not part of the application. In this case the JMS resource adapter is required to use the thread context ClassLoader to find the application class stored in the ObjectMessage.

Although there are good reasons for using the thread context ClassLoader, there are also times when it’s used somewhat inappropriately.

Poor Use of the Thread Context ClassLoader

The thread context ClassLoader is extremely useful, but as we’ve mentioned before it’s seen as something of a Get out of Jail Free card. This is a problem, because it means the thread context ClassLoader is extensively used in places that it wasn’t originally intended for. This problem is exacerbated by the fact that most people using the thread context ClassLoader make assumptions about it that aren’t guaranteed to be true, particularly if you have an OSGi classloading model.

The biggest assumption that most people make about the thread context ClassLoader is that it can load internal library and server implementation classes. This may not seem like a big deal, but it leads to some sloppy coding. Libraries often end up using the thread context ClassLoader to load their own internal classes, even though it’s only supposed to find classes from the application. For most Java EE environments this will never be a problem, but, as we know, OSGi is a little different (see figure 12.5).

Figure 12.5. The thread context ClassLoader gives Java code in Bundle A visibility of classes that aren’t normally in its class space (solid gray area). But in an OSGi environment, the class space of the thread context ClassLoader (dotted area) may exclude classes normally visible to Bundle A.

OSGI and the Thread Context ClassLoader

In OSGi, the thread context ClassLoader isn’t defined. This immediately causes a significant problem for libraries that rely on it. For some libraries, the lack of a thread context ClassLoader is fatal, and others can only limp along in a less-than-desirable way.

Because the thread context ClassLoader isn’t defined by OSGi, framework implementations are free to do as they see fit. This means that the situation for libraries isn’t as bad as it might be. In Equinox, for example, if there’s no thread context ClassLoader explicitly set, then the framework provides one for you. This avoids unpleasant NullPointerExceptions, and generally makes life a lot easier, but there’s an important question. What classloader do you get?

In OSGi, the classloading model doesn’t lend itself well to providing a thread context ClassLoader. Clearly it doesn’t make much sense to rely on the Java SE model of a single thread context ClassLoader set when the thread is created. As threads pass through different bundles, it would be a huge violation of OSGi’s modularity for them all to see the internals of the bundle that created the thread. The Java EE model makes more sense, in that the thread context ClassLoader is set differently at various points through the execution of the application. Unfortunately, core OSGi doesn’t define things like servlets or EJBs, but it does define bundles. This means that the default thread context ClassLoader is the classloader for the bundle that loaded the currently executing class.

By setting the thread context ClassLoader in this way, Equinox prevents many libraries from completely collapsing, but it doesn’t help them to get visibility to resources outside their bundle. The bundle also becomes nonportable. As with reflective classloading, using the thread context ClassLoader in OSGi is an attempt to break the modularity rules built into the system. As a result, the consequences for using it are typically bad.

12.2.3. META-INF services

The awkwardness of reflection and the trickiness of the thread context ClassLoader compound one another if you need to adapt a JAR that uses META-INF services to run in an OSGi environment. Many libraries use this pattern to supply implementations of an interface without tightly coupling consumers to the implementation class. The way it works is that a ServiceLoader searches all text files in META-INF/services folders. Files must be named after interfaces and contain the name of an implementation class. We discussed this factory system in section 1.2.4, and concluded it was limited compared to OSGi services. Nonetheless, it’s a popular pattern, and chances are you’ll have to handle JARs that use it.

Normally, the ServiceLoader searches the class space for resources in the META-INF/services folder. As you saw in section 12.2.1, any JAR wanting to expose this file must cheat and export a pseudo-package called META-INF/services. But the ServiceLoader can only be wired to one META-INF/services package at a time, so the cheat clearly won’t scale well!

As if that wasn’t enough of a hurdle, the ServiceLoader uses the thread context ClassLoader to look for and load resources. We’ve already seen that this classloader is undefined in OSGi. Even if one is available, it’s unlikely to be able to see internal implementation classes, unless these classes are explicitly exported from the providing bundle. It goes without saying that exporting implementation packages is an OSGi anti-pattern, and completely subverts the encapsulation the META-INF services pattern was trying to achieve!

The good news is that enterprise OSGi has devised a way of making things work without requiring pseudo-packages and exported internals. The Service Loader Mediation Specification allows META-INF services to be made available through the Service Registry with relatively non-invasive manifest changes. All that’s required is an indication to the OSGi service loader extender that META-INF services should be processed. For example, to expose a service for the shop.Bananas interface, the following should be added to a bundle manifest:

Require-Capability: osgi.extender;
 filter:="(osgi.extender=osgi.
serviceloader.registrar)"
Provide-Capability: osgi.serviceloader; osgi.serviceloader=shop.Bananas

JARs that use the ServiceLoader.load() API can opt to have those calls magically transformed into ones that will have the correct visibility of required implementations in OSGi by requesting processing with a manifest header, as follows:

Require-Capability: osgi.extender;
 filter:="(osgi.extender=osgi.
serviceloader.processor)"

If you’re wondering what the requirements and capabilities are, they’re the same ones we introduced in chapter 7. Although they look long-winded in the manifest, they’re a robust way of ensuring that the necessary infrastructure is available, and also configuring that infrastructure at the same time.

Apache Aries has a project known as SPI Fly, which is one of the inspirations for the OSGi Service Loader Mediation Specification. SPI Fly uses a simpler mechanism to register services. For example, to process all META-INF/services files in a bundle, all that’s needed is the following header:

SPI-Provider: *

A full discussion of SPI Fly and service loader mediation is beyond the scope of this book, but we hope this introduction will point you in the right direction if you do encounter this Java SE pattern. There’s one more Java technology that can cause serious problems when moved into OSGi: Java serialization.

12.2.4. Serialization and deserialization

Most Java developers are at least vaguely familiar with Java’s support for serialization. Serialization is the process by which live Java Objects can be persisted, or transported, in a standard interchangeable binary data format. Serialization is supported by the base Java runtime, and because of this you might expect things to work as easily in OSGi as they do elsewhere. To an extent this is true, but there are some potentially thorny issues.

Serialization in OSGi

Serialization in OSGi is a relatively simple process, and does work more or less exactly like the serialization process in Java SE. A serializable Object can be passed to an ObjectOutputStream, at which point the entire Object graph connected to that one Object is also serialized. This is no different from Java SE.

Writing serializable objects in OSGi is a little more difficult than in normal Java, primarily because it’s not possible to serialize objects like BundleContexts or OSGi services. The dynamic nature of these Objects means that they can’t be relied upon to be recreated when Objects are later deserialized. On the whole this isn’t too big an issue, particularly for libraries that weren’t using OSGi services in the first place.

One of the biggest problems with serializing objects in OSGi is that the graph of connected Objects doesn’t necessarily come from one bundle. There must be a consistent class space, but fields in one object may refer to private implementation types from other bundles. This doesn’t cause a problem at the time of serialization; the serialization code in the virtual machine knows all of the implementation types and can easily handle the object graph.

Given that the object graph can be serialized, even when private implementation types are stored as fields, why is there a problem? The issue in an OSGi framework is at deserialization time. When a bundle tries to read an object from an ObjectInputStream, the underlying runtime needs to load all of the types in the object graph that were serialized. This is fine for classes that came from the bundle doing the deserialization, but not for ones that came from another bundle, which are almost certainly not exported. Unless the object graph is entirely contained within a single bundle, this will always be a problem.

You may think that things are alright as long as all the types in the object graph are visible through package imports, which is partly true. In cases where all types are either contained within a single bundle or visible to that bundle as package imports, you may find it helpful to write a custom serializer that accepts that bundle’s classloader of the serialized class as a parameter, as shown in the following listing.

Listing 12.8. An ObjectInputStream that can handle restricted class visibility

Even in the ideal case where all classes are visible to a nominated classloader, there’s the potential for more subtle problems. If the bundle has been reresolved since it serialized the object, then it may have been resolved to different packages. To combat this, it’s important to make sure serialization versioning is included in any declared package versions.

One commonly used type of library that demonstrates several of the problem patterns described in this section is the logging framework.

12.3. An example library conversion—logging frameworks in OSGi

A large number of logging frameworks are available, and it isn’t our intent to name and shame any particular implementation, but to point out the common patterns they use, and why this is a problem in OSGi. We’ll assume that you’ve already run the logging framework JAR(s) through a tool to convert them into OSGi bundles.

Logging frameworks are typically structured with an independent API. This is the part of the framework that applications code to, and typically has static factory methods for obtaining logger implementations. The applications then use the API to log out messages independently from the underlying logging implementation.

12.3.1. Common problems with loggers in OSGi

The structure of a logging framework sounds like a great, modular design that should be ideal for an OSGi environment. That would be the case if, for example, the application used the Service Registry to obtain a logging implementation. Using a static factory demonstrates the first big problem with logging frameworks.

Loading the Logging Implementation

The API for logging frameworks almost always contains the static factory for obtaining the logger. This means that the logging API has to load the logging implementation class. This isn’t a problem if the implementation is in the same bundle, but many implementations have pluggable backends that are packaged separately. These pluggable implementation classes aren’t API, but unless they’re packaged inside the same bundle they have to be exported anyway.

If the logging API were to have a hard dependency on each possible implementation, then it would mean that you needed all of the possible implementations to be installed into the OSGi framework for your logger to even resolve. Clearly this is a bad idea, meaning that the dependencies must be optional. But this still means that all of the potential implementations must be known in advance to be loadable!

Another problem introduced by the static factory is that it divorces the client (in this case, the class doing the logging) from the lifecycle of the logging implementation. If the implementation is stopped, or even uninstalled, then the client has no way of knowing that their logger needs to be thrown away.

The separation of the logging API from the implementation isn’t only an issue when trying to load the logging internals; it also causes problems when trying to load configuration files and customizations.

Client-Provided Resources

One of the common concerns for libraries is how they can be configured. For some libraries, the amount of configuration is sufficiently small that it can be provided in some sort of registration call, or even be passed in whenever the library is called. For most libraries, this solution is insufficient; as the amount of configuration grows, it becomes increasingly unmanageable in code. Furthermore, it’s unpleasant to require users to recompile their code to change their configuration.

Normally this sort of issue is solved by using an XML file or properties file, either in a fixed, library-specific location within the client JAR, or in a client-specified location. This is the most common way to configure a logging framework. As we’re sure you’ll remember from section 12.2.3, the way that the logging framework loads resources provided by the client is by using the thread context ClassLoader. For all the reasons we covered then, this doesn’t always work in OSGi. By the time the library code is invoked, the thread context ClassLoader can no longer see the content of the OSGi bundle.

Unfortunately, it’s not only configuration files that cause an issue. Logging frameworks, like many other types of libraries, often allow clients to configure customized plug-in classes using configuration. Clearly the logging framework uses the thread context ClassLoader to load these plug-in classes as well as the configuration, creating yet another problem.

Given the unpleasant problems we see, you might expect existing logging frameworks to be a lost cause in OSGi. Clearly, their Java EE behavior doesn’t stand much chance of working properly in OSGi. There are some things you can do to improve matters.

12.3.2. Avoiding problems in OSGi

You can improve the situation for logging frameworks in OSGi in a number of ways; some are more invasive to the code than others, but they do demonstrate useful ways to work around some of the problems that afflict Java libraries in OSGi. Many of these workarounds aren’t normally recommended, but it’s important to understand where rules can be bent.

Careful Packaging

One of the big problems we noted with logging frameworks is the fact that although the API and implementation are notionally separate, from a modularity perspective they’re tightly coupled. This is because the API needs to be able to load the implementation classes.

One of the things you can do to avoid the lifecycle and classloading problems associated with this packaging is to package the logging API and logging implementations as a single bundle. Clearly, there are advantages and disadvantages to this approach. One of the big advantages is that by including the API and implementation in the same bundle, you’re always guaranteed to be able to load the implementation. Another big advantage of packaging the API and implementation together is that it’s impossible for the implementation to be changed independently of the API, leaving clients in an inconsistent state.

Given the advantages of this packaging, you might expect it to be the default, but there are some negative side effects. First, you have to package any logging implementation with the API, which dramatically bloats the bundle. Given that different clients may want different implementations, this can be a significant problem. It also only helps for logging implementations that you know you need ahead of time.

Given that packaging the logging API and implementation together solves problems, you might expect that packaging the logging library with the client would be even better. To an extent this is true. If you package the logging library with the client, then it will gain visibility of any configuration files and plug-ins; on the other hand, you’ll have to bloat every single client, wasting runtime resources. Effectively, by doing this you’re creating an even worse version of the Java classpath!

Our recommendation is to avoid packaging the logging library with the client code, unless there’s only one client bundle. Even then you should think twice. What you can do is to package commonly used logging implementations along with the API. This will reduce some of the optional package import spaghetti, prevent some of the logging internals from having to be exported, and also avoid lifecycle problems.

Embedding JARs instead of converting them

The trick of embedding a plain Java library into an existing OSGi bundle, rather than converting it into its own bundle, works for more than loggers—it can be an effective workaround for all sorts of troublesome libraries. Because the embedded JAR shares a classloader with the consuming bundle, almost all class visibility issues vanish. But this convenience comes at the expense of reduced modularity and increased bloat. We recommend embedding JARs as an absolute last resort.

An alternative approach, used, for example, by SLF4J, is to package the implementation bundles as fragments of the API bundle. This neatly solves the implementation visibility issues. But fragments have a number of idiosyncratic behaviors, and not everyone likes using them, which is why this approach hasn’t been universally adopted.

Repackaging the logging API and implementation is only one of the ways in which the bundles can be altered to improve your situation. Another option is to try to make sure that the thread context ClassLoader has the correct view.

Changing the Thread Context ClassLoader

We’ve talked about how the thread context ClassLoader is the source of many issues for Java libraries in an OSGi framework. One of the more obvious things to do, therefore, is to set the context classloader to one that can load the resources you need it to.

Setting the thread context ClassLoader is an invasive thing to do. It absolutely requires that you change your code and recompile; it’s also not something that you would ever normally come across when writing Java code. For those of you who run with Java 2 Security enabled, you’ll also find that changing the thread context ClassLoader is a privileged operation.

Setting the thread context ClassLoader is as simple as in the following listing, but it’s also something that you must be careful about doing, particularly when it comes to setting it back afterward.

Listing 12.9. Setting the thread context ClassLoader
Thread current = Thread.currentThread();
ClassLoader original = current.getContextClassLoader();
try {
  current.setContextClassLoader(clToUse);

  //Make a call out to your library here

} finally {
  current.setContextClassLoader(original);
}

Given that setting the thread context ClassLoader is easy to do, it’s a good way of ensuring temporary visibility of classes and resources. But there are some significant drawbacks. As we mentioned earlier, you have to change your code. This is invasive and potentially risky. Not all libraries even use the thread context ClassLoader, instead assuming the resource will be on their classpath.

In order for the code change to be effective, you have to get hold of a classloader that can load the resources you need it to. If you can identify a class with visibility of the resources, then the classloader of the owning bundle is a good bet. Bundles don’t directly expose their classloader, but a call like ClassThatCanSeeResources.class.getClassLoader() will return a suitable classloader. If you have a bundle but no class, you can write a facade classloader that delegates to the bundle.loadClass() and bundle.loadResource() methods. As long as the bundle contains some classes, you can instead use bundle.getEntries() to introspect the bundle contents and find an arbitrary class name, and then use bundle .loadClass().getClassLoader() to get the classloader. Both of these methods are fragile and require caution.

The biggest problem with setting the thread context ClassLoader is that you can’t always be sure it will stay set. When you call out to third-party code, it’s entirely possible that the first thing it does is to change, or unset, the thread context ClassLoader. If it does, then you’re a little stuck! All we can suggest is that if you’re having thread context ClassLoader issues, then this is something to try.

If setting the thread context ClassLoader is too invasive for your taste, you don’t have the source, aren’t allowed to change the source by its license, or it doesn’t work, then all isn’t lost. Another, somewhat specialized, manifest header can come to the rescue. Given that this is the case, you may wonder why we haven’t been extolling its virtues throughout this chapter. The answer is simple: this header comes with a price.

12.3.3. DynamicImport-Package—a saving grace?

We’ve spent a lot of time throughout this book discussing the use of package imports to achieve modularity. By correctly describing your dependencies, you gain a powerful understanding of your application and a lot of flexibility. There has been one consistent message, which is that until all of your dependencies are available, your bundle can’t be used. This statement is weakened somewhat when using the optional directive, which allows your bundle to start in the absence of some packages. Importantly, the optional directive is an all-or-nothing deal: either your package will be wired when your bundle resolves, or it never will (unless the bundle is reresolved).

DynamicImport-Package operates like an optional import in that it won’t stop your bundle from resolving. If that was all DynamicImport-Package did it would be useless, but there’s an important difference. Whereas an optional import is either wired or not wired, you can think of DynamicImport-Package as imports that aren’t wired yet.

Dynamic package imports indicate to the OSGi framework that it should attempt to wire the package when the bundle first attempts to load something from it. If the wiring fails, then so does the attempt to load the resource. Critically, when a dynamic import has been successfully wired, it’s wired for good. At this point, a dynamic import is no different from a normal one.

Another key difference between dynamic imports and normal imports is that dynamic imports can be defined using wildcards, as follows. This allows a bundle to import a package that’s unknown when the bundle manifest is created. This is one of the most useful features of the DynamicImport-Package header:

DynamicImport-Package: fancyfoods.*

In the above example, see how you can import any package that starts with fancyfoods. into your bundle. You could be even more general and import any package using * with no prefix. As with normal package imports, you can add version ranges and attributes to your imports.

Dynamicimport-Package and Logging

You can use dynamic imports to help you with your logging library. If you think back to the problems we discussed, many of them were related to class and resource loading. If you add a DynamicImport-Package: * header to your logging API, then you no longer need to worry about all of the unpleasant imports for various logging implementations. In this case, the implementations can be wired dynamically as they’re loaded.

Dynamic imports can also help you with configuration and custom implementations. As long as the package containing the configuration file or custom class is exported, then there will be no problem loading that either. Dynamic imports seem to be the wonder solution to your logging problems! Unfortunately, this isn’t the case. There are a number of significant problems.

The Problems with Dynamic Imports

Dynamic imports are extremely powerful, but are also over-used. It may be hard to imagine how dynamic imports could be a problem at first, but there’s one example that clearly shows how DynamicImport-Package is a poor way to load resources.

Assume that your logging library expects to find configuration in the file META-INF/logging-config.xml. In this case, you would need to export META-INF as a package from the client bundle and dynamically import it using either Dynamic-Import-Package: * or DynamicImport-Package: META-INF. Let’s start by considering DynamicImport-Package: *. Although it’s fun to use, because it requires little thought on the part of the person writing the bundle, the performance and modularity implications are bad. Any time a resource is loaded, OSGi’s nicely structured classpath is turned into a big, slow, flat classpath. Searching this classpath is slow. Furthermore, the resource could end up being loaded from anywhere—you’ve bypassed all of OSGi’s explicit dependency management. DynamicImport-Package: META-INF is a little bit nicer, but it still doesn’t scale well to multiple client bundles. Which META-INF will you wire to? How do you version it sensibly? When the package is wired, either you’ll get a random configuration file, which would be disastrous, or, if you select the right bundle using attributes, your logging implementation is tied to exactly one client! Even in the case where you don’t have to export META-INF, you still end up in a situation where you can only wire to one bundle for the package, something that’s never going to cope with multiple versions of the same bundle in the runtime.

The problems with dynamic imports don’t stop there. Because dynamic imports behave like optional imports at resolution time, there’s no guarantee that the package will be available to the running bundle. This means that your library must cope with the possibility that one, some, or all of its dynamic imports won’t be available at runtime!

Effectively, dynamic imports are a means of circumventing the modularity of your bundle. If used properly, they’re an invaluable tool, but for the most part they’re best avoided.

12.3.4. Other problems that are more difficult to fix

The problems we’ve discussed so far have all had to do with modularization. Effectively, the libraries rely on the relatively poor modularity of Java SE and suffer problems when packaged as OSGi bundles. These sorts of problems can often (but not always) be fixed, if only in a limited way, by careful packaging. A different class of problems are altogether more unpleasant, though fortunately they’re also much rarer. These are problems to do with bundle lifecycle and the structure of the framework itself.

URLS for Classpath Scanning

Some libraries attempt to do clever things by finding resources on the application’s classpath. Typically, they look for resources using a pattern, but crucially they don’t know the name of the resource before they find it. One example of this is if you wish to find and run all classes that have a name ending in Test. To do this, the library typically looks for the root of the classpath by asking the classloader for the resource corresponding to "/". Clearly, there are modularity problems here, but even if the library found the correct root URL, there’s another, bigger problem waiting.

When the framework has a root URL, it typically identifies it as either being a file: or a jar: URL. After it has done this, it locates the directory or JAR on the filesystem, and starts to list its contents, looking for matching files. The issue here is that OSGi doesn’t require the framework to have a fixed layout on the filesystem, or even be on disk at all! The OSGi specification expects the framework to return URLs in a custom namespace. This completely breaks any scanning performed by non-OSGi-aware libraries.

The only way around this sort of problem is to rewrite the library (or plug into it, if you can) to be OSGi-aware when it’s scanning. The only safe ways to scan bundles are by using findEntries() on a Bundle or listResources() on a BundleWiring (the latter is only available on OSGi 4.3–compatible frameworks).

Resource and Thread Management

If the library you’re using creates its own threads or manages resources, for example database connections, then it needs to correctly terminate or close them to prevent erratic behavior and leaks. Given that many libraries are unaware of the OSGi lifecycle, it’s likely that the library won’t respond to either being stopped, or uninstalled, correctly, and will probably leave orphaned threads or resources.

This sort of problem can also be demonstrated if clients are stopped, uninstalled, or refreshed. Without proper resource management, the library can begin leaking memory, or worse, hanging on to things related to bundles that no longer exist.

These problems can be extremely difficult to fix, but in some cases it can be enough to add an Activator class to your bundle (see appendix A) and manifest. If this class is used to perform necessary cleanup when the library is stopped, then it can prevent the leakage that would otherwise occur.

12.4. Summary

In this chapter, we’ve taken an extensive look at how libraries developed for standard Java can be used in OSGi. We don’t intend this chapter to be a cautionary tale, although most of the difficulties we describe here are used as reasons why OSGi is too hard or broken. For the most part, libraries converted to run in OSGi can be used without any intervention or special effort; it’s merely unfortunate that some commonly used libraries suffer from the majority of the problems.

We hope that, having read this chapter, you understand how easy it is to take a JAR and package it as an OSGi bundle, preferably using a combination of tooling and hand-finishing. Although this isn’t always the end of the story, for many libraries this is all you need to do.

If you’re unlucky enough to come across problems after converting your library, we hope that you also feel confident enough to work through them. Some of the classloading problems that appear can be hairy, but most can be avoided or fixed with a little intelligence and some judiciously applied packaging changes.

Now that you’ve seen how to pull libraries into OSGi, even when they weren’t originally written to use it, you’ve reached an advanced level of knowledge. You’ve reached the point where your OSGi knowledge is sufficient for you to start building your own runtimes. In the next chapter, we’ll look at doing exactly that: comparing the features offered by a range of prepackaged runtimes with those that you can piece together yourselves.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.38.176