Skip to main content

Handle Workflow Events in AEM

Hey everybody, it's been almost three months that I have written a post. This weekend, I finally got some time and a use case which I am going to share with you.

We all know workflows play a very important role in the day to day activities of AEM. It is not unheard of that you as a user want to trigger some service/component/scheduler on some of the events in a workflow.

To achieve this, it is necessary that we listen to the workflow events in our code and perform our business logic as that event is triggered. In this post, we will see how we can achieve this using an EventHandler (to know how EventHandler works, see this post).

Since AEM has a rich set of APIs, we are fortunate that it has also an API through which we can listen to workflow-related events.

Steps:

Below are the steps through which we can listen to workflow events - 

Step #1

Create an Event Handler (WorkflowEventsHandler, in our case) which implements the org.osgi.service.event.EventHandler interface and implement its handleEvent(Event event) method.

Step #2

Since any EventHandler needs some topic to listen to, in our case, we will provide the topic as com.day.cq.workflow.event.WorkflowEvent.EVENT_TOPIC. WorkflowEvent provides an extension of Event that is used for propagating workflow-related information as OSGI events.

Step #3

We will implement handleEvent(Event event) method and get the event topic. We will check if the received topic is of type WorkflowEvent and if it is true then we will proceed further.

Step #4

Now, we will get the event type from the Event object. The event type gives us information about what kind of event our EventHandler has received. Since we can get multiple types of events in a workflow such as WORKFLOW_STARTED, WORKFLOW_COMPLETED, WORKFLOW_RESUMED etc. Based on our requirement, we can implement our business logic.

Code

The complete code for this EventHandler is below -

package org.redquark.demo.core.listeners;

import java.util.HashMap;
import java.util.Map;

import javax.jcr.Session;

import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowService;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.event.WorkflowEvent;
import com.day.cq.workflow.exec.Workflow;
import com.day.cq.workflow.exec.WorkflowData;

/**
 * @author Anirudh Sharma
 *
 */
@Component(service = EventHandler.class, immediate = true, property = {
  Constants.SERVICE_DESCRIPTION
    + "= This event handler listens to the events that occur in the life cycle of a Workflow",
  EventConstants.EVENT_TOPIC + "=" + WorkflowEvent.EVENT_TOPIC })
public class WorkflowEventsHandler implements EventHandler {

 private final Logger LOG = LoggerFactory.getLogger(this.getClass());

 @Reference
 private ResourceResolverFactory resourceResolverFactory;

 @Reference
 private WorkflowService workflowService;

 /**
  * This method returns the workflow session
  * 
  * @return {@link WorkflowSession}
  */
 private WorkflowSession getWorkflowSession() {
  try {
   // Creating and getting the service user map
   Map<String, Object> serviceUserMap = new HashMap<>();
   serviceUserMap.put(ResourceResolverFactory.SUBSERVICE, "eventingService");
   // Get the reference of ResourceResolver from the ResourceResolverFactory and
   // the Service User map
   ResourceResolver resourceResolver = resourceResolverFactory.getServiceResourceResolver(serviceUserMap);
   // Adapting ResourceResolver to the Session object
   Session session = resourceResolver.adaptTo(Session.class);
   WorkflowSession workflowSession = workflowService.getWorkflowSession(session);
   return workflowSession;
  } catch (Exception e) {
   LOG.error(e.getMessage(), e);
  }
  return null;
 }

 @Override
 public void handleEvent(Event event) {
  LOG.info("Received event of topic: {}", event.getTopic());
  WorkflowSession workflowSession = null;
  try {
   // Get the workflow session
   workflowSession = getWorkflowSession();
   // Get the received topic
   String topic = event.getTopic();
   // Check if the received topic is of type Workflow event
   if (topic.equals(WorkflowEvent.EVENT_TOPIC)) {
    // Get the type of event
    Object eventType = event.getProperty(WorkflowEvent.EVENT_TYPE);
    // Check for each types of events in a workflow
    if (eventType.equals(WorkflowEvent.WORKFLOW_STARTED_EVENT)) {
     LOG.info("Workflow has started");
     String instanceId = (String) event.getProperty(WorkflowEvent.WORKFLOW_INSTANCE_ID);
     try {
      // Get the reference of the Workflow object
      Workflow workflow = workflowSession.getWorkflow(instanceId);
      // Get the workflow data
      WorkflowData workflowData = workflow.getWorkflowData();
      LOG.info("Data in workflow: {}", workflowData.getPayload().toString());
     } catch (WorkflowException e) {
      LOG.error(e.getMessage(), e);
     }
    } else if (eventType.equals(WorkflowEvent.WORKFLOW_COMPLETED_EVENT)) {
     LOG.info("Workflow has completed");
    } else if (eventType.equals(WorkflowEvent.WORKFLOW_RESUMED_EVENT)) {
     LOG.info("Workflow is resumed");
    } else if (eventType.equals(WorkflowEvent.WORKFLOW_ABORTED_EVENT)) {
     LOG.info("Workflow is aborted");
    } else if (eventType.equals(WorkflowEvent.WORKFLOW_SUSPENDED_EVENT)) {
     LOG.info("Workflow is suspended");
    } else if (eventType.equals(WorkflowEvent.WORKITEM_DELEGATION_EVENT)) {
     LOG.info("Workflow is delegated");
    } else {
     LOG.warn("Something is wrong with the workflow");
    }
   }
  } catch (Exception e) {
   LOG.error(e.getMessage(), e);
  } finally {
   if (workflowSession != null) {
    workflowSession.getSession().logout();
   }
  }
 }

}
Deploy this code on AEM and start testing.

