The "One Laptop Per Child" project has a great device ready to ship, but there's no Java on there. Let's think about working together to put Java on OLPC!
In our last part we looked at how to register a service. Now we need to work out how to lookup and use that service from another bundle.
We will put the problem in the context of our requirements, which as before are inspired by Martin Fowler's
paper
on dependency injection. We have built a
MovieFinder
as a service and registered it with the service registry. Now we want to build a
MovieLister
that uses the
MovieFinder
to search for movies directed by a specific director. Our assumption is that the
MovieLister
itself will be a service to be consumed by some other bundle, e.g. a GUI application. The trouble is, OSGi services are dynamic... they come and they go. That means sometimes we want to call the
MovieFinder
service but it just isn't available!
So, what should the
MovieLister
do if the
MovieFinder
service is not present? Clearly the call to the
MovieFinder
is a critical part of the work done by
MovieLister
, so there only a few choices available to us:
Produce an error, e.g. return null or throw an exception.
Wait.
Don't be there in the first place.
In this article we're going to look at the first two options, as they are quite simple. The third option may not even make any sense to you yet, but hopefully it will after we look at some of the implications of the first two.
The first thing we need to do is define the interface for the
MovieLister
service. Copy the following into
osgitut/movies/MovieLister.java
:
Now create the file
osgitut/movies/impl/MovieListerImpl.java
:
package osgitut.movies.impl;
import java.util.*;
import osgitut.movies.*;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;
publicclass MovieListerImpl implements MovieLister {
privatefinal ServiceTracker finderTrack;
public MovieListerImpl(ServiceTracker finderTrack) {
this.finderTrack = finderTrack;
}
public List listByDirector(String name) {
MovieFinder finder = (MovieFinder) finderTrack.getService();
if(finder == null) {
returnnull;
} else {
return doSearch(name, finder);
}
}
private List doSearch(String name, MovieFinder finder) {
Movie[] movies = finder.findAll();
List result = new LinkedList();
for (int i = 0; i < movies.length; i++) {
if(movies[i].getDirector().indexOf(name) > -1) {
result.add(movies[i]);
}
}
return result;
}
}
This is probably our longest code sample so far! So what's going on here? Firstly you notice that the logic of actually searching for movies is separated into a
doSearch(String,MovieFinder)
method, to help us isolate the OSGi-specific code. Incidentally, the way we're performing the search is pretty stupid and inefficient, but that's not really important for the purposes of the tutorial. We only have two movies in our database anyway!
The interesting part is in the
listByDirector(String name)
method, which uses a
ServiceTracker
object to obtain a
MovieFinder
from the service registry.
ServiceTracker
is a very useful class which abstracts away a lot of unpleasant detail in the lowest levels of the OSGi API. However we still have to check whether the service was actually present. We assume that the
ServiceTracker
will be passed to us in our constructor.
Note that you may have seen elsewhere code that retrieves a service from the registry without using
ServiceTracker
. For example, it is possible to use the
getServiceReference
and
getService
calls on
BundleContext
. However the code you have to write is quite complex and it has to be careful to clear up after itself. In my opinion, there is very little benefit in dropping down to the low level API, and lots of problems with it. It's better to use
ServiceTracker
almost exclusively.
A good place to create a
ServiceTracker
is in the bundle activator. Copy this code into
osgitut/movies/impl/MovieListerActivator.java
:
package osgitut.movies.impl;
import java.util.*;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;
import osgitut.movies.*;
publicclass MovieListerActivator implements BundleActivator {
private ServiceTracker finderTracker;
private ServiceRegistration listerReg;
publicvoid start(BundleContext context) throws Exception {
// Create and open the MovieFinder ServiceTracker
finderTracker = new ServiceTracker(context, MovieFinder.class.getName(), null);
finderTracker.open();
// Create the MovieLister and register as a service
MovieLister lister = new MovieListerImpl(finderTracker);
listerReg = context.registerService(MovieLister.class.getName(), lister, null);
// Execute the sample search
doSampleSearch(lister);
}
publicvoid stop(BundleContext context) throws Exception {
// Unregister the MovieLister service
listerReg.unregister();
// Close the MovieFinder ServiceTracker
finderTracker.close();
}
privatevoid doSampleSearch(MovieLister lister) {
List movies = lister.listByDirector("Miyazaki");
if(movies == null) {
System.err.println("Could not retrieve movie list");
} else {
for (Iterator it = movies.iterator(); it.hasNext();) {
Movie movie = (Movie) it.next();
System.out.println("Title: " + movie.getTitle());
}
}
}
}
Now this activator is starting to look interesting. Firstly in the
start
method it creates a
ServiceTracker
object, which is used by the
MovieLister
we just wrote. It then "opens" the
ServiceTracker
which tells it to start tracking instances of the
MovieFinder
service in the registry. Then it creates our
MovieListerImpl
object and registers it in the service registry under the interface name
"MovieLister"
. Finally, just for the sake of being able to see something interesting when we start the bundle, the activator runs a simple search against the
MovieLister
and prints the result.
We need to build and install this bundle. I'm not going to give full instructions this time -- you should be able to refer back to the previous installments and work it out. Remember you also need to create a manifest file, and it has to refer to the
osgitut.movies.impl.MovieListerActivator
class as its
Bundle-Activator
. Also your
Import-Package
line needs to include the three packages that we're importing from other bundles, namely
org.osgi.framework
,
org.osgi.util.tracker
and
osgitut.movies
.
Once you have installed
MovieLister.jar
into the Equinox runtime, you can start it. At that point you will see one of two messages, depending on whether the
BasicMovieFinder
bundle is still running from last time. If it's not running you will see:
osgi> start 2
Could not retrieve movie list
However if it is running you will see following:
osgi> start 2
Title: Spirited Away
By stopping and starting each bundle, you should be able to get either message to appear at will. And that is almost all for this installment, except remember I said that one of the things you can do when a service is not available is to wait for it? With the code we already have, this is actually trivial: simply change line 16 of MovieListerImpl to call
waitForService(5000)
on the
ServiceTracker
instead of
getService()
, and add a
try/catch
block for the
InterruptedException
.
This will cause the
listByDirector()
method to hang for up to 5000 milliseconds waiting for the
MovieFinder
service to appear. If a
MovieFinder
service is installed in that time -- or, of course, if it was already there -- then we will immediately get it an be able to use it.
Generally though I would advise against suspending threads like this. Particularly in this case, it could be dangerous because the
listByDirector()
method is actually called from the
start
method of our bundle activator, which was called from a framework thread. Activators are meant to return quickly, because a number of other things need to happen when a bundle is activated. In fact in the worst case we could cause a deadlock, because we are effectively entering a
synchronized
block on an object owned by the framework, which might already by locked by something else. The general guideline is never perform any long-running or blocking operations in a bundle activator
start
method, or in any code called directly from the framework.
In the next installment we will take a look at the mysterious third option for dealing with absent dependencies: "Don't exist in the first place". Stay tuned!
Re: Getting Started with OSGi: Consuming a Service
In many ServiceTracker examples, we do not see the call to ServiceTracker.close(). If one uses a ServiceTrackerCustomizer implementation, it may be important to call ServiceTracker.close so the customizer can be called at removingService for each tracked service to do any untrack processing. This could include persistently saving data or something. So I think it is always wise to show the close call in example code. Without the call to close, the framework will properly unregister the ServiceTracker's ServiceListener and unget any services being tracked when the bundle is stopped but removingService will not be called. In your example, that is OK since all the default removingService method implementation does is unget a tracked service.
Re: Getting Started with OSGi: Consuming a Service
Of course, normally you'd have the bundle.start() method open a tracker, bundle.stop() close a tracker, and then do (probably more than one) request whilst the bundle is alive. Since this code only shows the processing once at startup, this subtlety is missed.
Re: Getting Started with OSGi: Consuming a Service
How can i look the MovieLister result (maybe call log information)? In fact, When i stop MovieFinder service, i can see this error information "Could not retrieve movie list".
Re: Getting Started with OSGi: Consuming a Service
I have created the bundles. I think correctly to have a MovieListerInterface.jar (which has only the MovieLister interface) and a MovieLister.jar (which has the MovieListerImpl and MovieListerActivator)
The MovieListerInterface.mf looks like
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Movie Lister Interface
Bundle-SymbolicName: MovieLister
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.MovieListerActivator
Import-Package: org.osgi.framework,org.osgi.util.tracker,osgitut.movies
Then MovieLister.mf looks like
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Movie Lister
Bundle-SymbolicName: MovieListerImpl
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.MovieListerActivator
Import-Package: org.osgi.framework ,org.osgi.util.tracker,osgitut.movies
The bundles both install without errors.
In the Console when starting 3 the following error occurs
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE system.bundle_3.2.2.R32x_v20070118
1 ACTIVE MoviesInterface_1.0.0
2 ACTIVE BasicMovieFinder_1.0.0
3 INSTALLED MovieLister_1.0.0
4 INSTALLED MovieListerImpl_1.0.0
osgi> start 3
org.osgi.framework.BundleException: The activator osgitut.movies.impl.MovieListe
rActivator for bundle MovieLister is invalid
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:141)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(Bund
leContextImpl.java:962)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(Bundl
eHost.java:317)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:256)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._st
art(FrameworkCommandProvider.java:239)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.
execute(FrameworkCommandInterpreter.java:145)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(F
rameworkConsole.java:293)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(Fra
meworkConsole.java:278)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(Framewo
rkConsole.java:213)
at java.lang.Thread.run(Thread.java:595)
Caused by: java.lang.ClassNotFoundException: osgitut.movies.impl.MovieListerActi
vator
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:402)
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:347)
at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(De
faultClassLoader.java:83)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at org.eclipse.osgi.framework.internal.core.BundleLoader.loadClass(Bundl
eLoader.java:278)
at org.eclipse.osgi.framework.internal.core.BundleHost.loadClass(BundleH
ost.java:227)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:134)
... 13 more
Nested Exception:
java.lang.ClassNotFoundException: osgitut.movies.impl.MovieListerActivator
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:402)
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:347)
at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(De
faultClassLoader.java:83)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at org.eclipse.osgi.framework.internal.core.BundleLoader.loadClass(Bundl
eLoader.java:278)
at org.eclipse.osgi.framework.internal.core.BundleHost.loadClass(BundleH
ost.java:227)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:134)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(Bund
leContextImpl.java:962)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(Bundl
eHost.java:317)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:256)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._st
art(FrameworkCommandProvider.java:239)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.
execute(FrameworkCommandInterpreter.java:145)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(F
rameworkConsole.java:293)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(Fra
meworkConsole.java:278)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(Framewo
rkConsole.java:213)
at java.lang.Thread.run(Thread.java:595)
Nested Exception:
java.lang.ClassNotFoundException: osgitut.movies.impl.MovieListerActivator
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:402)
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:347)
at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(De
faultClassLoader.java:83)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at org.eclipse.osgi.framework.internal.core.BundleLoader.loadClass(Bundl
eLoader.java:278)
at org.eclipse.osgi.framework.internal.core.BundleHost.loadClass(BundleH
ost.java:227)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:134)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(Bund
leContextImpl.java:962)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(Bundl
eHost.java:317)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:256)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._st
art(FrameworkCommandProvider.java:239)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.
execute(FrameworkCommandInterpreter.java:145)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(F
rameworkConsole.java:293)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(Fra
meworkConsole.java:278)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(Framewo
rkConsole.java:213)
at java.lang.Thread.run(Thread.java:595)
Starting 4 produces the following
osgi> start 4
Title: Spirited Away
Re: Getting Started with OSGi: Consuming a Service
Wayne,
It was not really necessary to split the interface and the implementation into two bundles, although there's no problem with that either.
The reason for the error appears to be that you have
osgitut.movies.impl.MovieListerActivator
as the activator for both the interface bundle ("MovieLister") and the implenentation bundle ("MovieListerImpl"). However the interface bundle doesn't contain the class
osgitut.movies.impl.MovieListerActivator
Simply remove the Bundle-Activator line from the manifest of the interface bundle – you don't need an activator for a bundle that only exports API to other bundles.
Re: Getting Started with OSGi: Consuming a Service
Hi, and thank you for your tutorial
I feel a little bit confused between
packages and bundles
, as I thought that
two different bundles shall be split into two different packages
, and, on the other side, that
one package shall contain only one bundle
.
On your previous tutorial, I made the following bundle :
Bundle : MoviesInterface
Movie (package osgitut.movies)
MovieFinder (package osgitut.movies)
At this point, everything was clear. But,
In this turorial, I create the following classes :
Bundle : MoviesInterface
MovieLister (package osgitut.movies)
It's not possible to put MovieListerImpl/Activator and BasicMovieFinderImpl/Activator because there must be only one activator. So I create another Bundle : MovieLister.
But, according to your source, there are in the same package, regardind to java syntax : so, we can have many bundle in the same package.
Re: Getting Started with OSGi: Consuming a Service
It's a good question, but in fact, the same package can exist in multiple bundles. It's good practice (at times) to ensure that each bundle has its own package name, but that's just so that you can find/guess which bundle a class/package came from. This is referred to as 'split packages' in the OSGi/Equinox documentation.
One reason for split packages is that your bundles have been refactored but you need to keep the same code API. This happens in Eclipse, as some of the 'org.eclipse.core.runtime' packages have been moved from the runtime bundle into org.eclipse equinox.common, whilst keeping the same package name.
(If you do split packages, you should both Export-Package and Import-Package the split package name, by the way; that prevents weird classloading errors later)
Re: the activator; you're right, a bundle can only have one activator. However, it's fairly easy to set up code that allows you do run multiple pieces of code (as well as allowing you to merge the activators manually). For example:
publicclass MyActivator implements BundleActivator {
public MyActivator() {
a = new OtherActivator();
b = new AnotherActivator();
...
}
publicvoid start() {
a.start();
b.start();
...
}
}
So, whilst you can only have a single Activator class, you can easily chain it to do other pieces of work.
I packaged only those two Lister related classes and the mf into the MovieLister.jar, the MovieInterface.jar has also been updated with the MovieLister (the interface) in it.
But, if I run it in windows command line, it shows that:
java.lang.NoClassDefFoundError: osgitut/movies/MovieLister
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2357)
at java.lang.Class.getConstructor0(Class.java:2671)
at java.lang.Class.newInstance0(Class.java:321)
at java.lang.Class.newInstance(Class.java:303)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:136)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(Bund
leContextImpl.java:970)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(Bundl
eHost.java:346)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:260)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:252)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._st
art(FrameworkCommandProvider.java:260)
at sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.
execute(FrameworkCommandInterpreter.java:145)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(F
rameworkConsole.java:291)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(Fra
meworkConsole.java:276)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(Framewo
rkConsole.java:218)
at java.lang.Thread.run(Thread.java:595)
But, it works fine in the Eclipse envirment.... I would like to know why. Thanks!
Re: Getting Started with OSGi: Consuming a Service
I had no any problem with the service registration example but with tracking service, i am get the following error message.
Error during the operation Error Invoking Method: org.osgi.framework.BundleException: java.lang.NullPointerException
com.prosyst.mbs.services.pmp.PMPException: Error Invoking Method: org.osgi.framework.BundleException: java.lang.NullPointerException
at com.prosyst.mbs.impl.services.pmp.Connection.invoke(Connection.java:317)
at com.prosyst.mbs.impl.services.pmp.RemoteMethodImpl.invoke(RemoteMethodImpl.java:66)
at com.prosyst.mc.impl.mbs.login.GatewayAdministratorImpl.startBundle(GatewayAdministratorImpl.java:246)
at com.prosyst.mc.impl.mbs.login.DeployedBundleImpl.start(DeployedBundleImpl.java:102)
at com.prosyst.mc.impl.mbs.mbsgam.events.GAMRemoteManager.startBundle(GAMRemoteManager.java:152)
at com.prosyst.mc.impl.mbs.mbsgam.events.GAMRemoteManager.run(GAMRemoteManager.java:111)
at java.lang.Thread.run(Unknown Source)
Re: Getting Started with OSGi: Consuming a Service
In my case, to get the MovieLister bundle starting properly, I had first to repackage the MoviesInterfaces bundle so that it also contains the osgitut.movies.MovieLister class:
jar -cfm MoviesInterface.jar MoviesInterface.mf osgitut/movie
Otherwise i had the following exception on MovieLister bundle start:
java.lang.NoClassDefFoundError: osgitut/movies/MovieLister
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Unknown Source)
at java.lang.Class.getConstructor0(Unknown Source)
at java.lang.Class.newInstance0(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
I think it's coming from the confusion that the same package (osgitut.movies) is :
- defined in both MoviesInterface and MovieLister bundles.
- exported by MoviesInterface
- imported by MovieLister
If you check OSGi R4 specs, on page 52-278, section 3.8.4 Overall Search Order, you will read:
"3. If the class is in a package that is imported using Import-Package or was imported dynamically in a previous load, then the request is delegated to the exporting bundle’s class loader; (...) If the request is delegated to an exporting class loader and the class or resource is not found, then the search terminates and the request fails"
In our case the Framework resolves the MovieLister.mf "Import-Package: osgitut.movies" with the MoviesInterface.mf "Export-Package: osgitut.movies"
then the osgitut.movies.MovieLister.class has to be package with exporter MoviesInterface or the class loading request fails.
Getting Started with OSGi: Consuming a Service
At 11:23 AM on Feb 21, 2007, Neil Bartlett
wrote:
We will put the problem in the context of our requirements, which as before are inspired by Martin Fowler's paper on dependency injection. We have built a
MovieFinderas a service and registered it with the service registry. Now we want to build aMovieListerthat uses theMovieFinderto search for movies directed by a specific director. Our assumption is that theMovieListeritself will be a service to be consumed by some other bundle, e.g. a GUI application. The trouble is, OSGi services are dynamic... they come and they go. That means sometimes we want to call theMovieFinderservice but it just isn't available!So, what should the
MovieListerdo if theMovieFinderservice is not present? Clearly the call to theMovieFinderis a critical part of the work done byMovieLister, so there only a few choices available to us:In this article we're going to look at the first two options, as they are quite simple. The third option may not even make any sense to you yet, but hopefully it will after we look at some of the implications of the first two.
The first thing we need to do is define the interface for the
MovieListerservice. Copy the following intoosgitut/movies/MovieLister.java:package osgitut.movies; import java.util.List; public interface MovieLister { List listByDirector(String name); }Now create the file
osgitut/movies/impl/MovieListerImpl.java:package osgitut.movies.impl; import java.util.*; import osgitut.movies.*; import org.osgi.framework.*; import org.osgi.util.tracker.ServiceTracker; public class MovieListerImpl implements MovieLister { private final ServiceTracker finderTrack; public MovieListerImpl(ServiceTracker finderTrack) { this.finderTrack = finderTrack; } public List listByDirector(String name) { MovieFinder finder = (MovieFinder) finderTrack.getService(); if(finder == null) { return null; } else { return doSearch(name, finder); } } private List doSearch(String name, MovieFinder finder) { Movie[] movies = finder.findAll(); List result = new LinkedList(); for (int i = 0; i < movies.length; i++) { if(movies[i].getDirector().indexOf(name) > -1) { result.add(movies[i]); } } return result; } }This is probably our longest code sample so far! So what's going on here? Firstly you notice that the logic of actually searching for movies is separated into a
doSearch(String,MovieFinder)method, to help us isolate the OSGi-specific code. Incidentally, the way we're performing the search is pretty stupid and inefficient, but that's not really important for the purposes of the tutorial. We only have two movies in our database anyway!The interesting part is in the
listByDirector(String name)method, which uses aServiceTrackerobject to obtain aMovieFinderfrom the service registry.ServiceTrackeris a very useful class which abstracts away a lot of unpleasant detail in the lowest levels of the OSGi API. However we still have to check whether the service was actually present. We assume that theServiceTrackerwill be passed to us in our constructor.Note that you may have seen elsewhere code that retrieves a service from the registry without using
ServiceTracker. For example, it is possible to use thegetServiceReferenceandgetServicecalls onBundleContext. However the code you have to write is quite complex and it has to be careful to clear up after itself. In my opinion, there is very little benefit in dropping down to the low level API, and lots of problems with it. It's better to useServiceTrackeralmost exclusively.A good place to create a
ServiceTrackeris in the bundle activator. Copy this code intoosgitut/movies/impl/MovieListerActivator.java:package osgitut.movies.impl; import java.util.*; import org.osgi.framework.*; import org.osgi.util.tracker.ServiceTracker; import osgitut.movies.*; public class MovieListerActivator implements BundleActivator { private ServiceTracker finderTracker; private ServiceRegistration listerReg; public void start(BundleContext context) throws Exception { // Create and open the MovieFinder ServiceTracker finderTracker = new ServiceTracker(context, MovieFinder.class.getName(), null); finderTracker.open(); // Create the MovieLister and register as a service MovieLister lister = new MovieListerImpl(finderTracker); listerReg = context.registerService(MovieLister.class.getName(), lister, null); // Execute the sample search doSampleSearch(lister); } public void stop(BundleContext context) throws Exception { // Unregister the MovieLister service listerReg.unregister(); // Close the MovieFinder ServiceTracker finderTracker.close(); } private void doSampleSearch(MovieLister lister) { List movies = lister.listByDirector("Miyazaki"); if(movies == null) { System.err.println("Could not retrieve movie list"); } else { for (Iterator it = movies.iterator(); it.hasNext();) { Movie movie = (Movie) it.next(); System.out.println("Title: " + movie.getTitle()); } } } }Now this activator is starting to look interesting. Firstly in the
startmethod it creates aServiceTrackerobject, which is used by theMovieListerwe just wrote. It then "opens" theServiceTrackerwhich tells it to start tracking instances of theMovieFinderservice in the registry. Then it creates ourMovieListerImplobject and registers it in the service registry under the interface name"MovieLister". Finally, just for the sake of being able to see something interesting when we start the bundle, the activator runs a simple search against theMovieListerand prints the result.We need to build and install this bundle. I'm not going to give full instructions this time -- you should be able to refer back to the previous installments and work it out. Remember you also need to create a manifest file, and it has to refer to the
osgitut.movies.impl.MovieListerActivatorclass as itsBundle-Activator. Also yourImport-Packageline needs to include the three packages that we're importing from other bundles, namelyorg.osgi.framework,org.osgi.util.trackerandosgitut.movies.Once you have installed
MovieLister.jarinto the Equinox runtime, you can start it. At that point you will see one of two messages, depending on whether theBasicMovieFinderbundle is still running from last time. If it's not running you will see:However if it is running you will see following:
By stopping and starting each bundle, you should be able to get either message to appear at will. And that is almost all for this installment, except remember I said that one of the things you can do when a service is not available is to wait for it? With the code we already have, this is actually trivial: simply change line 16 of MovieListerImpl to call
waitForService(5000)on theServiceTrackerinstead ofgetService(), and add atry/catchblock for theInterruptedException.This will cause the
listByDirector()method to hang for up to 5000 milliseconds waiting for theMovieFinderservice to appear. If aMovieFinderservice is installed in that time -- or, of course, if it was already there -- then we will immediately get it an be able to use it.Generally though I would advise against suspending threads like this. Particularly in this case, it could be dangerous because the
listByDirector()method is actually called from thestartmethod of our bundle activator, which was called from a framework thread. Activators are meant to return quickly, because a number of other things need to happen when a bundle is activated. In fact in the worst case we could cause a deadlock, because we are effectively entering asynchronizedblock on an object owned by the framework, which might already by locked by something else. The general guideline is never perform any long-running or blocking operations in a bundle activatorstartmethod, or in any code called directly from the framework.In the next installment we will take a look at the mysterious third option for dealing with absent dependencies: "Don't exist in the first place". Stay tuned!
14 replies so far (
Post your own)
Re: Getting Started with OSGi: Consuming a Service
In many ServiceTracker examples, we do not see the call to ServiceTracker.close(). If one uses a ServiceTrackerCustomizer implementation, it may be important to call ServiceTracker.close so the customizer can be called at removingService for each tracked service to do any untrack processing. This could include persistently saving data or something. So I think it is always wise to show the close call in example code. Without the call to close, the framework will properly unregister the ServiceTracker's ServiceListener and unget any services being tracked when the bundle is stopped but removingService will not be called. In your example, that is OK since all the default removingService method implementation does is unget a tracked service.Re: Getting Started with OSGi: Consuming a Service
Thanks BJ, you're right that it's an important step.The example code I gave does actually close the tracker, but perhaps I should draw more attention to that in the surrounding text.
Re: Getting Started with OSGi: Consuming a Service
Oh. Somehow I missed that. Well... Nevermind. Move along... nothing to see here.Re: Getting Started with OSGi: Consuming a Service
Of course, normally you'd have the bundle.start() method open a tracker, bundle.stop() close a tracker, and then do (probably more than one) request whilst the bundle is alive. Since this code only shows the processing once at startup, this subtlety is missed.Alex.
Re: Getting Started with OSGi: Consuming a Service
How can i look the MovieLister result (maybe call log information)? In fact, When i stop MovieFinder service, i can see this error information "Could not retrieve movie list".Re: Getting Started with OSGi: Consuming a Service
I have created the bundles. I think correctly to have a MovieListerInterface.jar (which has only the MovieLister interface) and a MovieLister.jar (which has the MovieListerImpl and MovieListerActivator)The MovieListerInterface.mf looks like
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Movie Lister Interface
Bundle-SymbolicName: MovieLister
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.MovieListerActivator
Import-Package: org.osgi.framework,org.osgi.util.tracker,osgitut.movies
Then MovieLister.mf looks like
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Movie Lister
Bundle-SymbolicName: MovieListerImpl
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.MovieListerActivator
Import-Package: org.osgi.framework ,org.osgi.util.tracker,osgitut.movies
The bundles both install without errors.
In the Console when starting 3 the following error occurs
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE system.bundle_3.2.2.R32x_v20070118
1 ACTIVE MoviesInterface_1.0.0
2 ACTIVE BasicMovieFinder_1.0.0
3 INSTALLED MovieLister_1.0.0
4 INSTALLED MovieListerImpl_1.0.0
osgi> start 3
org.osgi.framework.BundleException: The activator osgitut.movies.impl.MovieListe
rActivator for bundle MovieLister is invalid
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:141)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(Bund
leContextImpl.java:962)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(Bundl
eHost.java:317)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:256)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._st
art(FrameworkCommandProvider.java:239)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.
execute(FrameworkCommandInterpreter.java:145)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(F
rameworkConsole.java:293)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(Fra
meworkConsole.java:278)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(Framewo
rkConsole.java:213)
at java.lang.Thread.run(Thread.java:595)
Caused by: java.lang.ClassNotFoundException: osgitut.movies.impl.MovieListerActi
vator
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:402)
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:347)
at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(De
faultClassLoader.java:83)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at org.eclipse.osgi.framework.internal.core.BundleLoader.loadClass(Bundl
eLoader.java:278)
at org.eclipse.osgi.framework.internal.core.BundleHost.loadClass(BundleH
ost.java:227)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:134)
... 13 more
Nested Exception:
java.lang.ClassNotFoundException: osgitut.movies.impl.MovieListerActivator
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:402)
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:347)
at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(De
faultClassLoader.java:83)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at org.eclipse.osgi.framework.internal.core.BundleLoader.loadClass(Bundl
eLoader.java:278)
at org.eclipse.osgi.framework.internal.core.BundleHost.loadClass(BundleH
ost.java:227)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:134)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(Bund
leContextImpl.java:962)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(Bundl
eHost.java:317)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:256)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._st
art(FrameworkCommandProvider.java:239)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.
execute(FrameworkCommandInterpreter.java:145)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(F
rameworkConsole.java:293)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(Fra
meworkConsole.java:278)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(Framewo
rkConsole.java:213)
at java.lang.Thread.run(Thread.java:595)
Nested Exception:
java.lang.ClassNotFoundException: osgitut.movies.impl.MovieListerActivator
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:402)
at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(Bundl
eLoader.java:347)
at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(De
faultClassLoader.java:83)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at org.eclipse.osgi.framework.internal.core.BundleLoader.loadClass(Bundl
eLoader.java:278)
at org.eclipse.osgi.framework.internal.core.BundleHost.loadClass(BundleH
ost.java:227)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:134)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(Bund
leContextImpl.java:962)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(Bundl
eHost.java:317)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:256)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._st
art(FrameworkCommandProvider.java:239)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.
execute(FrameworkCommandInterpreter.java:145)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(F
rameworkConsole.java:293)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(Fra
meworkConsole.java:278)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(Framewo
rkConsole.java:213)
at java.lang.Thread.run(Thread.java:595)
Starting 4 produces the following
osgi> start 4
Title: Spirited Away
osgi>
Would this be correct?
Wayne
Re: Getting Started with OSGi: Consuming a Service
Wayne,It was not really necessary to split the interface and the implementation into two bundles, although there's no problem with that either.
The reason for the error appears to be that you have
osgitut.movies.impl.MovieListerActivatoras the activator for both the interface bundle ("MovieLister") and the implenentation bundle ("MovieListerImpl"). However the interface bundle doesn't contain the classosgitut.movies.impl.MovieListerActivator
Simply remove the Bundle-Activator line from the manifest of the interface bundle – you don't need an activator for a bundle that only exports API to other bundles.
Regards
Neil.
Re: Getting Started with OSGi: Consuming a Service
Hi!Why the finderTrack.getSerive() returns null? Why do we want to check this while we already registered the service on the other side?
public List listByDirector(String name) {
MovieFinder finder = (MovieFinder) finderTrack.getService();
if(finder == null) {
return null;
} else {
return doSearch(name, finder);
}
}
Re: Getting Started with OSGi: Consuming a Service
Hi wolverine,We have to check the return from getService() because the service could be registered or unregistered at any time.
Regards
Neil
Re: Getting Started with OSGi: Consuming a Service
Hi, and thank you for your tutorialI feel a little bit confused between packages and bundles , as I thought that two different bundles shall be split into two different packages , and, on the other side, that one package shall contain only one bundle .
On your previous tutorial, I made the following bundle :
Bundle : MoviesInterface
Movie (package osgitut.movies)
MovieFinder (package osgitut.movies)
Bundle : BasicMovieFinder
BasicMovieFinderImpl (package osgitut.movies.impl)
BasicMovieFinderActivator (package osgitut.movies.impl)
At this point, everything was clear. But,
In this turorial, I create the following classes :
Bundle : MoviesInterface
MovieLister (package osgitut.movies)
Bundle : MovieLister ?
MovieListerImpl (package osgitut.movies.impl)
MovieListerActivator (package osgitut.movies.impl)
It's not possible to put MovieListerImpl/Activator and BasicMovieFinderImpl/Activator because there must be only one activator. So I create another Bundle : MovieLister.
But, according to your source, there are in the same package, regardind to java syntax : so, we can have many bundle in the same package.
Can you tell us more about this?
Re: Getting Started with OSGi: Consuming a Service
It's a good question, but in fact, the same package can exist in multiple bundles. It's good practice (at times) to ensure that each bundle has its own package name, but that's just so that you can find/guess which bundle a class/package came from. This is referred to as 'split packages' in the OSGi/Equinox documentation.One reason for split packages is that your bundles have been refactored but you need to keep the same code API. This happens in Eclipse, as some of the 'org.eclipse.core.runtime' packages have been moved from the runtime bundle into org.eclipse equinox.common, whilst keeping the same package name.
(If you do split packages, you should both Export-Package and Import-Package the split package name, by the way; that prevents weird classloading errors later)
Re: the activator; you're right, a bundle can only have one activator. However, it's fairly easy to set up code that allows you do run multiple pieces of code (as well as allowing you to merge the activators manually). For example:
public class MyActivator implements BundleActivator { public MyActivator() { a = new OtherActivator(); b = new AnotherActivator(); ... } public void start() { a.start(); b.start(); ... } }So, whilst you can only have a single Activator class, you can easily chain it to do other pieces of work.
Hope this helps,
Alex.
Re: Getting Started with OSGi: Consuming a Service
Hello Neil,I got a problem when try the above sample.
My mf file content is:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Movie Lister
Bundle-SymbolicName: MovieLister
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.MovieListerActivator
Import-Package: org.osgi.framework,org.osgi.util.tracker,osgitut.movies;version="[1.0.0,2.0.0)"
I packaged only those two Lister related classes and the mf into the MovieLister.jar, the MovieInterface.jar has also been updated with the MovieLister (the interface) in it.
But, if I run it in windows command line, it shows that:
java.lang.NoClassDefFoundError: osgitut/movies/MovieLister
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2357)
at java.lang.Class.getConstructor0(Class.java:2671)
at java.lang.Class.newInstance0(Class.java:321)
at java.lang.Class.newInstance(Class.java:303)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleAct
ivator(AbstractBundle.java:136)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(Bund
leContextImpl.java:970)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(Bundl
eHost.java:346)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:260)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(Abstrac
tBundle.java:252)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._st
art(FrameworkCommandProvider.java:260)
at sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.
execute(FrameworkCommandInterpreter.java:145)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(F
rameworkConsole.java:291)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(Fra
meworkConsole.java:276)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(Framewo
rkConsole.java:218)
at java.lang.Thread.run(Thread.java:595)
But, it works fine in the Eclipse envirment.... I would like to know why. Thanks!
Re: Getting Started with OSGi: Consuming a Service
I had no any problem with the service registration example but with tracking service, i am get the following error message.Error during the operation Error Invoking Method: org.osgi.framework.BundleException: java.lang.NullPointerException
com.prosyst.mbs.services.pmp.PMPException: Error Invoking Method: org.osgi.framework.BundleException: java.lang.NullPointerException
at com.prosyst.mbs.impl.services.pmp.Connection.invoke(Connection.java:317)
at com.prosyst.mbs.impl.services.pmp.RemoteMethodImpl.invoke(RemoteMethodImpl.java:66)
at com.prosyst.mc.impl.mbs.login.GatewayAdministratorImpl.startBundle(GatewayAdministratorImpl.java:246)
at com.prosyst.mc.impl.mbs.login.DeployedBundleImpl.start(DeployedBundleImpl.java:102)
at com.prosyst.mc.impl.mbs.mbsgam.events.GAMRemoteManager.startBundle(GAMRemoteManager.java:152)
at com.prosyst.mc.impl.mbs.mbsgam.events.GAMRemoteManager.run(GAMRemoteManager.java:111)
at java.lang.Thread.run(Unknown Source)
Please Help!!
Re: Getting Started with OSGi: Consuming a Service
In my case, to get the MovieLister bundle starting properly, I had first to repackage the MoviesInterfaces bundle so that it also contains the osgitut.movies.MovieLister class:jar -cfm MoviesInterface.jar MoviesInterface.mf osgitut/movie
Otherwise i had the following exception on MovieLister bundle start:
java.lang.NoClassDefFoundError: osgitut/movies/MovieLister
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Unknown Source)
at java.lang.Class.getConstructor0(Unknown Source)
at java.lang.Class.newInstance0(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
I think it's coming from the confusion that the same package (osgitut.movies) is :
- defined in both MoviesInterface and MovieLister bundles.
- exported by MoviesInterface
- imported by MovieLister
If you check OSGi R4 specs, on page 52-278, section 3.8.4 Overall Search Order, you will read:
"3. If the class is in a package that is imported using Import-Package or was imported dynamically in a previous load, then the request is delegated to the exporting bundle’s class loader; (...) If the request is delegated to an exporting class loader and the class or resource is not found, then the search terminates and the request fails"
In our case the Framework resolves the MovieLister.mf "Import-Package: osgitut.movies" with the MoviesInterface.mf "Export-Package: osgitut.movies"
then the osgitut.movies.MovieLister.class has to be package with exporter MoviesInterface or the class loading request fails.