Latest Posts

Archives [+]

Categories [+]

Authors [+]

Sling and OSGi

If you build web applications with Apache Sling or CRX Quickstart you might want to extend the core functionality, e.g. for starting a background process or connecting to some legacy system.

Fortunately, this is easy as Sling is based on OSGi. OSGi (Open Services Gateway Initiative) defines an architecture for developing and deploying modular applications and libraries. As such, extending Sling with application-specific components ("bundles" as they are called in OSGi lingo) essentially means creating a bundle. In this post I will walk you through the complete process. All code and configuration is attached to this post as a deployable bundle.

One disclaimer before we start, though: if have no clue at all about OSGi you should get some basic understanding from TheServerSide, or JavaWorld (the TSS article uses the terms "repository" and "node" in a generic way, not for JCR repositories and nodes as in this post).

Once you have a basic notion what a bundle is you're good to go.

Create a bundle

A bundle is a jar file plus some meta information. Let us start with a simple service interface class. In OSGi development it is common practice to use interfaces and implementing classes.

package com.day.samples;

import javax.jcr.Node;
import javax.jcr.RepositoryException;

public interface HelloService {
  public String sayHello();
  public String getReversedNodePath(Node
    node) throws RepositoryException;
  public String getRepository();
  public void log(String text);
}

This is really just a plain old Java interface, nothing OSGi- or Sling-specific to be seen here, just some JCR classes are imported. I chose these four methods because they provide some insights how to develop OSGi bundles on top of Sling. However, they are not "required" in the sense that e.g. EJB 2 would require a certain structure of your classes. The implementing class looks like:

package com.day.samples.impl;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import org.apache.sling.jcr.api.SlingRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.samples.HelloService;

public class HelloServiceImpl implements
  HelloService{

  private SlingRepository repository;
  private static final Logger log =
    LoggerFactory.getLogger(HelloServiceImpl.class);

  public String sayHello() {
    return ("Hello World!!"); 
  }	
	
  public String getReversedNodePath(Node node)
    throws RepositoryException {
    return new
      StringBuffer(node.getPath()).reverse().
        toString();
  }

  public String getRepository() {		
    return
      repository.getDescriptor(
      SlingRepository.REP_NAME_DESC);
  }	
	
  protected void bindRepository(SlingRepository
    repository) {
    this.repository = repository;
  }
	
  protected void unbindRepository(SlingRepository
    repository) {
    this.repository = null;
  }

  public void log(String text) {
    log.error(text);
  }
}

The implementation imports the SlingRepository class and a logger class that comes with Sling. In order to compile this class you will need to have the Sling jars on your classpath. If you use CRX Quickstart find them in launchpad/felix/bundleXX.

For the bundle description we will need a descriptor file called MANIFEST.MF located in META-INF and contains:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloWorld Plug-in
Bundle-SymbolicName: HelloWorld
Bundle-Version: 1.0.0
Import-Package: org.osgi.framework;version="1.3.0",
 org.apache.sling.jcr.api,org.slf4j
Export-Package: com.day.samples;version="1.0.0"
Private-Package: com.day.samples.impl
Service-Component: OSGI-INF/serviceComponents.xml

Note that the packages that are used in the implementing class above must be imported. Also, the packages that shall be exposed from our bundle to other bundles must be marked as "exported". Further, there must be a reference to the second needed descriptor file OSGI-INF/serviceComponents.xml. This file contains:

<?xml version="1.0" encoding="UTF-8"?>
<components
  xmlns:scr="http://www.osgi.org/xmlns/scr/v1.0.0">
  <scr:component enabled="true" immediate="true"
    name="com.day.samples.impl.HelloServiceImpl">
    <scr:implementation
      class="com.day.samples.impl.HelloServiceImpl"/>
    <scr:service servicefactory="false">
      <scr:provide
        interface="com.day.samples.HelloService"/>
    </scr:service>
    <scr:property name="service.description"
      value="Say hello sample service"/>
    <scr:property name="service.vendor"
      value="Day"/>
    <scr:property name="service.pid"
      value="com.day.samples.impl.HelloServiceImpl"/>
    <scr:reference name="repository"
      interface="org.apache.sling.jcr.api.
        SlingRepository"
      cardinality="1..1" policy="static"
      bind="bindRepository"
      unbind="unbindRepository"/>
  </scr:component>
