Monday, March 23, 2020

OSGi Core R8 Specification Draft Available - Get Connected

The OSGi Core Expert Group has released a draft of the upcoming OSGi Core Release 8 specification. The draft includes two new specifications for the Core Framework as well as a number of smaller improvements.
  • 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
For this post, I will focus on the OSGi Connect specification included in chapter 60 of the draft specification. Karl Pauls previously blogged about OSGi Connect last year and now I would like to describe the progress made since that post for the draft specification.

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.
In order to install connect bundles, the Framework must be created with a module connector instance (defined by the interface org.osgi.framework.connect.ModuleConnector). The module connector hooks into the initialization of the framework as well as the activation, allowing it to interact with the Framework lifecycle and service layers. More importantly the module connector hooks into the installation of bundles in the Framework.  When a bundle is installed into the Framework a location string and an optional input stream to the bundle content is provided by the installer.  When no input stream content is provided the Framework must determine what the content of the bundle is.  Typically the Framework implementation will attempt to convert the location string into a URL and load the bundle content from there.

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.

Tuesday, March 17, 2020

OSGi Alliance IoT Vision

The OSGi Alliance started more than 20 years ago with the mission to develop specifications for connected homes and buildings – something we now consider part of the Internet of Things. Over the last couple of months, we have worked on an update of our IoT vision that describes challenges and requirements, and how OSGi addresses these in the chaotic IoT standards landscape. The challenges and requirements are not necessarily all new, but the ever-growing IoT landscape now requires more than ever standards to ensure that IoT solutions are economically and technically sustainable, maintainable, agile and evolvable over many decades. The OSGi Alliance with its specification for standardized software modularity and life-cycle management, connectivity of IoT devices, device management and software provisioning have been industry proven for many years with millions of deployments. The IoT Expert Group will continue to work on new specifications such as interoperability with oneM2M and support for real-time environments.

Please review the OSGi Alliance IoT vision and give us your feedback. Should you feel that we are on the right track, please join us to help make sustainable IoT solutions a reality.

Wednesday, January 22, 2020

To Embed or Not To Embed Your API Packages

For many years the OSGi Alliance, like other Java Standards organisations, has been creating APIs as part of its specifications. Multiple communities have then been involved in providing implementations of those APIs and specifications (see https://en.wikipedia.org/wiki/OSGi_Specification_Implementations).

In Java, not just OSGi, to use a specification you need both the API and the code which provides the API implementation. As a convenience many OSGi bundles implementing an API also provide the API package(s) from the bundle via ‘Substitutable Exports’ (see https://www.osgi.org/developer/white-papers/semantic-versioning/exporter-policy/). This means that the API package is both exported as well as imported by the implementation bundle. Because the OSGi framework controls the classloading on the package level, when multiple bundles export the API package(s), the OSGi framework can decide which exporting bundle ultimately provides the API package(s) to each bundle importing the API, that is, to each API consumer. This is not possible in “vanilla” Java, where the first API package(s) found on the classpath will be used, potentially causing problems if multiple versions of the API are present.

The use of substitutable exports had historical advantages because it made the job of a management agent easier when trying to find a set of functional bundles to install into an OSGi framework. By providing the API packages that they implement, implementations make themselves easier to deploy. The management agent (or a human) does not also have to find a bundle providing the API to make an implementation resolve. As the OSGi specifications continued to evolve the process to  search for dependencies was replaced by a solution that uses the standard Resolver and Repository services. Due to the limited metadata available, the use of substitutable exports also had advantages here. If a client bundle imports an API package and that API package is provided by another bundle which also provides an implementation of the API then a resolver will automatically pull in the implementation bundle along with the API when resolving the set of bundles to install.  If the API is provided by a separate bundle from the implementation then the resolver needed some proprietary way to discover the requirement for the implementation, and then to discover the bundles that provide the implementation.  In modern OSGi the “resolution problem” can now be completely solved by using the generic capability and requirements functionality provided by the OSGi core specification.  Not only can bundles declare requirements on the API using the Import-Package header, but they can also declare requirements on an implementation using the Require-Capability header.  Similarly bundles can declare that they provide an implementation using the Provide-Capability header.

In the past there was another benefit to substitutably exporting your API, getting API package(s) for individual OSGi specifications used to be tricky. The main Jar file that had them available was typically the osgi-cmpn.jar which contained all the API packages for all the Compendium specs and this Jar was certainly not designed or intended to be used at runtime.  Fortunately this last problem is now resolved since OSGi Release 6 as individual specification API jars are now available in Maven Central for each specification and these jars can be used at runtime.

So while the approach for embedding API packages has served the OSGi community well for many years, it requires a runtime supporting type visibility encapsulation, such as an OSGi Framework, that is in control of the class loading to select the appropriate package provider at runtime. Recently, work has started at the OSGi Alliance to support operating in environments where the class loader may not be provided by an OSGi Framework, or where there is no class loader at all. See RFC 243 OSGi Connect and RFC 245 Resource Encoding For Java Modules. This could be when running with the Java Platform Module System (JPMS), running as part of another framework such as Spring Boot, or even in a AOT-compiled environment such as SubstrateVM. Many existing OSGi bundles can work in these environments, providing the dynamic OSGi service model and allowing users to continue using popular OSGi technologies such as Config Admin, Declarative Services, the Converter and many others. However the embedding of API packages defined in specifications becomes a problem if there are multiple bundles that contain and provide the packages, since in these contexts there is no OSGi classloader to select the proper packages and hide, through type visibility encapsulation, the unused copies of the packages.

Going Forward

Going forward, to support all environments for your bundles, you should consider not embedding API packages that are defined by OSGi specifications. The better approach is to declare them as dependencies using Import-Package manifest headers. Most build tools will do this automatically for you. At runtime, simply use the API runtime bundles provided by OSGi. If you do embed the API packages in an implementation bundle then be sure to only embed the API packages you directly implement.  In most all cases you should not embed the APIs that you use but do not implement.  For example, if you use the OSGi Log Service you should avoid embedding the org.osgi.service.log package unless your bundle is actually providing the implementation of the Log Service.