Resolving - OSGi's Best Kept Secret?
The OSGi Framework uses the Resolver to wire together a given set of bundles at runtime, however what most people don’t know is that the resolver can also be used to select the set of required bundles that should be installed into your runtime.
Modularity and Dependencies
As soon as you create Modules, you create a dependency management problem.
Realizing this the OSGi Alliance has evolved, through extensive experience, a generic Requirements-Capabilities dependency management model.
This model consists of a small number of primitive concepts:
Artifact (a.k.a Thing) - For OSGi, the primary Artifact is the OSGi Bundle (a JAR file); but other examples of Artifacts include software Certificates, or physical components such as a secure USB key store.
Environment - The runtime Environment within which an Artifact may be installed: i.e. a physical host, a container, or an OSGi framework.
Resource - A formal description of the Artifact specifying what that Artifact can contribute to the host Environment (its Capabilities) and what it needs from the Environment to function (its Requirements).
Namespace - Capabilities and Requirements are defined in appropriate namespaces; every Requirement belongs to a namespace and can only require Capabilities in the same namespace.
Capability - Describes a feature or function of the Resource when installed in the Environment. A Capability has a type (specifying namespace) and a set of key/value properties. Properties are key value pairs, where the keys are strings and values can be scalars or collections of
Requirement - Specifies a Capability needed in an Environment. A Requirement consists of a type and an OSGi filter expressed as an LDAP expression. A Requirement can be mandatory or optional.
Resolving is then the process of constructing a complete, closed set of Resources from a list of initial Requirements, a description of the Environment’s Capabilities, and one or more repositories with available Resources. Once this list of resources is known the associated artifacts can be installed.
The Requirements / Capabilities Namespaces currently defined are:
osgi.identity - Used to identify a resource type and provide a unique name: e.g. for a Certificate the type could be x509 and the name could then its SHA-1 fingerprint.
osgi.ee - An OSGi Framework must register capabilities for all the execution environments the Java VM is known to be backward compatible with. For example, if the Java VM provides Java SE 6, then it is backward compatible with 1.2, 1.3, 1.4, 1.5, and 1.6. The osgi.ee capability defines the provided versions as a comma separated list.
osgi.native - Used to describe the native environment in which the Framework is executing. An OSGi Framework must provide a capability in the
osgi.nativenamespace that represents the native environment in which the Framework is executing.
osgi.content - Via which repositories can advertise different formats; each of those format capabilities being identified with a unique SHA-256 checksum and a URL.
osgi.wiring.package - A Requirements / Capabilities representation of the information in the Bundle manifest: i.e. Import-Package, DynamicImport-Package, and Export-Package.
osgi.service - A Requirements / Capabilities representation of the OSGi services used and provided by an OSGi bundle. This namespace is part of the OSGi compendium and is usually added automatically by tools.
osgi.implementation - Used as a capability to indicate when a bundle implements a feature that is designed to be used indirectly. A good example is when a bundle implements a whiteboard pattern. In this case there is no direct package or service link from the consumer to the implementation, and so a requirement for the
osgi.implementationcapability is usually added to the consumer instead. This namespace is part of the OSGi compendium and is usually added through the use of annotations.
osgi.contract - A Requirements / Capabilities representation of an API contract which does not use semantic versioning. You will commonly see this used for packages that come from Enterprise Java specifications. This namespace is part of the OSGi compendium and is usually added through the use bnd’s
osgi.wiring.bundle - Reflects the information in the bundle headers for the purpose of requiring another bundle: i.e. a Require-Bundle header creates a wire from the requiring bundle to the required bundle.
Of these, the last two are concerned with low-level wire-up of the Bundle assembles and can usually be ignored. For further information see the Framework Namespace Specification and the Common Namespace Specification.
Resolving & Repositories
A Repository is a collection of Resources.
Only the resources contained in the repositories provided to the resolver can be considered during the resolution process: i.e. the resolution process is scoped by these repositories.
One might consider a single large repository with all possible resources (e.g. Maven Central), or alternatively a number of tightly scoped repositories, one per application with minimal diversity of resources.
Each approach has its drawbacks:
- Resolving is an NP complete problem so resolution times quickly become long when the repository becomes large
- Without appropriate tooling many small tightly scoped repositories - while in principle a good idea - can become a management burden.
Hence usually a small number of curated repositories, each aligned to each Organizational business unit, is usually a good compromise.
The relationship between Repositories and the Resolution process is shown:
An important variable in the resolving process is
effective which defines the effectiveness under which the resolve operation is performed. The resolver will only look at requirements that it deems effective. The default effectiveness is
resolve. The effectiveness
active is a convention commonly used for situations that do not need to be resolved by the OSGi framework but are relevant in using the resolver for assembling applications.
As previously explained Maven Central is far too huge for us to consider resolving against. We definitely need curated sources of bundles that we can resolve against, ideally without adding too much management overhead.
When using Maven it turns out that a POM file is actually a pretty good source of bundles. Instead of searching the whole of Maven Central we can just search the transitive dependency graph defined by the POM file. As long as the POM is well maintained, with properly scoped dependencies, then it functions very well as the basis for a curated repository.
This is the approach taken by OSGi enRoute. The OSGi enRoute project provides a number of “index” poms which are designed to be used both for convenience in your Maven build, and also when you want to resolve your application. You will have seen that in each of the examples there is an Application module which gathers together the application bundles and exports them as a runnable JAR file. If you look a little more closely you will also see that these modules depend on the enRoute indexes, and on the other modules in the application. The reason for this is that application modules use the
bnd-indexer-maven-plugin to generate OSGi repositories based on the dependencies listed in their poms.
Working in this way reduces the repository management overhead in maintaining the dependencies in your applications pom file. If any of the other modules in the application change their dependencies then this is automatically reflected in the OSGi repository generated by the indexer. The only time a change is needed to the application project is if a new leaf module (i.e. one that isn’t depended on by other modules in the application) is added to the application.
Resolving in Quick Start?
When working through the quick start tutorial the OSGi resolver was run ether in eclipse, or manually via the cli: if in eclipse you’ll have seen the
Resolution Results window lists the set of Bundles required.
The enRoute release artifact
app.jar was then created with its own internal repository with contents determined by the resolution: to see this
jar xf app.jar.
$ ls META-INF app.jar launcher.properties start.bat aQute jar start $ cd jar $ ls biz.aQute.launcher-4.0.0.jar impl-1.0-SNAPSHOT.jar javax.json-api-1.0.jar logback-classic-1.2.3.jar logback-core-1.2.3.jar org.apache.aries.javax.annotation-api-0.0.1-SNAPSHOT.jar org.apache.aries.javax.jax.rs-api-0.0.1-SNAPSHOT.jar org.apache.aries.jax.rs.whiteboard-0.0.1-SNAPSHOT.jar org.apache.felix.configadmin-1.9.0-SNAPSHOT.jar org.apache.felix.framework-5.7.0-SNAPSHOT.jar org.apache.felix.http.jetty-3.4.7-R7-SNAPSHOT.jar org.apache.felix.http.servlet-api-1.1.2.jar org.apache.felix.scr-2.1.0-SNAPSHOT.jar org.osgi.service.jaxrs-1.0.0-SNAPSHOT.jar org.osgi.util.function-1.1.0-SNAPSHOT.jar org.osgi.util.promise-1.1.0-SNAPSHOT.jar slf4j-api-1.7.25.jar
Once the application is started the resolver in the OSGi framework runs, wiring together the Bundles in the application’s local repository to create your application.
Note that enRoute supports a simple standalone Application release/run model. More sophisticated runtime behaviors are enabled by OSGi including:
- Dynamic Bundle updates at runtime.
- Runtime assembly influenced by the runtime Environment’s Capabilities.
The OSGi Resolver is responsible for assembling composite artifacts from selected sets of self-describing OSGi Bundles: so enabling substitution and re-use.
For further details concerning the OSGi Resolver & Repository consult OSGi Core Release 7 specifications.