Forum Controls
Spotlight Features

The Rich Engineering Heritage Behind Dependency Injection

Andrew McVeigh takes us on a tour of the rich heritage behind dependency injection, what it represents, and tells us why its here to stay.

Java, the OLPC, and community responsibility

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!
Replies: 7 - Pages: 1  
Threads: [ Previous | Next ]
  Click to reply to this thread Reply

Getting Started with OSGi: Dynamic Service Tracking

At 8:11 AM on Mar 2, 2007, Neil Bartlett Javalobby Regulars wrote:

Welcome back to the EclipseZone OSGi mini series.

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.*;
 
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 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:

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 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 :

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 :

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tracking Movie Lister
Bundle-SymbolicName: TrackingMovieLister
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.TrackingMovieListerActivator
Import-Package: org.osgi.framework,
 org.osgi.util.tracker,
 osgitut.movies;version="[1.0.0,2.0.0)"
 


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:
  1. Start the BasicMovieFinder and start the TrackingMovieLister. Check that the message "MovieLister: added a finder" appears.
  2. Type the services command and check that there is a MovieLister service registered.
  3. Stop the BasicMovieFinder and check that the message "MovieLister: removed a finder" appears.
  4. 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
  Click to reply to this thread Reply
1. At 10:14 AM on Mar 2, 2007, Bob Balfe Javalobby Newcomers wrote:

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.
  Click to reply to this thread Reply
2. At 11:14 AM on Mar 3, 2007, BJ Hargrave Javalobby Newcomers wrote:

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;
}

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...
  Click to reply to this thread Reply
3. At 8:06 PM on Mar 3, 2007, Neil Bartlett Javalobby Regulars wrote:

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
  Click to reply to this thread Reply
4. At 9:41 PM on Mar 3, 2007, BJ Hargrave Javalobby Newcomers wrote:

Re: Getting Started with OSGi: Dynamic Service Tracking

Yes, please do.

See you at EclipseCon!
  Click to reply to this thread Reply
5. At 3:58 PM on Jun 16, 2007, Tobias Hofer Javalobby Newcomers wrote:

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.
  Click to reply to this thread Reply
6. At 3:17 PM on Jul 12, 2007, Richie Jefts Javalobby Newcomers wrote:

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.
  Click to reply to this thread Reply
7. At 7:32 AM on Oct 1, 2007, Rens Javalobby Newcomers wrote:

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

thread.rss_message