Service Listener – OSGi pattern in AEM

Forces

  • Model independent functions which are similar in nature.
  • Ability to plugin these independent functions on the fly.
  • Consume or invoke these functions in a controlled manner through a single facade.

DesignService Listener - OSGi pattern in AEM
Model independent functions as OSGi services implementing the same interface. Service Listener is another service which instructs the OSGi container to inject all the services implementing a specific interface. When the services becomes available, OSGi container injects them into the Service listener. Service listener maintains a registry or a list to hold reference to these services and invokes them based on the business logic implemented in it.

Example – Log service listener.OSGi Service Listener pattern implemented in AEM
We’ve a LogService interface which logs the given message.

/**
* Sample service for logging.
*/
public interface LogService {
/**
* Log the given message
* @param msg Message
*/
void log(String msg);
}

Assume that we’ve multiple implementations of log service – Error log service, request log service, etc.

/**
 * Error logging service - Sample LogService to demonstrate Service Listener pattern.
 */
@Component(label = "Compute Patterns - Error log service",
        description = "Sample service to log the error messages. Purpose of this service is to demonstrate OSGi service listener pattern.",
        metatype = true
)
@Service
public class ErrorLogServiceImpl implements LogService {
    private static final Logger log = LoggerFactory.getLogger(ErrorLogServiceImpl.class);

    @Override
    public void log(String msg) {
        log.info("ErrorLogServiceImpl - {}", msg);
    }
}
/**
 * Request logging service - Sample LogService to demonstrate Service Listener pattern.
 */
@Component(label = "Compute Patterns - Request log service",
        description = "Sample service to log the request messages. Purpose of this service is to demonstrate OSGi service listener pattern.",
        metatype = true
)
@Service
public class RequestLogServiceImpl implements LogService {
    private static final Logger log = LoggerFactory.getLogger(RequestLogServiceImpl.class);

    @Override
    public void log(String msg) {
        log.info("RequestLogServiceImpl - {}", msg);

    }
}

We also have a LogServiceListener which logs the given message using the registered LogService implementations.

/**
 * Sample service interface for listening OSGi services implementing LogService.
 */
public interface LogServiceListener {
    /**
     * Invoke all the registered log services.
     */
    void invokeLogServices();
}

A hypothetical sample LogServiceListener implementation which simply pass on the log message to all the registered log services. In real life implementations, this should atleast check the type of message and pass on to the appropriate log service implementation.

/**
 * Service Listener pattern  implementation - Sample implementation for demonstrating Service listener pattern.
 * This service listens for OSGi services which implement LogService interface.
 * When a LogService is available, Service Component Runtime (SCR) calls the bind method and when it goes unavailable,
 * unbind method will be called.
 */
@Component(label = "Compute Patterns - Log Service Listener",
        description = "Sample service listener pattern demonstration.")
@Reference(name = LogServiceListenerImpl.METHOD_NAME_TO_BIND,
        referenceInterface = LogService.class,
        cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
        policy = ReferencePolicy.DYNAMIC
)
@Service
public class LogServiceListenerImpl implements LogServiceListener {
    private static final Logger log = LoggerFactory.getLogger(LogServiceListenerImpl.class);

    /**
     * Name of the local method to be bound when a service is available in Service Component Runtime.
     */
    protected static final String METHOD_NAME_TO_BIND = "logService";

    /**
     * Thread safe hash map to contain the registered LogService references.
     */
    private static ConcurrentHashMap<String, LogService> serviceMap = new ConcurrentHashMap<>();

    @Override
    public void invokeLogServices() {
        for (Map.Entry<String, LogService> entry : serviceMap.entrySet()) {
            entry.getKey();
            entry.getValue().log("Hello from LogService Listener.");
        }
    }

    @Activate
    protected void activate(final Map<String, String> config) {
        log.info("LogServiceListenerImpl - ACTIVATED");
    }

    @Deactivate
    protected void deactivate(Map<String, String> config) {
        log.info("LogServiceListenerImpl - DEACTIVATED");
    }

    private void bindLogService(LogService logService, Map<Object, Object> props) {
        serviceMap.put(logService.getClass().toString(), logService);
        log.debug("Service bound - {}", logService.getClass().toString());
        log.debug("Total number of services in registry - {}", serviceMap.size());
    }

    private void unbindLogService(LogService logService, Map<Object, Object> props) {
        if (serviceMap.containsKey(logService.getClass().toString())) {
            serviceMap.remove(logService.getClass().toString());
            log.debug("Service unbound - {}", logService.getClass().toString());
            log.debug("Total number of services in registry - {}", serviceMap.size());
        }
    }
}

Three interesting snippets that make this work – @Reference annotation, the  bind methods and the map which maintains the list of bound services.

  • @Reference(referenceInterface = LogService.class) instructs the container to inject all OSGi services implementing this LogService interface.
  • @Reference(policy = ReferencePolicy.DYNAMIC) instructs the container to keep the listener service available as the log services come and go.
  • @Reference(cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE) instructs the container that the log service is optional and multiple for this interface. So, even if there is no log service, log listener will be active.
  • @Reference(name = LogServiceListenerImpl.METHOD_NAME_TO_BIND) instructs the container to invoke the bindMETHOD_NAME_TO_BIND method when a log service is available and invoke  unbindMETHOD_NAME_TO_BIND method when a log service becomes unavilable.
  • Once a service implementation has been bound, it’s been put into the map which acts as a registry. The service listener could use this registry to access these service implementations and implement business logic using them.
Service Listener – OSGi pattern in AEM