Repository-Managed Components - BloomReach Experience - Open Source CMS
06-12-2019

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. 

/hippo:configuration/hippo:modules:
  /example-module:
    jcr:primaryType: hipposys:module
    hipposys:className: org.example.modules.ExampleModule
    /hippo:moduleconfig:
      jcr:primaryType: hipposys:moduleconfig
      exampleConfigurationOption: exampleConfigurationOptionValue

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;


/**
 * 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.register(service = new ExampleService() {
            @Override
            public void execute() {
                doExecute();
            }
        }, ExampleService.class);
    }

    private void doExecute() {
        // implementation
    }

    @Override
    public void shutdown() {
        HippoServiceRegistry.unregister(service, ExampleService.class);
    }
}


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;

/**
 * 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);
    }

}

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 general 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

/hippo:configuration/hippo:modules:
  /example-module:
    jcr:primaryType: hipposys:module
    hipposys:className: org.example.modules.ExampleModule
    hipposys:cmsonly: true
    /hippo:moduleconfig:
      jcr:primaryType: hipposys:moduleconfig
      exampleConfigurationOption: exampleConfigurationOptionValue

if the ExampleModule relies on CMS code.

Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?