OSGiAndNetBeansMetadataMapping

When discussing OSGiAndNetBeans proposal Richard Hall and Jesse Glick come up with following idea how to map existing NetBeans manifest tags to OSGi. The mapping is 1:1 trying to mirror all the functionality of NetBeans runtime container in OSGi.

Mapping NetBeans specification dependencies to OSGi

NetBeans allows a module to declare a dependency on another module's public API packages. In OSGi there are two options for declaring dependencies: Import-Package and Require-Bundle. Unless there is good reason to do otherwise, we should try to use Import-Package. Assume we have some provider module in NetBeans:

OpenIDE-Module: some.module
OpenIDE-Module-Specification-Version: 2.1
OpenIDE-Module-Public-Packages: x.y.z

As far as I can understand, NetBeans specification version is implicitly the version number of all exported packages (i.e., the exported packages cannot have their own version number). In OSGi, both the bundle and the packages have versions. For our mapping, we will assume that the bundle version and the package version are the same, but they need not be. Thus, we map the above to OSGi as follows:

Bundle-SymbolicName: some.module
Bundle-Version: 2.1
Export-Package: x.y.z; version=2.1

Then the following NetBeans public API dependency:

OpenIDE-Module-Module-Dependencies: some.module > 2.1

Is mapped to OSGi as follows:

Import-Package: x.y.z; version="(2.1,3)"

JesseGlick comments: That is not an accurate mapping. You must consider the major release version in NetBeans (implicitly -1 but can probably be considered to default to 0) as the major component of the version. Any larger specification version with the same major release version is compatible. Also note that > in NB really means >=.

Therefore some.module > 2.1 should be mapped to
version="[0.2.1,1)"
and some.module/2 > 13.14.15 to
version="[2.13.14.15,3)"
etc.

When a major release version range is given, you can also do a straightforward mapping; the specification version constraint applies only to the lowest major release version in the range,

thus some.module/2-3 > 1.0 maps to
version="[2.1.0,4)"
(3.0 is fine) and so on.

RichardHall comments: That sounds fine, please feel free to edit my mappings. I used the (2.1,3) mapping since that was the translation presented in the previous section. There might be some issues if four components in the version number are actually needed, since OSGi versions are only three numerical components.

If this dependencies needs to be narrowed down to a specific module, then the following mapping could be used:

Import-Package: x.y.z; version="(2.1,3)"; bundle-symbolic-name="some.module"

Mapping NetBeans implementation dependencies to OSGi

NetBeans allows a module to declare a dependency on another module's implementation packages. Recall that a NetBeans module explicitly declares its public API via OpenIDE-Module-Public-Packages. This means that the implementation API is implicitly every other package contained in the module. Reconsider the previous NetBeans module:

OpenIDE-Module: some.module
OpenIDE-Module-Specification-Version: 2.1
OpenIDE-Module-Public-Packages: x.y.z

The package x.y.z comprises the module's public API, but assume that there are implementation packages named x.y.impl1, x.y.impl2, and x.y.impl3. Then, to map the above module to OSGi, we need to declare these packages to be exported as well:

Bundle-SymbolicName: some.module
Bundle-Version: 2.1
Export-Package: x.y.z; version=2.1, x.y.impl1; x.y.impl2; x.y.impl3; version=2.1; nonpublic="true"; mandatory:="nonpublic"

JesseGlick comments: This does not look right. Getting access to all packages of the other module is the benefit of an implementation dependency. The cost is that you are restricted to running against that exact OpenIDE-Module-Implementation-Version. The specification version is irrelevant in this case.

RichardHall comments: The issue here is that I am not fully up-to-speed on how NetBeans does its versioning. Restricting the dependency to a specific implementation version should not be an issue as a mention at the end of this section. We just have to figure out how we will map the implementation version token to our exports. For example, the implementation version token could be attached to both public and private packages, then the implementation imports would import all of its packages specifying the implementation version token so they could only match against that version (and effectively that provider). Other possibilities might exist, but we have to discuss all of the various version tokens in both module systems and figure out their relationships.

This OSGi bundle exports the public API package and all implementation packages; again we assume that bundle and package versions are the same. The implementation packages are guarded by a mandatory attribute which is a simple mechanism that requires that any importer of the package also specify the attribute; this prevents importers from accidentally wiring to implementation packages and could also be used by tooling to give warnings. (NOTE: The nonpublic attribute has no special meaning to OSGil; OSGi exports support arbitrary attributes).

Given this definition of the module, public API dependencies work the same as previously described, while implementation dependencies are similar, but include the mandatory attribute, such as (we assume that an implementation dependency also imports the public API packages):

Import-Package: x.y.z; version="(2.1,3)", x.y.impl1; x.y.impl2; x.y.impl3; version=[2.1,2.1]; nonpublic="true"

Likewise, these could be narrowed to a specific module if needed:

Import-Package: x.y.z; version="(2.1,3)"; bundle-symbolic-name="some.module", x.y.impl1; x.y.impl2; x.y.impl3; version=[2.1,2.1]; nonpublic="true"; bundle-symbolic-name="some.module"

Keep in mind that in OSGi it is not necessary to import every implementation package, only those that are needed. So, if the given module only used x.y.impl2, then its dependency could be described as:

Import-Package: x.y.z; version="(2.1,3)"; bundle-symbolic-name="some.module", x.y.impl2; version=[2.1,2.1]; nonpublic="true"; bundle-symbolic-name="some.module"

The BND tool from Peter Kriens can simplify this process greatly by automatically calculating the proper Import-Package value.

Since OSGi imports and exports support arbitrary attributes, it would also be possible to further narrow implementation dependencies if necessary, such as including an implementation version token.

Mapping NetBeans generic provide/require tokens

NetBeans allows modules to declare generic provide/require tokens using OpenIDE-Module-Provides (?) and OpenIDE-Module-Requires, respectively. OSGi does not have an analogous mechanism. However, it is my understanding that these tokens are largely used to determine the activation ordering of modules. Given that, it would be possible to support this mechanism as a layer on top of the OSGi framework.

JesseGlick comments: Provide/require tokens (also OpenIDE-Module-Needs and OpenIDE-Module-Recommends) do affect load order of modules, but this is a minor side effect; load order is irrelevant for the great majority of modules. (It can affect layer masking, in the absence of a proper fix for Issue 141925; and it affects the order in which ModuleInstalls are called, in case anyone cares.) Tokens also have no effect whatsoever on class loader wiring. The primary purpose of tokens is to ensure that the right service provider modules are in fact enabled when they ought to be, so inversion of control works. For example, openide.io says OpenIDE-Module-Recommends: org.openide.windows.IOProvider because any client of this API ought to have an SPI implementation available. core.output2 says OpenIDE-Module-Provides: org.openide.windows.IOProvider and so advertises itself as the likely implementation, but other implementation modules could be freely swapped in instead. The API client only interacts with the implementation by finding the interface in lookup. I do not know enough about OSGi to say whether there is a clear equivalent of this system.

RichardHall comments: Some discussion is still needed in this area, but it still sounds like this should be possible as a layer on top of Felix.

It is not specifically necessary to use the OSGi activation mechanism; instead, a NetBeans activation layer could be created that would do any necessary activation of NetBeans modules according to its normal rules. The OSGi framework would then only be responsible for resolving module dependencies (i.e., the class loader wiring); this is an example of the OSGi extender pattern. Of course, it is still potentially possible to define a hybrid approach, where a standard NetBeans bundle activator is used for all NetBeans modules, which handles any necessary activation dependencies. The important thing, is that I believe this is do-able in a reasonable way.

Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo