openHAB: openHAB’s implementation of OSGi
In our second essay, ‘From Vision to Architecture’, we briefly introduced openHAB’s architecture. As part of that, we introduced the standard on which openHAB is developed, namely OSGi (Open Services Gateway initiative). We learned about OSGi containers, services, and bundles, which was necessary to describe the architecture. In this essay, we want to dig a little deeper and get our hands dirty on finding out how openHAB has implemented this OSGi framework. First, we will give you a refresher on the OSGi framework. Then you will be well prepared to find out how openHAB uses OSGi. We will discuss how the OSGi framework helps and hinders openHAB. Additionally, we take a critical look at the framework and the added costs in terms of complexity and run time overhead. Finally, we consider alternatives to the OSGi framework.
Table of Contents
- Refresher on OSGi
- OSGi Overview
- Hindering Factors
- Examples of other projects using OSGi
Refresher on OSGi
The OSGi framework is a dynamic component system for Java, in which small modules can be loaded and shared dynamically to create larger applications. OSGi comprises of multiple layers, of which some (execution environment, modules, life cycle, and services) make up the OSGi container. We can simply use open-source containers for this. openHAB, for example, uses Equinox, which is developed by the Eclipse Foundation. Such an OSGi container is necessary to run bundles for an application. But what are bundles? These are the smallest unit of modularization in OSGi. Basically, they are separate pieces of functionality and together form an application. In order to “work together”, the bundles need to communicate, which they do using OSGi Services. Here services can subscribe and publish data to other services. As a result the entire system is implemented as separate bundles when using the OSGi framework.
openHAB makes extensively use of the OSGi Java structure. In the following section we expand on how OSGi is used within openHAB.
Above you can see the relationship graph of all openhab-core bundles. Some of the groups have been colored to easily identify them since they form the majority of the core codebase. These are the colors:
It is interesting, albeit not quite surprising, that the majority of the bundles are related to modelling and I/O operations (purple and blue respectively). Furthermore, the entire core codebase only relies on a few external bundles (shown as oval nodes), while most functionality is implemented in openHAB’s own bundles. This could be a very deliberate choice since third-party code can prove to be unreliable at times in both code quality and how it handles exceptions.
Run time dependencies
As discussed in the first essay, openHAB uses Karaf Runtime for run-time dependency installation. Karaf is a OSGi framework that can be run within OSGi. At openHAB’s startup, the installed packages and dependencies are loaded and started altogether. Whenever the user installs a new addon Karaf installs the belonging dependencies, and OSGi will start the addon after installation. OSGi takes care of crashed packages as part of the OSGi lifecycle.
Basic configuration components
openHAB ships defaulted with its core components that can be found in the openhab core repository and was explained in the Bundles Dependencies. On top of the core components, the openHAB community has the following add-ons installed and loaded by default.
Basic UI and HABPanel
The Basic UI and HABPanel are maintained on an external repository and are shipped with the default installation. The UI is created with Vue.JS and is packed in an OSGi package by the Eclipse Jetty Project.
The rrd4j persistence service is a database that does not grow in size. This database is used for storing numerical data of a running openHAB instance. openHAB allows the user to choose other data persistence providers1.
User configuration components
Besides openHAB’s default components, openHAB can extend by installing add-ons. These add-ons are categorized as follows:
- Automation, scripting add-ons for using external languages or scripting modules for implementing rules, scripts and schedules.
- Bindings, Basic add-ons for connecting various external hardware or online services, also called software adapters.
- Misc, add-on service for integration in external systems and other services.
- Persistence, add-on service allowing backend connectors for storing (ex. historical data).
- Transformations, add-ons for translating technical values and internationalization
- User Interfaces, add-ons for allowing the use of alternative user interfaces
- Voice add-ons for converting between text and speech.
All add-ons run as OSGi bundle and are maintained by the community on the openHAB addons repository.
In Java, one of the primary ways of providing modularisation within code is through the interface. An interface defines a set of methods which the class that implements it must provide a method body for. A method which accepts parameters of a given interface works with all classes that implement that interface.
The developers of openHAB use this technique throughout the entire codebase. One example of this can be found in the way the
org.openhab.binding.pioneeravr binding uses these interfaces as a method of abstraction:
Using this approach, any class which implements the AvrConnection interface can be controlled using the binding. Not only does this provide an easy path for extending the supported devices, it also allows for new developers to easily get started without having to familiarize with the entire codebase.
We have personally experienced that OSGi is a great framework to work with. It allows for portability, adaptability to changing environments and allows applications to access external bundle repositories. The framework requires modularity, a perfect environment for a system like openHAB that needs to work with continuously changing outside dependencies. However, there are some limitations of the OSGi framework. OSGi enables Java packages to be shared across other bundles. However, this does require a compatibility layer meaning all newer versions of a package need to be backwards compatible with older packages2. This backwards compatibility has been a troublesome area in the past where the step from openHAB 1 to openHAB 2 required a compatibility layer for package APIs to be backwards compatible with those implemented in the openHAB 1.x add-ons. This layer caused a lot of added complexity to the entire openHAB 2 structure. openHAB 3 had to rewrite much of these packages to remove this extra layer3.
OSGi has been designed to work in many environments, which also includes low memory ones. OSGi, therefore, tries to limit the amount of overhead the system has as much as possible and is designed to improve execution times because of its smaller class space. The classloader causes the most considerable overhead in the OSGi framework. OSGi uses dynamic class loading to programmatically load classes at run-time instead of having a class dependency at compile time. For some application, this method can reduce the overhead of loading all possible drivers. Instead, the classloader will load the drivers an openHAB bundle that might not have supplied or installed. The problem lies that using a dynamic classloader causes dependencies to be hidden from the compiler. Hidden dependencies can lead to missing dependencies, which lead to
ClassNotFoundException—causing a significant amount of overhead dealing with these exceptions 4.
We have seen that OSGi is a Java framework to develop and deploy modular components. In this section we take a look at some alternatives of the OSGi framework, although to our surprise there are less alternatives than we had expected. The obvious one would be the Java Platform Module System (JPMS), which we will mainly talk about. On a side note, there seems to be some confusion online, as some people are questioning the difference between the OSGi framework and some other project which is used in a different context or for a different purpose. For example, comparing the framework to Docker5. We hope that our essays on the OSGi framework help to clear away some of this confusion.
Java Platform Module System (JPMS)
JPMS was created with the goal of creating a scalable and approachable modular system for the Java platform. It was released as a significant new feature of Java SE 9 in 2017 and aimed at being a simpler alternative to the pre-existing module system, namely the OSGi framework, which was originally created for IoT ecosystems. Note that the OSGi framework, although highly popular, is not a part of the Java platform unlike JPMS. We will go through some of the similarities and differences on a high level.
First of all, isolation is essential in a module system. All modules should be clearly separated and not interfere with one another. We see that both OSGi and JPMS, although implemented differently, have isolation on the code level. The OSGi framework is considered complex by some, as people run into trouble when applying the OSGi framework to an already existing and not so very modular application. Ultimately, for JPMS this process is not less complex6. Dependencies between OSGi and JPMS work differently. OSGi restricts that a bundle should import every package it depends on (expect java.*), which is automated using tooling. While JPMS relies on module depencies, which could result in unnessary imports78. Finally, JPMS has very little support for versioning, while OSGi has embraced it9.
We also came across Zephyr, a next-generation plugin framework written in Java10. In December 2019, the project was introduced as an OSGi alternative and created out of frustration from the complexity when using Spring in combination with OSGi11. At the bottom of the introduction page is a discussion on their critique about OSGi and why there is a need for an alternative12.
Microsoft Add-In Framework (MAF)
One other alternative to OSGi that we found is the Microsoft Add-In Framework (MAF). As we just saw that Zephyr is quite new, MAF is relatively old, and people ask whether it is still alive13. Some issues and it not being promoted as much might explain why it does not seem to be as popular. We will only name the key differences, which are that MAF (C#) is obviously property of Microsoft, while OSGi (Java) is from an open standards organization OSGi Alliance.