The following best practices are recommended when building OSGi bundles using Maven:
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.
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>
Tip | |
---|---|
Properties of the form |
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.
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 |
To ensure that the private packages are not exported, you can
add an entry of the form !
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
PackagePattern
${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 |
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 followingversion
settings:*;version="${@}"
If a particular package has export version
1.2.4.8
, the generated import version resolves to1.2.4.8
.*;version="${version;==;${@}}"
If a particular package has export version
1.2.4.8
, the generated import version resolves to1.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.
Tip | |
---|---|
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. |
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:
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.
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 theExport-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 followingImport-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>
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, 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.