Chapter 31. Nuxeo Event Service

Table of Contents

31.1. Nuxeo event model
31.1.1. Event
31.1.2. EventContext
31.1.3. EventListener
31.1.4. Transactions and Events
31.1.5. EventBundle
31.1.6. PostCommitEventListener
31.2. Using Events
31.2.1. Firing Events
31.2.2. Contributing an EventListener
31.2.3. Contributing a PostCommitEventListener
31.3. JMS and Nuxeo Events
31.3.1. JMS integration
31.3.2. Enabling JMS bridge
31.3.3. From 5.1 event model to 5.2

This chapter presents the event model used in Nuxeo 5.2. For more informations about the differences between this model (5.2-M4+) and the former model used in Nuxeo 5.1, please refer to last section.

31.1. Nuxeo event model

31.1.1. Event

When an event occurs in Nuxeo, an org.nuxeo.ecm.core.event.Event object is created and propagated.

Event is a lightweight object that consist of :

  • a event name

  • a timestamp

  • some control flags

    flags are used to give informations about the event nature and how it must be processed and forwarded ()

  • an EventContext

    provides informations on the context assocated to the event when it was fired

31.1.2. EventContext

Each Event if always associated to an EventContext that holds information about the operation that triggered the event.

Default implementation is very abstract to be very generic and reusable in a lot of situations. Basically en EventContext holds :

  • Arguments

    represents arguments of the operation being processed when the event is fired (ordoned list of Serializable Objects)

  • Properties

    associates named properties that can be shared between sources of events and listeners

Because the document repository is one of the main sources of events, a DocumentEventContext implementation is provided. DocumentEventContext hold :

  • The core session used when the event was fired

    (this is the first argument)

  • Principal that was logged in when the event was fired

    (this is the second argument)

  • The source DocumenModel

    (this is the third argument)

  • An optional category

    (this is a named property)

  • An optional comment

    (this is a named property)

EventContext can be used to produce events :

DocumentEventContext ctx = new DocumentEventContext(getCoreSession(), getPrincipal(), sourceDocument);
	Event event = ctx.newEvent("MyEvent");

31.1.3. EventListener

When an Event is fired, the EventService will call all EventListener in a row.

EventListeners are called synchronously and in an ordered way.

EventListeners have direct access to the original EventContext (CoreSession, User identity ...) and have the possibility to alter this context. Typically an EventListener can intercep all document creation events and automaticall set some fields in the DocumenModel (ex: creation date).

EventListeners can be java classes or scripts.

31.1.4. Transactions and Events

Event firing and EventListeners execution always occur in the same transaction (if any) and in the orginal context.

This means :

  • EventListener must be fast

    other wise all transaction may become slow

  • EventListener can rollback the current transaction

    either by throwing an unchecked Exception or by setting the ROLLBACK flag.

Events that occur in the same transaction are stacked in an EventBundle unless they are flagged INLINE.

When transaction commits, the associated EventBundle will be fired and stack will be cleaned. In non transactionnal environment, events with the COMMI flag will play the role of placeholders.

Typically, if no JTA environment is available, all call to CoreSession.save() will fire the event "SAVE" that will do a pseudo-commit on stacked events.

31.1.5. EventBundle

EventBundle represent the stack of events that have occured in the same transaction.

And end of transaction, the bundle will be fired.

31.1.6. PostCommitEventListener

PostCommitListeners are notified when EventBundle are fired.

There are 2 types of PostCommitEventListeners :

  • Synchronous PostCommitEventListener

    Execution occurs after the transaction commits, but before the call has returned to the client.

  • Asynchronous PostCommitEventListener

    Execution occurs after the transaction commits, and after the call has returned to the client.

