JPMS Libraries
Java developers face a challenge today of building JPMS libraries, let alone when adding a secondary goal that those libraries be usable OSGi bundles. Accuracy and consistency of metadata can often become a problem reducing the time spent focusing on more productive aspect of the library.
A key OSGi innovation is the use of annotations to significantly reduce (in many cases to completely eliminate) the configuration needed to describe an OSGi bundle in build descriptors. See Bundle Annotations.
Creating JPMS Libraries with bnd
Just one of the additional benefits of using bnd is the ability to generate the module-info.class
along side all of the traditional OSGi metadata.
When applying the instruction -jpms-module-info
bnd will collect any relevant information and using several heuristics automatically generates the module-info.class
.
Name, Version & Access
When calculating the module name the following is considered:
- If the
-jpms-module-info
instruction contains a key, the key is used as the module name e.g.-jpms-module-info: foo.module
, the module name isfoo.module
- If the
Automatic-Module-Name
header is found in the manifest, the value is used as the module name - Otherwise, the
Bundle-SymbolicName
(as calculated) is used as the module name
When calculating the module version, the following is considered:
- If the
-jpms-module-info
instruction contains a key having aversion
attribute, theversion
attribute value is used as the module version e.g.-jpms-module-info: foo.module;version=1.2.3
, the module version is1.2.3
- Otherwise, the
Bundle-Version
(as calculated) is used as the module version
When calculating the module access, the following is considered:
- If the
-jpms-module-info
instruction contains a key having anaccess
attribute, theaccess
attribute value is used as the module access. Multiple comma separate values are allowed but quotes are required. e.g. the module access is0x0020
(OPEN):-jpms-module-info: foo.module;access=0x0020
With multiple flags:
-jpms-module-info: foo.module;access='OPEN,SYNTHETIC'
Legal values are:
OPEN
oropen
,0x0020
,32
SYNTHETIC
orsynthetic
,0x1000
,4096
MANDATED
ormandated
,0x8000
,32768
- If the header
Require-Capability
contains any capability in theosgi.extender
namespace, the module access is open - Otherwise, module access is
0
Note that for the above rules, the earliest matching rule wins.
Requires
When calculating the module requires the following is considered:
-
If the
-jpms-module-info
instruction contains a key having amodules
attribute, themodules
attribute value is first split on commas (,
) and each segment is added as a raw required module name e.g.-jpms-module-info: foo.module;modules='java.desktop,java.logging'
, the modulesjava.desktop
, andjava.logging
are added to module requires -
In addition, if the
-jpms-module-info
instruction contains a key having aee
attribute, theee
attribute indicates the Java module name mapping table to use for Java SE packages using bnd’saQute.bnd.build.model.EE
definitions which define a set of Java module name mapping tables keyed byEE
. e.g.-jpms-module-info: foo.module;ee=JavaSE-10
, bnd will use the Java module name mapping table for Java SE 10 when determining module name for a given Java SE packageIf no
ee
attribute is specified, bnd will use the Java module name mapping table for Java SE 11 when determining module name for a given Java SE package -
If an imported package is associated with a module name, the module is added to module requires
Note: Non-Java SE packages are associated with module names by indexing all packages on the classpath of the bnd
analyzer
where the providing jar’s module’s name is:- obtained from the jar’s
module-info.class
- or, obtained from the jar’s
Automatic-Module-Name
- or, calculated from the jar’s filename by stripping off any version suffix (beginning with the first occurrence of a dash followed by a digit
-\d
) - otherwise, no module name is associated with the exported package
- obtained from the jar’s
Requires - Access: Transitivity
Bnd will set the access to transitive
if any package exported by the bundle has a uses
constraint on a package of the required module.
Requires - Access: Static
Bnd will set the access to static
if the module is specified in the -jpms-module-info
instruction and does not actual have any imports.
Bnd will set the access to static
if all the packages imported from the module are any combination of resolution:=optional
, resolution:=dynamic
or match the Dynamic-ImportPackage
instruction.
Requires - Version
Bnd does not currently track a require
’s version.
Exports
Module exports will be mapped from all OSGi exported packages by default which can be managed easily with use of the Bundle Annotation @org.osgi.annotation.bundle.Export
on package-info.java
.
@org.osgi.annotation.bundle.Export
package com.acme.foo;
Targeted exports (via the exports .. to ..
clause) are supported with use of the @aQute.bnd.annotation.jpms.ExportTo
. This annotation specifies the module name(s) to which a exported is targeted.
@org.osgi.annotation.bundle.Export
@aQute.bnd.annotation.jpms.ExportTo("com.acme.bar")
package com.acme.foo;
Note: The @ExportTo
annotation is only relevant in conjunction with the @Export
annotation.
Opens
Module opens are supported with use of the @aQute.bnd.annotation.jpms.Open
annotation on package-info.java
. This annotation optionally specifies the module name(s) to which the opens is targeted.
@aQute.bnd.annotation.jpms.Open
package com.acme.foo;
Uses
Module uses are supported transparently with use of the bnd @aQute.bnd.annotation.spi.ServiceConsumer
SPI Annotation.
Provides
Module provides are supported transparently with use of the bnd @aQute.bnd.annotation.spi.ServiceProvider
SPI Annotation.
Main-Class
The module main class attribute is supported with use of the @aQute.bnd.annotation.jpms.MainClass
annotation applied to the main class’s Java type.
@aQute.bnd.annotation.jpms.MainClass
public class Main {
public static void main(String... args) {...}
}
Java 8 Support
Bnd’s module-info.class
generation is supported when building with Java 8 or higher. (Yes! I did say Java 8.)
Advanced Options
There are scenarios where the heuristics used by bnd don’t give the desired result because the necessary information is not available or is incorrect.
The -jpms-module-info-options
instruction provides some capabilities to help the developer handle these scenarios. The instruction uses the package header syntax similar to many other bnd instructions. The keys of these instructions are module names and there are 4 available attributes. They are:
-
substitute
- If bnd generates a module name matching the value of this attribute it should be substituted with the key of the instruction. e.g.-jpms-module-info-options: java.enterprise;substitute="geronimo-jcdi_2.0_spec"
means that if bnd calculates the module name to be
geronimo-jcdi_2.0_spec
it should replace it withjava.enterprise
-
ignore
- If the attributeignore="true"
is found the require matching the key of the instruction will not be added. e.g.-jpms-module-info-options: java.enterprise;ignore="true"
means ignore the module
java.enterprise
-
static
- If the attributestatic="true|false"
is found the access of the module matching the key of the instruction will be set to match. e.g.-jpms-module-info-options: java.enterprise;static="true"
means make the
require
for modulejava.enterprise
static
-
transitive
- If the attributetransitive="true|false"
is found the access of the module matching the key of the instruction will be set to match. e.g.-jpms-module-info-options: java.enterprise;transitive="true"
means make the
require
for modulejava.enterprise
transitive
The following is an example with multiple attributes and instructions:
-jpms-module-info-options: \
java.enterprise;substitute="geronimo-jcdi_2.0_spec";static=true;transitive=true,\
java.management;ignore=true;
Exporting Executable JARs
Bnd’s export
functionality allows for the creation of executable jars which contain fully assembled applications whose only external dependency is a JDK.
When the -jpms-module-info
instruction is used in a bndrun
file that is the target of the export function the executable jar that is created will contain a generated module-info.class
containing whatever information bnd’s JPMS heuristics could glean from the assembly.
e.g.
-jpms-module-info: \
${project.artifactId};\
version=${project.version};\
ee=JavaSE-${java.specification.version}
The example above produces a module-info.class whose module id comes from the property project.artifactId
, version from the property project.version
and ee
from the property java.specification.version
.
Launcher Main Class
When the -jpms-module-info
instruction is used in a bndrun
file that is the target of the export function bnd launcher’s main class will be applied to the module info implicitly.