Tuesday, February 27, 2018

OSGi R7 Highlights: Java 9 Support

With the Java 9 release, the Java Platform Module System (JPMS) is a major feature that impacts many areas of the Java Platform.  The JPMS is used to modularize the class libraries provided by the Java Platform.  In addition, it has been proposed that the JPMS can be used by developers to modularize their own applications, although the OSGi Alliance cautions against its use here.

Meanwhile the OSGi Core specification has provided a stable platform for developing modular Java applications for well over 17 years.  The OSGi Core R7 specification continues this tradition by providing updates to the OSGi Core specification which are fully compatible with previous versions of the OSGi Core specification.  OSGi Core R7 Framework implementations will be able to run on top of Java 9 without requiring any changes to existing OSGi application bundles (assuming no internal packages from the Java platform are being used).

The OSGi Core R7 specification has a couple of new features that build upon some features new to Java 9.  This includes support for multi-release JARs and runtime discovery of packages provided by the JPMS modules loaded by the platform.  First I will tell you about the multi-release JAR support.

Multi-Release JAR Support

Java 9 introduces a new type of JAR called a multi-release JAR.  A multi-release JAR file allows for a single JAR to support multiple major versions of the Java platform. For example, a multi-release JAR file can depend on both the Java 8 and Java 9 major platform releases, where some class depend on APIs in Java 8 and other class depend on APIs in Java 9.

The purpose of multi-release JARs is to support alternative implementation of select classes to deal with changes in the visible APIs of the Java platform.  That is, it is not meant as a means to supply new function or new API on different Java platform versions. As a best practice new APIs should be released in an updated version of an existing bundle or released as part of a new bundle.

The OSGi Core R7 specification adds support for multi-release JAR files. An OSGi bundle file can be a multi-release JAR by adding the following manifest header to the bundle manifest:

 Multi-Release: true

Any JAR included on the bundle class path can also be independently declared as a multi-release JAR by including the same manifest header:

  Multi-Release: true

in its own JAR manifest. For example, an OSGi bundle can include third-party multi-release JARs on its own bundle classpath. In this case the third-party multi-release JARs will be loaded as multi-release JARs by the OSGi Framework even if the bundle JAR itself is not a multi-release JAR.

When a JAR on the bundle class path is a multi-release JAR, then the Framework must search the JAR's versioned directories when attempting to locate a class or resource in the JAR. For more information on how classes and resources are loaded from multi-release JARs see the multi-release JAR documentation.

Different implementations of a Java class for different versions of the Java platform can affect the requirements of the bundle.  For example, what packages need to be imported from the running Java platform. The R7 Framework supports supplemental manifest files that can be used to specify alternative values for the Import-Package and Require-Capability manifest headers for different versions of the Java platform.

When a bundle file is a multi-release JAR, then the Framework must also look for a supplemental manifest file, OSGI-INF/MANIFEST.MF, in the versioned directories. For example:

  META-INF/versions/9/OSGI-INF/MANIFEST.MF

The Framework finds the highest versioned manifest available in the multi-release JAR which can be applied to the running Java platform and uses the Import-Package and Require-Capability manifest headers as replacements for the values specified in the bundle's manifest.

The purpose of the supplemental manifest is to specify requirements on capabilities provided by different versions of the Java platform which are needed by the implementation classes for that Java platform version. As a best practice, the supplemental manifests should not contain additional requirements on capabilities which are not supplied by the Java platform for the Java version associated with the supplemental manifest.

For more information about multi-release JAR support you can refer to the following sections of the OSGi Core R7 specification:
  1. Bundle File Multi-release JAR
  2. Bundle class path Multi-release Container

Runtime Discovery of Java Platform Packages

Now that the Java platform is modularized in Java 9, the runtime can be configured to load only the modules which are required by the application.  This allows for a smaller custom runtime that is tailored to the needs of a specific application.  The set of java.* packages provide by the running Java platform is no longer constant for a specific version of the Java platform.

Bundles typically depend on different packages by using requirements specified with the Import-Package header, but as of the OSGi Core R4 specification, bundles have been prohibited from using Import-Package to specify requirements on java.* packages.  Instead bundles have used the osgi.ee namespace (or the deprecated Bundle-RequiredExecutionEnvironment header) to specify a dependency on specific versions of the Java platform. It has been assumed that the set of java.* packages available for a specific version of the Java platform is constant and therefore there is no need to specify additional requirements on specific java.* packages.