It is important to understand that in both cases, each PostCommitEventListener is executed in a separated transaction. Each PostCommitEventListener may commit or rollback without affecting other listeners and the main transaction. In the case of asynchronous PostCommitEventListener, the EventContext is not exactly the same as the orginal one, this is a recustructed EventContext :

  • Security context is switched to a System login

    (but informations about the orginal Principal is conserved)

  • The CoreSession that may be associated to EventContext is a System one

    (since orginal user's CoreSession may have bee closed)

  • Associated DocumentModels are "re-fetched" from the CoreSession

31.2. Using Events

31.2.1. Firing Events

Firing an event is very simple :

// get EventProdicer Service
EventProducer evtProducer = Framework.getService(EventProducer.class);

// prepare EventContext properties
Map<String, Serializable> props = new HashMap<String, Serializable>();
props.put("myinfo", "foo-bar");

// create simple EventContext
EventContext ctx = new InlineEventContext(principal, props);

// create the event from the context
Event event = ctx.newEvent(eventId);

// fire the event
evtProducer.fireEvent(event);

This exemple demontrate firing of a simple event that won't be stacked in a bundle (not tied to any transaction).

// Create EventContext bound to a Document related operation
DocumentEventContext ctx = new DocumentEventContext(session,principal, myDoc);
ctx.setCategory("MyEventCategory");
ctx.setComment("MyComment");

// prepare EventContext properties
Map<String, Serializable> props = new HashMap<String, Serializable>();
props.put("myinfo", "foo-bar");
ctx.setProperties(props);

// fire the event
producer.fireEvent(event);

This exemple demontrate firing of a Document related event.

31.2.2. Contributing an EventListener

Event listener are contributed via listener extension point of org.nuxeo.ecm.core.event.EventServiceComponent.

Contributed listener can be :

  • A java class

    must implement the EventListener interface

  • A script

Here is an example of a contributed EventListener is Java :

<extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener">
    <listener name="myListener" 
              async="false" 
              postCommit="false" 
              class="com.myproject.listener.MySyncEventListener" 
              priority="140">
    </listener>
</extension>

The async and postCommit attribute are not necessary since the java class interface already provides this information.

Here is an example of a contributed EventListener is Java :

<extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener">
    <listener name="myScriptListener" 
              async="false" 
              postCommit="false" 
              script="script/listener.groovy"
              priority="145">
    </listener>
</extension>

In this case, the async and postCommit attribute are necessary. The groovy script will receive as input the Event.

31.2.3. Contributing a PostCommitEventListener

PostCommitEventListener are contributed in the same way that EventListener, using the same extension point. The EventService will determine that the listener is a PostCommitEventListener based on :

  • The interface of the contributed java class

    Interface PostCommitEventListener

  • the async and postcommit attribute.

Exactly like for synchronous EventListener, PostCommitEventListener can be a Java class or a script.

On contrary of synchronous EventListeners, PostCommitEventListener will receive an EventBundle (containing all events associated to a transaction), instead of a single Event.

Here is an example of a contributed PostCommitEventListener is Java :

<extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener">
    <listener name="myAsyncListener" 
              async="false" 
              postCommit="true" 
              class="com.myproject.listener.MyAsyncEventListener" 
              priority="140">
    </listener>
</extension>

The postCommit flag is not really useful since the java interface already defines the listener as PostCommit. The async flag is used to define if the PostCommitListener should be processed before or after returning the call to the client.

31.3. JMS and Nuxeo Events

31.3.1. JMS integration

JMS can be used to relay events from one JVM to other JVMs hosting Nuxeo Components. If a multi-server deployment is used, it allow to have an async PostCommitEventListener running on one JVM even if the event was fired on another JVM.

In multi-JVM deployment scenarios, the EventService must be deployed on each JVM.

Separated instances of the EventService will be linked via the JMS bus.

The bridge is done with 2 additionnal bundles :

  • nuxeo-core-event-jms

    PostCommitEventListener that forwards EventBundle to a JMS Topic.

  • nuxeo-platform-event-dispatcher

    MessageDrivenBean that intercepts JMS message, recustructuc the EventBundle and associated context and fire the EventBundle in the local EventService.

In order to avoid any loop or duplicated EventListener execution :

  • nuxeo-core-event-jms never process EventBundle that have already been relayed via JMS

  • nuxeo-platform-event-dispatcher only process EventBundle comming from another JMV

31.3.2. Enabling JMS bridge

In default mono-JVM Nuxeo Bundle, the JMS bridge is not deployed and not activated.

In order to activate the JMS bridge you have to deploy nuxeo-core-event-jms and nuxeo-platform-event-dispatcher.

If you want to force JMS usage in mono-JVM deployment, you can add org.nuxeo.ecm.event.forceJMS=true in nuxeo.properties.

31.3.3. From 5.1 event model to 5.2

31.3.3.1. Main differences

The API for sending an event is now far more simple in the new model:

  • Only one API in 5.2

  • There was 2 APIs in the 5.1

    One for Core Events and one for JMS events

Writing an asynchronous eventListener is also simpler :

  • no need a write a MessageDrivenBean

  • no need to handle Authentication and Repository initialization

  • no need to handle transactions

One of the key benefit of the refactoring, is that JMS is no longer a required dependency : you can have asynchronous listeners without JMS. This means :

  • we can embed more services in a WebEngine/Jetty package

  • you can now easily test your eventListeners in JUnit without needing JMS/MDB setup

31.3.3.2. About compatibility

A bundle nuxeo-core-event-compat is provided. When deployed, this bundle will :

  • to run "old style" CoreEventListeners

  • to enable JMS forwarding on the 5.1 JMS Topic

This means that with this compatibility module, you can run old CoreEventListeners and MessageDrivenBeans.

For sending JMS events using the old API, you will need to deploy nuxeo-platform-events-api