Appendix A. OSGi—the basics

OSGi is a big subject, and we’re not going to attempt to cover all of it in a single appendix. Instead, we’ll be going over the basics of OSGi at a high level. We’ll also delve into greater detail into some aspects of OSGi that may not be familiar to most readers, but that are important to understand when writing enterprise OSGi applications. For a more comprehensive introduction to OSGi and OSGi reference, have a look at OSGi in Action by Richard Hall, Karl Pauls, Stuart McCulloch, and David Savage (Manning, 2011).

A.1. Where did OSGi come from, and where is it going?

One of the stories of software engineering has been that of increasing abstraction and resulting improvements in modularity. The earliest programs were written in assembly language, which mapped directly to the instruction set of the machine executing the program, or coded directly as machine instructions. There was no higher-level structure above the individual machine codes, little abstraction, and limited scope for sharing and reusing programs. Because there was so little abstraction between the code and the hardware it ran on, even slight hardware changes could mean that the application had to be extensively rewritten. Higher-level languages introduced subroutines, allowing code to be grouped into named functions and reused. The abstraction away from raw machine instructions also meant that code could be recompiled rather than rewritten for new hardware. Next came libraries, large groupings of code with a separate interface and implementation. This abstraction allowed different pieces of code to be changed independently from one another without rebuilding entire applications; these libraries are therefore modular code. Finally, object orientation provided finer-grained modularity by grouping data and behavior together into encapsulated objects. This abstraction substantially improved the level to which changes in a program could be isolated from the rest of the program, and the level to which code could be reused, marking a dramatic improvement in modularity.

Although object orientation provided good modularity, it didn’t provide enough modularity, particularly for embedded systems. A nonprofit industry consortium known as the OSGi Alliance set out to design a system that enabled greater modularity in Java. In addition to Sun, IBM, and Oracle, its original members were, for the most part, mobile phone manufacturers like Motorola and Ericsson, and networking companies like Lucent and Nortel. There were also energy companies involved, such as Electricité de France and the late Enron Communications. OSGi is used in a surprising range of systems, from cars and locomotives to set-top boxes and application servers.

Because of the small physical size of an embedded device, the Java classpath for each software component had to be well-defined and compact. Devices might be physically appearing and disappearing off the network at any time as users plugged them in, so the coupling model had to be loose and fully dynamic. Modern application servers have a lot of disk space accessible to them, but they still have classpath problems. Because they’re trying to do so much, their classpath will bloat uncontrollably unless there’s some sort of dependency-management scheme in place. Similarly, the enormous span of the system means that the abilities to keep couplings loose and dynamically register and unregister components are essential.

OSGi came to the attention of the mainstream Java community in 2003, when Eclipse made the technical shift to OSGi. Eclipse was already using a home-rolled modularity system to power its plug-in architecture, so it made a lot of sense to switch to a more standardized solution. After Eclipse made the leap, application server vendors then started to base their servers on OSGi. IBM WebSphere, Oracle WebLogic, Apache Geronimo, GlassFish, and JBoss are now all built out of OSGi bundles. OSGi is firmly in place in middleware implementations, and most middleware vendors are members of the OSGi Alliance.

A.2. Versions

We discuss OSGi versioning in sections 1.2.1 and 5.1.1. But versioning is important enough that it’s worth revisiting to re-emphasize the basics and cover some of the subtler points before we continue with our OSGi refresher.

Bundles, exported packages, and imported packages may all have versions. Bundles and package exports specify an exact version, but package imports use a version range. Although versions are optional, being disciplined about specifying versions is a good practice and can avert a range of compatibility problems as development progresses.

Version ranges are defined using the standard mathematical convention for intervals. Square brackets represent an inclusive range, and round parentheses an exclusive range.

A.2.1. The semantic versioning scheme

Versioning is a way of communicating about what’s changing (or not changing) in software, and so the language used must be shared. Consumers of a class must be able to distinguish between changes that will break them by changing an API, and changes that are internal only.

