Event Listeners and Scheduling

Table of Contents

9.1. Introduction
9.2. Concepts
9.3. Adding an event listener
9.4. Upgrading an event listener
9.5. Adding an event
9.6. From CoreEvents to JMS Messages
9.7. Adding a JMS message listener
9.8. Scheduling

9.1. Introduction

Events and event listeners have been introduced at the Nuxeo core level to allow pluggable behaviours when managing documents (or any kinds of objects of the site).

Whenever an event happens (document creation, document modification, relation creation, etc...), an event is sent to the event service that dispatches the notification to its listeners. Listeners can perform whatever action when receiving an event.

9.2. Concepts

A core event has a source which is usually the document model currently being manipulated. It can also store the event identifier, that gives information about the kind of event that is happening, as well as the principal connected when performing the operation, an attached comment, the event category, etc..

Events sent to the event service have to follow the org.nuxeo.ecm.core.api.event.CoreEvent interface.

A core event listener has a name, an order, and may have a set of event identifiers it is supposed to react to. Its definition also contains the operations it has to execute when receiving an interesting event.

Event listeners have to follow the org.nuxeo.ecm.core.listener.EventListener interface.

Several event listeners exist by default in the nuxeo platform, for instance:

  • DublincoreListener: it listens to document creation/modification events and sets some dublincore metadata accordingly (date of creation, date of last modification, document contributors...)

  • DocUidGeneratorListener: it listens to document creation events and adds an identifier to the document if an uid pattern has been defined for this document type.

  • DocVersioningListener: it listens to document versioning change events and changes the document version numbers accordingly.

9.3. Adding an event listener

Event listeners can be plugged using extension points. Here are some examples of event listeners registration.

<?xml version="1.0"?>
<component name="DublinCoreStorageService" version="1.0.0">
  <extension target="org.nuxeo.ecm.core.listener.CoreEventListenerService"
     point="listener">
    <listener name="dclistener"
      class="org.nuxeo.ecm.platform.dublincore.listener.DublinCoreListener"
      order="120" />
  </extension>
</component>

Example 9.1. DublincoreListener registration sample


<?xml version="1.0"?>
<component name="org.nuxeo.ecm.platform.uidgen.service.UIDGeneratorService">
  <extension target="org.nuxeo.ecm.core.listener.CoreEventListenerService"
      point="listener">
    <listener name="uidlistener"
        class="org.nuxeo.ecm.platform.uidgen.corelistener.DocUIDGeneratorListener"
        order="10">
      <event>documentCreated</event>
    </listener>
  </extension>
</component>

Example 9.2. UIDGenerator listener registration sample with event filtering


The only thing needed to add an event listener is to declare its name and its class. Sometimes the order in which listeners are called matters so an integer order can be set to control it. A filtering on event ids can be done when registering it too, though the notification method could handle it too.

For instance, the UIDgenerator service will only be notified when the event service receives a document creation event.

9.4. Upgrading an event listener

Since release of Nuxeo EP version 5.0 M3, events involving documents send the document model as source of the event. They used to send the document itself, which was wrong and has been changed in a compatible way.

Old school event listeners should still work ok for now, but should be migrated soon as the compatibility may introduce bugs and will be removed shortly.

To migrate your event listener, make it implement the empty interface org.nuxeo.ecm.core.listener.DocumentModelEventListener, and make it deal with a DocumentModel instead of a Document as event source.

If your event listener does not care about the source, or the event it deals with is not a document, you do not have to do anything.

9.5. Adding an event

To add an event, you have to create it and then notify listeners passing the even to the listener service. Here is a sample code on how to do it:

CoreEvent coreEvent = new CoreEventImpl(eventId, source, options,
       getPrincipal(), category, comment);

CoreEventListenerService service = NXCore.getCoreEventListenerService();
 
if (service != null) {
    service.notifyEventListeners(coreEvent);
} else {
    throw new ClientException("Can't get Event Listener Service");
}

9.6. From CoreEvents to JMS Messages

Events that are fired at the core level are forwarded to a JMS topic called NXPMessage.

This forwarding is done by a dedicated CoreEventListener (called JMSEventListener contributed by the nuxeo-platform-events-core bundle).

In order to be sure that when an JMS event is received the associated DocumentModel is available, all document oriented messages that may occur at core level are forwarded to the JMS topic when the session repository is saved (ie: when data is committed).

In some cases, depending on own the Core API is used, some messages can be duplicated within the same transaction (like modifying several times the same document), the JMSEventListener marks all duplicated messages before sending them to JMS, its JMS messages receiver to choose to process or not the duplicated messages.

During the forwarding on the JMS Topic, the coreEvents are converted to EventMessage. The main difference is that the EventMessage does not contains the DocumentData (ie: all schemas and fields are unloaded), this is done in order to avoid overloading JMS.

9.7. Adding a JMS message listener

The simplest way to add a JMS message listener is simply to define a Message Driven Bean that is bound to the NXPMessage Topic.

Here is a simple example a the definition of such a MDB :

@MessageDriven(activationConfig = {
  @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
  @ActivationConfigProperty(propertyName = "destination", propertyValue = "topic/NXPMessages"),
  @ActivationConfigProperty(propertyName = "providerAdapterJNDI", 
    propertyValue = "java:/NXCoreEventsProvider"),
  @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge") })
@TransactionManagement(TransactionManagementType.CONTAINER)
public class NXAuditMessageListener implements MessageListener {

    private static final Log log = LogFactory.getLog(NXAuditMessageListener.class);

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void onMessage(Message message) {
        try {

            final Serializable obj = ((ObjectMessage) message).getObject();
            if (!(obj instanceof DocumentMessage)) {
                log.debug("Not a DocumentMessage instance embedded ignoring.");
                return;
            }

            DocumentMessage docMessage = (DocumentMessage) obj;

            String eventId = docMessage.getEventId();
            log.debug("Received a message with eventId: " + eventId);
...

The DocumentMessage is a subclass of the DocumentModel.

An important point to remember is that the MDB is executed asynchronously in a dedicated thread:

  • there is no JAAS Session established: you can not access the repository without this

  • the DocumentMessage is not bound to an existing CoreSession: you can not use the DocumenMessage to do lazy loading (ie: DocumentMessage.getProperty())

So, in order to extract some document oriented properties of the document associated to the event, you must:

  • Establish a JAAS Session

  • get a connected DocumentModel using the DocumentRef provided by the DocumentMessage

Here is a code sample for this:

LoginContext lc;
CoreSession session;
String repositoryName = docMessage.getRepositoryName();
try {
    log.debug("trying to connect to ECM platform");
    lc = Framework.login();
    session = Framework.getService(RepositoryManager.class).getRepository(repositoryName).open();
    DocumentModel connectedDoc = session.getDocument(docMessage.getRef());
...
} finally {
   if (session != null)
      CoreInstance.getInstance().close(session.getSessionId())
   if (lc != null)
      lc.logout();
}

9.8. Scheduling

XXX TODO: FG