Obser­va­tion is a JCR mech­a­nism that allows code to lis­ten for events gen­er­ated dur­ing repos­i­tory oper­a­tions and react to them in var­i­ous ways. Two types of obser­va­tion may be sup­ported by a JCR repos­i­tory: Asyn­chro­nous Obser­va­tion and Jour­naled Obser­va­tion. The for­mer allows an appli­ca­tion to react to events as they occur in real time, the lat­ter to query the repos­i­tory for a log of past events. Not all imple­men­ta­tions of JCR are required to sup­port one or both types of obser­va­tion, but an appli­ca­tion can query whether a repos­i­tory sup­ports using code like the following:

if (repository.getDescriptor(Repository.OPTION_OBSERVATION_SUPPORTED).equals(“true”))

or

if (repository.getDescriptor(Repository.OPTION_JOURNALED_OBSERVATION

_SUPPORTED).equals(“true”))

The six type of events that can be detected are as follows:

  • Node added
  • Node moved
  • Node removed
  • Prop­erty added
  • Prop­erty removed
  • Prop­erty changed

A sev­enth event type, called “Per­sist”, may also appear in cer­tain cir­cum­stances related to Jour­naled Observation.

Notice that, even if obser­va­tion is sup­ported, your appli­ca­tion code is not guar­an­teed to be able to be noti­fied of all events and, even if it could, react­ing to a large num­ber of events might be imprac­ti­cal from a per­for­mance stand­point. As the JCR 2.0 spec­i­fi­ca­tion says:

The scope of event report­ing is implementation-dependent. An imple­men­ta­tion should make a best-effort attempt to report all events, but may exclude events if report­ing them would be imprac­ti­cal given imple­men­ta­tion or resource lim­i­ta­tions. For exam­ple, on an import, move or remove of a sub­graph con­tain­ing a large num­ber of items, an imple­men­ta­tion may choose to report only events asso­ci­ated with the root node of the affected graph and not those for every subitem in the structure.

Reg­is­ter­ing an event listener

An event lis­tener is a class that imple­ments the javax.jcr.observation.EvenListener inter­face. This inter­face declares one method that must be imple­mented by derived classes:

void onEvent(EventIterator it);

Notice that the argu­ment to the onEvent method is an Even­tIt­er­a­tor (a sub­class of RangeIt­er­a­tor that adds an extra Event nex­tEvent() method for con­ve­nience). This is because events are dis­patched in bun­dles of related events, rather than as sin­gle events. A bun­dle cor­re­sponds to a sin­gle atomic change to a per­sis­tent work­space and con­tains only events caused by that change.

Lis­ten­ers apply per work­space, not repository-wide; they only receive events for the work­space in which they are registered.

The imple­men­ta­tion must then be reg­is­tered with the Obser­va­tion­Man­ager by call­ing its addE­ventLis­tener method, whose sig­na­ture is as follows:

void addEventListener(EventListener lis­tener,
int event­Types,
java.lang.String absPath,
boolean isDeep,
java.lang.String[] uuid,
java.lang.String[] node­Type­Name,
boolean noLocal)

Let’s have a look at the argu­ments for this method.

EventLis­tener listener

This is an instance of a class imple­ment­ing the EventLis­tener inter­face, as explained above.

int event­Types

This rep­re­sents the set of events our lis­tener is inter­ested in, as a bit­wise OR of the inte­ger val­ues of the con­stants defined in the Event inter­face for the var­i­ous event types, which are:

Event.NODE_ADDED
Event.NODE_MOVED
Event.NODE_REMOVED
Event.PERSIST
Event.PROPERTY_ADDED
Event.PROPERTY_CHANGED
Event.PROPERTY_REMOVED

java.lang.String absPath

Only events asso­ci­ated with the node whose path is absPath (or its sub­graph if isDeep is true) will trig­ger a notification.

boolean isDeep

If true, include descen­dants of the node pointed to by absPath in those being lis­tened for events.

java.lang.String[] uuid

If not null, only lis­ten for events rel­a­tive to the nodes iden­ti­fied by the UUIDs con­tained in the array. You can pass null if you don’t want to fil­ter by node id, but be care­ful that pass­ing an empty array will fil­ter out all events.

java.lang.String[] node­Type­Name

If not null, only lis­ten for events rel­a­tive to the nodes whose type names are con­tained in the array. You can pass null if you don’t want to fil­ter by node type, but be care­ful that pass­ing an empty array will fil­ter out all events.

boolean noLo­cal

If true, ignore events gen­er­ated by the ses­sion through which the lis­tener was reg­is­tered. Use­ful for avoid­ing infi­nite loops of event lis­ten­ers trig­ger­ing events themselves.

Typ­i­cally, an event lis­tener will be imple­mented as an OSGi com­po­nent that reg­is­ters itself when activated:

pro­tected void activate(ComponentContext con­text) throws Excep­tion {
ses­sion = repository.loginAdministrative(null);
obser­va­tion­Man­ager = session.getWorkspace().getObservationManager();

observationManager.addEventListener(this, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, “/”, true, null,
null, true);
LOGGER.info(“********added JCR event lis­tener”);
}

Unreg­is­ter­ing the event listener

The com­po­nent should take care of unreg­is­ter­ing itself when deac­ti­vated, in order to avoid mem­ory leaks:

pro­tected void deactivate(ComponentContext com­po­nent­Con­text) {
try {
if (obser­va­tion­Man­ager != null) {
observationManager.removeEventListener(this);
LOGGER.info(“********removed JCR event lis­tener”);
}
}
catch (Repos­i­to­ryEx­cep­tion re) {
LOGGER.error(“********error remov­ing the JCR event lis­tener”, re);
}
finally {
if (ses­sion != null) {
session.logout();
ses­sion = null;
}
}
}

Han­dling the event

The fol­low­ing sam­ple illus­trates an event han­dler that reacts to events of type PROPERTY_ADDED and PROPERTY_CHANGED on all nodes, as per the addE­ventLis­tener call above and appends an excla­ma­tion mark to the end of the value of “jcr:title” prop­er­ties. In this case, it is imper­a­tive that the value of the noLo­cal para­me­ter to addE­ventLis­tener be false, oth­er­wise we would be trig­ger­ing another prop­erty change event and would keep adding “!” to the title forever.

pub­lic void onEvent(EventIterator it) {
while (it.hasNext()) {
Event event = it.nextEvent();
try {
LOGGER.info(“********new prop­erty event: {}”, event.getPath());
Prop­erty changed­Prop­erty = session.getProperty(event.getPath());
if (changedProperty.getName().equalsIgnoreCase(“jcr:title”)
&& !changedProperty.getString().endsWith(“!”)) {
changedProperty.setValue(changedProperty.getString() + “!”);
session.save();
}
}
catch (Excep­tion e) {
LOGGER.error(e.getMessage(), e);
}
}
}

Ref­er­ences