August 26, 2020
Howdy fellow devs π! Capturing events in a very common use case in any software system and AEM is no exception. There are many use cases where we need to perform some operation when something specific happens in AEM.
There are various ways by which event handling can be done in AEM -
We can create an Event Handler by following below steps -
EventHandler
interface.EventConstants.EVENT_TOPIC
.handleEvent(Event event)
method to trigger the job.Let us create a custom event handler that will be triggered when a page is activated and executes a workflow on the path for which the replication is triggered. The main thing to see here is how we capture the event. Execution of the workflow has the same logic we discussed in the last post discussing trigger workflow programmatically.
core
bundle in the project and create a new class named org.redquark.aem.tutorials.core.events.handler.PageActivationEvenHandler
and paste the following code in it - package org.redquark.aem.tutorials.core.events.handler;
import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkflowData;
import com.adobe.granite.workflow.model.WorkflowModel;
import com.day.cq.replication.ReplicationAction;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ResourceResolver;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.redquark.aem.tutorials.core.services.ResourceResolverService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import static org.redquark.aem.tutorials.core.constants.AppConstants.EQUALS;
@Component(
immediate = true,
service = EventHandler.class,
property = {
Constants.SERVICE_DESCRIPTION + "= This event handler listens the events on page activation",
EventConstants.EVENT_TOPIC + EQUALS + ReplicationAction.EVENT_TOPIC
}
)
public class PageActivationEvenHandler implements EventHandler {
private static final String TAG = PageActivationEvenHandler.class.getSimpleName();
private static final Logger LOGGER = LoggerFactory.getLogger(PageActivationEvenHandler.class);
@Reference
ResourceResolverService resourceResolverService;
@Override
public void handleEvent(Event event) {
LOGGER.debug("{}: event is registered: {}", TAG, event.getTopic());
try {
// Get the payload path from the request
String[] paths = (String[]) event.getProperty("paths");
// Get the payload path as the first page
String payloadPath = paths[0];
if (!StringUtils.isEmpty(payloadPath)) {
// Getting the resource resolver
final ResourceResolver resolver = resourceResolverService.getResourceResolver();
// Get the workflow session from the resource resolver
final WorkflowSession workflowSession = resolver.adaptTo(WorkflowSession.class);
// Workflow model path - This is the already created workflow
final String model = "/var/workflow/models/aemtutorials/update_referenced_assets";
// Get the workflow model object
final WorkflowModel workflowModel = Objects.requireNonNull(workflowSession).getModel(model);
// Create a workflow Data (or Payload) object pointing to a resource via JCR
// Path (alternatively, a JCR_UUID can be used)
final WorkflowData workflowData = workflowSession.newWorkflowData("JCR_PATH", payloadPath);
// Start the workflow!
workflowSession.startWorkflow(workflowModel, workflowData);
LOGGER.info("Workflow: {} started", model);
} else {
LOGGER.error("{}: Payload path is not valid", TAG);
}
} catch (WorkflowException e) {
LOGGER.error("{}: exception occurred: {}", TAG, e.getMessage());
}
}
}
EventHandler.class
. Also, the EVENT_TOPIC
is set to the ReplicationAction.EVENT_TOPIC
which will trigger this event handler when a page is replicated.handleEvent (Event event)
method, we are getting the path on which this event handler is triggered (this is ultimately the same path on which we performed the replication).update-referenced-asset
workflow programmatically (Remember we created this workflow in Day 13 - AEM Workflows Part II).We can achieve event handling at the JCR level by implementing the EventListener
interface in a class. JCR Observer is the lowest-level event handling in AEM. As its name indicates, it is at the JCR level and allows to listen to JCR-level events, gathered in sets (corresponding to persistence transactions). The javax.jcr.observation.Event
lists the following types -
1. Event.NODE_ADDED
2. Event.NODE_MOVED
3. Event.NODE_REMOVED
4. Event.PERSIST
5. Event.PROPERTY_ADDED
6. Event.PROPERTY_CHANGED
7. Event.PROPERTY_REMOVED
Let us create an event listener that listens to the event when a new node is added at a specified path (/aemtutorials/data/todo
in our case).
and paste following code in it - package org.redquark.aem.tutorials.core.events.listener;
import org.apache.sling.api.resource.ResourceResolver;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.redquark.aem.tutorials.core.services.ResourceResolverService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import java.util.Objects;
@Component(service = EventListener.class, immediate = true)
public class PropertyEventListener implements EventListener {
private static final String TAG = PropertyEventListener.class.getSimpleName();
private static final Logger LOGGER = LoggerFactory.getLogger(PropertyEventListener.class);
@Reference
ResourceResolverService resourceResolverService;
@Activate
protected void activate() {
try {
// Getting the resource resolver
ResourceResolver resourceResolver = resourceResolverService.getResourceResolver();
// Adapting the Resource Resolver to Session
Session session = resourceResolver.adaptTo(Session.class);
// Adding the event listener
Objects.requireNonNull(session).
getWorkspace().
getObservationManager().
addEventListener(
this, Event.PROPERTY_ADDED,
"/aemtutorials/data/todo", true, null,
null, false
);
} catch (RepositoryException e) {
LOGGER.error("{}: exception occurred: {}", TAG, e.getMessage());
}
}
@Override
public void onEvent(EventIterator events) {
try {
// Loop through all the events
while (events.hasNext()) {
// Get the current event
Event event = events.nextEvent();
LOGGER.info("{}: Event was added by: {} at path: {}", TAG, event.getUserID(), event.getPath());
}
} catch (RepositoryException e) {
LOGGER.error("{}: exception occurred: {}: ", TAG, e.getMessage());
}
}
}
EventListener.class
. We need to implement the onEvent(EventIterator events)
method in which we will write our business logic.activate()
method we need to register the listener. While registering it, we are providing the JCR path under which we want to listen in case a property is added.onEvent
method, we are iterating over the iterator and logging who triggered this event and at what path./aemtutorials/data/todo
, create a new property in any child node and you will see that the event is logged in the logs.Congratulations!! π we have successfully created event handlers and event listeners to understand JCR and Sling level events. I hope you enjoyed this post.
I would love to hear your thoughts on this and would like to have suggestions from you to make it better.
As usual, you can find the complete code on my GitHub. If you find it useful, consider giving it a star β.
Happy Learning π and Namaste π.