</components>

This file configures which implementation of the interface shall be used. It also configures the "injection" of the repository variable into the HelloServiceImpl class (highlighted). The "setter" is configured in the "bind" attribute. HelloServiceImpl must implement this method.

For creating an OSGi bundle you just need to compile these classes and package them in a jar file together with the descriptors (the complete bundle is attached).

The Sling console

The Sling console is a web application that comes with Sling and CRX Quickstart that allows you (among other things) to deploy new bundles. It is located at http://localhost:7402/system/console/list. Bundle details can be seen by clicking on a bundle name. This reveals e.g. the bundle version and the exported packages. For example, note that the org.slf4j package imported by HelloServiceImpl is exported by a bundle named "Sling-OSGi LogService Implementation".

Upload your bundle jar file, press "Install or Update" and "Refresh Packages". Your bundle should now show up in the list of bundles. If it has not been started start it yourself by pressing "start".

Accessing the service

In order to be useful the deployed OSGi service should be accessible from esp templates. That works like this:

<%
var service =
  sling.getService(
  Packages.com.day.samples.HelloService);
%>

<%= service.sayHallo() %>

The HelloService also contains a method getReversedNodePath(Node node) that takes a JCR node as a parameter (it diabolically returns the node's path as a reversed string). For passing the currently processed node to this method use:

<%= service.getReversedNodePath(currentNode)%>

The method log(String text) in HelloService writes into Sling's log file. This can come in handy for debugging purposes. Note again that the logger package needs to be imported in MANIFEST.MF in order to be visible.

In the method getRepository() the name of the current repository is returned. This method illustrates how to access the repository from within the bundle and thus perform manipulations or queries on the repository.

Final remarks

If you care to look at Sling's source code to learn more you will notice the lack of configuration files like serviceComponents.xml. The reason for this is that the Sling develpers use a Maven plugin that automatically generates these artifacts. You can still find them in the generated jar files, of course.

To learn more have a look at the sample applications that come with Sling. Find them in samples/simple-demo and samples/webloader. The webloader demo implements a background service that fills the repository with publicly available files.

 
(optional)
6 comments
Note that the maven-bundle-plugin and maven-scr-plugin will generate the manifest and serviceComponents files automatically. That requires using Maven, but saves a lot of work as soon as things get more complex.
0 Replies » Reply
Hi Michael,

To learn more have a look at the sample applications that come with Sling.

how do I run these sample applications? I didn't find any info in the Sling documentation ...

TIA!

-- Andreas
0 Replies » Reply
Hi Andreas,

for example the webloader sample:

1) compile the service part and th UI part. For this check out the Sling sources and do
mvn install
in samples/webloader/service and sample/webloader/ui
2) this will give you jar files in the respective target directories. Deploy these bundles (not the *sources* version) in the OSGi console and make sure they are started.
3) Hit /bin/sling/webloader.html

HTH
Michael
0 Replies » Reply
Michael, I'll try this - thanks a lot!
0 Replies » Reply
thanks for this, michael. i was able to get something similar working after i kicked maven around. (also, had to peak into Sling source code to find out what exactly SlingRepository does.)

my question is: for scr.reference, how is it possible to know that the interface SlingRepository is available as a component? i'm looking at my service info in the console (under Components) and see that the SlingRepository interface is provided by the com.day.crx.sling.client.impl.CRXSlingClientRepository service, but under that i don't see a reference to SlingRepository.

just confusing to know what's available. is there any easier way to see what can be referenced?
0 Replies » Reply
@kaiser, the "components" page of the OSGi console does not list all services, as some components are service factories.

You can see the interfaces of all services under /system/console/config, after the "*** Services" header.

For example, on my CQ5.2 test system, I see the following there:

37=[
org.apache.sling.jcr.api.SlingRepository,
com.day.crx.CRXRepository,
javax.jcr.Repository]
...
component.name=
com.day.crx.sling.client.impl.CRXSlingClientRepository

Which shows that this service 37 provides 3 service interfaces, it's those that you can use in scr.reference annotations.

0 Replies » Reply