Nuxeo Server

Events and Listeners

Updated: September 22, 2017 Page Information Edit on GitHub

When you need to integrate some features of an external application into the Nuxeo Platform, or want the Platform to push data into an external application, using the Nuxeo event system is usually a good solution.

The system allows to contribute event listeners (Java classes) or event handlers (defined in Studio) that subscribe to Platform events such as user login/logout, document creation, document update, document move,  workflow started, etc. Those Java classes or automation chains are then executed either synchronously in the transaction, or synchronously out of the transaction, or asynchronously.

The event bus system is a nice way to implement some triggers in the document repository, and to let the repository notify other elements of your IT system of the changes that happen in the repository.

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.event.Event 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.event.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 Dublin Core 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.

Registering an Event Listener

Check Nuxeo CLI to bootstrap your Event Listener.

Event listeners can be registered using extension points. Here are example event listeners registrations from the Nuxeo Platform:

  <extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener">
    <listener name="dclistener" async="false" postCommit="false" priority="120"
        class="org.nuxeo.ecm.platform.dublincore.listener.DublinCoreListener">
      <event>documentCreated</event>
      <event>beforeDocumentModification</event>
      <event>documentPublished</event>
      <event>lifecycle_transition_event</event>
      <event>documentCreatedByCopy</event>
    </listener>
  </extension>
  <extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener">
    <listener name="annotationsVersionEventListener" async="true" postCommit="true"
        class="org.nuxeo.ecm.platform.annotations.repository.service.VersionEventListener">
      <event>documentCheckedIn</event>
      <event>documentRemoved</event>
      <event>versionRemoved</event>
      <event>documentRestored</event>
    </listener>
  </extension>

When defining an event listener, you should specify:

  • a name, useful to identify the listener and let other components disable/override it,
  • whether the listener is synchronous or asynchronous (default is synchronous),
  • whether the listener runs post-commit or not (default is false),
  • an optional priority among similar listeners,
  • the implementation class, that must implement org.nuxeo.ecm.core.event.EventListener for synchronous inline listeners or org.nuxeo.ecm.core.event.PostCommitEventListener for others (post-commit or asynchronous listeners),
  • an optional list of event ids for which this listener must be called (it is strongly recommended to explicitly list the relevant events, for performance reasons).

There are several kinds of listeners:

  • synchronous inline listeners are run immediately in the same transaction and same thread, this is useful for listeners that must modify the state of the application like the beforeDocumentModification event.
  • synchronous post-commit listeners are run after the transaction has committed, in a new transaction but in the same thread, this is useful for logging.
  • asynchronous listeners are run after the transaction has committed, in a new transaction and a separate thread, this is useful for any long-running operations whose result doesn't have to be seen immediately in the user interface.

Processing an Event Using an Event Listener

Here is a simple event listener:

import org.nuxeo.ecm.core.event.Event;
import org.nuxeo.ecm.core.event.EventContext;
import org.nuxeo.ecm.core.event.EventListener;
import org.nuxeo.ecm.core.event.impl.DocumentEventContext;

public class BookEventListener implements EventListener {

    public void handleEvent(Event event) {
        EventContext ctx = event.getContext();
        if (!(ctx instanceof DocumentEventContext)) {
            return;
        }
        DocumentModel doc = ((DocumentEventContext) ctx).getSourceDocument();
        if (doc == null) {
            return;
        }
        String type = doc.getType();
        if ("Book".equals(type)) {
            process(doc);
        }
    }

    public void process(DocumentModel doc) {
        ...
    }
}

Note that if a listener expects to modify the document upon save or creation, it must use events emptyDocumentModelCreated or beforeDocumentModification, and not save the document, as these events are themselves fired during the document save process.

If a listener expects to observe a document after it has been saved to do things on other documents, it can use events documentCreated or documentModified.

Sending an Event

It is not as common as having new listeners, but sometimes it's useful to send new events. To do this you have to create an event bundle containing the event, then send it to the event producer service:

EventProducer eventProducer eventProducer = Framework.getService(EventProducer.class);
DocumentEventContext ctx = new DocumentEventContext(session, session.getPrincipal(), doc);
ctx.setProperty("myprop", "something");
Event event = ctx.newEvent("myeventid");
eventProducer.fireEvent(event);

You can also have events be sent automatically at regular intervals using the Scheduling Periodic Events, see that section for more information.

