Service sharing between modules

November 14, 2023

OSGi bundles may declare or implement OSGi services that will then be registered in a global framework service registry. Through the registry, other bundles may access the services to interact with them. This simply but powerful mechanism makes it possible to decouple bundles while still allowing strong interactions between them. We will illustrate how to do this by using the example of how our external provider bundles are setup.

Simple services with Declarative Services

The easiest way to expose OSGi services to other bundles is to use Declarative Services. Jahia OSGi container comes with felix SCR, which allows you to declare your services with XML descriptors. Fortunately, these descriptors can be generated automatically by the maven bundle plugin based on DS annotations in your code. 

In order to enable class scanning, you need to add an instruction to your maven-bundle-plugin configuration, like here :

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <_dsannotations>*</_dsannotations>
        </instructions>
    </configuration>
</plugin>

The _dsannotations instruction will tell the plugin which classes to parse, and will generate an XML file based on your annotations.

Components

In DS, services are exposed as "components", which have their own lifecycle and configuration. Declaring a component is done by adding the org.osgi.service.component.annotations.Component annotation on a class. A method can be called at intialization with the org.osgi.service.component.annotations.Activate annotation.

@Component(service = {MyServiceInterface.class})
public class MyService implements MyServiceInterface {

    @Activate
    public void activate(BundleContext context) {
        ...
    }

    ...
}

The following exampe will expose a servlet service, that will be used by the HTTP whiteboard bundle to expose it under /modules/myservlet : 

@Component(service = {javax.servlet.http.HttpServlet.class, javax.servlet.Servlet.class}, property = {"alias=/myservlet"})
public class MyServlet extends HttpServlet {
    ...
}

Referencing other services

Referencing a service or a list of services is done with the org.osgi.service.component.annotations.Reference annotation on a setter :

@Reference
public void setSomething(Something value) {
    this.value = value;
}

Using a list of services is done the same way, using the cardinality option :

@Reference(cardinality = ReferenceCardinality.MULTIPLE)
public void addService(Service service) {
    this.services.add(service);
}

public void removeService(Service service) {
    this.services.remove(service);
}

More annotations and options can be used, more details can be found in the specifications : https://osgi.org/specification/osgi.cmpn/7.0.0/service.component.html and the annotations description: https://osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-component.annotations

Simple services with Blueprint

As an alternative to Declarative Services, Blueprint can be used to expose OSGI services. Blueprint is a dependency injection framework for bundles. It provides a way to declare services and wiring using XML definition resources. The Blueprint specification is derived from the Spring Dynamic Modules project.

This is directly available in Jahia and useable out of the box. Enabling Blueprint for a bundle is just a matter of adding XML files in the OSGI-INF/blueprint folder with the following content:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

   <!-- Your definitions go here! -->

</blueprint>

Service declaration

First we have a Jahia module that provides an interface and implements it. We will use the service and reference tags to register the service with OSGi’s service registry. This can done by using the XML as in this example:

<bean id="foo" class="com.acme.foo.impl.FooImpl">
    <property name="locks">
        <list>
            <value>jmix:lockable.j:locktoken</value>
            <value>jmix:lockable.j:lockTypes</value>
            <value>mix:lockable.jcr:lockIsDeep</value>
            <value>mix:lockable.jcr:lockOwner</value>
        </list>
    </property>
</bean>

<service ref="foo" interface="com.acme.foo.api.Foo"/>

Note that for more complex service registrations, it might be a good idea to have two separate modules, one for the interfaces, and another one for the implementation. This way you can deploy each separately, for example if the interfaces are stable but the implementation is still ongoing.

Once the service has been registered, in our second module, we can use a reference XML tag to access the registered service from the OSGi service registry:

<reference id="foo" interface="com.acme.foo.api.Foo"/>
    
<bean class="com.acme.bar.Bar">
    <property name="handler" ref="foo"/>
</bean>

But the work is not yet complete. An important step must still be completed: package export and import.

The Blueprint specification can be found at https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.blueprint.html

Export-Package instruction

To make the service interface available to other bundles, we must export the package in which it is located. This is achieved by adding the following line to the Felix Maven Bundle Plugin configuration:

<Export-Package>com.acme.foo.api</Export-Package>

Import-Package instruction

Now the last piece of the puzzle is the Import-Package instruction. In most cases this doesn’t need to be manually configured as the Felix Maven Bundle Plugin will scan the module’s code for any dependencies and pick up the service reference.

Services with the Spring Framework

Using the Spring Framework in Jahia to declare services and handle services lifecycle and wiring is not recommended. Consider using OSGI alternatives instead.

For modules developed in prior versions of Jahia that rely on Spring Framework, Jahia 8 provides a compatibilty mode for using the former Spring integration. To use the Spring Framework and the extender provided by Jahia, you have to explicitly enable it by adding the following instruction in your module's pom.xml file:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <require-capability>osgi.extender;filter:="(osgi.extender=org.jahia.bundles.blueprint.extender.config)"</require-capability>        
        </instructions>
    </configuration>
</plugin>

The extender will parse the files in META-INF/spring folder and created an OSGi enabled context from there.

Other service declaration and referencing mechanisms

In OSGi there are a lot of different ways of registering and referencing services. In the above example, we have illustrated the most common one for Jahia modules, but we will quickly list other alternatives:

  • CDI with OSGi : CDI provides a different way to do internal component wiring and expose services in OSGi. It provides more features than DS, and is a lighter alternative to Spring. Jahia does not provide out of the box CDI, but some implementations are available with Karaf.
  • OSGi ServiceTracker: it is possible to manually register services using code, but OSGi provides a ServiceTracker class that makes it easier to track services as they appear and disappear at runtime due to OSGi highly dynamic nature. As this much lower level it is available in all OSGi frameworks and usable out of the box in Jahia. It is however much more difficult to setup so it not recommended unless you have a good reason to use it (such as building your own extender for example).
  • Manual service registration: this is always possible but really not recommended, unless you really know what you are doing. As services may appear or disappear any time, your implementation must handle this properly, and this can be quite complicated to handle.