Building a new OSGi module

November 11, 2022

Introduction

Starting with Digital Factory 7.0, new modules must now be written using OSGi bundles. As OSGi is a mature and powerful dynamic module system for Java, it becomes a lot easier to build full-fledged Digital Experience Manager modules that can interact with each other, while avoiding complex interdependencies that might make maintenance and deployment complex. At the same time, it makes it possible to leverage the already available OSGi bundles such as the Felix Web Console or the Felix Shell to quickly add functionality to a Digital Experience Manager installation.

What is OSGi?

OSGi (aka Open Services Gateway initiative) is a dynamic module system for Java. It is currently the most powerful and most mature dynamic module system available for Java applications. In OSGi, modules are actually called “bundles”. They are specialized JARs that include extra MANIFEST.MF entries that declare the dependencies between modules, as well as versioning and package information made available to other modules. In effect, most existing JAR can be converted to an OSGi bundle with little work (they are even automatic transformation tools available). In the OSGi runtime, only packages that are exported will be available and visible to other bundles, only if the using bundles also import them. So in effect there can be fine-grained control of accessible Java packages (as well as associated versions) between bundles.

A minimal OSGi bundle

An OSGi bundle is basically a classic Java JAR file with additional metadata information inside the META-INF/MANIFEST.MF file, such as:

  • Bundle identifier (symbolic name)
  • Bundle version
  • Bundle package imports and exports
  • (Optional) Bundle activator

Here is an example of a minimal OSGi bundle:

META-INF/MANIFEST.MF:
Bundle-SymbolicName: org.jahia.modules.example
Bundle-Version: 1.0

Why OSGi?

Taken from the OSGi’s official website:

From the developers point of view:

OSGi reduces complexity by providing a modular architecture for today's large-scale distributed systems as well as small, embedded applications. Building systems from in-house and off-the-shelf modules significantly reduces complexity and thus development and maintenance expenses. The OSGi programming model realizes the promise of component-based systems.

From the business point of view:

The OSGi modular and dynamic model reduces operational costs and integrates multiple devices in a networked environment, tackling costly application development, maintenance and remote service management.

The key reason OSGi technology is so successful is that it provides a very mature component system that actually works in a surprising number of environments. The OSGi component system is actually used to build highly complex applications like IDEs (Eclipse), application servers (GlassFish, IBM Websphere, Oracle/BEA Weblogic, Jonas, JBoss), application frameworks (Spring, Guice), industrial automation, residential gateways, phones, and so much more.”

Despite an initial learning curve that requires learning how to setup OSGi modules properly (especially their dependencies), OSGi benefits quickly make themselves visible in even small projects.

You have multiple options to build a new OSGi module, and we will quickly present them here by starting with the easiest all the way to the most complex. Depending on your needs and skills, all of these might be interesting at some point in your projects.

Using the Digital Experience Manager Studio to create a new project

The easiest way to create a new OSGi module is simply to create a new module using the Digital Experience Manager Studio that allows you to create and modify module or template projects directly from the Digital Experience Manager server development environment. It will by default use the new OSGi packaging and all you will have to do is simply customize it for your needs.

We will not give the details here on how to create a new project using the Studio, as this is already explained in detail in the Digital Experience Manager templating guide, available on the Jahia.com website.

Using a Maven Archetype

We provide a Maven archetype to get started quickly with a new project. The Maven Archetype is also used internally by the Digital Experience Manager Studio to initialize a new project. The steps below guide you through the process of creating a new project.

  1. Create a new project using a Maven Archetype:
    mvn archetype:generate -Dfilter=org.jahia.archetypes:
    (do not forget the colon at the end of the filter value)
  2. Select the required archetype, e.g.:
    2: remote -> org.jahia.archetypes:jahia-module-archetype (Archetype for creating a new module project to be run on a Digital Experience Manager server)
  3. Chose the latest archetype version available from the list
  4. Enter project metadata and confirm
  5. Change into project directory and build using:
    mvn clean install

You can then open the Maven project in your favorite IDE and start building your Digital Experience Manager OSGi module.

6.3 From scratch

First and foremost, if you’re ok using the Digital Experience Manager Modules parent project, it will automatically configure both the Felix Maven Bundle Plugin and Jahia Maven Plugin to use defaults that make sense for most projects. To do so simply set as a parent to your Maven project:

