Created

Apr 18, 2012

Event Handling in CQ

Posted by Nicolas Peltier

Event handling can be done in a lot of different 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 handlers and jobs
- At the CQ level with workflows & launchers
- Particular case of scheduled events
- Particular case of POST to the repository

JCR Observer

JCR Observer is the lowest-level event handling in CQ. As its name indicates it, is at JCR level and allows to listen to JCR-level events, gathered in sets (corresponding to persistence transactions). javax.jcr.observation.Event lists following types:

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

In order to listen to such an event, you have to:

·       Manage a live session through which you will listen to the repository
·       Decide how and when you listen to it.
·       Implement the handler that receive all JCR events filtered by your definition.

A typical 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);
  }
 }
}


Cautions:

·       In this example we don’t care about filtering results, but in real life you’d surely do.

·       In lot of cases using JCR Observer is not cluster aware: If the same code is on every cluster node, every change to the cluster farm will trigger the treatment N times.

·       Please double-check the performances of your event handling in case it’s called –very – often.

 

Conclusions

The low level aspect of the JCR observer allows the developer to do more or less whatever he wants, but can bring lot of additional development just for performances, usability and portability. So please use it only when needed.
 

Event Handler & Jobs

Sling offers within CQ an application level model for handling events, which is called “Sling eventing support”. You can find its deployment in usage in Felix console through web console plugins, bundles and components. The events processed will be those sent by applications, and they will bring application related information. An event must have a topic, that will constitute the “queue” for those events, to which listeners can register.

Here is a simple example:

Application 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 asynchronous sending, you can synchronously send your event by using sendEvent API (with the performance risks it can imply).

Handler

This handler is listening to the kind of event above, plus Replication 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 situation where your application wants the guarantee of having its event processed. This is the job use case, in which you will use JobUtil and JobProcessor.

Note that often your listener can be also a JobProcessor, like following :

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

CQ topics you can listen to :

Synchronous:

·       ForumEvent.Type.TopicAdded

·       ForumEvent.Type.PostAdded

Asynchronous:

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

·       Replication event with ReplicationAction.EVENT_TOPIC and ReplicationAction API

·       Workflow event (you should look following section)

You’ll have good statistics, monitoring, and configuration at the felix level about jobs and events.

Conclusions

This framework is definitely a good way to go for handling your event, sometimes, it’s even worth surrounding JCR Observation with Sling Eventing in order to manage it more gracefully then.

Workflows


Last but not the least, you can handle events through workflows, with usage of the workflow launcher. This is plainly documented already, easy and fast to write, modular, cluster aware.

A few cautions to keep in mind though:

·       There is more processing than in previous approaches.

·       Your treatment will be archived and visible among other treatments.

 

Two good reasons to use workflows are :

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

·       if the event handling process should be frequently customized / parameterized by non-admin users.

Scheduling

In case you want to do scheduled treatment, use the sling Scheduler 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 documentation about scheduler.expression format, you have documentation and samples here

Final note : SlingPost processors

Handling a POST write to the repository to do processing at the time the data is written is a common developer task.

Often we see jcr observers handlers while a much simpler approach could be done (in case the POST is actually handled by the SlingPostServlet, which is very often the case): a SlingPostServlet post-processor should be implemented for this.

COMMENTS