Service sharing between modules

  Written by The Jahia Team
 
Developers
   Estimated reading time:

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

Using Spring with OSGi

Spring can still be used in module to expose and consume services. If you want to use the Spring framework and extender provided by the code, you need to enable it by adding this instruction :

<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.

Spring osgi:service and osgi:reference tags

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

<bean id="ProviderInitializerService" class="org.jahia.modules.external.id.ExternalProviderInitializerServiceImpl">
    <property name="hibernateSessionFactory" ref="moduleSessionFactory"/>
    <property name="cacheProvider" ref="ehCacheProvider"/>
    <property name="extensionProvider" ref="DefaulJCRStoreProvider"/>
    <property name="overridableItemsForLocks">
        <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>
<osgi:service id="ExternalProviderInitializerService" ref="ProviderInitializerService" interface="org.jahia.modules.external.ExternalProviderInitializerService"/>

Note that for more complex service registrations, it might be a good idea to have two separate modules, one for the interfaces, and another 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 Spring OSGi service reference XML tag to access the registered service in the OSGi service registry:

<osgi:reference id="ExternalProviderInitializerService"
                interface="org.jahia.modules.external.ExternalProviderInitializerService"/>
    <bean class="org.jahia.modules.external.MountPointListener">
    <property name="externalProviderInitializerService" ref="ExternalProviderInitializerService"/>
</bean>

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

OSGi:list usage

You can define a list of services implementing a specified interface. Note that if the current bundle provides both the interface and an implementation, the implementation reference must be set as optional.

<osgi:list id="ModuleGlobalObjectList" interface="org.jahia.services.content.rules.ModuleGlobalObject"
 availability="optional">
 <osgi:listener ref="org.jahia.modules.default.ModuleRegistry" bind-method="osgiBind"unbind-method="osgiUnbind"/>
</osgi:list>

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>org.jahia.modules.external</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. Also, the Jahia Maven Plugin will also pick up the reference inside the Spring XML file.

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:

  • OSGi BluePrint: this is most recent service framework in OSGi. It is based on the Spring OSGi implementation but is now part of the standard. This is directly available in Jahia and is useable out of the box.
  • 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.