Manifest headers are challenging to keep in sync with the code in the bundle. It often takes several attempts to get all the details correct.
One of the goals of bnd is to eliminate such issues by relying on Java’s type system to express the semantics of OSGi metadata.
To address this bnd pioneered manifest annotations which evolved into OSGi’s bundle annotations. A bundle annotation is used to express metadata that cannot otherwise be derived from code.
A bundle annotation is applied to a type or package and when processed by bnd will cause the generation of corresponding manifest headers (and header clauses). Generating manifest headers from type safe structures is far less likely to result in errors, simplifies the developers life and is more conducive to code refactoring which won’t result in information loss.
The following example shows the preferred way to handle package versioning by applying the @Export
and @Version
bundle annotations to com/acme/package-info.java
.
@Export
@Version("1.3.4")
package com.acme;
which results in the manifest header:
Export-Package: com.acme;version="1.3.4"
Some developers do not want to rely on the additional dependency of bnd/OSGi annotations. For this reason, it is possible to also apply the annotations textually in comments to the files in the META-INF/services
.
This trick avoids a compile time dependency. These Service Loader files have the name of a Service Loader service and contain the names of the implementation classes. One fully qualified name per line.
To make these annotations in the comments more readable, it is possible to import the fully qualified name of the annotation.
META-INF/services/com.example.ServiceType:
#import aQute.bnd.annotation.spi.ServiceProvider
#@ServiceProvider(resolution:=optional)
com.example.impl.Impl2
#@ServiceProvider(attribute:List<String>="a=1, b=2")
com.example.impl.Impl1
The processing is identical to the normal class based annotation processing. The #class
macro will be set to the implementation class. The #value
will be set in all cases to the service type unless overridden.
This behavior can be controlled with the -metainf-services instruction. Default is annotation
which processes the textual annotations above, while the other convenience strategy auto
automatically creates Provide-Capability
headers for services without any textual annotations.
While the above is a compromise, clearly using the real class annotation is far superior:
The analysis of these files happens after the analyzer plugins have been run. These plugins can add files if so desired.
Since the annotations & imports happen in the comments, it is not possible to diagnose any errors. If the comment does not match its regular expression, it will be silently ignored.
Though Java class files contain enough information to find code dependencies, there are many dependencies that are indirect. OSGi extenders for instance are often a requirement to make a bundle function correctly but often client bundles have no code dependency on the extender. For example, Declarative Services (DS) went out of its way to allow components to be Plain Old Java Objects (POJO). The result is that resolving a closure of bundles starting from a DS client bundle would not drag in the Service Component Runtime (SCR), resulting in a satisfied but rather idle closure.
The solution was to describe the requirement for the runtime SCR dependency using Requirements and Capabilities. But again, writing these complex clauses in the manifest by hand is both error prone and painful.
The @Requirement
and @Capability
annotations were designed to address this issue. These annotations can be used to create custom bundle annotations, described later on. Let’s discuss the DS example.
Recent DS specifications require implementations to provide the following capability:
Provide-Capability: osgi.extender;
osgi.extender="osgi.component";
version:Version="1.4.0";
uses:="org.osgi.service.component"
While this provides a capability that can be required, we need a requirement to be generated from client code that uses DS. Enter recent versions of DS annotations which are meta-annotated with @RequireServiceComponentRuntime
, a custom bundle annotation which is specified as:
@Requirement(
namespace = ExtenderNamespace.EXTENDER_NAMESPACE,
name = ComponentConstants.COMPONENT_CAPABILITY_NAME,
version = ComponentConstants.COMPONENT_SPECIFICATION_VERSION)
@Retention(RetentionPolicy.CLASS)
public @interface RequireServiceComponentRuntime { }
If you inspect the source code for @Component
you’ll find it is meta-annotated with @RequireServiceComponentRuntime
. When you write a DS component using @Component
as follows
@Component
class Foo { ... }
and because of the inherent bundle annotations it holds, the following manifest clause is generated
Require-Capability: \
osgi.extender; \
filter:="(&(osgi.extender=osgi.component)(version>=1.4.0)(!(version>=2.0.0)))"
The invisible link created between user code and the indirect requirement is a powerful mechanism that enables automatic validation of a bundle closure.
The actual requirement filter:
directive is constructed from an AND
of the filter()
, name()
, and version()
annotation methods. All fields are optional. The name field will create an assertion that the given namespace equals the value of the name()
annotation method. For example, if the namespace is com.example.foo
and the name()
method has the value bar
then the filter is (com.example.foo=bar)
. If a version is specified, it will be expanded to a filtered version-range expression. The convention of using the namespace name as the property key is commonly used in OSGi specification. For example, the filter (osgi.wiring.package=com.example.foo)
is the filter for an Import-Package com.example.foo
while osgi.wiring.package
is the namespace for the packages.
For example:
@Requirement(namespace = "NAMESPACE", name="NAME", version="1.2.3", filter="(foo=${#foo})")
@Retention(RetentionPolicy.CLASS)
public @interface RequireSomething {
int foo();
}
@RequireSomething(foo=3)
class Foo {...}
This will generate a manifest Require-Capability header of:
Require-Capability: \
NAMESPACE; \
filter:="(&(foo=3)(NAMESPACE=NAME)(version>=1.2.3)(!(version>=2.0.0)))"
Bundle annotations aren’t just about package versioning or requirements and capabilities. They are about lifting metadata out of our code to avoid, among other things, error prone duplication of information. A common example is the bundle activator. Bundle Activators are require to be described in a manifest header. This association is not visible to refactoring tools and as such can easily end up out of sync.
The @Header
annotation exists to address this problem.
package com.acme;
@Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}")
public Activator implements BundleActivator { ... }
results in the manifest header:
Bundle-Activator: com.acme.Activator
You’ll note the string ${@class}
used in the above example. String fields in bundle annotations are processed through bnd’s macro processor. This macro processor provides access to all default and builder macros. More info on bnd macros can be found in the macros chapter.
Bnd also provides access to certain key properties of the current processing state.
The @Header
example above used the macro ${@class}
which lifted the @class
property holding the class name of the activator into the header to avoid having to duplicate it. This also means that refactoring the activator won’t cause the manifest to get out of sync.
In the case that a bundle annotation is used as a meta annotation then the methods on the annotated annotation are available as macros as well with a name prefixed with #
. That is, if the annotated annotation has a method foo()
, then the macro ${#foo}
can be used to refer to its value. See Accessor Properties for more details.
Certain bundle annotations have a second important use. We know that if applied to a type or package bundle annotations result in a clause in the manifest. However, many can be used as meta-annotations to a second annotation. The second annotation is considered a custom bundle annotation. The custom bundle annotation results in a manifest clause only when applied to a type or package.
This makes it possible to create an annotation for a subsystem. For example, an annotation @ASL_2_0
that sets the Bundle-License
header to the Apache Software License version 2.0.
@BundleLicense(
name = "https://www.opensource.org/licenses/apache2.0.php",
link = "https://www.apache.org/licenses/LICENSE-2.0.html",
description = "Apache Software License 2.0")
@interface ASL_2_0 {}
// takes effect when applied to a type
@ASL_2_0
class Foo { ... }
When creating custom bundle annotations a common requirement is to make them parameterizable such that the values of the custom bundle annotation feed into the header clauses resulting from the bundle annotation applied to it (remember; a custom bundle annotation is meta-annotated with a bundle annotation.)
OSGi specifies two annotations, @Attribute
and @Directive
, for this purpose. Any methods of the custom bundle annotation annotated with @Attribute
or @Directive
will result in those becoming additional attributes or directives respectively of the resulting header clause when a value is supplied.
@Attribute
@Attribute
allows you to add new or update existing attributes from the bundle annotation.
@Capability(namespace = "foo.namespace")
@interface Extended {
@Attribute("foo.attribute") // this attribute enhances the @Capability
String value();
}
// usage
@Extended("bar")
class Foo {}
which results in the manifest header:
Provide-Capability: foo.namespace;foo.attribute=bar
@Directive
@Directive
behaves similarly; with some caveats. You can add new or update existing directives for namespaces not defined by OSGi specifications.
@Capability(namespace = "foo.namespace")
@interface Extended {
@Directive("foo.directive")
String value();
}
// usage
@Extended("bar")
class Foo {}
results in the manifest header:
Provide-Capability: foo.namespace;foo.directive:=bar
However, namespaces defined by OSGi specifications will be validated and will not accept directives which are not part of the spec unless they are prefixed with x-
.
@Capability(namespace = "osgi.extender", name = "bar", version = "1.0.0")
@interface Extended {
@Directive("foo")
String value();
}
// usage
@Extended("bar")
public class Foo {}
will result in an error:
Unknown directive 'foo:' for namespace 'osgi.extender' in 'Provide-Capability'. Allowed directives are [effective:,uses:], and 'x-*'.
It should be noted that it’s possible to elide such errors using bnd’s -fixupmessages
instruction.
This next example however:
@Capability(namespace = "osgi.extender", name = "bar", version = "1.0.0")
@interface Extended {
@Directive("x-foo")
String value();
}
// usage
@Extended("bar")
public class Foo {}
results in the manifest header:
Provide-Capability: osgi.extender;osgi.extender=bar;version:Version="1.0.0";x-foo:=bar
It should be noted that default values for methods annotated with @Attribute
and @Directive
are deemed to be for documentation purposes only and will not be emitted into resulting headers.
For more customisation options see chapter on Accessor Properties.
OSGi bundle annotations can be found in the osgi.annotation
(e.g. org.osgi:osgi.annotation:7.0.0
) bundle.
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.annotation</artifactId>
<version>7.0.0</version>
</dependency>
Bnd bundle annotations can be found in the biz.aQute.bnd.annotations
(e.g. biz.aQute.bnd:biz.aQute.bnd.annotation:5.0.0
) bundle.
<dependency>
<groupId>biz.aQute.bnd</groupId>
<artifactId>biz.aQute.bnd.annotation</artifactId>
<version>${bnd.version}</version>
</dependency>
OSGi Bundle Annotations:
@Attribute
@Capability
@Directive
@Export
@Header
@Requirement
Bnd Bundle Annotations:
@BundleCategory
– Sets the bundle category, existing categories are defined in an enum.@BundleContributors
– Creates an OSGi header for contributors that maps to the Maven contributors element.@BundleCopyright
– Sets the copyright header.@BundleDevelopers
– Creates an OSGi header for developers that maps to the Maven developers element.@BundleDocUrl
– Provides a documentation URL.@BundleLicense
- Creates entries in the Bundle-License
header.
@ASL_2_0
@BSD_2_Clause
@BSD_3_Clause
@CDDL_1_0
@CPL_1_0
@EPL_1_0
@GPL_2_0
@GPL_3_0
@LGPL_2_1
@MIT_1_0
@MPL_2_0
@ServiceConsumer
- Generates requirements in support of the consumer side of the Service Loader Mediator specification.@ServiceProvider
- Generates requirements and capabilities in support of the provider side of the Service Loader Mediator specification. Also generates META-INF/service
descriptors.