Repository managed components
Introduction
Hippo Repository provides the ability to declare and configure so called 'daemon modules' that can be implemented to extend the functionality of the repository. Daemon modules are singleton components whose lifecycle is managed by the repository. Some of them expose services to other parts of the system through the Hippo Service registry while others provide self-contained functionality. The Repository Scheduler is an example of the former, the event log cleanup module is an example of the latter type.
Implementing
At a minimum, repository managed components must implement the DaemonModule interface. This interface exposes the two main lifecycle methods the system will call on the component during system startup and system shutdown respectively:
/** * Lifecycle callback method that is called when the component is started. * * @param session a {@link Session} that can be used throughout this module's * life. * @throws RepositoryException */ public void initialize(Session session) throws RepositoryException; /** * Lifecycle callback method that is called by the repository before shutting * down */ public void shutdown();
Optionally, a daemon module may request to receive configuration by implementing the ConfigurableDaemonModule interface:
/** * Lifecycle callback to allow a {@link DaemonModule} to configure itself. * This method is called on startup iff there is a module config node, * and before {@link #initialize} is called. * * @param moduleConfig the node containing the configuration of this module * @throws javax.jcr.RepositoryException */ void configure(Node moduleConfig) throws RepositoryException;
Configuring
To register a module with the repository add a hipposys:module node to the daemon module registry at /hippo:configuration/hippo:modules. Specify the hipposys:className property as the fully qualified class name of the daemon module implementation. Below this registration node you are able to declare a child node named hippo:moduleconfig that contains the internal configuration of the module implementation itself. You are free to use any node type you wish for this module configuration node: this is the node that will be passed into the component during its configure lifecycle phase.
<sv:node xmlns:sv="http://www.jcp.org/jcr/sv/1.0" sv:name="example-module"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>hipposys:module</sv:value> </sv:property> <sv:property sv:name="hipposys:className" sv:type="String"> <sv:value>org.example.modules.ExampleModule</sv:value> </sv:property> <sv:node sv:name="hippo:moduleconfig"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>nt:unstructured</sv:value> </sv:property> <sv:property sv:name="exampleConfigurationOption" sv:type="String"> <sv:value>exampleConfigurationOptionValue</sv:value> </sv:property> </sv:node> </sv:node>
Service registration
Some modules might expose their functionality as a service to other parts of the system. In that case they will need to leverage the HippoServiceRegistry to export their services to. The following example module captures the idea:
package org.example.modules; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.onehippo.cms7.services.HippoServiceRegistry; import org.onehippo.cms7.services.SingletonService; /** * Implements and exposes an example service over the service registry. */ @ProvidesService(types = ExampleService.class) public class ExampleModule implements DaemonModule { private ExampleService service; @Override public void initialize(final Session session) throws RepositoryException { HippoServiceRegistry.registerService(service = new ExampleService() { @Override public void execute() { doExecute(); } }, ExampleService.class); } private void doExecute() { // implementation } @Override public void shutdown() { HippoServiceRegistry.unregisterService(service, ExampleService.class); } } @SingletonService interface ExampleService { void doExecute(); }
Dependency management
You might have noticed in the above example the use of the @ProvidesService annotation. This annotation lets the system know about any services the module provides. A module may provide and declare to provide multiple service types in this manner. The purpose of this annotation is to allow the automatic resolution of inter-module dependencies: when a dependent module uses the related @RequiresService annotation, the latter module will be started after the former and similarly shutdown before it. Like the @ProvidesService annotation, the @RequiresService annotation exposes a types attribute where you list the service classes the module expects, but it also exposes an optional optional attribute. The latter tells the system whether or not to fail loading the module if a dependency is unsatisfied. If you specify the optional attribute it needs to be an array of booleans that is equal in length to the array of types that is specified in the types attribute.
Reconfiguration
A module's configuration may be changed at any time during its lifetime. It is up to the module itself to decide whether and how to handle such reconfiguration events. One strategy is for instance not to cache the configuration settings obtained from the module configuration properties but to read them anew each time they are needed. Alternatively, module implementations may override AbstractReconfigurableDaemonModule to be informed about reconfiguration events as they occur in the repository. Note that doConfigure is called both on startup and when the configuration changes so it is important to handle both cases in this method.
package org.onehippo.repository.modules; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.onehippo.cms7.services.HippoServiceRegistry; import org.onehippo.cms7.services.SingletonService; /** * Implements and exposes an example service over the service registry. */ @ProvidesService(types = ExampleService.class) public class ExampleReconfigurableModule extends AbstractReconfigurableDaemonModule { private final Object configurationLock = new Object(); private ExampleService service; private String exampleConfigurationOption; @Override protected void doConfigure(final Node moduleConfig) throws RepositoryException { synchronized (configurationLock) { exampleConfigurationOption = moduleConfig.getProperty("exampleConfigurationOption").getString(); } } @Override protected void doInitialize(final Session session) throws RepositoryException { HippoServiceRegistry.registerService(service = new ExampleService() { @Override public void execute() { doExecute(); } }, ExampleService.class); } private void doExecute() { synchronized (configurationLock) { // do it } } @Override protected void doShutdown() { HippoServiceRegistry.unregisterService(service, ExampleService.class); } } @SingletonService interface ExampleService { void doExecute(); }
You need to be aware that any services the module exposes should keep working after reconfiguration. Dependent modules may be holding a reference to the service object you registered and they need to be able to continue to use that instance. The above example handles this correctly and also locks access to the service during reconfiguration.
CMS webapp only Daemon Module
If you have a Daemon Module that registers a service that depends on CMS java classes (or more generall on (a) class(es) that are available in the CMS webapp only), but you deploy CMS and site separately, you have to mark the Daemon Module to only run if the CMS webapp is available. You can do so by adding
hipposys:cmsonly = true
Thus for the example-module above, it would become
<sv:node xmlns:sv="http://www.jcp.org/jcr/sv/1.0" sv:name="example-module"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>hipposys:module</sv:value> </sv:property> <sv:property sv:name="hipposys:className" sv:type="String"> <sv:value>org.example.modules.ExampleModule</sv:value> </sv:property> <sv:property sv:name="hipposys:cmsonly" sv:type="Boolean"> <sv:value>true</sv:value> </sv:property> <sv:node sv:name="hippo:moduleconfig"> <sv:property sv:name="jcr:primaryType" sv:type="Name"> <sv:value>nt:unstructured</sv:value> </sv:property> <sv:property sv:name="exampleConfigurationOption" sv:type="String"> <sv:value>exampleConfigurationOptionValue</sv:value> </sv:property> </sv:node> </sv:node>
if the ExampleModule relies on CMS code.