Getting Started with OSGi: Dynamic Service Tracking

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

你可能感兴趣的:(eclipse,IOC,ide,osgi,Go)