The OSGi Alliance recommends a scheme called semantic versioning. It’s simple, but it carries much more information about what’s changing than normal versions. Every version consists of four parts: major, minor, micro, and qualifier (M.m.µ.q). A change to the major part of a version number—for example, changing 2.5.0 to 3.0.0—indicates that the code change isn’t backward compatible. Removing a method or changing its argument types are examples of this kind of breaking change. A change to the minor part indicates a change that is backwards compatible for a consumer of an API, but not implementation providers. For example, the minor version should be incremented if a method is added to an interface. If a change doesn’t affect the externals, it should be indicated by a change to the micro version. Such a change could be a bug fix, or a performance improvement, or even the removal of methods from classes that are internal to a bundle. Having a strong division between bundle internals and bundle externals means the internals can be changed dramatically without anything other than the micro version needing to change. Finally, the qualifier is used to add extra information like a build date.

A.2.2. Guarantees of compatibility

One of the benefits provided by the semantic versioning scheme is a guarantee of compatibility. A module will be bytecode compatible with any versions of its dependencies where only the minor or micro version has changed.

Backward compatibility is no guarantee of forward compatibility, and so modules should not try to run with dependencies with minor versions lower than the ones they were compiled against.

A.2.3. Coexistence of implementations

The other major benefit provided by versioning is that it allows different versions of a module to coexist in the same system. If the modules weren’t versioned, there would be no way of tagging them as different and isolating them from one another. With versioned modules, OSGi classloading allows each module to use the version of its dependencies that is most appropriate (see figure A.1).

Figure A.1. The transitive dependencies of a module (the dependencies of its dependencies) may have incompatible versions. OSGi allows the implementations to coexist by isolating them from one another.

When importing a package, it’s always a good idea to add version constraints. Usually, the minimum is the version currently being used, and the maximum is the next major increment (non-inclusive). It’s not sensible to accept the next major increment, because you know (because of semantic versioning) that this version will have breaking changes in it. Minor version increments can be safely accepted, and they may bring bug fixes or other improvements. The following import statement will resolve against any version of the food.nice package above or equal to 1.4.3, but less than 2.0.0 (which would contain a breaking change):

Import-Package: food.nice;version="[1.4.3, 2.0.0)"

A.2.4. Why everything has a version

Every exported package and bundle in OSGi has a version. Even if you don’t specify an explicit version, you still have an implicit version of 0.0.0. This version may or may not be what you want. For example, let’s assume you release a package without specifying a version (your version is 0.0.0):

Export-Package: food.experimental

Other bundles can import the package in several ways. If no version is specified as follows, any available version of the package will be imported:

Import-Package: food.experimental

This is equivalent to the following:

Import-Package: food.experimental;version="0.0.0"
Warning: Precise Versions and Version Ranges

Package imports are always ranges. If only one version is specified on a package import, it’s a minimum version, not an exact version. This isn’t totally intuitive, so it’s easy to forget, even for developers who’ve been using OSGi for years. To specify a precise version of 1.0.0 on a package import, you would need to write version="[1.0.0, 1.0.0]". Specifying version="1.0.0" means any version greater than 1.0.0.

But this import is pretty vague. The importer risks being broken by changes to the food.experimental that aren’t backward compatible. To avoid this, the importer should explicitly specify a minimum and maximum range as follows:

Import-Package: food.experimental;version="[0.0.0,1.0.0)"

But what’s a sensible range? The rules of semantic versioning don’t apply to versions below 1.0.0. Some projects (including Apache Aries) treat all minor increments below versions 1.0.0 as potentially breaking changes. One sensible upper range is "[0.0.0,0.1.0)". On the other hand, when you release a new version of the food.experimental package, you may decide to reform your nonversioning ways, and start on a clean slate with a sensible version of 1.0.0. Neither an import of "[0.0.0,1.0.0)" nor "[0.0.0,0.1.0)" would be satisfied by a food.experimental package exported with version 1.0.0, even if nothing significant had changed. The net effect of this ambiguity is uncertainty for importers of the package. They won’t know whether a change from 0.0.0 to 1.0.0 is a breaking change or a trivial change to move to a versioned package. The easiest way to avoid all the confusion is to start off as you mean to go on and give packages meaningful versions from the first release.

A.2.5. Consumers and providers, not clients and implementors

One of the most confusing things about semantic versioning is that it can’t be boiled down to, “I implement interface X, therefore I need a version range of [1,1.1).” This is why we talk about API providers and consumers rather than any less generic terms.

