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!
Last time we looked at how to consume a service, using a scenario inspired by Martin Fowler: a
MovieLister
depends on a
MovieFinder
to search for movies directed by a specified director. We looked also at strategies for dealing with the dynamic nature of OSGi services, in particular what
MovieLister
should do if it cannot find an instance of
MovieFinder
.
There is another possibility that we didn't consider last time: what if there is more than one
MovieFinder
available? After all, any bundle can register a service under the
MovieFinder
interface, and all bundles are equal in the eyes of the registry.
We could simply ignore the problem, and in fact that's what our code last time did. By calling the
getService()
method on a
ServiceTracker
, we receive an arbitrary single instance of
MovieFinder
chosen by the service registry. There are various ways to influence the decision (for example a
SERVICE_RANKING
property that can be specified with the service registration), but as a consumer we will never have full control over this decision. And in fact it's a good thing not to have too much control -- after all, we really should be able to use any instance of
MovieFinder
. This is somewhat the point of using interfaces in the first place.
Alternatively, it might be useful sometimes to be aware of and use multiple service instances. For example, if there are multiple
MovieFinder
services available, it probably means that there are multiple sources of movie data that the
MovieLister
could take advantage of. By using all of them when performing a movie search, we are able to cast the net wider and produce better search results for the user.
Another change we would like to make goes back to the problem discussed from last time: what is the correct thing to do when there is no
MovieFinder
service available at all? We took the simple route of returning
null
whenever the
listByDirector
method was called, and the
MovieFinder
was unavailable. But what if we made it impossible for methods on
MovieLister
to be called when the
MovieFinder
isn't present?
MovieLister
is a service, just like
MovieFinder
. If the
MovieFinder
service disappears, how about making
MovieLister
disappear as well? In other words, we want the
MovieLister
service to have a one-to-many dependency on
MovieFinder
. In the last part of the tutorial, we had a zero-to-one dependency.
Let's make some changes to the
MovieListerImpl
class. Replace it with the following:
package osgitut.movies.impl;
import java.util.*;
import osgitut.movies.*;
publicclass MovieListerImpl implements MovieLister {
private Collection finders =
Collections.synchronizedCollection(new ArrayList());
protectedvoid bindFinder(MovieFinder finder) {
finders.add(finder);
System.out.println("MovieLister: added a finder");
}
protectedvoid unbindFinder(MovieFinder finder) {
finders.remove(finder);
System.out.println("MovieLister: removed a finder");
}
public List listByDirector(String director) {
MovieFinder[] finderArray = (MovieFinder[])
finders.toArray(new MovieFinder[finders.size()]);
List result = new LinkedList();
for(int j=0; j<finderArray.length; j++) {
Movie[] all = finderArray[j].findAll();
for(int i=0; i<all.length; i++) {
if(director.equals(all[i].getDirector())) {
result.add(all[i]);
}
}
}
return result;
}
}
We've actually removed all OSGi dependencies from
MovieListerImpl
-- it is now a pure POJO. However it requires somebody or something to track the
MovieFinder
services and supply them to it through the
bindFinder
method, so to do this we create a new file called
osgitut/movies/impl/MovieFinderTracker.java
as follows:
This class overrides the
ServiceTracker
class that we talked about last time, and customizes the way the
ServiceTracker
behaves when services come and go. Specifically, the
addingService
method is called when a
MovieFinder
service is added, and the
removedService
is called when a
MovieFinder
is removed. There is also a
modifiedService
method that we could override, but we don't need it here.
It's worth taking a close look at the code in these two methods. First, in
addingService
we see that the parameter passed to us is a
ServiceReference
rather than the actual service implementation object.
ServiceReference
is a lightweight handle that can be passed freely around as a parameter, and it can be used to obtain the properties of the service, i.e. those that were supplied along with the service registration. Crucially, obtaining a
ServiceReference
object does not cause the OSGi framework to increment the usage count for the target service. You could therefore think of this as similar to the
WeakReference
class in the Java Reflection APIs.
The first thing we do with the
ServiceReference
in our example is to obtain the real
MovieFinder
service object from it. To do this we need the
BundleContext
again -- remember, all interaction with the OSGi framework is done through the
BundleContext
interface. Handily our superclass
ServiceTracker
keeps the
BundleContext
reference in a protected member field called
context
that we can use directly.
Next we bind the finder into our
MovieListerImpl
using the
bindFinder
method we defined above, and then we register the
MovieListerImpl
as a service itself, under the
MovieLister
interface. Note that we are careful only to register the service if it was not already registered, because in this scenario we want to have a single
MovieLister
that tracks multiple
MovieFinder
services.
Finally, we return from the method. There's another interesting point here -- the return type of
addingService
is simply Object, so what should we return? In fact, the
ServiceTracker
doesn't care, we can return whatever we like. However, the object we return from
addingService
will be given back to us when
modifiedService
and/or
removedService
are called. So if you look at the first line of our
removedService
method, you see that we immediately cast the Object to a
MovieFinder
so that it can be unbound from the lister. Then we unregister the
MovieLister
service if the number of tracked finders has dropped to zero.
Generally, whatever we do in
addingService
has to be undone in the
removedService
method. Therefore we should return from the
addingService
method whatever will help us find whatever it was we did. This could be a key in a
HashMap
, or it could be a
ServiceRegistration
object, or (as in this case) it could be the actual service object.
As a final step in
removedService
, we have to "unget" the service that we previous "got". This is very important because it causes the service registry to decrement the service usage counter, which allows the service to be freed when the counter drops to zero.
Now we need an activator,
osgitut/movies/impl/TrackingMovieListerActivator.java
:
And I will leave it to you as an exercise to build and deploy this bundle to Equinox. Once this is done, try the following sequence of steps to see that everything is working:
Start the BasicMovieFinder and start the TrackingMovieLister. Check that the message "MovieLister: added a finder" appears.
Type the
services
command and check that there is a
MovieLister
service registered.
Stop the BasicMovieFinder and check that the message "MovieLister: removed a finder" appears.
Type the
services
command again and check that there is no
MovieLister
service registered.
What we have done here has sown the seeds of a very powerful technique. We have tied the lifecycle of one service to the lifecycle of another service -- in fact, multiple other services. Taking this technique further, we could have another service that is tied to the
MovieLister
, and yet another service that depends on that one, and so on. We end up constructing a graph of interdependent services, but unlike the graphs of beans constructed by some static IOC containers, our graph is robust, self-healing, and able to adjust to a changing world.
On the other hand, there are some problems with the code we've written. The
MovieFinderTracker
and
TrackingMovieListerActivator
classes are really just a load of boilerplate, and if we do start expanding this system then we're going to get pretty tired of writing the same code over and over, with just slight modifications each time. Therefore in the next installment we will look at a way for all of that code to be replaced by just a couple of lines of XML!
Also in the next installment we will stop building everything with command line tools in a single directory. My aim when starting this tutorial was to show that OSGi is a simple yet powerful framework, and that you don't need a powerful, heavyweight IDE like Eclipse to develop OSGi bundles. When something appears to be "too easy" there is always the suspicion that the IDE has performed some black magic on our behalf. I hope I have shown that that is not the case, OSGi does not require black magic. On the other hand, if the directory where you have been putting this code looks anything like mine, you're starting to yearn for a proper development environment. So am I. This isn't a problem with OSGi, of course -- any Java project would rapidly get out of control like this if using just the standard tools.
However, I'm afraid that you're going to have to wait until after EclipseCon to read the next part. I'll be jumping on a plane in less than 24 hours on my way to Santa Clara. Hope to see you there!
My thanks go to BJ Hargrave for suggesting a better solution for the concurrent code
Re: Getting Started with OSGi: Dynamic Service Tracking
Very nice application Neil. I commented on this pattern you describe on my site and how other patterns can be used in conjunction with OSGI services. For instance, I mention the Chain of Responsibility pattern using the SERVICE_RANKING as the order or evaluation.
Re: Getting Started with OSGi: Dynamic Service Tracking
Great example.
In the spirit of being pedantic , I would like to observe that the MovieFinderTracker class is not thread-safe which is important in a ServiceTracker since ServiceEvents are synchronously delivered from potentially numerous threads. The issue is the registration field which is shared mutable state. Reading and writing of this field MUST be protected.
Here is a thread-safe version:
private boolean registering = false;
public Object addingService(ServiceReference reference) {
MovieFinder finder = (MovieFinder) context.getService(reference);
lister.bindFinder(finder);
synchronized(this) {
finderCount ++;
if (registering)
return finder;
registering = (finderCount == 1);
if (!registering)
return finder;
}
Re: Getting Started with OSGi: Dynamic Service Tracking
BJ,
You're quite right. I was trying to make this code thread-safe, but evidently I screwed up. It just goes to show that locking over shared memory is not the way to build concurrent systems, but that's a whole other discussion...
With your permission I'll edit your fixes directly into the post, for the benefit of people who might not read the comment thread.
Re: Getting Started with OSGi: Dynamic Service Tracking
Its an interesting point to let the MovieLister disappear, if MoveFinder service disappears. But your ServiceTracker implementation does not conform to the OSGi API specification.
The ServiceTrackerCustomizer interface specifies:
"The methods in this interface may be called as the result of a ServiceEvent being received by a ServiceTracker object. Since ServiceEvents are synchronously delivered by the Framework, it is highly recommended that implementations of these methods do not register (BundleContext.registerService), modify (ServiceRegistration.setProperties) or unregister (ServiceRegistration.unregister) a service while being synchronized on any object."
Probably there's another way to achieve the same result.
Re: Getting Started with OSGi: Dynamic Service Tracking
His ServiceTracker does conform to the the spec. MovieFinderTracker does not register/unregister MovieLister while synchronized. The synchronization is only around the "registering" variable.
Re: Getting Started with OSGi: Dynamic Service Tracking
Neil Thanks for the great Tutorial!
For an OSGi-noob like me it real is a good start.
Now I'm wandering what I need to add to my own (pojo) application to integrate this OSGi framework Equinox. Until now all interaction with the framework was through the OSGi console (via command line). What do I need to add to my code just to start this framework and use this plugins/bundles in my application?
I came accross OSGi while looking for a simple plugin mechanism. Besides OSGi I found the JPF (Java Plugin Framework) which is very easy to use. But OSGi has the benefit of being a real standard I think. But sofar I haven't been able to fully integrate this Equinox in my own application.
Getting Started with OSGi: Dynamic Service Tracking
At 8:11 AM on Mar 2, 2007, Neil Bartlett
wrote:
Last time we looked at how to consume a service, using a scenario inspired by Martin Fowler: a
MovieListerdepends on aMovieFinderto search for movies directed by a specified director. We looked also at strategies for dealing with the dynamic nature of OSGi services, in particular whatMovieListershould do if it cannot find an instance ofMovieFinder.There is another possibility that we didn't consider last time: what if there is more than one
MovieFinderavailable? After all, any bundle can register a service under theMovieFinderinterface, and all bundles are equal in the eyes of the registry.We could simply ignore the problem, and in fact that's what our code last time did. By calling the
getService()method on aServiceTracker, we receive an arbitrary single instance ofMovieFinderchosen by the service registry. There are various ways to influence the decision (for example aSERVICE_RANKINGproperty that can be specified with the service registration), but as a consumer we will never have full control over this decision. And in fact it's a good thing not to have too much control -- after all, we really should be able to use any instance ofMovieFinder. This is somewhat the point of using interfaces in the first place.Alternatively, it might be useful sometimes to be aware of and use multiple service instances. For example, if there are multiple
MovieFinderservices available, it probably means that there are multiple sources of movie data that theMovieListercould take advantage of. By using all of them when performing a movie search, we are able to cast the net wider and produce better search results for the user.Another change we would like to make goes back to the problem discussed from last time: what is the correct thing to do when there is no
MovieFinderservice available at all? We took the simple route of returningnullwhenever thelistByDirectormethod was called, and theMovieFinderwas unavailable. But what if we made it impossible for methods onMovieListerto be called when theMovieFinderisn't present?MovieListeris a service, just likeMovieFinder. If theMovieFinderservice disappears, how about makingMovieListerdisappear as well? In other words, we want theMovieListerservice to have a one-to-many dependency onMovieFinder. In the last part of the tutorial, we had a zero-to-one dependency.Let's make some changes to the
MovieListerImplclass. Replace it with the following:package osgitut.movies.impl; import java.util.*; import osgitut.movies.*; public class MovieListerImpl implements MovieLister { private Collection finders = Collections.synchronizedCollection(new ArrayList()); protected void bindFinder(MovieFinder finder) { finders.add(finder); System.out.println("MovieLister: added a finder"); } protected void unbindFinder(MovieFinder finder) { finders.remove(finder); System.out.println("MovieLister: removed a finder"); } public List listByDirector(String director) { MovieFinder[] finderArray = (MovieFinder[]) finders.toArray(new MovieFinder[finders.size()]); List result = new LinkedList(); for(int j=0; j<finderArray.length; j++) { Movie[] all = finderArray[j].findAll(); for(int i=0; i<all.length; i++) { if(director.equals(all[i].getDirector())) { result.add(all[i]); } } } return result; } }We've actually removed all OSGi dependencies from
MovieListerImpl-- it is now a pure POJO. However it requires somebody or something to track theMovieFinderservices and supply them to it through thebindFindermethod, so to do this we create a new file calledosgitut/movies/impl/MovieFinderTracker.javaas follows:package osgitut.movies.impl; import org.osgi.framework.*; import org.osgi.util.tracker.*; import osgitut.movies.*; public class MovieFinderTracker extends ServiceTracker { private final MovieListerImpl lister = new MovieListerImpl(); private int finderCount = 0; private ServiceRegistration registration = null; public MovieFinderTracker(BundleContext context) { super(context, MovieFinder.class.getName(), null); } private boolean registering = false; public Object addingService(ServiceReference reference) { MovieFinder finder = (MovieFinder) context.getService(reference); lister.bindFinder(finder); synchronized(this) { finderCount ++; if (registering) return finder; registering = (finderCount == 1); if (!registering) return finder; } ServiceRegistration reg = context.registerService( MovieLister.class.getName(), lister, null); synchronized(this) { registering = false; registration = reg; } return finder; } public void removedService(ServiceReference reference, Object service) { MovieFinder finder = (MovieFinder) service; lister.unbindFinder(finder); context.ungetService(reference); ServiceRegistration needsUnregistration = null; synchronized(this) { finderCount --; if (finderCount == 0) { needsUnregistration = registration; registration = null; } } if(needsUnregistration != null) { needsUnregistration.unregister(); } } }This class overrides the
ServiceTrackerclass that we talked about last time, and customizes the way theServiceTrackerbehaves when services come and go. Specifically, theaddingServicemethod is called when aMovieFinderservice is added, and theremovedServiceis called when aMovieFinderis removed. There is also amodifiedServicemethod that we could override, but we don't need it here.It's worth taking a close look at the code in these two methods. First, in
addingServicewe see that the parameter passed to us is aServiceReferencerather than the actual service implementation object.ServiceReferenceis a lightweight handle that can be passed freely around as a parameter, and it can be used to obtain the properties of the service, i.e. those that were supplied along with the service registration. Crucially, obtaining aServiceReferenceobject does not cause the OSGi framework to increment the usage count for the target service. You could therefore think of this as similar to theWeakReferenceclass in the Java Reflection APIs.The first thing we do with the
ServiceReferencein our example is to obtain the realMovieFinderservice object from it. To do this we need theBundleContextagain -- remember, all interaction with the OSGi framework is done through theBundleContextinterface. Handily our superclassServiceTrackerkeeps theBundleContextreference in a protected member field calledcontextthat we can use directly.Next we bind the finder into our
MovieListerImplusing thebindFindermethod we defined above, and then we register theMovieListerImplas a service itself, under theMovieListerinterface. Note that we are careful only to register the service if it was not already registered, because in this scenario we want to have a singleMovieListerthat tracks multipleMovieFinderservices.Finally, we return from the method. There's another interesting point here -- the return type of
addingServiceis simply Object, so what should we return? In fact, theServiceTrackerdoesn't care, we can return whatever we like. However, the object we return fromaddingServicewill be given back to us whenmodifiedServiceand/orremovedServiceare called. So if you look at the first line of ourremovedServicemethod, you see that we immediately cast the Object to aMovieFinderso that it can be unbound from the lister. Then we unregister theMovieListerservice if the number of tracked finders has dropped to zero.Generally, whatever we do in
addingServicehas to be undone in theremovedServicemethod. Therefore we should return from theaddingServicemethod whatever will help us find whatever it was we did. This could be a key in aHashMap, or it could be aServiceRegistrationobject, or (as in this case) it could be the actual service object.As a final step in
removedService, we have to "unget" the service that we previous "got". This is very important because it causes the service registry to decrement the service usage counter, which allows the service to be freed when the counter drops to zero.Now we need an activator,
osgitut/movies/impl/TrackingMovieListerActivator.java:package osgitut.movies.impl; import org.osgi.framework.*; public class TrackingMovieListerActivator implements BundleActivator { private MovieFinderTracker tracker; public void start(BundleContext context) { tracker = new MovieFinderTracker(context); tracker.open(); } public void stop(BundleContext context) { tracker.close(); } }And a manifest,
TrackingMovieLister.mf:And I will leave it to you as an exercise to build and deploy this bundle to Equinox. Once this is done, try the following sequence of steps to see that everything is working:
servicescommand and check that there is aMovieListerservice registered.servicescommand again and check that there is noMovieListerservice registered.What we have done here has sown the seeds of a very powerful technique. We have tied the lifecycle of one service to the lifecycle of another service -- in fact, multiple other services. Taking this technique further, we could have another service that is tied to the
MovieLister, and yet another service that depends on that one, and so on. We end up constructing a graph of interdependent services, but unlike the graphs of beans constructed by some static IOC containers, our graph is robust, self-healing, and able to adjust to a changing world.On the other hand, there are some problems with the code we've written. The
MovieFinderTrackerandTrackingMovieListerActivatorclasses are really just a load of boilerplate, and if we do start expanding this system then we're going to get pretty tired of writing the same code over and over, with just slight modifications each time. Therefore in the next installment we will look at a way for all of that code to be replaced by just a couple of lines of XML!Also in the next installment we will stop building everything with command line tools in a single directory. My aim when starting this tutorial was to show that OSGi is a simple yet powerful framework, and that you don't need a powerful, heavyweight IDE like Eclipse to develop OSGi bundles. When something appears to be "too easy" there is always the suspicion that the IDE has performed some black magic on our behalf. I hope I have shown that that is not the case, OSGi does not require black magic. On the other hand, if the directory where you have been putting this code looks anything like mine, you're starting to yearn for a proper development environment. So am I. This isn't a problem with OSGi, of course -- any Java project would rapidly get out of control like this if using just the standard tools.
However, I'm afraid that you're going to have to wait until after EclipseCon to read the next part. I'll be jumping on a plane in less than 24 hours on my way to Santa Clara. Hope to see you there!
My thanks go to BJ Hargrave for suggesting a better solution for the concurrent code
7 replies so far (
Post your own)
Re: Getting Started with OSGi: Dynamic Service Tracking
Very nice application Neil. I commented on this pattern you describe on my site and how other patterns can be used in conjunction with OSGI services. For instance, I mention the Chain of Responsibility pattern using the SERVICE_RANKING as the order or evaluation.Re: Getting Started with OSGi: Dynamic Service Tracking
Great example.In the spirit of being pedantic
Here is a thread-safe version:
private boolean registering = false;
public Object addingService(ServiceReference reference) {
MovieFinder finder = (MovieFinder) context.getService(reference);
lister.bindFinder(finder);
synchronized(this) {
finderCount ++;
if (registering)
return finder;
registering = (finderCount == 1);
if (!registering)
return finder;
}
ServiceRegistration reg = context.registerService(
MovieLister.class.getName(), lister, null);
synchronized(this) {
registering = false;
registration = reg;
}
return finder;
}
public void removedService(ServiceReference reference, Object service) {
MovieFinder finder = (MovieFinder) service;
lister.unbindFinder(finder);
context.ungetService(reference);
ServiceRegistration needsUnregistration = null;
synchronized(this) {
finderCount --;
if (finderCount == 0) {
needsUnregistration = registration;
registration = null;
}
}
if(needsUnregistration != null) {
needsUnregistration.unregister();
}
}
You were also missing the ungetService call in removedService (even though the text mentions the importance of this).
I think your articles are excellent. I just want to make them more accurate!
PS. Sorry the code formatting is nonexistent. All the leading whitespace seems to be gone...
Re: Getting Started with OSGi: Dynamic Service Tracking
BJ,You're quite right. I was trying to make this code thread-safe, but evidently I screwed up. It just goes to show that locking over shared memory is not the way to build concurrent systems, but that's a whole other discussion...
With your permission I'll edit your fixes directly into the post, for the benefit of people who might not read the comment thread.
Many thanks,
Neil
Re: Getting Started with OSGi: Dynamic Service Tracking
Yes, please do.See you at EclipseCon!
Re: Getting Started with OSGi: Dynamic Service Tracking
Its an interesting point to let the MovieLister disappear, if MoveFinder service disappears. But your ServiceTracker implementation does not conform to the OSGi API specification.The ServiceTrackerCustomizer interface specifies: "The methods in this interface may be called as the result of a ServiceEvent being received by a ServiceTracker object. Since ServiceEvents are synchronously delivered by the Framework, it is highly recommended that implementations of these methods do not register (BundleContext.registerService), modify (ServiceRegistration.setProperties) or unregister (ServiceRegistration.unregister) a service while being synchronized on any object."
Probably there's another way to achieve the same result.
Re: Getting Started with OSGi: Dynamic Service Tracking
His ServiceTracker does conform to the the spec. MovieFinderTracker does not register/unregister MovieLister while synchronized. The synchronization is only around the "registering" variable.Re: Getting Started with OSGi: Dynamic Service Tracking
Neil Thanks for the great Tutorial!For an OSGi-noob like me it real is a good start.
Now I'm wandering what I need to add to my own (pojo) application to integrate this OSGi framework Equinox. Until now all interaction with the framework was through the OSGi console (via command line). What do I need to add to my code just to start this framework and use this plugins/bundles in my application?
I came accross OSGi while looking for a simple plugin mechanism. Besides OSGi I found the JPF (Java Plugin Framework) which is very easy to use. But OSGi has the benefit of being a real standard I think. But sofar I haven't been able to fully integrate this Equinox in my own application.
Thanks!
Rens