Using ServiceListener

Why ServiceListener?

Framework will send service events to a bundle that registers ServiceListener. Services can be retrieved according to the name of particular service, or according to the filter definition. A filter specifies about which services the bundle should be notified. Filter is a string in LDAP format.
In other words: a bundle will be notified when particular service is registered in the framework. When ServiceEvent is received a bundle can start using the service.

In this example we will implement a ServiceListener which will start or stop the user thread when specific ServiceEvent will be received.
User thread will represent the user of calculator service. A thread will periodically call add() method.

Calculator service is the same as in previous example ((CalculatorService)).

Calculator user thread

First let's create dummy thread that periodically calls add() method from CalculatorService. Remember that you need to add CalculatorService from previous example to the build path.

package si.matjazcerkvenik.calculatoruser;

import si.matjazcerkvenik.calculator.CalculatorService;


public class CalculatorUserThread extends Thread {

private CalculatorService cs = null;

public CalculatorUserThread(CalculatorService s) {
cs = s;
}

private boolean running = true;

@Override
public void run() {

while (running) {

int a = (int) (5 * Math.random());
int b = (int) (5 * Math.random());

System.out.println("CalculatorService: " + a + " + " + b + " = "
+ cs.add(a, b));

try {
sleep(3000);
} catch (InterruptedException e) {
}

}

}

public void stopThread() {
running = false;
}

}

Implementing ServiceListener

MyListener must implement ServiceListener interface and provide implementation of serviceChanged(e) method. Every time when a service will be registered in the framework (ie. when service will be started) it will call serviceChanged(e) method with ServiceEvent as input parameter. According to the type of ServiceEvent (REGISTERED, MODIFIED or UNREGISTERING) the user thread will be started or stopped. Class MyListener also implements methods for starting and stopping user thread.

package si.matjazcerkvenik.calculatoruser;

import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;

import si.matjazcerkvenik.calculator.CalculatorService;

public class MyListener implements ServiceListener {

private CalculatorService cs = null;
private CalculatorUserThread t = null;

public void serviceChanged(ServiceEvent e) {

switch (e.getType()) {
case ServiceEvent.REGISTERED:
System.out.println("Service REGISTERED");
cs = (CalculatorService) Activator2.bc.getService(e.getServiceReference());
startUserThread();
break;
case ServiceEvent.MODIFIED:
System.out.println("Service MODIFIED");
stopUserThread();
cs = (CalculatorService) Activator2.bc.getService(e.getServiceReference());
startUserThread();
break;
case ServiceEvent.UNREGISTERING:
System.out.println("Service UNREGISTERED");
stopUserThread();
break;

default:
break;
}

}

public void startUserThread() {
t = new CalculatorUserThread(cs);
t.start();
}

public void stopUserThread() {
if (t == null) {
return;
}
t.stopThread();
try {
t.join();
} catch (InterruptedException e) {
}
cs = null;
}

}

Activator

Activator class first specifies filter string. Filter will select only services that match the filter (in our case only one service will fit).
Filter and listener will be registered into the framework via BundleContext.

Remark: first we retrieve all services that match filter and then call ServiceEvent.REGISTERED for every service. This forces ServiceListener to call serviceChanged(e) method. If service would already be running, REGISTERED event would not be sent.

package si.matjazcerkvenik.calculatoruser;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceReference;

import si.matjazcerkvenik.calculator.CalculatorService;

public class Activator implements BundleActivator {

public static BundleContext bc = null;
private MyListener listener = new MyListener();

public void start(BundleContext ctx) throws Exception {
bc = ctx;
System.out.println(bc.getBundle().getHeaders()
.get(Constants.BUNDLE_NAME)
+ " starting...");

String filter = "(objectclass=" + CalculatorService.class.getName() + ")";
bc.addServiceListener(listener, filter);

ServiceReference<?> references[] = bc.getServiceReferences((String) null, filter);
for (int i = 0; references != null && i < references.length; i++) {
listener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, references[i]));
}
}

public void stop(BundleContext arg0) throws Exception {
System.out.println(bc.getBundle().getHeaders()
.get(Constants.BUNDLE_NAME)
+ " stopping...");
listener.stopUserThread();
listener = null;
bc = null;

}

}

Manifest file

Manifest file includes Import-Package parameter that specifies the package of CalculatorService.

Manifest-Version: 2.0
Bundle-Name: calculatoruser
Bundle-SymbolicName: calculatoruser
Bundle-Version: 1.0.0
Bundle-Description: OSGi example
Bundle-Vendor: Knopflerfish
Bundle-Activator: si.matjazcerkvenik.calculatoruser.Activator
Bundle-Category: example
Import-Package: org.osgi.framework,si.matjazcerkvenik.calculator

Build project

Build the project with the following Ant build file.

<?xml version="1.0" encoding="UTF-8" ?>
<project name="calculatoruser" default="all" >

<property name="lib.dir" location="lib" />

<target name="all" depends="clean,init,compile,jar" />

<target name="init" >
<mkdir dir="build/classes" />
</target>

<target name="compile" >
<javac destdir="build/classes" debug="on" srcdir="src" >
<classpath>
<fileset dir="${lib.dir}" includes="**/*.jar" />
</classpath>
</javac>
</target>

<target name="jar" >
<jar basedir="build/classes" jarfile="dist/${ant.project.name}.jar"
compress="true" includes="**/*" manifest="META-INF/manifest.mf" />
</target>

<target name="clean" >
<delete dir="build" />
</target>
</project>

Deploying

Deploy the bundle in the framework. Make sure that also CalculatorService bundle is deployed.
In previous example the service must be running before the user is started. Otherwise an exceptions is thrown that the service is not available when the user bundle tries to retrieve it.
With ServiceListener it is not important which bundle is started first. The user bundle will be notified via ServiceListener that new service is available and it will start the user thread. To test the functionality first start the user bundle. No printouts to the console will be made because the thread is not running yet. Next start the service bundle. Notice that user bundle detects REGISTERED event and starts the thread. Now you should see calculations.