12 Observation

A repository may support observation, which enables an application to receive notification of persistent changes to a workspace. JCR defines a general event model and specific APIs for asynchronous and journaled observation. A repository may support asynchronous observation, journaled observation or both.

Whether an implementation supports asynchronous or journaled observation can be determined by querying the repository descriptor table with the keys

Repository.OPTION_OBSERVATION_SUPPORTED or

Repository.OPTION_JOURNALED_OBSERVATION_SUPPORTED.

A return value of true indicates support (see §24.2 Repository Descriptors).

12.1 Event Model

A persisted change to a workspace is represented by a set of one or more events. Each event reports a single simple change to the structure of the persistent workspace in terms of an item added, changed, moved or removed. The six standard event types are:

NODE_ADDED,
NODE_MOVED,
NODE_REMOVED,
PROPERTY_ADDED,
PROPERTY_REMOVED and
PROPERTY_CHANGED.

A seventh event type,

PERSIST,

may also appear in certain circumstances (see §12.7.3 Event Bundling in Journaled Observation).

12.2 Scope of Event Reporting

The scope of event reporting is implementation-dependent. An implementation should make a best-effort attempt to report all events, but may exclude events if reporting them would be impractical given implementation or resource limitations. For example, on an import, move or remove of a subgraph containing a large number of items, an implementation may choose to report only events associated with the root node of the affected graph and not those for every subitem in the structure.

12.2.1 Externally Caused Events

Some implementations may expose capabilities through the JCR API while also being writable through a mechanism external to JCR. Whether events are generated for changes made through such external means is left up to the implementation.

12.3 The Event Object

Each event generated by the repository is represented by an Event object.

12.3.1 Event Types

The type of an Event is retrieved through

int Event.getType()

which returns one of the int constants found in the Event interface: NODE_ADDED, NODE_MOVED, NODE_REMOVED, PROPERTY_ADDED, PROPERTY_REMOVED, PROPERTY_CHANGED or PERSIST.

12.3.2 Event Information

Each Event is associated with a path, an identifier and an information map, the interpretation of which depend upon the event type.

The event path is retrieved through

String Event.getPath(),

the identifier through

String Event.getIdentifier()

and the information map through

java.util.Map Event.getInfo()

If the event is a NODE_ADDED or NODE_REMOVED then,

If the event is NODE_MOVED then,

If the event is a PROPERTY_ADDED, PROPERTY_CHANGED or PROPERTY_REMOVED then,

If the event is a PERSIST (see §12.6.3 Event Bundling in Journaled Observation) then Event.getPath() and Event.getIdentifier() return null and Event.getInfo() returns an empty Map.

12.3.3 Event Information on Move and Order

On a NODE_MOVED event, the Map object returned by Event.getInfo() contains parameter information from the method that caused the event. There are three JCR methods that cause this event type: Session.move, Workspace.move and Node.orderBefore.

If the method that caused the NODE_MOVE event was a Session.move or Workspace.move then the returned Map has keys srcAbsPath and destAbsPath with values corresponding to the parameters passed to the move method, as specified in the Javadoc.

If the method that caused the NODE_MOVE event was a Node.orderBefore then the returned Map has keys srcChildRelPath and destChildRelPath with values corresponding to the parameters passed to the orderBefore method, as specified in the Javadoc.

12.3.3.1 Externally Caused NODE_MOVED Event

In a repository that reports events caused by mechanisms external to JCR (see §12.2.1 Externally Caused Events), the keys and values found in the information map returned on a NODE_MOVED are implementation-dependent.

12.3.4 User ID

An Event also records the identity of the Session that caused it.

String Event.getUserID()

returns the user ID of the Session, which is the same value that is returned by Session.getUserID() (see §4.4.1 User).

12.3.5 User Data

An Event may also contain arbitrary string data specific to the session that caused the event. A session may set its current user data using

void ObservationManager.setUserData(Sting userData).

Typically a session will set this value in order to provide information about its current state or activity. Any events produced by the session while its user data is set to particular value will carry that value with them. A process responding to these events will then be able to access this information through

String Event.getUserData()

and use the retrieved data to provide additional context for the event, beyond that provided by the identify of the causing session alone.

12.3.6 Event Date

An event also records the time of the change that caused it. This acquired through

long Event.getDate()

The date is represented as a millisecond value that is an offset from the epoch January 1, 1970 00:00:00.000 GMT (Gregorian). The granularity of the returned value is implementation-dependent.

12.4 Event Bundling

A repository that supports observation may support event bundling under asynchronous observation, journaled observation, or both.

In such a repository, events are produced in bundles where each corresponds to a single atomic change to a persistent workspace and contains only events caused by that change (see §10.1 Types of Write Methods).

For example, given a session with a set of pending node and property additions, on persist, a NODE_ADDED or PROPERTY_ADDED is produced, as appropriate, for each new item. This set of events is the event bundle associated with that particular persist operation. By grouping events together in this manner, additional contextual information is provided, simplifying the interpretation of the event stream.

12.4.1 Event Ordering

In both asynchronous and journaled observation the order of events within a bundle and the order of event bundles is not guaranteed to correspond to the order of the operations that produced them.

12.5 Asynchronous Observation

Asynchronous observation enables an application to respond to changes made in a workspace as they occur.

An application connects with the asynchronous observation mechanism by registering an event listener with the workspace. Listeners apply per workspace, not repository-wide; they only receive events for the workspace in which they are registered. An event listener is an application-specific class implementing the EventListener interface that responds to the stream of events to which it has been subscribed.

This observation mechanism is asynchronous in that the operation that causes an event to be dispatched does not wait for a response to the event from the listener; execution continues normally on the thread that performed the operation.

12.5.1 Observation Manager