A provider of an API isn’t only the bundle that exports the package, but also any bundle that provides a backing implementation for use by a consumer. A good example here would be the Servlet API and a Web Container implementation (for example, Jetty). The Servlet API bundle is clearly a provider, but less obviously, so is Jetty. This is because consumers of the Servlet API (web applications, to the developer) expect the presence of a Web Container as well as the API classes.

It doesn’t normally take much of an effort for people to understand that providers may not export the API they use, they can still fall back on the mental model that if they implement an interface then that makes them a provider. Unfortunately, this isn’t correct! Once again the Servlet API provides a good example. Clearly a web application is a consumer of the Servlet API, but web applications extensively implement abstract classes and interfaces from the Servlet API. Try writing a functional servlet without extending Servlet or HttpServlet. There are plenty more examples in the Servlet API, but also in other APIs.

To truly understand semantic versioning, you need to have a clear division between API interfaces that are implemented or extended by a provider (for example HttpServletRequest), and ones that are implemented or extended by the consumer (for example HttpServlet). If you add a method to an interface implemented by a consumer, then this is a breaking change that needs a major version increment. After all, it would be pretty disastrous for most web applications if you were suddenly expected to implement a new doSuperGet() method on all your servlets!

A.2.6. Semantic versions and marketing versions

It’s important to remember that semantic versions are different from marketing versions. Versioning is about communication, and it only works if everyone uses the same language. Because not everyone is using semantic versioning, marketing versions can go out of step with semantic versions. This can feel counterintuitive; for example, the Servlet 3 API is correctly versioned at 2.6. Similarly, JPA 2 packages should be versioned at 1.1. The OSGi Enterprise Specification specifies versions for these packages, but several of the servers we discussed in chapter 13 include bundles that were released into the wild with incorrect versions of the javax.servlet and javax.persistence packages. This can cause difficulties for consumers of these packages, which can’t use the semantic versioning rules on the imports.

A.3. Bundles

Bundles are one of the most basic OSGi constructs. A bundle is a normal Java JAR, but with extra metadata in its manifest that identifies it as an OSGi bundle. Bundles have special dependency management and classloading behavior that allow much greater modularity. Unlike normal JARs, bundles also have a lifecycle.

A.3.1. Manifest headers

The most important OSGi manifest header is the bundle symbolic name, but there are many other possible headers. Table A.1 lists the defined OSGi manifest headers. We’ve included all of the core and enterprise OSGi headers; more are defined in the Compendium Specification. (Confused about which specification is which? Have a look at appendix B.)

Table A.1. OSGi manifest headers

Header

Attributes and directives

Comments

Bundle-ActivationPolicy lazy blueprint.timeout See section A.4.
Bundle-Activator   See section A.4.2.
Bundle-Blueprint   See section 2.3.3.
Bundle-Category    
Bundle-Classpath   Defaults to ".". Particularly useful for WABs, where WEB-INF/classes is a sensible value.
Bundle-ContactAddress    
Bundle-Copyright    
Bundle-Description    
Bundle-DocURL    
Bundle-Icon size  
Bundle-License description link  
Bundle-Localization    
Bundle-ManifestVersion   Should always be present and set to 2.
Bundle-Name    
Bundle-NativeCode language osname osversion processor selection-filter  
Bundle-RequiredExecutionEnvironment   In common use until v4.3, but now deprecated.
Bundle-SymbolicName singleton Required.
Bundle-UpdateLocation    
Bundle-Vendor    
Bundle-Version   See section A.2.
DynamicImport-Package bundle-version version See section 12.3.3.
Export-Package bundle-symbolic-name bundle-version exclude include specification-version uses version See sections 5.1.2 and 7.2.1.
Export-Service   Deprecated.
Fragment-Host bundle-version extension See sections 2.2.4 and 7.1.2.
Import-Package bundle-symbolic-name bundle-version resolution specification-version version See sections 5.1.2 and 7.2.1.
Import-Service   Deprecated.
InitialProvisioning-Entries    
Meta-Persistence   See chapter 3.
Provide-Capability effective:1 uses See chapter 7.
Remote-Service   See section 10.4.2.
Require-Bundle bundle-version resolution visibility See section 5.1.2.
Require-Capability effective:1 filter resolve See chapter 7.
SCA-Configuration   See section 10.6.
Service-Component   Path to declarative services metadata files. Wildcards may be used. See section 6.2.3.
Web-ContextPath   See section 2.2.1.

