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).
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).
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.
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.
Each event generated by the repository is represented by an Event object.
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.
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,
Event.getPath() returns the absolute path of the node that was added or removed.
Event.getIdentifier() returns the identifier of the node that was added or removed.
Event.getInfo() returns an empty Map object.
If the event is NODE_MOVED then,
Event.getPath() returns the absolute path of the destination of the move.
Event.getIdentifier() returns the identifier of the node that was moved.
Event.getInfo() returns a Map containing parameter information from the method that caused the event (see §12.4.3 Event Information on Move and Order).
If the event is a PROPERTY_ADDED, PROPERTY_CHANGED or PROPERTY_REMOVED then,
Event.getPath() returns the absolute path of the property that was added, changed or removed.
Event.getIdentifier() returns the identifier of the parent node of the property that was added, changed or removed.
Event.getInfo() returns an empty Map object.
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.
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.
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.
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).
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.
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.
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.
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.
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.
Registration of event listeners is done through the ObservationManager object acquired from the Workspace through
ObservationManager Workspace.getObservationManager().
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.
Which events a listener receives are determined as follows.
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.
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.
If the noLocal parameter is true, then events generated by the Session through which the listener was registered are ignored.
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().
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.
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.
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.
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.
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).
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)
EventListenerIterator
ObservationManager.
getRegisteredEventListeners()
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)
void
ObservationManager.
removeEventListener(EventListener
listener)
void ObservationManager.setUserData(String userData)
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.
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)
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.
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.
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.
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.