Registration of event listeners is done through the ObservationManager object acquired from the Workspace through

ObservationManager Workspace.getObservationManager().

12.5.2 Adding an Event Listener

An event listener is added to a workspace with

void ObservationManager.
addEventListener(EventListener listener,
int eventTypes,
String absPath,
boolean isDeep,
String[] uuid,
String[] nodeTypeName,
boolean noLocal)

The EventListener object passed is provided by the application. As defined by the EventListener interface, this class must provide an implementation of the onEvent method:

void EventListener.onEvent(EventIterator events)

When an event occurs that falls within the scope of the listener (see 12.6.3 Event Filtering), the repository calls the onEvent method invoking the application-specific logic that processes the event.

12.5.3 Event Filtering

Which events a listener receives are determined as follows.

12.5.3.1 Access Privileges

An event listener will only receive events for which its Session (the Session associated with the ObservationManager through which the listener was added) has sufficient access privileges.

12.5.3.2 Event Types

An event listener will only receive events of the types specified by the eventTypes parameter of the addEventListener method. The eventTypes parameter is an int composed of the bitwise AND of the desired event type constants.

12.5.3.3 Local and Nonlocal

If the noLocal parameter is true, then events generated by the Session through which the listener was registered are ignored.

12.5.3.4 Node Characteristics

Node characteristic restrictions on an event are stated in terms of the associated parent node of the event. The associated parent node of an event is the parent node of the item at (or formerly at) the path returned by Event.getPath().

12.5.3.4.1 Location

If isDeep is false, only events whose associated parent node is at absPath will be received.

If isDeep is true, only events whose associated parent node is at or below absPath will be received.

It is permissible to register a listener for a path where no node currently exists.

12.5.3.4.2 Identifier

Only events whose associated parent node has one of the identifiers in the uuid String array will be received. If this parameter is null then no identifier-related restriction is placed on events received. Note that specifying an empty array instead of null results in no nodes being listened to. The uuid is used for backwards compatibility with JCR 1.0.

12.5.3.4.3 Node Type

Only events whose associated parent node is of one of the node types in the nodeTypeNames String array will be received. If this parameter is null then no node type-related restriction is placed on events received. Note that specifying an empty array instead of null results in no nodes being listened to.

12.5.4 Re-registration of Event Listeners

The filters of an already-registered EventListener can be changed at runtime by re-registering the same EventListener Java object with a new set of filter arguments. The implementation must ensure that no events are lost during the changeover.

12.5.5 Implementation-Specific Restrictions

In addition to the filters placed on a listener though the addEventListener method, the scope of observation support, in terms of which subgraphs are observable, may also be subject to implementation-specific restrictions. For example, in some repositories observation of changes in the jcr:system subgraph may not be supported (see 3.11 System Node).

12.5.6 Event Iterator

In asynchronous observation the EventIterator holds an event bundle or a single event, if bundles are not supported. EventIterator inherits the methods of RangeIterator and adds an Event-specific next method:

Event EventIterator.nextEvent()

(see §5.9 Iterators)

12.5.7 Listing Event Listeners

EventListenerIterator ObservationManager.
getRegisteredEventListeners()

12.5.7.1 EventListenerIterator

Methods that return a set of EventListener objects (such as ObservationManager.getRegisteredEventListeners) do so using an EventListenerIterator. The EventListenerIterator class inherits the methods of RangeIterator and adds an EventListener-specific next method:

EventListener EventListenerIterator.nextEventListener()

(see §5.9 Iterators)

12.5.8 Removing Event Listeners

void ObservationManager.
removeEventListener(EventListener listener)

12.5.9 User Data

void ObservationManager.setUserData(String userData)

12.6 Journaled Observation

Journaled observation allows an application to periodically connect to the repository and receive a report of changes that have occurred since some specified point in the past (for example, since the last connection). Whether a repository records a per-workspace event journal is up to the implementation's configuration.

12.6.1 Event Journal

The EventJournal of a workspace instance is acquired by calling either

EventJournal ObservationManager.getEventJournal()

or

EventJournal getEventJournal(int eventTypes,
String absPath,
boolean isDeep,
String[] uuid,
String[] nodeTypeName,
boolean noLocal)
.

Events reported by this EventJournal instance will be filtered according to the current session's access rights, any additional restrictions specified through implementation-specific configuration and, in the case of the second signature, by the parameters of the method. These parameters are interpreted in the same way as in the method addEventListener.

An EventJournal is an extension of EventIterator that provides the additional method skipTo(Calendar date).

void EventJournal.skipTo(Calendar date)

12.6.2 Journaling Configuration

An implementation is free to limit the scope of journaling both in terms of coverage (that is, which parts of a workspace may be observed and which events are reported) and in terms of time and storage space. For example, a repository can limit the size of a journal log by stopping recording after it has reached a certain size, or by recording only the tail of the log (deleting the earliest event when a new one arrives). Any such mechanisms are assumed to be within the scope of implementation configuration.

12.6.3 Event Bundling in Journaled Observation

In journaled observation dispatching is done by the implementation writing to the event journal.

If event bundling is supported a PERSIST event is dispatched when a persistent change is made to workspace bracketing the set of events associated with that change. This exposes event bundle boundaries in the event journal.

Note that a PERSIST event will never appear within an EventIterator since, in asynchronous observation, the iterator itself serves to define the event bundle.

In repositories that do not support event bundling, PERSIST events do not appear in the event journal.

12.7 Importing Content

Whether events are generated for each node and property addition that occurs when content is imported into a workspace (see §11 Import) is left up to the implementation.

12.8 Exceptions

The method EventListener.onEvent does not specify a throws clause. This does not prevent a listener from throwing a RuntimeException, although any listener that does should be considered to be in error.