A.3.2. The bundle context

Every active bundle has a bundle context. The bundle context acts as an intermediary between the bundle and the outside framework. The bundle context can be used to programmatically install new bundles and query the existing bundles. It can also be used for more advanced functions, such as requesting persistent storage areas, subscribing to events, registering services, and retrieving services.

A.3.3. Dependency management

One of the main functions of OSGi bundles is to allow dependencies to be explicitly declared and resolved. Resolving is an import concept, and we discussed it in depth in chapter 7. Resolving is the process by which the OSGi framework determines whether bundles have all of their required package imports, and which bundles should supply these package imports. (It’s the absence of resolving in standard Java that results in the dreaded ClassNotFoundExceptions at runtime.) We say a bundle is wired to another bundle if it provides classes to that bundle.

Dependencies may be declared with Import-Package: or Require-Bundle: headers. As we explained in chapter 5, using Import-Package: is a best practice.

A.3.4. Classloading

OSGi’s classloading uses a different model from conventional Java classloading. Instead of every class in the virtual machine being loaded by a single monolithic classloader, classloading responsibilities are divided among a number of classloaders (see figure A.2). Each bundle has an associated classloader that loads classes contained within the bundle itself. If a bundle is wired to a second bundle, its classloader will delegate to the other bundle’s classloader for classes exported by the second bundle. In addition to the bundle classloaders, there are environment classloaders that handle core JVM classes.

Figure A.2. The JVM contains many active classloaders in an OSGi environment. Each bundle has its own classloader. These classloaders delegate to the classloaders of other bundles for imported classes, and to the environment’s classloader for core classes.

Each classloader has well-defined responsibilities. If a classload request can’t be satisfied, it will pass it up the delegation chain (see figure A.3).

Figure A.3. The classloaders form a delegation chain. If a classloader can’t load a given class, it passes the request to the next classloader in the chain.

Substitutability

Somewhat surprisingly, being included in a bundle doesn’t guarantee that a package will be wired to that bundle. This is a principle known as substitutability. It allows bundles to maintain a consistent class space between them by standardizing on one variant of a package, even when multiple variants are exported. Figure A.4 shows the class space for a bundle that exports a substitutable package.

Figure A.4. The class space for a bundle includes all of its private classes, and the public classes of any bundle it’s wired to. It doesn’t necessarily include all of the bundle’s public classes, because some might be imported from other packages instead.

Before version 4 of OSGi, substitutability was guaranteed; a bundle implicitly imported all the packages it exported. From version 4 onward, substitutability must be explicitly enabled by importing exported packages. This is an OSGi best practice. The following manifest exports a substitutable package:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.supermarket.core
Bundle-Version: 1.0.0
Export-Package: com.supermarket.api
Import-Package: com.supermarket.api, some.other.package

A.3.5. Fragments

Fragments are extensions to bundles. They attach to a host bundle and act in almost every way as if they were part of the host (see figure A.5). They allow bundles to be customized depending on their environment. For example, translated resource files can be packaged up by themselves into a fragment and only shipped if needed. Fragments can also be used to add platform-specific code to a generic host.

Figure A.5. A bundle fragment attaches to its host and shares a classloader.

Some OSGi developers prefer to avoid bundle fragments. Although they have some useful behaviors, many of the normal rules for OSGi bundles don’t apply to fragments, which can cause things to go awry in subtle ways. A colleague of ours describes fragments as bundle flakes!

A.4. Bundle lifecycles

Unlike normal JARs, OSGi bundles have a lifecycle. They can be stopped and started on demand, with their classloaders and classes appearing and disappearing from the system in response. Figure A.6 shows the complete state machine for OSGi bundles.

Figure A.6. Bundles may move between the installed, resolved, starting, active, and stopping states. A starting bundle can be lazily activated, and if so it won’t move to the active state (crossing the dashed line) until it’s needed by another bundle. A bundle is resolved if it’s installed and all its dependencies are also resolved or started. When a bundle is uninstalled, it’s no longer able to start, nor can it provide packages to any new bundles.

A.4.1. Activation policies

