Service Trackers

Now that Liferay is promoting more modular plugins deployed into an OSGi runtime, you have to consider how your own code, living in its own module, can rely on services in other modules for functionality. You must account for the possibility of service implementations being swapped out or removed entirely if your module is to survive and thrive in an OSGi environment. It’s easy for Liferay DXP 7.0 developers who need to call services from their @Component classes. They just use another Declarative Services (DS) annotation, @Reference, to get a service reference. The component activates when the referenced service is available.

If you’re able to use DS and leverage the @Component and @Reference annotations, you should. DS handles much of the complexity of handling service dynamism for you transparently.

If you can’t use DS to create a Component, keep reading to learn how to implement a Service Tracker to look up services in the service registry.

Figure 1: Service implementations that are registered in the OSGi service registry can be accessed using Service Trackers.

Figure 1: Service implementations that are registered in the OSGi service registry can be accessed using Service Trackers.

What scenarios might require using a service tracker? Keep in mind we’re focusing on scenarios where DS can’t be used. This typically involves a non-native (to OSGi) Dependency Injection framework.

Using a Service Tracker, your non-OSGi application can access any service registered in the OSGi runtime, including your own Service Builder services and the services published by Liferay’s modules (like the popular UserLocalService).

Implementing a Service Tracker

Although you don’t have the luxury of using DS to manage your service dependencies, you can call services from the service registry with a little bit of code.

To implement a service tracker you can do this:

import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.util.tracker.ServiceTracker;

Bundle bundle = FrameworkUtil.getBundle(this.getClass());
BundleContext bundleContext = bundle.getBundleContext();
ServiceTracker<SomeService, SomeService> serviceTracker =
    new ServiceTracker(bundleContext, SomeService.class, null);
serviceTracker.open();
SomeService someService = serviceTracker.waitForService(500);

To simplify your code, you can create a class that extends org.osgi.util.tracker.ServiceTracker.

public class SomeServiceTracker
    extends ServiceTracker<SomeService, SomeService> {

    public SomeServiceTracker(Object host) {
        super(
            FrameworkUtil.getBundle(host.getClass()).getBundleContext(),
            SomeService.class, null);
    }
}

From the initialization part of your logic that uses the service, call your service tracker constructor. The Object host parameter is used to obtain your own bundle context and in order to give accurate results must be an object from your own bundle.

ServiceTracker<SomeService, SomeService> someServiceTracker =
    new SomeServiceTracker(this);

Remember to open the service tracker before using it, typically as early as you can.

someServiceTracker.open();

The most basic usage of a Service Tracker is to interrogate the service’s state. In your program logic, for example, check whether the service is null before using it:

SomeService someService = someServiceTracker.getService();

if (someService == null) {
    _log.warn("The required service 'SomeService' is not available.");
}
else {
    someService.doSomethingCool();
}

Service Trackers have several other utility functions for introspecting tracked services.

Later when your application is being destroyed or undeployed, close the service tracker.

someServiceTracker.close();

Implementing a Callback Handler for Services

If there’s a strong possibility the service might not be available, or if you need to track multiple services, the Service Tracker API provides a callback mechanism which operates on service events. To use this, override ServiceTracker’s addingService and removedService methods. Their ServiceReference parameter references an active service object.

Here’s an example ServiceTracker implementation from the OSGi Alliance’s OSGi Core Release 7 specification:

new ServiceTracker<HttpService, MyServlet>(context, HttpService.class, null) {

    public MyServlet addingService(ServiceReference<HttpService> reference) {
        HttpService httpService = context.getService(reference);
        MyServlet myServlet = new MyServlet(httpService);
        return myServlet;
    }

    public void removedService(
        ServiceReference<HttpService> reference, MyServlet myServlet) {
        myServlet.close();
        context.ungetService(reference);
    }
}

When the HttpService is added to the OSGi registry, this ServiceTracker creates a new wrapper class, MyServlet, which uses the newly added service. When the service is removed from the registry, the removedService method cleans up related resources.

As an alternative to directly overloading ServiceTracker methods, create a org.osgi.util.tracker.ServiceTrackerCustomizer:

class MyServiceTrackerCustomizer 
    implements ServiceTrackerCustomizer<SomeService, MyWrapper> {
    
    private final BundleContext bundleContext;
    
    MyServiceTrackerCustomizer(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }
    
    @Override
    public MyWrapper addedService(
        ServiceReference<SomeService> serviceReference) {
        
        // Determine if the service is one that's interesting to you.
        // The return type of this method is the `tracked` type. Its type 
        // is what is returned from `getService*` methods; useful for wrapping 
        // the service with your own type (e.g., MyWrapper).
        if (isInteresting(serviceReference)) {
            MyWrapper myWrapper = new MyWrapper(
                serviceReference, bundleContext.getService());
            
            // trigger the logic that requires the available service(s)
            triggerServiceAddedLogic(myWrapper);
            
            return myWrapper;
        }
        
        // If the return is null, the tracker is effectively ignoring any further
        // events for the service reference
        return null;
    }

    @Override
    public void modifiedService(
        ServiceReference<SomeService> serviceReference, MyWrapper myWrapper) {
        // handle the modified service
    }

    @Override
    public void removedService(
        ServiceReference<SomeService> serviceReference, MyWrapper myWrapper) {

        // finally, trigger logic when the service is going away
        triggerServiceRemovedLogic(myWrapper);
	}

}

Register the ServiceTrackerCustomizer by passing it as the ServiceTracker constructor’s third parameter.

ServiceTrackerCustomizer<SomeService, MyWrapper> serviceTrackerCustomizer =
    new MyServiceTrackerCustomizer();

ServiceTracker<SomeService, MyWrapper> serviceTracker = 
    new ServiceTracker<>(
    	bundleContext, SomeService.class, serviceTrackerCustomizer);

There’s a little boilerplate code you need to produce, but now you can look up services in the service registry, even if your plugins can’t take advantage of the Declarative Services component model.

« Semantic VersioningWaiting on Lifecycle Events »
Was this article helpful?
0 out of 0 found this helpful