package org.marketcetera.photon.commons.osgi; import org.apache.commons.lang.ObjectUtils; import org.marketcetera.photon.commons.Validate; import org.marketcetera.util.misc.ClassVersion; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; /* $License$ */ /** * Tracks OSGi services for a particular class name, selecting the service with * the highest {@link Constants#SERVICE_RANKING ranking}. * <p> * The {@link HighestRankedTracker} must be {@link ServiceTracker#open() opened} * for tracking to begin. When opened the {@link IHighestRankedTrackerListener} * will be notified of the current highest ranked service if one exists. * <p> * Note that clients may not be updated if a service ranking changes. In other * words, the behavior of this object is only predictable when services are * added and removed. This is due to <a * href="http://bugs.eclipse.org/288365">http://bugs.eclipse.org/288365</a>. * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: HighestRankedTracker.java 16154 2012-07-14 16:34:05Z colin $ * @since 2.0.0 */ @ClassVersion("$Id: HighestRankedTracker.java 16154 2012-07-14 16:34:05Z colin $") public final class HighestRankedTracker extends ServiceTracker { /** * Callback interface for clients to be notified when the highest ranked * service changes. */ @ClassVersion("$Id: HighestRankedTracker.java 16154 2012-07-14 16:34:05Z colin $") public interface IHighestRankedTrackerListener { /** * The highest ranked service changed. * <p> * This method will not be called concurrently since it is called while * the {@link HighestRankedTracker} is holding a lock. It may, however, * be called from different threads. * * @param newService * the new service, may be null if none are available */ void highestRankedServiceChanged(Object newService); } private final String mClazz; private final IHighestRankedTrackerListener mCallback; private final Object mLock = new Object(); private ServiceReference mLatest; /** * Constructor. * * @param context * the bundle context used to obtain services * @param clazz * the class name of the services to be tracked * @param callback * callback for clients to handle state changes */ public HighestRankedTracker(BundleContext context, String clazz, IHighestRankedTrackerListener callback) { super(context, clazz, null); Validate.notNull(callback, "callback"); //$NON-NLS-1$ mClazz = clazz; mCallback = callback; } @Override public Object addingService(ServiceReference reference) { Object service = super.addingService(reference); update(); return service; } @Override public void modifiedService(ServiceReference reference, Object service) { update(); } @Override public void removedService(ServiceReference reference, Object service) { super.removedService(reference, service); update(); } private void update() { synchronized (mLock) { ServiceReference previous = mLatest; /* * Use the context to get the highest ranked service reference. */ mLatest = context.getServiceReference(mClazz); if (!ObjectUtils.equals(mLatest, previous)) { if (mLatest != null) { /* * Since we are not maintaining a map from service reference * to service, we get the service again from the context. * This will increase the reference count again (it was * already done in serviceAdded) so we need to unget after * we are done. */ Object service = context.getService(mLatest); try { mCallback.highestRankedServiceChanged(service); } finally { context.ungetService(mLatest); } } else { mCallback.highestRankedServiceChanged(null); } } } } }