May
07
Sling and OSGi
filed under sling osgi tutorial | posted by Michael Marth
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.
Related Posts
4 comments
-
Bertrand Delacretaz on 7/5/2008
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.
-
Andreas Hartmann on 13/5/2008
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 -
Michael Marth on 15/5/2008
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 -
Andreas Hartmann on 17/5/2008
Michael, I'll try this - thanks a lot!
hello.jar