A bundle has some control over when it moves between these states. After a bundle gets started, it may move immediately from the STARTING state to the ACTIVE state (eager activation), or it may wait until one of its classes is loaded (lazy activation). The default policy is eager activation; lazy activation may be enabled by adding the following to the manifest:

Bundle-ActivationPolicy: lazy

A.4.2. Bundle activators

Before moving into the ACTIVE state, a bundle will have a chance to perform some initialization. If a bundle declares an activator, its activator will be notified whenever the bundle is started or stopped. The activator class is declared in the bundle manifest as follows:

Bundle-Activator: com.supermarket.Activator

Although they’re widely used and handy in core OSGi, bundle activators aren’t such a common pattern in enterprise OSGi. Chapter 6 demonstrates how Blueprint allows you to do everything you can do with an activator, but in a much more flexible and powerful way.

A.4.3. Installation and resolution

Installation is the process by which new bundles are added to an existing OSGi framework at runtime. This can be done in a number of framework-specific ways, but there’s a commonly used, framework-independent mechanism for installing a bundle from any local or remote location.

The BundleContext can be used to install a bundle using a string URL that points to a bundle file somewhere in the ether, as follows:

org.osgi.framework.BundleContext.installBundle(String location)

After installation into a framework, an OSGi bundle is in the INSTALLED state, and is a pretty boring, inert object. An INSTALLED bundle doesn’t have a classloader and can’t provide code or packages to anyone. What happens to the bundle next is the real magic of OSGi, and is known as the resolution process. When all of a bundle’s dependencies are available in the OSGi framework (there are exported packages available for all of its imports), then the framework resolver will attempt to resolve the bundle. This process creates fixed wires between package imports and exports, obeying the versioning criteria declared in the metadata. If a consistent set of wires can be created for a bundle, then that bundle is said to be RESOLVED. A RESOLVED bundle is much more interesting than an INSTALLED bundle—it has a classloader, and so classes and resources can be loaded from the moment the bundle resolves. The wires created by the resolver have a huge impact on how classes are loaded in OSGi, but also guarantee that dependencies are available at runtime and eliminate the risk of a NoClassDefFoundError.

A.4.4. Starting and stopping bundles

After a bundle has entered the RESOLVED state, it’s eligible to be STARTED. Given that classes can be loaded from a bundle as soon as it’s resolved, some people get confused as to what starting a bundle means. A few things happen when a bundle starts, such as the invocation of a bundle’s BundleActivator, if it has one, and moving the bundle to the ACTIVE state.

The most important change on starting a bundle is that an active BundleContext is created for a bundle as it starts. The BundleContext can be used by a bundle to interact with the framework, influence the lifecycle of other bundles, and most importantly, access the OSGi Service Registry.

Stopping a bundle is, unsurprisingly, more or less the inverse of starting a bundle. The framework moves the bundle back into the RESOLVED state, it calls the stop method on the bundle’s BundleActivator, and it ensures that any services registered by the bundle are unregistered from the Service Registry.

A.4.5. Uninstalling and updating bundles

When a bundle is no longer needed in a runtime it can be uninstalled. Contrary to what you might expect, uninstalling a bundle doesn’t remove it from the runtime! Uninstalled bundles are marked so that they’re no longer eligible to provide packages when the framework resolver attempts to resolve new bundles, but they continue to provide packages to existing bundles.

An uninstalled bundle that’s still providing packages to other bundles (that aren’t also uninstalled) can’t be removed immediately; doing so would forcibly take the package away from the running bundles and result in a rather unpleasant mess. Uninstalled bundles are only able to be discarded by the framework when no other resolved bundle is wired to them. Typically, after a bundle is uninstalled, the framework is refreshed using PackageAdmin.refreshPackages() or bundle.adapt(FrameworkWiring.class).refreshBundles(). This causes the framework to reresolve any bundles that are using packages from an uninstalled bundle. This allows the framework to clear up the uninstalled bundles. An uninstalled bundle that’s unable to be removed because it’s still providing packages to another bundle is sometimes known as a zombie bundle.

