Skip to content

Latest commit

 

History

History
340 lines (238 loc) · 17.8 KB

230-manifest-annotations.md

File metadata and controls

340 lines (238 loc) · 17.8 KB
order title layout
230
Bundle Annotations
default

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"

META-INF/services Annotations

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:

  • Help from the IDE
  • Impossible to make typos
  • Not necessary to manually maintain the META-INF/services/

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.

@Requirement & @Capability Annotations

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)))"   

Arbitrary Manifest Headers

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

Macros

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.

  • @class - gives the fully qualified name of the annotated class
  • @class-short - gives the simple name of the annotated class
  • @package - gives the package of the annotated class
  • @version - gives the package version of the annotated class

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.

Custom Bundle Annotations

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 { ... }

Adding Attributes and Directives

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.

Accessor Properties

For more customisation options see chapter on Accessor Properties.

Where to find Bundle Annotations

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>

List of Bundle Annotations

OSGi Bundle Annotations:

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.