Tuesday, August 6, 2013

OSGi Contracts (wonkish)

Let's talk about versions ... again. Though the OSGi has a very elegant package version model there are still many that think this is too much work. They do not want to be bothered by the niceties of semantic versions and just want to use, let's say, Servlet 3.0. For those people (seemingly not interested in minimizing dependencies) the OSGi Alliance came up with contracts in OSGi Core, Release 5.0.0. A contract allows you to:
  1. Declare that a bundle provides the API of the given specification
  2. Require that the API comes from a bundle that made the declaration
This very common pattern is called the Capability/Requirement (C/R) model in OSGi, it underlies all of its dependency concepts like Import/Export package and others; it forms the foundation of the OSGi Bundle Repository. If you ever want to know what is happening deep down inside a framework than look at the Wiring API and you see the requirements and capabilities in their most fundamental form.
Capabilities declare a set of properties that describe something that a bundle can provide. A Requirement in a bundle has a filter that must match a capability before this bundle can be resolved. To prevent requirements matching completely unrelated capabilities they must both be defined in the same namespace,, where the namespace then defines the semantics of the properties. Using the C/R model we were able to describe most of the OSGi dependencies with surprisingly few additional concepts. For a modern OSGi resolver there is very little difference between the Import-Package and Require-Bundle headers.
So how do those contracts work? Well, the bundle that provides the API for the contract has a contract capability. What this means is that it provides a Provide-Capability clause in the osgi.contract namespace, for example:

Bundle P:
  Provide-Capability: 
     osgi.contract;
      osgi.contract=Servlet;
      uses:="javax.servlet,javax.servlet.http";
      version="3.0"
  Export-Package: javax.servlet, javax.servlet.http

This contract defines two properties, the contract name (by convention this is the namespace name as property key) and the version. A bundle that wants to rely on this API can add the following requirement to its manifest:
Bundle R:
  Require-Capability: osgi.contract;
    filter:="(&(osgi.contract=Servlet)(version=3.0))"
  Import-Package: javax.servlet, javax.servlet.http

Experienced OSGi users should have cringed at these versionless packages, cringing becomes a gut-reaction at the sight of versionless packages. However, in this case it actually cannot harm. The previous example will ensure that Bundle P will be the class loader for the Bundle R for packages javax.servlet, javax.servlet.http. The magic is in the uses: directive, if the Require-Capability in bundle R is resolved to the Provide-Capability in bundle P then bundle R must import these packages from bundle P.
Obviously bnd has support for this (well, since today, i.e. version osgi:biz.aQute.bndlib@2.2.0.20130806-071947 or later). First bnd can make it easier to create the Provide Capability header since the involved packages are in the Export-Package as well as in the Provide-Capability headers. The do-no-repeat-yourself mantra dictated am ${exports} macro. The ${exports} macro is replaced by the exported packages of the bundle, for example:
Bundle P:
  Provide-Capability: 
    osgi.contract;
      osgi.contract=Servlet;
      uses:="${exports}";
      version="3.0"
  Export-Package: javax.servlet, javax.servlet.http

That said, the most extensive help you get from bnd is for requiring contracts. Providing a contract is not so cumbersome, after all you're the provider so you have all the knowledge and the interest in providing metadata. Consuming a contract is less interesting and it is much harder to get the metadata right. In a similar vein, bnd analyzes your classes to find out the dependencies to create the Import-Package statement, doing this by hand is really hard (as other OSGi developing environments can testify!).
So to activate the use of contracts, add the -contract instruction:
bnd.bnd:
  -contract: *

This instruction will give bnd permission to scan the build path for contracts, i.e. Provide-Capability clauses in the osgi.contract namespace. These declared contracts cause a corresponding requirement in the bundle when the bundle imports packages listed in the uses clause. In the example with Bundle R, bnd will automatically insert the Require-Capability header and remove any versions on the imported packages.
Sometimes the wildcard for the -contract instruction can be used to limit the contracts that are considered. Sometimes you want a specific contract but not others. Other times you want to skip a specific contract. The following example skips the 'Servlet' contract:
bnd.bnd:
  -contract: !Servlet,*

The tests provide some examples for people that want to have a deeper understanding: https://github.com/bndtools/bnd/blob/next/biz.aQute.bndlib.tests/src/test/ContractTest.java Contracts will be part of the bnd(tools) 2.2 release (hopefully) at the end of this summer, until then they are experimental. Enjoy. Peter Kriens @pkriens Update: Last example to skip the 'Servlet' contract was reversed, updated the text to show a reverse example (anything BUT Servlet).

1 comment:

  1. Nice post, Peter! Yes, hopefully these contracts should make life easier if you are working with a technology that comprises multiple packages.

    Couple of small comments:
    * Contracts were actually introduced in the Enterprise R5 spec (not Core R5), section 135.3.
    * You mention 'OSGi Bundle Repository', but I think you might mean the OSGi Repository Service, which can actually contain more than bundles, in fact it can contain any type of resource. Enterprise R5 spec chapter 132.
    * More more details on how to use the contracts namespace people can take a look at RFC 180 (Portable Java Contracts) which is included in the latest EA draft.

    ReplyDelete

Blog Archive