Updating a bundle is an operation that isn’t possible in standard Java. An update is like an atomic uninstall/reinstall operation that allows the content of a bundle to be changed. The full explanation of bundle update behavior is rather involved, and a complete description isn’t in the scope of this book, but one of the more common side effects is as a direct result of the behavior for an uninstallation. If a bundle A1 is being updated to A2, and it provides packages to another bundle B, then the old version of the bundle A1 can’t be removed without breaking B. This situation doesn’t stop the update from occurring, but it does mean that you end up with both A1 and A2 available at the same time! B will continue to use A1 (until it’s refreshed), but future resolutions will all wire to A2. Similarly to uninstallation, refreshing the framework after an update allows the framework to clear up old versions of bundles and to generally sort everything out.

If you’re confused by uninstallation and update, we don’t blame you; they’re conceptually the most difficult part of the bundle lifecycle (though writing an OSGi resolver would be a harder task if you were to implement a framework!). Probably the most important thing to take from this is that dynamic dependency management is hard, and sometimes you can end up in some pretty odd situations. Fortunately OSGi has solved these issues with well defined, if slightly interesting, consequences.

A.4.6. Managing dependencies in a dynamic system

Bundles may be stopped at any time. What happens to bundles that depend on the stopped bundle? In most cases, very little. When a bundle is stopped, it still has a ClassLoader, so its exported packages remain available to other bundles that are wired to it. One thing that does change is that the stopped bundle no longer has a BundleContext. This means that any services that the bundle registered while it was started are forcibly unregistered by the framework (assuming the bundle didn’t tidy up after itself!). Obviously, bundles that depended on these services are no longer able to use them, but if the stopped bundle is started again, then things usually (in a well-designed system) return to their original state.

Uninstalling a bundle is a much more destructive operation than stopping it. An uninstalled bundle isn’t able to provide packages in any resolution, and it definitely can’t provide any services. The framework automatically removes uninstalled bundles, but sometimes this isn’t possible. If the uninstalled bundle was providing packages to other bundles before it was uninstalled, then these wirings will keep the uninstalled bundle’s ClassLoader alive. This is often called a zombie bundle; the bundle isn’t alive anymore, and there’s no way to bring it back, but somehow it’s still walking around. The system can be reconciled by restarting the framework, by calling refreshPackages on the PackageAdmin service, or by calling refreshBundles on the FrameworkWiring service. Any bundles that depended on the now-absent uninstalled bundle will be stopped and re-resolved. This may mean that they move all the way back to the INSTALLED state.

Stopping and uninstalling aren’t the only actions that can impact dependent bundles. OSGi bundles can be dynamically updated, and an update may remove previously exported packages or introduce new package requirements. As with uninstalled bundles, the removed exports will remain available to existing users (from the old pre-update version of the bundle) until the framework is restarted or refreshed.

A.5. Services

Services are an extremely important part of OSGi. Services allow bundles to share object instances in a loosely coupled way.

Figure A.7 shows a simple OSGi service, represented by a triangle. The pointy end faces the provider, not the consumer. The reason is that the service can only have one provider, but it can have lots of consumers.

Figure A.7. A service that is provided by one bundle and used by another bundle. The triangle points toward the service provider.

A.5.1. The Service Registry

Management and discovery of services is handled by a shared Service Registry. Services are registered and retrieved using interface names and optional query parameters. (We’ll come to service filters in section A.5.3.)

Services are registered on the bundle context. (If you’re using Blueprint, this happens under the covers for you.) Services can only be provided by bundles that are STARTED. When a service is registered by a bundle, it provides one or more class names that mark the API of the service, and the service object itself. Optional properties can provide extra information about the service, and can be used to filter what services get returned when services are looked up. Service properties aren’t intended for use by the service itself.

If Blueprint or Declarative Services aren’t used, registration of a service can be done in a couple of lines of code as follows:

By using String interface names and simple property types, the OSGi Service Registry allows OSGi bundles to avoid worrying about their class spaces. If two versions of a service interface exist, then it’s only possible to use an implementation that you share a view of the interface with. If you were to try to use a service that was in a different class space from your bundle, then you would generate a ClassCastException. This may seem odd, but in Java, a class is identified not only by its name, but by the classloader that loaded it.

A.5.2. Accessing services

In conventional OSGi, services are looked up using the bundle context. As you’ve seen, enterprise OSGi allows services to be accessed declaratively instead. This is almost always a best practice, because it’s both easier and more robust. What are you missing out on by using Blueprint? The following example looks up a service, but without any error-handling code:

