Jahia OSGi Implementation

November 14, 2023

OSGi Framework startup

The OSGi framework is started once Jahia Spring context is instantiated. In turn, the FrameworkService initializes the Apache Karaf instance which registers all the bundles that will initially be started and made available to all deployed bundles. Of particular note, the FrameworkService loads its configuration from the digital-factory-data/karaf/etc/config.properties file that contains important settings such as which packages are made available to all bundles.

OSGi Servlet Bridge

The OSGi servlet bridge consists of two parts, a proxy servlet that is mapped as a servlet in the WEB-INF/web.xml file of the Jahia web application and then the actual bridge that will make it possible to call “regular” servlets inside the OSGi framework.

Jahia Module Extender

The core of the module integration is in the jahia/bundles/jahiamodule-extender project. Here, the Activator class is the starting point that registers the bundle listeners to listen for the deployment and undeployment of bundles and performs registration work inside Jahia. This is where most of the OSGi “magic” is happening, including the registration and dispatching to JSPs.

Bundle packaging

Jahia can have both system bundles that will be handled by Apache Karaf (such as Apache Karaf extensions) and Jahia bundles (common Jahia modules).

Jahia relies on Apache Karaf for system bundles. All the system bundles are in the Apache Karaf folder, by default at digital-factory-data/karaf. From here, you can use all the Apache Karaf framework to deploy any bundle,  feature, or kar file. See more information here:
https://karaf.apache.org/manual/latest

Jahia has a watched directory located in digital-factory-data/modules that contains all Jahia installed bundles.

OSGi and Jahia Module States

Bundle life cycle

With the installation of a bundle in the OSGi runtime, the bundle is persisted in a local bundle cache. The OSGi runtime then tries to resolve all dependencies of the bundle.

If all required dependencies are resolved, the bundle is in the RESOLVED status otherwise it is in the INSTALLED status.

If several bundles exist which would satisfy the dependency, then the bundle with the highest version is used. If the versions are the same, then the bundle with the lowest install ID will be used (the bundle gets a unique identifier assigned by the framework during the installation). If the bundle is started, its status is STARTING. Afterwards it gets the ACTIVE status.

This life cycle is depicted in the following graphic:

Jahia extends the default OSGi lifecycle states to add intermediary states that give more information about the state of a module. You can find the description of these states in this table.

State name OSGi Jahia Description
UNINSTALLED x   Bundle is not installed
UNRESOLVED x   Bundle is installed, but dependencies haven’t been resolved
RESOLVED x x Bundle is installed and all dependencies have been resolved
INSTALLED x x Bundle was installed, but not started
UPDATED x x Bundle was updated
STOPPED x   Bundle was stopped
STOPPING x x Bundle is stopping
STARTING x x Bundle is starting
WAITING_TO_BE_IMPORTED   x Bundle is waiting to import its content, waiting for a dependency to import its content
STARTED x x Bundle is fully started and available
SPRING_STARTING   x Bundle is started but is waiting for Spring context to be available
SPRING_NOT_STARTED   x Module failed to start due to an error creating module's Spring context
INCOMPATIBLE_VERSION   x The Jahia required version of the module is incompatible with the current one
ERROR_WITH_DEFINITIONS   x Bundle cannot start due to definitions error
ERROR_WITH_RULES   x Module failed to start due to an error compiling included business rules (Drools)

Recommendations

As previously stated, when a bundle requiring a dependency is deployed, if this dependency is provided by several versions of a different bundle, then the highest version is used. This is an OSGi standard.

However, this may not work well with Jahia modules, which only allow one version of a module to be started/in use. When keeping several bundles of the same module on the platform, the dependency resolution will become unpredictable, as it will depend on the deployment order of your modules.

Simple example:

Module A depends on Module B. 

Scenario 1 Scenario 2
  1. Deploy moduleB-1.0
  2. Deploy moduleB-2.0
    Both moduleB versions are deployed, 2.0 is started
  3. Deploy moduleA-1.0 depending on moduleB
    -> moduleA uses the dependency provided by moduleB 2.0
  1. Deploy moduleB-1.0
  2. Deploy moduleA-1.0 depending on moduleB
    -> moduleA uses the dependency provided by moduleB 1.0
  3. Deploy moduleB-2.0
    Both versions are deployed, module-B2.0 is started, but moduleA still uses the dependency provided by moduleB-1.0

This example shows how the order of the operations can impact the dependency resolution.

Even though Jahia tries to handle such situations by performing wiring refreshes upon deployment, because of transitive and circular dependencies it is not possible to address all the cases in a consistent way. 

Therefore, Jahia does not support several versions of a module being deployed at the same time in production mode. After deploying a new version of a module, you need to undeploy the previous one. This is meant to ensure reliable OSGi wiring between Jahia modules. Please note that this restriction is not enforced in the product.

Listening to bundle states

You can listen to module state changes in another bundle by implementing a BundleListener. It's quite a common way to detect features in other bundles and react when they are installed or started.

However, you must be careful as BundleListener are asynchronous the state of the module may have changed by the time your listener is executed. Consider using SynchronousBundleListener instead if:

  • you need to listen to a state change and want to be sure that the state has not changed again
  • your listener performs any operation that might change the state of a bundle