Tuesday, July 27, 2010

Standalone OSGi Resolver

When designing complex software system, modularity in one of the key aspects that folks have in mind. When I say "complex" I mean more than a handful of subsystems that evolve with individual life cycles and are developed by possibly geographically disconnected teams. Key to success are well defined integration points that also have the flexibility to evolve.

Naturally you separate API from implementation and have some way or another to discover/load the implementation for a given API. If your chosen infrastructure is worth telling your friends about, it supports multiple versions of any API and can dynamically start/stop/update the services that your subsystems use to collaborate with each other. The installed pieces form consistent class spaces with the same class possibly loaded multiple times in different versions and wired such that shared types are loaded from the same source. Any container environment that supports user defined plugins would have requirements similar to these. Take Eclipse, or a J2EE application server for example. 

As my colleague Hal puts it
"OSGi is like toilet paper, sooner or later you are going to want to use it."

Capabilities and Requirements 

An OSGi bundle is an ordinary jar with a couple of standard headers in its Manifest. Here I list a few important ones as a reference. The exhaustive set can be found in the OSGi Core Specification.

Bundle-SymbolicName: com.acme.daffy
The Bundle-SymbolicName header specifies a non-localizable name for this bundle. The bundle symbolic name together with a version must identify a unique bundle.
Bundle-Version: 1.1
The Bundle-Version header specifies the version of this bundle
Export-Package: org.osgi.util.tracker;version=1.3
The Export-Package header contains a declaration of exported packages.
Import-Package: org.osgi.util.tracker,org.osgi.service.io;version=1.4
The Import-Package header declares the imported packages for this bundle.
Require-Bundle: com.acme.chess
The Require-Bundle header specifies that all exported packages from another bundle must be imported, effectively requiring the public interface of another bundle.
In an abstract way, the bundle headers form a set of Capabilities that a bundle provides and another set of Requirements that a bundle has on the environment or other bundles. In most cases Requirements can be mandatory or optional and in some cases Requirements can be satisfied by Capabilities from the same bundle.

When the Framework makes a connection between a Requirement and a Capability it is said to have established a Wire. This happens at the RESOLVE phase of a bundle. The details of how an OSGi Framework works out the wires between the bundles is not our subject. Instead it is sufficient to know that every OSGi Framework implements this non-trivial, non-linear OSGi resolution algorithm in one way or another. Some Frameworks do this more successful than others and a distinguishing metric would be the success/error response time and ability to form consistent class spaces that include if not all but most of the installed bundles.

Why use a standalone Resolver

For most use cases the OSGi resolution algorithm is an implementation detail at the Framework level that you don't need to worry about. You install a few Bundles, start them or trigger a class load on an installed bundle and the Framework resolves the installed bundles transparently. Although there is an API method on the PackageAdmin to resolve bundles explicitly it is not what you would do normally.

The need for a resolver comes into the game when you want to do impact analysis or provide a smart repository. Impact analysis is when you have a running system and want to know if a set of bundles would resolve on that system without actually modifying the system. With a smart repository you can say, if have these requirements, give me the bundles that I need to install to provide the needed capabilities.

The Apache Felix Resolver

Every other month, when we both can make it, I meet Richard at the Enterprise Expert Group (EEG) meetings. Richard rewrote Felix resolver to work on the general notion of capabilities and requirements. This is part of felix-framework-3.0.1 and forms the foundation of what comes next.

Standalone Resolver Requirements

We had a number of requirements on an OSGi Resolver that I'd like to share with you here:
  • Independent of Core Framework implementation
  • Not bound to the Manifest as the meta data provider
  • Works on abstract notion of Module, Capability, Requirement, Wire
  • Explicit wiring for all mandatory requirements
  • Easy and intuitive user API
  • Extensible SPI for Resolver providers
  • Sensible default implementation stubs for Resolver providers
  • Callbacks for framework integration and resolved modules
  • Attachment support on various resolver atrifacts
Every framework parses/validates and caches the Manifest. So we did not want the resolver to do this again. Instead we provide a resolver meta data model that can be constructed in memory (i.e. mapped to from whatever the framework uses internally). Additionally, we provide an OSGi meta data model that can also be constructed in memory or from the Manifest.

In the JBoss Application Server we provide an OSGi view for non-OSGi deployments and the ability to access/inject OSGi services in other component models. For example CDI components can have OSGi services injected and vice versa. You will see this functionality in AS7. For that reason the resolver needs to work on the abstract notion of Module, Capability, Requirement which may come from non-OSGi deployments that nevertheless take part in OSGi resolution.

If a module is resolved, it is guaranteed that every mandatory Requirement has a Wire to a Capability.

Resolver clients can register callbacks to get informed when the Resolver changes the wiring for a Module.

The Resolver object model can have arbitrary state attached to it, which removes the need to map client state to resolver state and vice versa.

Working with the Resolver

The jbosgi-resolver project is hosted on GitHub together with all the other JBoss OSGi projects that we currently work on.  When you use Maven, you can declare a dependency on the resolver like this:

<dependency>
  <groupId>org.jboss.osgi.resolver</groupId>
  <artifactId>jbosgi-resolver-api</artifactId>
  <version>1.0.0</version>
</dependency>
<dependency>
  <groupId>org.jboss.osgi.resolver</groupId>
  <artifactId>jbosgi-resolver-felix</artifactId>
  <version>3.0.1</version>
</dependency>

<repository>
  <id>jboss-public-repository-group</id>
  <name>JBoss Public Maven Repository Group</name>
  <url>https://repository.jboss.org/nexus/content/groups/public/</url>
  <layout>default</layout>
  <releases>
    <enabled>true</enabled>
    <updatePolicy>never</updatePolicy>
  </releases>
  <snapshots>
    <enabled>true</enabled>
    <updatePolicy>never</updatePolicy>
  </snapshots>
</repository>

Lets start by creating a Resolver instance and add a Module to it that has its Capabilities and Requirements populated from Manifest.

// Create a resolver instance
XResolver resolver = XResolverFactory.getResolver();

// Create the resolver module
XModuleBuilder builder = XResolverFactory.getModuleBuilder();
XModule module = builder.createModule(1, manifest);

// Add the module to the resolver
resolver.addModule(module); 

Now you would create and add a few more modules until you actually trigger the resolve process

// Resolve the modules and report resolver errors
Set resolved = resolver.resolveAll(unresolved);
for (XModule resModule : unresolved)
{
   if (resModule.isResolved() == false)
   {
      Exception ex = resModule.getAttachment(XResolverException.class);
      log.error("Cannot resolve: " + resModule, ex);
   }
} 

The resolver uses the above mentioned Attachement API to feed back the individual resolve exceptions. You can also register a ResolverCallback when you need to do locking or want to get informed about resolver progress.

A few changes were necessary to felix-framework-3.0.1 to abstract resolver dependencies on actual framework implementation. These can be picked up here.

May this be useful

7 comments: