domingo, 23 de marzo de 2014

Date Range Query Medicine for CQ AEM

One of the most annoying features of xpath queries is the performance problem of date range queries. QueryBuilder tends to perform badly. You can see Marcel's presentation in AdaptTo if you want to know the details. Thus, when faced with this problem, there are several alternatives:
  • modifying the granularity of the dates,so that you select up to the day and exclude hours/min/secs
  • using a node structure in which the date is saved as the path; for example,
    • yyyy/mm/dd/my_content_node
  • or converting the date property into a long 
I am going to focus on the third one: ways to convert a date property into a long property.

Approach
The basic idea is to leverage the JCR Observation offered in AEM to add a property of type long each time a property of type date is created or modified.

First I created my own namespace (thanks this notes) by just going to

http://<host>:<port>/crx/explorer/nodetypes/index.jsp

I selected my own namespace netval so that there will be no conflicts with existing properties. 

Then I registered my class DatePropertyEventListener that implements the EventListener interface. The class needs to use the @Component annotation. You have to register for the event listener in the @Activate method. Make sure you get the ComponentContext in the activate method in order to register for the event. 

In my onEvent I used the EventIterator to get the property. I then filtered those properties that are of type date. Conveniently the Property class can return almost any type of value being String, long, Calendar, etc. In my case I needed the value as a long. 

Once I have the value, I can set it in the new property that has netval as the namespace and the name of the property in order to avoid collisions. For example, if the attribute name was activationDate the new attribute would be:

netval:activationDate

Code
The first thing is to register and de-register the event listener in the @Activate and @Deactivate methods. 

A quick look at my DatePropertyEventListener class reveals the following. The observationManager and admin session are class attributes. They will be used in the @Deactivate  nd Activate methods. It is in the @Activate method where we register the event listener. 

observationManager = adminSession.getWorkspace().getObservationManager();
observationManager.addEventListener(this,
Event.PROPERTY_ADDED|Event.PROPERTY_CHANGED,
                "/content/news/", // absolute path
                true, // isDeep
                null, // uuid
                null, // nodeTypeName
                true // no local
        );

Note that the absolute path is restrictive to the root path of your content. I highly recommend being as specific as possible in order not to receive too much noise.

The onEvent it is easy to get the date property

        while( eventIterator.hasNext() ){
            final Event event = eventIterator.nextEvent();
            if(PropertyType.DATE == getPropertyType(event) ){

Caveats and Warnings
This procedure should be disabled for operations that modify many nodes, like during a migration of content.

A suggestion is to have a producer-consumer data structure (like LinkedBlockedQueue) that will allow you to process the events more efficiently.