Table of Contents
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.
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
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");
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.
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.
EventBundle represent the stack of events that have occured in the same transaction.
And end of transaction, the bundle will be fired.
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
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.
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
.
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.
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
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.
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
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