Red Hat Training

A Red Hat training course is available for Red Hat Fuse

B.2. Building OSGi Bundles

Overview

The following best practices are recommended when building OSGi bundles using Maven:

Use package prefix as the bundle symbolic name

Use your application's package prefix as the bundle symbolic name. For example, if all of your Java source code is located in sub-packages of org.fusesource.fooProject, use org.fusesource.fooProject as the bundle symbolic name.

Artifact ID should be derived from the bundle symbolic name

It makes sense to identify a Maven artifact with an OSGi bundle. To show this relationship as clearly as possible, you should use base the artifact ID on the bundle symbolic name. Two conventions are commonly used:
  • The artifact ID is identical to the bundle symbolic name—this enables you to define the bundle symbolic name in terms of the artifact ID, using the following Maven bundle instruction:
    <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
  • The bundle symbolic name is composed of the group ID and the artifact ID, joined by a dot—this enables you to define the bundle symbolic name in terms of the group ID and the artifact ID, using the following Maven bundle instruction:
    <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
Note
Properties of the form project.* can be used to reference the value of any element in the current POM. To construct the name of a POM property, take the XPath name of any POM element (for example, project/artifactId) and replace occurrences of / with the . character. Hence, ${project.artifactId} references the artifactId element from the current POM.

Always export packages with a version

One of the key advantages of the OSGi framework is its ability to manage bundle versions and the possibility of deploying multiple versions of a bundle in the same container. In order to take advantage of this capability, however, it is essential that you associate a version with any packages that you export.
For example, you can configure the maven-bundle-plugin plug-in to export packages with the current artifact version (given by the project.version property) as follows:
<Export-Package>
  ${project.artifactId}*;version=${project.version}
</Export-Package>
Notice how this example exploits the convention that packages use the artifact ID, project.artifactId, as their package prefix. The combination of package prefix and wildcard, ${project.artifactId}*, enables you to reference all of the source code in your bundle.

Use naming convention for private packages

If you define any private packages in your bundle (packages that you do not want to export), it is recommended that you identify these packages using a strict naming convention. For example, if your bundle includes implementation classes that you do not want to export, you should place these classes in packages prefixed by ${project.artifactId}.impl or ${project.artifactId}.internal.
Note
If you do not specify any Export-Package instruction, the default behavior of the Maven bundle plug-in is to exclude any packages that contain a path segment equal to impl or internal.
To ensure that the private packages are not exported, you can add an entry of the form !PackagePattern to the Maven bundle plug-in's export instructions. The effect of this entry is to exclude any matching packages. For example, to exclude any packages prefixed by ${project.artifactId}.impl, you could add the following instruction to the Maven bundle plug-in configuration:
<Export-Package>
  !${project.artifactId}.impl.*,
  ${project.artifactId}*;version=${project.version}
</Export-Package>
Note
The order of entries in the Export-Package element is significant. The first match in the list determines whether a package is included or excluded. Hence, in order for exclusions to be effective, they should appear at the start of the list.

Import packages with version ranges

In order to benefit from OSGi version management capabilities, it is important to restrict the range of acceptable versions for imported packages. You can use either of the following approaches:
  • Manual version ranges—you can manually specify the version range for an imported package using the version qualifier, as shown in the following example:
    <Import-Package>
      org.springframework.*;version="[2.5,4)",
      org.apache.commons.logging.*;version="[1.1,2)",
      *
    </Import-Package>
    Version ranges are specified using the standard OSGi version range syntax, where square brackets—that is, [ and ]—denote inclusive ranges and parentheses—that is, ( and )—denote exclusive ranges. Hence the range, [2.5,4), means that the version, v, is restricted to the range, 2.5 <= v < 4. Note the special case of a range written as a simple number—for example, version="2.5", which is equivalent to the range, [2.5,infinity).
  • Automatic version ranges—if packages are imported from a Maven dependency and if the dependency is packaged as an OSGi bundle, the Maven bundle plug-in automatically adds the version range to the import instructions.
    The default behavior is as follows. If your POM depends on a bundle that is identified as version 1.2.4.8, the generated manifest will import version 1.2 of the bundle's exported packages (that is, the imported version number is truncated to the first two parts, major and minor).
    It is also possible to customize how imported version ranges are generated from the bundle dependency. When setting the version property, you can use the ${@} macro (which returns the original export version) and the ${version} macro (which modifies a version number) to generate a version range. For example, consider the following version settings:
    *;version="${@}"
    If a particular package has export version 1.2.4.8, the generated import version resolves to 1.2.4.8.
    *;version="${version;==;${@}}"
    If a particular package has export version 1.2.4.8, the generated import version resolves to 1.2.
    *;version="[${version;==;${@}},${version;=+;${@}})"
    If a particular package has export version 1.2.4.8, the generated import version range resolves to [1.2,1.3).
    *;version="[${version;==;${@}},${version;+;${@}})"
    If a particular package has export version 1.2.4.8, the generated import version range resolves to [1.2,2).
    The middle part of the version macro—for example, == or =+—formats the returned version number. The equals sign, =, returns the corresponding version part unchanged; the plus sign, +, returns the corresponding version part plus one; and the minus sign, -, returns the corresponding version part minus one. For more details, consult the Bnd documentation for the version macro and the -versionpolicy option.
