Saturday, August 12, 2017

OSGi Service Trackers

Requirement is to have a dynamic mapping with an interface implementation provided by the main OSGi bundle. At a given time there could only be a default and a single custom implementation. For this purpose I'm using OSGi ServiceTracker to dynamically assign the implementation.


Use case:

This interface "org.siriwardana.sample.core.MessageHandlerFactory" will be exported by the core bundle and will be implemented by the default bundle and a custom implementation.

/**
 *
 * Interface for message handler factory. Custom deployments should implement this interface
 */
public interface MessageHandlerFactory {

    MessageHandler getHandler(String messageType);
}

Following is the org.siriwardana.sample.core.MessageHandler interface which is also exported by the core bundle.


/**
 *
 * Custom message handler which should be implemented by the custom deployment bundle to handle
 */
public interface MessageHandler {

    /**
     *Create the message with a custom implementation.
     * @return String
     */
    String createReqMsg();

    /**
     *
     * Handle response of the request as per the custom implementation.
     */
    void handleResponse (String response);

    /**
     *
     * Handle error as per the custom implementation
     */
    void onError(Exception e);

}

Default service bundle and a custom implementation service bundle will be available at runtime and the custom implementation will get the priority by the consumer bundle.

Solution:
ServiceTracker (org.osgi.util.tracker.ServiceTracker) and ServiceTrackerCustomizer (org.osgi.util.tracker.ServiceTrackerCustomizer) will be used to dynamically used by the consumer bundle.

Following bundle activator implementation will demonstrates the solution. 

/**
 * @scr.component name="org.siriwardana.sample.consumer" immediate="true"
 */
public class ServiceComponent {

    private static Log LOGGER = LogFactory.getLog(ServiceComponent.class);
    private static final String MESSAGE_HANDLER_DEFAULT = "default";

    private ServiceTracker serviceTracker;
    private BundleContext bundleContext;
    private ServiceRegistration defaultHandlerRef;

    @SuppressWarnings("unchecked")
    protected void activate(ComponentContext context) {
        bundleContext = context.getBundleContext();

        Dictionary<String, String> props = new Hashtable<>();
        props.put(Constants.MESSAGE_HANDLER_KEY, MESSAGE_HANDLER_DEFAULT);

        if (bundleContext != null) {
           
            ServiceTrackerCustomizer trackerCustomizer = new Customizer();
            serviceTracker = new ServiceTracker(bundleContext, MessageHandlerFactory.class.getName(), trackerCustomizer);
            serviceTracker.open();
            LOGGER.debug("ServiceTracker initialized");
        } else {
            LOGGER.error("BundleContext cannot be null");
        }
    }

    protected void deactivate(ComponentContext context) {

        defaultHandlerRef.unregister();
        serviceTracker.close();
        serviceTracker = null;
        LOGGER.debug("ServiceTracker stopped. Cloud Default handler bundle deactivated.");
    }

    private void setMessageHandlerFactory(ServiceReference<?> reference) {

        MessageHandlerFactory handlerFactory = (MessageHandlerFactory) bundleContext.getService(reference);
        LOGGER.debug("MessageHandlerFactory  is acquired");
        ServiceDataHolder.getInstance().setHandlerFactory(handlerFactory);
    }

    private void unsetMessageHandlerFactory(MessageHandlerFactory handlerFactory) {

        LOGGER.debug("MessageHandlerFactory  is released");
        ServiceDataHolder.getInstance().setHandlerFactory(null);
    }

    /**
     *
     * Service tracker for Message handler factory implementation
     */
    private class Customizer implements ServiceTrackerCustomizer {

        @SuppressWarnings("unchecked")
        public Object addingService(ServiceReference serviceReference) {

            LOGGER.debug("ServiceTracker: service added event invoked");
            ServiceReference serviceRef = updateMessageHandlerService();
            return bundleContext.getService(serviceRef);
        }

        public void modifiedService(ServiceReference reference, Object service) {
            LOGGER.debug("ServiceTracker: modified service event invoked");
            updateMessageHandlerService();
        }

        @SuppressWarnings("unchecked")
        public void removedService(ServiceReference reference, Object service) {

            if (reference != null) {
                MessageHandlerFactory handlerFactory = (MessageHandlerFactory) bundleContext.getService(reference);
                unsetMessageHandlerFactory(handlerFactory);
                LOGGER.debug("ServiceTracker: removed service event invoked");
                updateMessageHandlerService();
            }
        }

        private ServiceReference updateMessageHandlerService() {

            ServiceReference serviceRef = null;
            try {
                ServiceReference<?>[] references = bundleContext
                        .getAllServiceReferences(MessageHandlerFactory.class.getName(), null);
                for(ServiceReference<?> reference : references) {
                    serviceRef = reference;
                    if (!MESSAGE_HANDLER_DEFAULT
                            .equalsIgnoreCase((String) reference.getProperty(Constants.MESSAGE_HANDLER_KEY))) {
                        break;
                    }
                }
                if (serviceRef != null) {
                    LOGGER.debug("ServiceTracker: HandlerFactory updated. Service reference: " + serviceRef);
                    setMessageHandlerFactory(serviceRef);
                } else {
                    LOGGER.debug("ServiceTracker: HandlerFactory not updated: Service reference is null");
                }
            } catch (InvalidSyntaxException e) {
                LOGGER.error("ServiceTracker: Error while updating the MessageHandlers. ", e);
            }
            return serviceRef;
        }
    }
}

Following is the custom implementation bundle which will register it's service for the MessageHandlerFactory interface.


/**
 * @scr.component name="org.siriwardana.custom" immediate="true"
 */
public class CustomMessageHandlerFactoryComponent {

    private static final String MESSAGE_HANDLER = "CUSTOM";
    private static Log LOGGER = LogFactory.getLog(CustomMessageHandlerFactoryComponent.class);

    private ServiceRegistration serviceRef;

    protected void activate(ComponentContext context) {

        BundleContext bundleContext = context.getBundleContext();
        Dictionary<String, String> props = new Hashtable<>();
        props.put(Constants.MESSAGE_HANDLER_KEY, MESSAGE_HANDLER);

        serviceRef = bundleContext.registerService(MessageHandlerFactory.class, new CustomMessageHandlerFactory(), props);
        LOGGER.debug("Custom Message handler impl bundle activated ");
    }

    protected void deactivate(ComponentContext context) {
        serviceRef.unregister();
        LOGGER.debug("Custom Message handler impl bundle deactivated ");
    }
}

When every an Interface implementation is available, ServiceTracker will update the consumer bundle.