Testing

For testing, the following steps can be taken - 

Step #1

Choose a workflow and run it.

Step #2

You should be able to see logs getting printed as - 

2020-03-01 13:25:19.407 INFO [org.redquark.demo.core.listeners.WorkflowEventsHandler] Received event of topic: com/day/cq/workflow/event
2020-03-01 13:25:19.409 INFO [org.redquark.demo.core.listeners.WorkflowEventsHandler] Workflow has started
2020-03-01 13:25:19.412 INFO [org.redquark.demo.core.listeners.WorkflowEventsHandler] Data in workflow: /content/dam/we-retail/en/activities/biking/mountain-biking.jpg

Conclusion

In this post, we saw how can we create an EventHandler and perform our business logic when our required event triggers.

You can find the code to this in my GitHub repository.

I hope you enjoyed this post. Your suggestions and feedback are always welcome. Feel free to befriend me on FacebookTwitter or Linked In or say Hi by email.

Comments

Popular posts from this blog

Day 00: AEM Developer Series

image source:  https://solutionpartners.adobe.com/ Hello everyone! Welcome to this AEM development series. We can all see the revolution of Digital Marketing today. Companies are dying to be a part of it and they have made this a war for the Digital Marketing tools. Adobe is way ahead in this war and has gained a lot of market capture. They are leaders in the Digital Marketing platforms since 2014-15. One of the flagship product in Adobe's Digital Marketing suite is Adobe Experience Manager (AEM) . Since AEM is in huge demand, the people who know how to develop on AEM are also in huge demand. But developing on AEM is not easy as it is made up of various open-source technologies such as Apache Felix (OSGi), Apache Sling, Apache Oak and Adobe's own technologies like Granite, HTL etc. Learning all these technologies in conjunction can sometimes become confusing and frustrating 😫. When I first started learning AEM in 2016, I was dumbfounded to see there is

Day 05: Working with Sling Servlets in AEM

AEM Developer Series Day 00: AEM Developer Series Day 01: Introduction to AEM Day 02: AEM Architecture Day 03: Setting up AEM Development Environment Day 04: Developing First OSGi Bundle Day 05: Working with Sling Servlets in AEM Day 06: Playing with Sling Post Servlet Day 07: Creating your first component in AEM Day 08: Dueling with JavaScript Use API Day 09: Dueling with Java User API Day 10: Getting to know Sling Models Day 11: Client Libraries in Action Day 12: Creating your custom OSGi Configuration Day 13: Schedulers in AEM Day 14: Eventing in AEM Day 15: Custom Workflows in AEM Day 16: Creating JMX Beans in AEM Day 17: Working with QueryBuilder API Day 18: Working with Granite Datasources in AEM Day 19: Replication API in Action Day 20: Working with Users and Groups in AEM A Servlet is a class used to extend the capabilities of servers that host applications accessed by means of a request-response programming model. For such applications, Servlet tec

Day 03: Setting up AEM Development Environment

AEM Developer Series Day 00: AEM Developer Series Day 01: Introduction to AEM Day 02: AEM Architecture Day 03: Setting up AEM Development Environment Day 04: Developing First OSGi Bundle Day 05: Working with Sling Servlets in AEM Day 06: Playing with Sling Post Servlet Day 07: Creating your first component in AEM Day 08: Dueling with JavaScript Use API Day 09: Dueling with Java User API Day 10: Getting to know Sling Models Day 11: Client Libraries in Action Day 12: Creating your custom OSGi Configuration Day 13: Schedulers in AEM Day 14: Eventing in AEM Day 15: Custom Workflows in AEM Day 16: Creating JMX Beans in AEM Day 17: Working with QueryBuilder API Day 18: Working with Granite Datasources in AEM Day 19: Replication API in Action Day 20: Working with Users and Groups in AEM Welcome to the third day of learning AEM development. In previous posts, we discussed the AEM architecture and in this post, we are going to see how to set up an AEM development e