- OSGi Connect - Provides a mechanism to connect bundles in the Framework with content managed outside of the Framework itself
- OSGi Condition Service - Provide a mechanism to signal that a condition is satisfied at runtime
A Bit of History
Over the last 20 years, the OSGi Framework has provided a solid foundation to develop modular software. This foundation is built upon the various layers provided by the Core OSGi Framework itself.
At the bottom of the foundation is the module layer. This layer provides the rules for how modules (or bundles) can share or hide the packages they include in their own bundle JAR. The Framework provides the class loader implementation that enforces the rules of the module layer. This includes a rich capabilities and requirements model and a resolver that wires up the requirements to the available capabilities in the Framework. Once a bundle is installed and resolved it is then able to participate in the layers that are built on top of the module layer.
The lifecycle layer provides control over the activation of the bundles resolved in the Framework. Activation provides an entry point for each bundle to allow them to interact with the Framework and other bundles installed in the Framework. A bundle may include a bundle activator which allows them to execute code when they are activated. The activation concept also allows for more powerful runtimes to be built on top that can use introspection of the bundle capabilities and provide a mechanism for enabling the capabilities of the bundle at runtime. This concept is called the extender pattern. Two good examples of the extender pattern are the OSGi Declarative Services specification and the CDI Integration specification. These two examples provide powerful dependency injection runtimes for developers.
The service layer provides a dynamic, concise and consistent programming model for developers, simplifying the development and deployment of services by de-coupling the service's specification from its implementations. This model allows developers to bind to services only using their interface specifications. The selection of a specific implementation, optimized for a specific need or from a specific vendor, can thus be deferred to runtime. The service layer model provides a common layer which allows for things like declarative service components and CDI components have dependencies on services available in the service registry. This is powerful because it allows the components from declarative services and CDI to interact with each other through a shared layer without needing to know the details of each other’s runtimes.
The combination of the activation model with the service model in OSGi provides for an unparalleled platform for developing loosely coupled components that are highly configurable at runtime. In order to use this powerful tool, developers must be able to live within the confines of the OSGi Framework module layer. In some scenarios, it is challenging to live within the OSGi module layer as it is defined today. The following are a few examples:
- Modules built into a jlink image. The jlink tool was introduced in Java 9 along with the Java Platform Module System (JPMS) and provides a way to “right size” the JVM along with a set of application modules to provide an image that only includes what is required by an application’s modules. At runtime Java modules are typically loaded by class loaders provided by the JVM. In the case of a jlink image all the modules are loaded by a single class loader. This limits the ability to load bundle content out of the modules included in a jlink image. Similar limitations exist with modules provided on the module path.
- Applications natively compiled using something like Graal Substrate. With native-image compilation not only is it not possible to have a custom class loader, the classes at runtime are not really loaded at all. Instead they simply exist with the execution of the native-image. This limits the ability to load bundles out of content compiled into the native-image.
- Bundles included on the class path or more generally loaded by some form of the URLClassLoader. The concept of the Java class path has been around since the early days of Java and many of today’s popular frameworks take advantage of the class path environment. For example, Spring Boot loader mimics the class path when a Spring Boot application is packaged as a uber JAR (a JAR with many embedded JARs). It has been challenging to integrate OSGi technologies into such environments.
- Other environments that compile Java into alternative deployment artifacts, such as Android, limit the ability of the framework to control the actual installation and deployment of new bundles.
The Idea to Connect
It would be helpful to allow content that lives outside of the module layer to participate in the lifecycle and service layers of OSGi. The new OSGi Connect specification introduces a mechanism that allows content managed outside of the Framework control to be represented (or connected) with a bundle installed in the Framework. Because the content is managed outside of the Framework itself it may not follow all the rules of the OSGi module layer. For example, multiple bundles connected to outside content may be loaded by the same class loader. The class loader loading the content may not provide the same isolation as the Framework managed class loaders. With Connect now that content has the ability to participate in the activation model as well as the service layer of the framework.
The Connect specification introduces a concept of a module connector. The module connector is called by the framework when the framework needs to access content of the bundle. The content of the bundle includes the following:
- A list of entries contained in the bundle and access to read from them. For example, a declarative service component XML file, the bundle manifest, or any other resources included in the bundle.
- An optional map of bundle manifest headers. Typically bundle manifest headers are specified in the bundle’s META-INF/MANIFEST.MF entry, but for connect content the headers may come from an alternate source.
- An optional class loader for the bundle. If a class loader is provided then the framework must use it for the bundle and must not create a framework managed class loader.
When a module connector is used the Framework must first ask the module connector if it can provide content for the bundle location. If the module connector can provide content then it supplies a connect module to the framework (defined by the interface org.osgi.framework.connect.ConnectModule). A connect module is then associated with the connect bundle installed in the framework. For each revision of the connect bundle the connect module provides connect content to the Framework (defined by the interface org.osgi.framework.connect.ConnectContent). A connect content provides access to read entries out of the content, provides an optional class loader for the content and may provide the bundle manifest headers for the content.
To create a new Framework that uses a module connector a new framework factory has been introduced with the interface org.osgi.framework.connect.ConnectFrameworkFactory. A Framework implementation that supports the Connect specification provides a ConnectFrameworkFactory just like the Framework provides the org.osgi.framework.launch.FrameworkFactory. A launcher that is looking for a factory to create a Framework instance can use the Java ServiceLoader to load a ConnectFrameworkFactory implementation. This factory can then be used to create a new Framework instance that uses a module connector instance.
Seeing it in Action
While developing the Connect specification, Karl Pauls (Apache Felix project lead) and myself (Tom Watson - Eclipse Equinox project lead) have been busy implementing the Connect specification in our respective OSGi Framework implementations. I have also been working on a project called Atomos that I used as a proof of concept to test out the ability of the Connect specification to connect bundles to various content from different environments, such as: a jlink image, Graal substrate, Spring Boot uber JAR and an Android Dexified JAR (still a work in progress).
With the Connect specification entering into the draft phase, Karl and I thought it would be a good idea to have my Atomos project contributed to the Apache Felix project. This has been done and is now available in the GitHub repository https://github.com/apache/felix-atomos
Among other things, the Atomos project provides a runtime with a module connector and a launcher that can be used to easily launch a framework and a set of bundles contained on the class path or module path. For example, to launch a framework with the Apache Felix Gogo console you could have the following directory containing the necessary JARs:
bundles/
bundles/atomos.osgi.framework-0.0.1-SNAPSHOT.jar
bundles/jline-3.14.0.jar
bundles/org.apache.felix.atomos.runtime-0.0.1-SNAPSHOT.jar
bundles/org.apache.felix.gogo.command-1.1.0.jar
bundles/org.apache.felix.gogo.jline-1.1.0.jar
bundles/org.apache.felix.gogo.runtime-1.1.2.jar
bundles/org.eclipse.osgi-3.16.0.tjwatson_osgiConnect11.jar
The org.apache.felix.atomos.runtime JAR is the Atomos runtime snapshot built from the felix-atomos GitHub repo. It contains the Atomos module connector implementation and the AtomosLauncher class which discovers the framework implementation and launches it with the Atomos module connector. The org.eclipse.osgi JAR is a snapshot of the Equinox framework that implements the connect specification. The atomos.osgi.framework JAR is a Java module that acts as a facade for the Framework implementation so that the Atomos runtime module can require it instead of requiring directly the org.eclipse.osgi module. This allows Atomos to work with other Framework implementations, such as Felix. The atomos.osgi.framework JAR is only required if you are launching from the module path.
To launch Atomos with the Gogo console using the class path, the following Java command can be used:
java -cp "bundles/*" org.apache.felix.atomos.launch.AtomosLauncher
To launch using the module path instead the following can be used:
java -p bundles -m org.apache.felix.atomos.runtime
Both will give you a gogo console with the bundles, Framework implementation, and Atomos all loaded from the same class loader. The Atomos runtime does support Java 8 when using the class path, but class path mode can also be used with Java 11. If you are using Java 11 or higher you will notice that a number of other bundles are installed from the modules included in the JVM. Atomos discovers all the modules that got loaded and will map each of them as an OSGi connect bundle. This includes not only the modules from the specified module path, but also the modules from the boot layer of the JVM. It includes things like the java.base module. For example, if you run the Gogo command “lb” you will see things like the following:
g! lb | grep java.base
38|Active | 1|java.base (11.0.6)|11.0.6
Atomos will read the module descriptors and generate an equivalent OSGi bundle manifest for it so that it can be represented as a connected bundle in the Framework.
At this point, you have a fully functional OSGi Framework instance. You can even install other bundles dynamically. Any additional bundles that you install will not be connect bundles because they will not have been included on the original class path or module path at launch. That means they will have the typical Framework managed class loader and have all the expected behaviors of living in the OSGi module layer.
The Atomos project also has a number of examples that can be looked at in https://github.com/apache/felix-atomos/tree/master/atomos.examples. This includes a jlink, Spring loader and a few Graal Substrate examples. The Spring Loader and substrate examples all include a version of the Apache Felix WebConsole and the substrate examples also include the Felix SCR (Declarative Services implementation) and a set of test bundles that have declarative service components. On my laptop the substrate examples can be launched in an impressive 40 milliseconds.
We are also working on a Maven plugin in Atomos to make the configuration of a Graal substrate compilation easier and automatic. Also in the works is the ability to produce something that can easily be used on Android.
I am excited about the upcoming OSGi Core R8 specification release with the Connect specification. I think this will help enable the use of OSGi technologies in environments that were not previously easy to do and in some cases not possible. Now go download the draft specification and read more details about the new Connect specification.