Note
In practice, you are likely to find that the majority of imported packages can be automatically versioned by Maven. It is, typically, only occasionally necessary to specify a version manually.

Avoid importing packages that you export

Normally, it is not good practice to import the packages that you export (though there are exceptions to this rule). Here are some guidelines to follow:
  • If the bundle is a pure library (providing interfaces and classes, but not instantiating any classes or OSGi services), do not import the packages that you export.
  • If the bundle is a pure API (providing interfaces and abstract classes, but no implementation classes), do not import the packages that you export.
  • If the bundle is a pure implementation (implementing and registering an OSGi service, but not providing any API), you do not need to export any packages at all.
    Note
    The registered OSGi service must be accessible through an API interface or class, but it is presumed that this API is provided in a separate API bundle. The implementation bundle therefore needs to import the corresponding API packages.
  • A special case arises, if an implementation and its corresponding API are combined into the same bundle. In this case, the API packages must be listed amongst the export packages and amongst the import packages. This configuration is interpreted in a special way by the OSGi framework: it actually means that the API packages will either be exported or imported at run time (but not both).
    The reason for this special configuration is that, in a complex OSGi application, it is possible that an API package might be provided by more than one bundle. But you do not want multiple copies of an API to be exported into OSGi, because that can lead to technical problems like class cast exceptions. When a package is listed both in the exports and in the imports, the OSGi resolver proceeds as follows:
    1. First of all, the resolver checks whether the package has already been exported from another bundle. If so, the resolver imports the package, but does not export it.
    2. Otherwise, the resolver uses the local API package and exports this package, but it does not import the package.
Assuming you want to avoid importing the packages that you export, there are two alternative approaches you can take, as follows:
  • (Recommended) The most effective way of suppressing the import of exported packages is to append the -noimport:=true setting to package patterns in the Export-Package instruction. For example:
    <Export-Package>
      ${project.artifactId}*;version=${project.version};-noimport:=true
    </Export-Package>
    The marked packages are now not imported, irrespective of what is contained in the Import-Package instruction.
  • An alternative way of avoiding the import is to add one or more package exclusions to the Maven bundle plug-in's Import-Package element (this was the only possibility in earlier versions of the Maven bundle plug-in). For example, the following Import-Package element instructs the Maven bundle plug-in to exclude all packages prefixed by the artifact ID, ${project.artifactId}:
    <Import-Package>
      !${project.artifactId}*,
      org.springframework.*;version="[2.5,4)",
      org.apache.commons.logging.*;version="[1.1,2)",
      *
    </Import-Package>

Use optional imports with caution

When an imported package is specified with optional resolution, this allows the bundle to be resolved without resolving the optional package. This affects the resolution order of the bundles which, in turn, can affect the runtime behavior. You should therefore be careful with optional imports, in case they have some unintended side effects.
A package is optional when it appears in the Import-Package manifest header with the resolution:="optional" setting appended to it. For example, the following example shows an Import-Package instruction for the Maven bundle plug-in that specifies an optional import:
<Import-Package>
  org.springframework.*;version="[2.5,4)",
  org.apache.commons.logging.*;version="[1.1,2)";resolution:="optional",
  *
</Import-Package>

Avoid using the Require-Bundle header

Avoid using the Require-Bundle header, if possible. The trouble with using the Require-Bundle header is that it forces the OSGi resolver to use packages from the specified bundle. Importing at the granularity of packages, on the other hand, allows the resolver to be more flexible, because there are fewer constraints: if a package is already available and resolved from another bundle, the resolver could use that package instead.