String interfaceName = SpecialOffer.class.getName();
ServiceReference ref = ctx.getServiceReference(interfaceName);
SpecialOffer lister = (SpecialOffer) ctx.getService(ref);

After you’ve added in code for error-handling, cleanup, and accounted for OSGi’s dynamism, the code to cleanly look up a service is much longer. Almost every line needs a null check. This is why Blueprint is a best practice!

Cleaning up afterwards

When code is done with a service, it should call ungetService to release the service. Forgetting to unget services prevents the framework from being able to free up the resources associated with a service, and may prevent an application from operating properly.

When using the standard lookup method, the OSGi framework ensures that you only ever find services that share your class space for their declared API. This guarantees that you can always use any service that you find using that method, though it does sometimes lead to some confusing debugging when a lookup ignores a seemingly good service!

Consuming multiple services

What happens when multiple providers of the service have been registered? The service consumer has a choice between getting one, or getting a list containing all of them (see figure A.8). If the service is something like a credit card processing service, it’s only necessary to take a payment once. In this situation, one service provider is sufficient, and it probably doesn’t matter too much which provider is chosen. In the case of a logging service, on the other hand, logged messages should be sent to all the available loggers as follows, rather than one of them:

Figure A.8. A service consumer may consume multiple instances of a service.

Service Lifecycles

Services are much more dynamic than you may be accustomed to. They may appear and disappear at any time. This allows for more interesting applications, but it also creates some challenges when handling services. Blueprint does a lot of this work for you, which is why it’s preferable to the raw service APIs.

If a bundle is stopped, any services it registered are automatically unregistered. Users of those services need to be able to cope with the possibility of their services going away. For example, a logging service might be writing to a remote disk that is disconnected. Instead of the service staying available even though the disk it manages is gone, the entire service will be unregistered.

What happens to users of a service if the bundle hosting the service is stopped? The service is unregistered, but instances that have already been created won’t magically vanish. But they’ll be stale. The behavior of such services is undefined; they may continue to work fine, or they may throw exceptions. To avoid being left holding stale services, the use of ServiceTracker objects to detect when services are unregistered is a good practice in conventional OSGi. But it adds a lot to the volume of code required to use services. The enterprise OSGi technologies of Declarative Services and Blueprint both provide neater handling of the service dynamism.

Using ServiceTrackers

Although ServiceReferences are the most obvious way to access services programmatically, the API is pretty low-level and subject to several potential problems. Therefore, even developers who aren’t using Declarative Services or Blueprint tend to prefer a higher-level API: the ServiceTracker. As the name suggests, ServiceTrackers allow you to track services as they’re registered and unregistered, and take appropriate actions. But they also simplify normal service access by eliminating the need to unget services. For this reason, using service trackers instead of service references is considered a best practice. The following code gets a service using a ServiceTracker, but with the error handling code that was missing from the earlier ServiceReference example:

String name = SpecialOffer.class.getName();
ServiceTracker<SpecialOffer, SpecialOffer> tracker;
tracker = new ServiceTracker<SpecialOffer, SpecialOffer>(
        ctx, name, null);
tracker.open();
SpecialOffer offer = (SpecialOffer) tracker.getService();
if (offer == null) {
    throw new RuntimeException(
            "The SpecialOffer service is not available.");
}
tracker.close();

ServiceTrackers also allow you to access services in a way that’s slightly insulated from startup order effects; because bundles may start in any order in an OSGi framework, services may be registered in any order. Code that expects services to always be present may work most of the time, but then drive you crazy with timing bugs. As with most things to do with service access, Blueprint eliminates this complexity. If you aren’t using Blueprint, it would be a good idea to wait for a service as follows, rather than getting it and expecting it to be present:

SpecialOffer offer = (SpecialOffer) tracker.waitForService(TIMEOUT);

A.5.3. Filters

Filters are widely used in OSGi. They’re sometimes used in bundle manifests, but their main use is in looking up services. The OSGi filter syntax will be familiar to those who write LDAP filters. Every filter expression has the following general format:

(
Attribute
        Operator
        Value
)