<parent>
    <artifactId>jahia-modules</artifactId>
    <groupId>org.jahia.modules</groupId>
    <version>7.2.0.0</version>
    <relativePath/>
</parent>

If you prefer not to use Digital Experience Manager module parent project, you will have to setup the plugins yourself, as explained here. To build an OSGi project, it is recommended to use the Felix Maven Bundle Plugin to help with the basic packaging. This is fairly easy to setup. First change the project’s packaging to bundle:

<packaging>bundle</packaging>

Then configure and add the plugin to the project:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-Name>${project.name}</Bundle-Name>
            <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
            <Bundle-Category>jahia-module</Bundle-Category>
            <Implementation-Title>${project.name}</Implementation-Title>
            <Implementation-Version>${project.version}</Implementation-Version>
            <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
            <Implementation-URL>${project.organization.url}</Implementation-URL>
            <Specification-Title>${project.name}</Specification-Title>
            <Specification-Version>${project.version}</Specification-Version>
            <Specification-Vendor>${project.organization.name}</Specification-Vendor>
            <!-- Jahia manifest attributes -->
            <Jahia-Depends>default</Jahia-Depends>
            <Jahia-Module-Type>module</Jahia-Module-Type>
            <Jahia-Root-Folder>${project.artifactId}</Jahia-Root-Folder>
            <Jahia-Source-Folders>${project.basedir}</Jahia-Source-Folders>
            <Jahia-Static-Resources>/css,/icons,/images,/img,/javascript</Jahia-Static-Resources>
            <Export-Package></Export-Package>

 

This is the default minimal configuration for building a Jahia OSGi module bundle.

However, there are a few things that the Felix Bundle Maven plugin cannot do, it cannot scan in non-Java resources for package uses such as:

  • JSPs
  • Taglibs
  • Groovy files
  • Spring descriptors
  • Content definitions
  • Content import files

Fortunately, we provide a new goal in the Jahia Maven Plugin that will integrate with the Felix Bundle plugin that will scan all the standard Jahia module resources for you and build the required import package statements in the Felix Bundle plugin configuration.

Here is an example of setting up the Jahia Maven Plugin to scan for dependencies:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-Name>${project.name}</Bundle-Name>
            <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
            <Bundle-Category>jahia-module</Bundle-Category>
            <Implementation-Title>${project.name}</Implementation-Title>
            <Implementation-Version>${project.version}</Implementation-Version>
            <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
            <Implementation-URL>${project.organization.url}</Implementation-URL>
            <Specification-Title>${project.name}</Specification-Title>
            <Specification-Version>${project.version}</Specification-Version>
            <Specification-Vendor>${project.organization.name}</Specification-Vendor>
            <!-- Jahia manifest attributes -->
            <Jahia-Depends>default</Jahia-Depends>
            <Jahia-Module-Type>module</Jahia-Module-Type>
            <Jahia-Root-Folder>${project.artifactId}</Jahia-Root-Folder>
            <Jahia-Source-Folders>${project.basedir}</Jahia-Source-Folders>
            <Jahia-Static-Resources>/css,/icons,/images,/img,/javascript</Jahia-Static-Resources>
            <Export-Package></Export-Package>
            <!-- uncomment if you also configure the Jahia Maven plugin jahia:dependencies goal
            <Import-Package>*,${jahia.plugin.projectPackageImport}</Import-Package>
            <Provide-Capability>${jahia.plugin.providedNodeTypes}</Provide-Capability>
            <Require-Capability>${jahia.plugin.requiredNodeTypes}</Require-Capability>
            -->
            <Embed-Dependency>*; scope=compile; type=!pom;
                inline=true</Embed-Dependency>
            <Embed-Transitive>true</Embed-Transitive>
            <_removeheaders>
                Include-Resource,
                Private-Package,
                Embed-Dependency,
                Embed-Transitive
            </_removeheaders>
        </instructions>
        <archive>
            <addMavenDescriptor>false</addMavenDescriptor>
        </archive>
    </configuration>
</plugin>

The plugin will generate the value for the property jahia.plugin.projectPackageImport that was already inserted in the Felix Maven Bundle Plugin configuration we had previously setup. Now the project is ready for building, which you can simply do using:

mvn clean install

Note, please, if your module uses node type or mixin definitions from another module, it is recommended to explicitly define the dependency between modules. See Node type definitions section in the chapter 2.4.1. of our Development Best Practices guide for more details.