Handling Errors

Sometimes, you may want to handle errors that occurred in an inline listener in the UI layer. This is a little bit tricky but do-able.

In the listener, you should register the needed information in a place that is shared with the UI layer. You can use the document context map for this.

...
   DocumentEventContext ctx = (DocumentEventContext) event.getContext();
   DocumentModel doc = ctx.getSourceDocument();
   ScopedMap data = doc.getContextData();
   data.putScopedValue(MY_ERROR_KEY, "some info");
...

Another thing is to ensure that the current transaction will be rolled back. Marking the event as rollback makes the event service throw a runtime exception when returning from the listener.

...
  TransactionHelper.setTransactionRollbackOnly();
  event.markRollBack();
  throw new NuxeoException("rolling back");
...

Then the error handling in the UI layer can be managed like this

...
  DocumentModel doc = ...;
  try {
      // doc related operations
  } catch (NuxeoException e) {
      Serializable info = doc.getContextData(MY_ERROR_KEY);
      if (info == null) {
          throw e;
      }
      // handle code
  }
...

Asynchronous vs Synchronous Listeners

Asynchronous listeners will run as Work instances in a separated WorkManager thread. This means the main transaction, the one that raised the event, won't be blocked by the listener processing.

Therefore if the processing done by the listener may be long, this is a good candidate for asynchronous processing.

However, there are some impacts in moving a synchronous listener to an asynchronous one:

  • the listener signature needs to change
    • rather than receiving a single event, it will receive an EventBundle that contains all events inside the transaction
  • because the listener runs in a separated transaction it can not rollback the source transaction (it is too late anyway)
  • the listener code needs to be aware that it may have to deal with new cases
    • typically, the listener may receive an event about a document that has since then been deleted

Forwarding Events to an External Event Bus

The beginning of an integration with RabbitMQ is available in the nuxeo-sandbox GitHub repository. While it would require more implementation work for being used in a production environment, it provides a good sample of how to integrate with an external event bus.

Performances and Monitoring 

Using listeners, especially synchronous ones, may impact the global performance. Typically, having synchronous listeners that do long processing will reduce the scalability of the system.

In that kind of situation, using an asynchronous listener is the recommended approach:

  • The interactive transaction is no longer tied to listener execution.
  • The WorkManager allows to configure how many asynchronous listeners can run in concurrency.

To monitor execution of the listeners, you can use the Admin Center / Monitoring / Nuxeo Event Bus to

  • activate the tracking of listeners execution,
  • see how much time is spent in each listener.

For diagnostic and testing purpose, you can use the EventAdminService to activate / deactivate listeners one by one.

The EventServiceAdmin is accessible:

 


3 days ago manonlumeau NXDOC-1323: Update BDE doc
a year ago Arnaud Kervern 29
a year ago Arnaud Kervern 28
2 years ago Solen Guitter 27 | Update related pages
2 years ago Alain Escaffre 26
2 years ago Ronan Daniellou 25 | Workmanager allow + s
2 years ago Ronan Daniellou 24 | tried -> tied
2 years ago Ronan Daniellou 23 | code need -> needs
2 years ago Ronan Daniellou 22 | plural
3 years ago Solen Guitter 21 | Merging Event Bus page with Events and Listeners page
4 years ago Thibaud Arguillere 20
4 years ago Solen Guitter 19 | Moved Common Events section to new dedicated page
4 years ago Solen Guitter 18
4 years ago Solen Guitter 17 | Removed related topics from TOC
4 years ago Solen Guitter 16
4 years ago Solen Guitter 15 | Added related pages
4 years ago Solen Guitter 14
4 years ago Solen Guitter 13
4 years ago Thierry Delprat 12
6 years ago Florent Guillaume 10
6 years ago Florent Guillaume 11 | Migrated to Confluence 4.0
6 years ago Stéphane Lacoin 9
7 years ago Solen Guitter 8 | reference lifecycle_transition_event
7 years ago Solen Guitter 7 | reference lifecycle_transition_event
7 years ago Alain Escaffre 6 | reference lifecycle_transition_event
7 years ago Florent Guillaume 5
7 years ago Florent Guillaume 4
7 years ago Florent Guillaume 3
7 years ago Florent Guillaume 2
8 years ago Admin name placeholder 1
History: Created by Admin name placeholder