With Java 9 this is no longer a valid assumption.  With OSGi Core R7, the osgi.ee namespace should only be used to specify the minimal level of the Java platform required by the Java class bytecode level included in the bundle.  Dependencies on specific java.* packages should to be specified using the Import-Package header.

The OSGi Core R7 specification now allows bundles to use the Import-Package header to import java.* packages.  The OSGi Framework implementation is required to discover the all available java.* packages in the running Java platform and automatically provide the java.* package as a separate system packages (see the system packages configuration property).  The system bundle is the only bundle that is allowed to provide java.* packages. It is an error for normal bundles to attempt to export java.* packages using the Export-Package header.

When importing java.* packages only the package name should be specified, no other matching attributes are needed.  For example:

  Import-Package: java.sql

Note that bundle class loaders in R7 continue to have full visibility to all available java.* packages available in the running Java platform.  The class and resource loads for the java.* packages will continue to be boot delegated by the framework implementation even when the bundle does not specify any Import-Package header. The java.* package requirements are only necessary to prevent a bundle from resolving if the necessary java.* packages are not available at runtime.  For example, if a bundle must only resolve if the java.sql module is loaded.

For more information about java* package support you can refer to the following sections of the OSGi Core R7 specification:
  1. Execution Environment
  2. Parent Delegation 

Bundles Supporting R6 and R7 Frameworks

The OSGi Core R6 specification prohibits bundles from importing java.* packages and will throw a BundleException if such a bundle is attempted to be installed. Bundles that must work on OSGi R6 and earlier Frameworks but also want to run on R7 Frameworks have two options:

  1. Do not import the java.* packages.  The bundle will continue to work on an R7 framework because java.* packages are still boot delegated by the bundle class loaders. This implies the bundle may resolve when running on Java 9 even when all the java.* packages required by the bundle are not available.
  2. Package the bundle as a multi-release JAR to allow alternative values for Import-Package to be specified.
A bundle that must still be able to be installed on an OSGi R6 or earlier Framework must not specify java.* packages in their main bundle manifest Import-Package header. A supplemental manifest must also be included to be able to specify java.* package imports.

This supplemental manifest must contain the original Import-Package header from the main bundle manifest as well as any java.* package required by the bundle. When running on an OSGi R6 Framework, this supplemental manifest will be ignored and therefore the bundle will install successfully as the bundle did not specify an import for the java.* packages in the main manifest.

If a bundle will always require OSGi R7 or greater Framework, there is no need to use multi-release JARs for this purpose even when the bundle must support Java 8 or earlier. This is because OSGi R7 frameworks must provide the java.* package capabilities on all Java platform versions the framework implementation supports. As long as the java.* package is available on the running Java platform, the bundle with an import for the java.* package must resolve.

I advise using option 1 here because it is by far the most simple approach.  If you really have a strong need to use this new R7 feature then I recommend you do not try to produce a single bundle version that supports both R6 and R7 Frameworks.  Instead simply release a new version of your bundle that only supports OSGi R7 or greater Frameworks.

In Summary

The OSGi Core R7 release continues the tradition by providing a stable modular platform with updates to the OSGi Core specification which are fully compatible with previous versions of the OSGi Core specification.  OSGi bundle developers can continue to develop advanced modular applications and run their applications on Java 9 without having to commit to migrating to the green initial release of JPMS.

The OSGi Core R7 specification does add new functionality in support of new features available in Java 9, such as the support for multi-release JARs and importing of java.* packages.  The OSGi specifications will continue to evolve in order to provide the necessary tools for developing advanced modular applications.

4 comments:

  1. Is there any support for module-info.java?

    ReplyDelete
    Replies
    1. In what sense? OSGi uses different metadata for modularity than JPMS and supports a more rich model of package-level sharing and a dynamic service model.

      I don't expect that OSGi will support JPMS modules directly, but tools, like Bnd, could be modified to generate OSGi metadata using JPMS metadata so that a jar could contain both sets of metadata.

      Delete
  2. "In what sense" is part of the question. :-)
    E.g.: Should OSGi bundles provide a module-info.class? Or how can we build custom runtimes with jlink for OSGi applications? Could part of the OSGi module layer be replaced with the standard Java module system (extend it somehow)?

    ReplyDelete
    Replies
    1. I have started investigating something like that with https://github.com/tjwatson/atomos

      Delete