Tuesday, July 31, 2018

OSGi R7 Highlights: Bundle Annotations

The OSGi Core Release 7 specification introduces some new bundle annotations for use by programmers. These annotations focus on generating bundle metadata in the bundle's manifest. Like the Declarative Services and Metatype annotations, the bundle annotations are CLASS retention annotations and are intended for use by tools that generate bundles such as Bnd. Bnd includes support for Gradle, Maven and Eclipse via the Bndtools Eclipse plugins.

Package Export


One of the key features of OSGi is, of course, modularity through encapsulation of classes in bundles. But we still need to share classes between bundles to get things done! So importing and exporting package is important. When building bundles, we must make sure that an Export-Package header is present in the bundle's manifest to identify which packages in the bundle are to be exported and thus available for other bundles to import. Normally, the list of packages to export is described to the bundle building tool. For example, when using Bnd, we can specify the -exportcontents or Export-Package instruction in the bnd.bnd file to tell Bnd what packages must be exported from the generated bundle. These instructions support wildcards, include and exclude information so that the bundle developer does not have to list all the desired export package names.

But this information is separate from a package being exported. So a new Export annotation is now available which can be applied to the package source in its package-info.java file.

@org.osgi.annotation.bundle.Export
@org.osgi.annotation.versioning.Version("1.0")
package my.package;

When a bundle containing this package is generated, the Export-Package header in the bundle's manifest will list the package with its version number. When using the Export annotation you must also use the Version annotation to specify the version of the package.

The Export annotation also includes some elements to provide control of the export package information of the package. If you need to specify some specific attributes, or directives, for the package, the attribute element can be used to specify them. Normally when a package is exported, you want it also imported to allow the framework the choice to substitute the import for the export when resolving the bundle at runtime. This is generally the best practice. Bnd will do this automatically when it detects that at least one other package in the bundle uses the exported package. (If no other package in the bundle uses the exported package, then there would be no value in substitutably importing the package.) The substitution element can be used to override the default behavior. Finally, the uses element can be used to replace the calculated uses information of the exported package. These latter two elements are highly specialized and the calculated values are almost always the best choice.

Capabilities and Requirements


The OSGi R4.3 spec introduced the concept of capabilities and requirements to the OSGi specifications which were further refined into the Resource API Specification and Framework Namespaces Specification in OSGi R5.

Capabilities and requirements let a bundle offer capabilities to other bundles and require capabilities from other bundles. This metadata is placed in the Provide-Capability and Require-Capability manifest headers which can be used by resolvers such as the framework resolver to match bundles and wire them together at runtime. Resolving during development, such as in Bnd, or provisioning can also use this information to construct a set of bundles which will work together. So as a developer writing bundles, you want to make sure the capabilities offered by your bundle and requirements needed by your bundle are properly expressed in your bundle's manifest. But writing manifest headers is no fun and subject to mistakes when code if refactored or assembled into a different bundle.

So a new set of annotations is introduced to make managing the capabilities and requirements of a bundle more error-proof and straightforward. The new Capability annotation can be used on a type or package to declare that a capability is offered by that package when it is included in a bundle. The Capability annotation must declare the namespace of the capability and can optionally declare additional information about the capability such as a name, a version, the effective time of the capability, and uses constraints as well as any additional attributes and directives. For example, a Declarative Services SCR extender can use this annotation to declare it offers the extender capability for osgi.component version 1.4.

@Capability(namespace=ExtenderNamespace.EXTENDER_NAMESPACE,
  name="osgi.component", version="1.4.0")

The new Requirement annotation can be used on a type or package to declare that a capability is required by that package when it is included in a bundle. The Requirement annotation must declare the namespace of the requirement and can optionally declare additional information about the requirement such as a name, a version, the effective time of the requirement, and a filter which must match a capability as well as any additional attributes and directives for the requirement. For example, a bundle using  Declarative Services can use this annotation to declare it requires the extender capability for osgi.component version 1.4.

@Requirement(namespace=ExtenderNamespace.EXTENDER_NAMESPACE,
  name="osgi.component", version="1.4.0")

As useful as this is to help generate the proper requirement in the bundle's manifest for a Declarative Services SCR extender, you don't even have to put this in your source code. This is because the Capability and Requirement annotations are supported as meta-annotations! This means you don't have to use these annotations directly in your code, you just need to use an annotation which itself is annotated with these annotations to get their benefit. For example, the RequireServiceComponentRuntime annotation is defined by the Declarative Services specification and it is annotated with the above Requirement annotation example.

@Requirement(namespace = ExtenderNamespace.EXTENDER_NAMESPACE,
  name = "osgi.component", version = "1.4.0")
public @interface RequireServiceComponentRuntime {}

So this captures all the details of the requirement in a single, easy-to-use annotation. Furthermore, the standard Component annotation, which is used by all Declarative Services components, is now annotated with the RequireServiceComponentRuntime annotation. So this means that just by writing a component and using the Component annotation, your bundle's manifest will automatically contain the requirement for the Declarative Services extender capability.

Other OSGi specifications now also take advantage of this meta-annotation support to ensure the proper requirements end up in your bundle's manifest when you use the specification. For example, the Http Whiteboard Specification defines the RequireHttpWhiteboard annotation which is itself annotated with a Requirement for the osgi.http implementation namespace. And most of the Http Whiteboard component property types are themselves annotated with the RequireHttpWhiteboard annotation. So by using one of the Http Whiteboard component property types in your bundle, your bundle's manifest will automatically contain the requirement for the Http Whiteboard implementation capability.

If you define your own capability namespaces for your applications, make sure to define your own requirement annotations annotated with Requirement to make it simple for your users to take advantage of the meta-annotation support and automatically get the desired requirements in their bundle's manifest. You can also use the new Attribute and Directive annotations on the elements of your requirement or capability annotation so that these elements can be automatically mapped onto attributes or directives in the requirement or capability generated in the bundle's manifest.

Simple Manifest Headers


And finally, there is the humble Header annotation. It can be used on a type or package if you just need to get a simple manifest header in the bundle's manifest. For example,

@Header(name=Constants.BUNDLE_CATEGORY, value="osgi")

will put Bundle-Category: osgi in the bundle's manifest.

Conclusion


The addition of the new bundle annotations to the OSGi specifications and support for them in tooling like Bnd make building bundles easier and less error-prone. If you are designing API which includes defining a capability namespace, make sure to design some requirement annotations for your namespace and use them to make users of your API much happier!

PS. While the OSGi Core R7 specification is done, tooling which supports the new bundle annotations may still be under development (at the time of this writing).


Want to find out more about OSGi R7?

This is one post in a series of 12 focused on OSGi R7 Highlights.  Previous posts are:
  1. Proposed Final Draft Now Available
  2. Java 9 Support
  3. Declarative Services
  4. The JAX-RS Whiteboard
  5. The Converter
  6. Cluster Information
  7. Transaction Control
  8. The Http Whiteboard Service
  9. Push Streams and Promises 1.1
  10. Configuration Admin and Configurator
  11. Log Service
Be sure to follow us on Twitter or LinkedIn or subscribe to our Blog feed to find out when it's available.

No comments:

Post a Comment