Event han­dling can be done in a lot of dif­fer­ent ways within CQ, that all have their cost, their impact, and their benefits.

5 ways of doing so are detailed here :

- At the JCR level with observation

- At the Sling level with event han­dlers and jobs

- At the CQ level with work­flows & launchers

- Par­tic­u­lar case of sched­uled events

- Par­tic­u­lar case of POST to the repository

 

JCR Observer

is the lowest-level event han­dling in CQ. As its name indi­cates it, is at JCR level and allows to lis­ten to JCR-level events, gath­ered in sets (cor­re­spond­ing to per­sis­tence trans­ac­tions). javax.jcr.observation.Event lists fol­low­ing types:

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

In order to lis­ten to such an event, you have to:

·       Man­age a live ses­sion through which you will lis­ten to the repos­i­tory
·       Decide how and when you lis­ten to it.
·       Imple­ment the han­dler that receive all JCR events fil­tered by your definition.

A typ­i­cal usage is

@Component
public class ExampleObservation implements EventListener {
 Logger log = LoggerFactory.getLogger(this.getClass());
 private Session adminSession;
 @Reference
 SlingRepository repository;
 @Activate
 public void activate(ComponentContext context) throws Exception {
 log.info("activating ExampleObservation");
 try {
   adminSession = repository.loginAdministrative(null);
   adminSession.getWorkspace().getObservationManager().addEventListener(
      this, //handler
      Event.PROPERTY_ADDED|Event.NODE_ADDED, //binary combination of event types
      "/apps/example", //path
      true, //is Deep?
      null, //uuids filter
      null, //nodetypes filter
      false);
  } catch (RepositoryException e){
  log.error("unable to register session",e);
  throw new Exception(e);
 }
}
@Deactivate
public void deactivate(){
 if (adminSession != null){
  adminSession.logout();
 }
}
 
public void onEvent(EventIterator eventIterator) {
  try {
    while (eventIterator.hasNext()){
      log.info("something has been added : {}", eventIterator.nextEvent().getPath());
    }
   } catch(RepositoryException e){
   log.error("Error while treating events",e);
  }
 }
}

Cau­tions:

·       In this exam­ple we don’t care about fil­ter­ing results, but in real life you’d surely do.

·       In lot of cases using JCR Observer is not clus­ter aware: If the same code is on every clus­ter node, every change to the clus­ter farm will trig­ger the treat­ment N times.

·       Please double-check the per­for­mances of your event han­dling in case it’s called –very – often.

Con­clu­sions

The low level aspect of the JCR observer allows the devel­oper to do more or less what­ever he wants, but can bring lot of addi­tional devel­op­ment just for per­for­mances, usabil­ity and porta­bil­ity. So please use it only when needed.

Sling offers within CQ an appli­ca­tion level model for han­dling events, which is called “Sling event­ing sup­port”. You can find its deploy­ment in usage in Felix con­sole through web con­sole plu­g­ins, bun­dles and com­po­nents. The events processed will be those sent by appli­ca­tions, and they will bring appli­ca­tion related infor­ma­tion. An event must have a topic, that will con­sti­tute the “queue” for those events, to which lis­ten­ers can register.

Here is a sim­ple example:

Appli­ca­tion level event sending

@Reference
EventAdmin eventAdmin;
public void createExample(…) throws RepositoryException {
 ...
 final Dictionary<String,Object> properties = new Hashtable();
 properties.put(Example.PN_NAME, name);
 properties.put(Example.PN_DESC,description);
 Event event = new Event(Example.EVENT_TOPIC, properties);
 eventAdmin.postEvent(event);

Note here that eventAdmin.postEvent is the asyn­chro­nous send­ing, you can syn­chro­nously send your event by using sendE­vent API (with the per­for­mance risks it can imply).

Han­dler

This han­dler is lis­ten­ing to the kind of event above, plus Repli­ca­tion events

@Component
@Service
@Property(name="event.topics",value= {ReplicationAction.EVENT_TOPIC, Example.EVENT_TOPIC})
public class ExampleEventHandler implements EventHandler {
 Logger log = LoggerFactory.getLogger(this.getClass());
 public void handleEvent(Event event) {
  if (event.getTopic().equals(Example.EVENT_TOPIC)){
   log.info("Example {}, with description {} has been created...",event.getProperty(Example.PN_NAME),event.getProperty(Example.PN_DESC));
  } else if (event.getTopic().equals(ReplicationAction.EVENT_TOPIC)){
   ReplicationAction action = ReplicationAction.fromEvent(event);
   log.info("User {} has tried to replicate {}",action.getUserId(),action.getPath());
  }
 }
}

Finally, you can be in the sit­u­a­tion where your appli­ca­tion wants the guar­an­tee of hav­ing its event processed. This is the job use case, in which you will use JobU­til and JobProcessor.

Note that often your lis­tener can be also a JobProces­sor, like following :

public void handleEvent(org.osgi.service.event.Event event) {
 if (EventUtil.isLocal(event)) {
  JobUtil.processJob(event, this);
 }
}

CQ top­ics you can lis­ten to :

Syn­chro­nous:

·       ForumEvent.Type.TopicAdded

·       ForumEvent.Type.PostAdded

Asyn­chro­nous:

·       Page Events (MSM, CRUD, …) with PageEvent.EVENT_TOPIC and PageEvent.fromEvent API

·       Repli­ca­tion event with ReplicationAction.EVENT_TOPIC and Repli­ca­tion­Ac­tion API

·       Work­flow event (you should look fol­low­ing section)

You’ll have good sta­tis­tics, mon­i­tor­ing, and con­fig­u­ra­tion at the felix level about jobs and events.

Con­clu­sions

This frame­work is def­i­nitely a good way to go for han­dling your event, some­times, it’s even worth sur­round­ing JCR Obser­va­tion with Sling Event­ing in order to man­age it more grace­fully then.

Last but not the least, you can han­dle events through work­flows, with usage of the work­flow launcher. This is plainlydoc­u­mented already, easy and fast to write, mod­u­lar, clus­ter aware.

A few cau­tions to keep in mind though:

·       There is more pro­cess­ing than in pre­vi­ous approaches.

·       Your treat­ment will be archived and vis­i­ble among other treatments.

Two good rea­sons to use work­flows are :

·       if of course there’s a human step at one stage of the processing.

·       if the event han­dling process should be fre­quently cus­tomized / para­me­ter­ized by non-admin users.

In case you want to do sched­uled treat­ment, use the sling Sched­uler by doing so:

@Component
@Service
@Properties({
 @Property(name="scheduler.expression" ,value="0 0/10 * * * ?"),
 @Property(name="scheduler.concurrent",boolValue=false)
})
public class ScheduledExample implements Runnable {
 Logger log = LoggerFactory.getLogger(this.getClass());
 @Reference
 ExampleService exampleService;
 public void run() {
  log.info("My example : {}", exampleService.sayHello());
 }
}

For doc­u­men­ta­tion about scheduler.expression for­mat, you have doc­u­men­ta­tion and sam­ples here

Han­dling a POST write to the repos­i­tory to do pro­cess­ing at the time the data is writ­ten is a com­mon devel­oper task.

Often we see jcr observers han­dlers while a much sim­pler approach could be done (in case the POST is actu­ally han­dled by the Sling­Post­Servlet, which is very often the case): a Sling­Post­Servlet post-processor should be imple­mented for this.