The Service Locator pattern is a well-established mechanism for accessing local and remote services in a consistent manner:

1
2
3
public interface ServiceLocator {
    <T> T findService(String serviceName) throws ServiceNotAvailableException;
}

Using a structured service name interface we can improve uniformity and reduce the potential for typos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum ServiceName {
  SomeService("SomeService");

  private final String filter;
  ...

  /**
    * @return a filter string used to identify the service classification/location.
    */
  String getFilter();
}

public interface ServiceLocator {

    <T> T findService(ServiceName serviceName) throws ServiceNotAvailableException;
}

In an OSGi environment, the recommended approach for retrieving services is via the org.osgi.util.tracker.ServiceTracker class:

1
2
3
4
BundleContext context = ...
ServiceTracker tracker = new ServiceTracker(context, SomeService.class.getName(), null);
tracker.open();
SomeService service = (SomeService) tracker.getService();

We can combine these two patterns to provide a more familiar and manageable approach to locating services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.util.HashMap;
import java.util.Map;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;

public class OsgiServiceLocator implements ServiceLocator {
    private final BundleContext context;
    private final Map<ServiceName, ServiceTracker> serviceTrackers;

    /**
     * @param context the bundle context in which to find services
     */
    public OsgiServiceLocator(BundleContext context) {
        this.context = context;
        serviceTrackers = new HashMap<ServiceName, ServiceTracker>();
    }

    @SuppressWarnings("unchecked")
    public <T> T findService(ServiceName serviceName) throws ServiceNotAvailableException {
        ServiceTracker tracker = serviceTrackers.get(serviceName);
        if (tracker == null) {
            synchronized (serviceTrackers) {
                tracker = serviceTrackers.get(serviceName);
                if (tracker == null) {
                    tracker = new ServiceTracker(context, context.createFilter(serviceName.getFilter()), null);
                    tracker.open();
                    serviceTrackers.put(serviceName, tracker);
                }
            }
        }
        final T service = (T) tracker.getService();
        if (service == null) {
            throw new ServiceNotAvailableException("Service matching [" + serviceName.getFilter() + "] not found.");
        }
        return service;
    }
    
    /**
     * Clean up resources.
     */
    public void reset() {
        for (ServiceTracker tracker : serviceTrackers.values()) {
            tracker.close();
        }
        serviceTrackers.clear();
    }
}

An example usage might be something like this:

1
2
3
4
5
6
7
8
9
10
11
    
import org.osgi.framework.Constants;

public enum ServiceName {
  SomeService("(" + Constants.OBJECTCLASS + "=" + SomeService.class.toString() + ")");
  ...
}

BundleContext context = ...
ServiceLocator serviceLocator = new OsgiServiceLocator(context);
SomeService service = serviceLocator.findService(ServiceName.SomeService);

Conclusion

By implementing the Service Locator pattern in an OSGi context we provide consistency and familiarity for code that is not OSGi-aware. This reduces the dependency on OSGi and improves the maintainability of our code.