Local Observation Module

Last modified by Manuel Leduc on 2023/10/10 13:59

Provides the ability to listen to internal XWiki events such as events when document change, when an action is executed, etc.

TODO: Add architecture diagram here

Features

  • Supports any kind of event, including custom events
  • API to listen to a particular event type
  • API to listen to any event type
  • API to register event listeners
    • Automatically as component
    • Programmatically
  • API to unregister event listeners programmatically
  • API to send events to all listeners

Examples

See this Writing an Event Listener Tutorial.

Consuming and producing events

Observe document save events in the wiki, and fire a custom user created event when the saved document is a user document.

The first class is the event listener, the second defines the custom event itself.

An org.xwiki.observation.AbstractEventListener helper is provided.

package com.example;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;

import org.xwiki.bridge.event.DocumentCreatedEvent;
import org.xwiki.bridge.event.DocumentDeletedEvent;
import org.xwiki.bridge.event.DocumentUpdatedEvent;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.observation.EventListener;
import org.xwiki.observation.ObservationManager;
import org.xwiki.observation.event.Event;

import com.xpn.xwiki.doc.XWikiDocument;

import com.example.event.UserCreationEvent;

// Declare that this class is a component
@Component
// The unique name of the component among the implementations of EventListener. It's not mandatory but the common practice is to use the same String for both the name of the listener and the name of the component.
@Named("usercreation")
// An optional annotation allowing, since XWiki 15.4, to indicate the priority in which a listener should be called. The lower the number, the higher the priority (the default value is 1000).
@Priority(1000)
public class UserCreationEventListener extends AbstractEventListener
{
   @Inject
   private ComponentManager componentManager;

   /**
     * The observation manager that will be use to fire user creation events. Note: We can't have the OM as a
     * requirement, since it would create an infinite initialization loop, causing a stack overflow error (this event
     * listener would require an initialized OM and the OM requires a list of initialized event listeners)
     */

   private ObservationManager observationManager;

   public UserCreationEventListener()
   {
       super("usercreation", new DocumentCreatedEvent());
   }

   /**
     * {@inheritDoc}
     */

   public void onEvent(Event event, Object source, Object data)
   {
        XWikiDocument document = (XWikiDocument) source;
        String wikiName = document.getDocumentReference().getWikiReference().getName();
        DocumentReference userClass = new DocumentReference(wikiName, "XWiki", "XWikiUsers");

       if (document.getXObject(userClass) != null) {
           // Create a map to hold our new event data
           Map<String,String> userData = new HashMap<String,String>();
            userData.put("firstName", document.getXObject(userClass).getStringValue("firstName"));
            userData.put("lastName", document.getXObject(userClass).getStringValue("lastName"));
            userData.put("email", document.getXObject(userClass).getStringValue("email"));
           // Fire the user created event
           UserCreatedEvent newEvent = new UserCreationEvent();
            getObservationManager().notify(newEvent, source, userData);
       }
   }

   private ObservationManager getObservationManager()
   {
       if (this.observationManager == null) {
           try {
               this.observationManager = componentManager.getInstance(ObservationManager.class);
           } catch (ComponentLookupException e) {
               throw new RuntimeException("Cound not retrieve an Observation Manager against the component manager");
           }
       }
       return this.observationManager;
   }

}

Definition of the custom event:

package com.example.event;

import org.xwiki.bridge.event.AbstractDocumentEvent;
import org.xwiki.observation.event.Event;
import org.xwiki.observation.event.filter.EventFilter;

/**
 * {@link Event} generated when a new user is created.
 */

public class UserCreationEvent extends AbstractDocumentEvent
{
   /**
     * The version identifier for this Serializable class. Increment only if the <i>serialized</i> form of the class
     * changes.
     */

   private static final long serialVersionUID = 1L;

   /**
     * Constructor initializing the event filter with an
     * {@link org.xwiki.observation.event.filter.AlwaysMatchingEventFilter}, meaning that this event will match any
     * other document update event.
     */

   public UserCreatedEvent()
   {
       super();
   }

   /**
     * Constructor initializing the event filter with a {@link org.xwiki.observation.event.filter.FixedNameEventFilter},
     * meaning that this event will match only update events affecting the document matching the passed document name.
     *
     * @param documentName the name of the updated document to match
     */

   public UserCreatedEvent(String documentName)
   {
       super(documentName);
   }

   /**
     * Constructor using a custom {@link EventFilter}.
     *
     * @param eventFilter the filter to use for matching events
     */

   public UserCreatedEvent(EventFilter eventFilter)
   {
       super(eventFilter);
   }
}

Writing an Event Listener in Groovy in a Wiki page

See the tutorial.

Writing an Event Listener in Velocity in a Wiki page

The documentation of the wiki components contains, as an example, the creation of an Event Listener.

Fold events

An event tagged as "Fold" can be sent by a task that generates some events during its execution. Then, these generated events can be seen as children of the main task. In addition, the Activity Stream will not record these child events.

This is an example of a custom fold event:

package org.xwiki.bridge.event;

import org.xwiki.observation.event.BeginFoldEvent;

public class MyTaskBeginEvent extends AbstractDocumentEvent implements BeginFoldEvent
{
   /**
     * The version identifier for this Serializable class. Increment only if the <i>serialized</i> form of the class
     * changes.
     */

   private static final long serialVersionUID = 1L;

   /**
     * Constructor initializing the event filter with an
     * {@link org.xwiki.observation.event.filter.AlwaysMatchingEventFilter}, meaning that this event will match any
     * other document update event.
     */

   public MyTaskBeginEvent()
   {
       super();
   }

   /**
     * Constructor initializing the event filter with a {@link org.xwiki.observation.event.filter.FixedNameEventFilter},
     * meaning that this event will match only update events affecting the document matching the passed document name.
     *
     * @param documentName the name of the updated document to match
     */

   public MyTaskBeginEvent(String documentName)
   {
       super(documentName);
   }

   /**
     * Constructor using a custom {@link EventFilter}.
     *
     * @param eventFilter the filter to use for matching events
     */

   public MyTaskBeginEvent(EventFilter eventFilter)
   {
       super(eventFilter);
   }
}

Then you need to define the corresponding EndFoldEvent :

package org.xwiki.bridge.event;

import org.xwiki.observation.event.EndFoldEvent;

public class MyTaskEndEvent implements EndFoldEvent
{
   /**
     * The version identifier for this Serializable class. Increment only if the <i>serialized</i> form of the class
     * changes.
     */

   private static final long serialVersionUID = 1L;

   /**
     * Constructor initializing the event filter with an
     * {@link org.xwiki.observation.event.filter.AlwaysMatchingEventFilter}, meaning that this event will match any
     * other document update event.
     */

   public MyTaskEndEvent()
   {
       super();
   }

   /**
     * Constructor initializing the event filter with a {@link org.xwiki.observation.event.filter.FixedNameEventFilter},
     * meaning that this event will match only update events affecting the document matching the passed document name.
     *
     * @param documentName the name of the updated document to match
     */

   public MyTaskEndEvent(String documentName)
   {
       super(documentName);
   }

   /**
     * Constructor using a custom {@link EventFilter}.
     *
     * @param eventFilter the filter to use for matching events
     */

   public MyTaskEndEvent(EventFilter eventFilter)
   {
       super(eventFilter);
   }
}

Get Connected