April 6, 2020
Estimated Post Reading Time ~

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.

Design


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 become available, the OSGi container injects them into the Service listener. The service listener maintains a registry or a list to hold a reference to these services and invokes them based on the business logic implemented in it.

Example – Log service listener.


We have a LogService interface that 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 passes on the log message to all the registered log services. In real life implementations, this should at least 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, the 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 unavailable.
  • 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.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.