(There shouldn’t be any whitespace between the elements in the filters.) Attribute is the name of the property to be selected for, Operator is a Boolean operator, and Value is the value to be matched. The "=", "~=","<=", ">=", and "!" operators are supported. The reserved characters "","*","(", and ")" must be escaped if they’re contained in the value.

For example,

(aisle=fruit)

matches something whose aisle is fruit.

More complex constructions are also possible using and ("&") and or ("|") in front of expressions surrounded by parentheses. For example,

(&(aisle=fruit) (colour=blue))

will match something whose aisle is fruit and whose color is blue (probably a blueberry!).

Substrings may be matched using the * operator; for example,

(colour=b*)

will match items whose color begins with “b”—“blue”, for example, and also “brown.”

Like many things, filters are created using the BundleContext, as follows:

Filter f = ctx.createFilter("(log.type=text)");
ServiceReference[] refs;
refs = ctx.getServiceReferences(Logger.class.getName(), f.toString());

So far we’ve been discussing what you can do in an OSGi application. Now let’s consider how you run an OSGi application.

A.6. OSGi frameworks

These core elements of OSGi—bundles, lifecycle management, and service infrastructure—are supported by an environment known as an OSGi framework. It’s useful to think of each element as a layer in an OSGi onion. (Like onions, OSGi has been known to make grown developers cry, but gives wonderful benefits.) Figure A.9 shows how the layers stack together. It’s possible to use OSGi bundles without ever touching bundle lifecycles or services. To take advantage of OSGi services, on the other hand, an application must already be making use of bundles.

Figure A.9. The functionality of OSGi includes a number of layers. The white elements are provided by OSGi, the gray elements are prerequisites, and the black elements are user content. Applications will take advantage of the core OSGi framework by providing modules, possibly with sophisticated lifecycles. These modules may communicate with one another and the supporting platform using OSGi services. Security is provided across all layers.

In addition to the layers we’ve already discussed, the OSGi platform includes a security layer that provides extensions to the Java 2 security architecture. The security layer cuts across the other layers. For a fuller discussion of OSGi security, see OSGi in Action (Hall et al., Manning, 2011).

A number of OSGi frameworks are in wide use today. The most popular are Eclipse Equinox, Apache Felix, Knopflerfish, and ProSyst. Equinox underpins the Eclipse IDE and Rich Client Platform, whereas Felix is at the heart of the Apache Tomcat application server. ProSyst is embedded in devices like routers and automotive GPS systems.

A.7. The OSGi console

The front-loading of dependency management in OSGi can take some getting used to. “Why can’t my bundle start?” is a common worry for the new OSGi developer—and also for the experienced one. The most convenient way to control the lifecycle of a bundle, inspect bundle states, and diagnose bundles that refuse to start is by using an OSGi console. The OSGi console is also a good way of exploring an OSGi framework to see what packages and services are available. An OSGi console is a fairly low-level tool, particularly in an enterprise environment, and the command syntax of the textual consoles can be intimidating. But a console can help debug OSGi issues, so it’s worth familiarizing yourself with one. Figure A.10 shows the Felix Gogo console, with the minimal set of bundles for the Felix framework and the Gogo console itself. (The Gogo console is an extension to the base Felix console, but one we recommend if you’re not using Equinox or a higher-level console like Karaf.)

Figure A.10. The Felix OSGi console, in a minimal Felix installation. Bundles may be queried, installed, started, and stopped. Available bundles may be listed using the lb command.

OSGi consoles do vary depending on the framework implementation. Figure A.11 shows the Equinox console. A number of third-party consoles are available, some of which have more inviting user interfaces. The essential capabilities are similar between implementations.

Figure A.11. The Equinox OSGi console. Available bundles may be listed using the ss command. In this case, the Fancy Foods application is installed, but many of its dependencies aren’t, so most of the application bundles don’t resolve (and are shown as INSTALLED only).

A.8. Summary

This appendix has been a relatively rapid introduction to OSGi. If you’re new to OSGi, we hope it helps give you a grounding in the basics; if you’re more experienced with OSGi, we hope it’s shed light on some of the finer points of core OSGi that are important to getting the most out of enterprise OSGi. A full introduction to core OSGi is beyond the scope of the book, but many excellent resources are available if you’re still keen to know more.

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

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