AEM Custom workflow process Step

AEM custom workflow process step is allow us to execute ECMAScript or OSGI service.

Implement WorkflowProcess interface and override execute() method to create custom workflow process. Refer this URL to read more about workflow, model and its steps.

Follow below syntax to implement workflow process step.

@Component(service = WorkflowProcess.class, 
        property = {"process.label=Practice Custom Workflow Process" })
public class PracticeCustomWorkflowProcess implements WorkflowProcess{
    
    public void execute(WorkItem workItem, WorkflowSession wfSession, MetaDataMap metaDataMap)
            throws WorkflowException {

    }
}

WorkItem: This contains workflow data as shown below

WorkflowSession: It provides the complete capability to manage workflow models and instances. Workflow session allows us to provide complete on workflow model, items, active items, workflows(running, abort, completed).

MetaDataMap: It provide access to data map and arguments passed as an input.

Process Step Implementation

Follow below step to create process and consume as part of workflow process step.

1. Create a Java class. Implement WorkflowProcess interface and override execute method.

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@Component(service = WorkflowProcess.class,
        property = {"process.label=Practice Custom Workflow Process" })
public class PracticeCustomWorkflowProcess implements WorkflowProcess{

    protected final Logger logger = LoggerFactory.getLogger(PracticeCustomWorkflowProcess.class);

    public void execute(WorkItem workItem, WorkflowSession wfSession,
        MetaDataMap metaDataMap) throws WorkflowException {
            String payloadType = workItem.getWorkflowData().getPayloadType();

            if (StringUtils.equals(payloadType, "JCR_PATH")) {
                logger.error("Payload type: {}", payloadType);

                String path = workItem.getWorkflowData().getPayload().toString();
                logger.error("Payload path: {}", path);
            }

            String args = metaDataMap.get("PROCESS_ARGS", String.class);
            logger.error("Process args: {}", args);
    }
}

2. Create workflow model and process step to consume above created custom process step. Open URL and click on Models tile for navigating to Models section.

3. Click on Create button to create a brand new model

3. Provide Title for workflow model and click on Done button.

4. Select newly created workflow model as click on Edit button to add workflow steps.

5. Click on Drag components here parsys to drag and drop step.

6. Select Process Step option.

7. Configure Process Step and provide above create custom step as part of step1.

8. Click on Sync will pick latest configurations from workflow model while execution.

9. Open any page to Start Workflow as shown below.

10. Select workflow created earlier as shown below and click on Start Workflow to start workflow execution.

11. It will call execute() method from PracticeCustomWorkflowProcess.java class as soon as click on Start Workflow button.


Workflow model mapping files

Creation of workflow model and steps will create two xml mapping files as shown below:

1. Below file will show the nodes as number of steps we dragged and dropped as part of workflow model.

It will also contain mapping in between nodes as part of transition tag as shown below:

 /var/workflow/models/practice-content-workflow-model

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:isCheckedOut="{Boolean}false"
    jcr:primaryType="cq:WorkflowModel"
    jcr:uuid="7a74ccf1-a1d8-4907-9a2a-8b2b0186f0ff"
    sling:resourceType="cq/workflow/components/model"
    description="No Description"
    title="Practice Content Workflow Model">
    <metaData
        cq:generatingPage="/conf/global/settings/workflow/models/practice-content-workflow-model/jcr:content"
        cq:lastModified="{Long}1668164088033"
        cq:lastModifiedBy="admin"
        jcr:primaryType="nt:unstructured"/>
    <nodes jcr:primaryType="nt:unstructured">
        <node0
            jcr:primaryType="cq:WorkflowNode"
            title="Start"
            type="START">
            <metaData jcr:primaryType="nt:unstructured"/>
        </node0>
        <node1
            jcr:primaryType="cq:WorkflowNode"
            title="Process"
            type="PROCESS">
            <metaData 
                jcr:primaryType="nt:unstructured"
                PROCESS="com.javadoubts.core.workflow.PracticeCustomWorkflowProcess"/>
        </node1>
        <node2
            jcr:primaryType="cq:WorkflowNode"
            title="End"
            type="END">
            <metaData jcr:primaryType="nt:unstructured"/>
        </node2>
    </nodes>
    <transitions jcr:primaryType="nt:unstructured">
        <node0_x0023_node1
            jcr:primaryType="cq:WorkflowTransition"
            from="node0"
            rule=""
            to="node1">
            <metaData jcr:primaryType="nt:unstructured"/>
        </node0_x0023_node1>
        <node1_x0023_node2
            jcr:primaryType="cq:WorkflowTransition"
            from="node1"
            to="node2">
            <metaData jcr:primaryType="nt:unstructured"/>
        </node1_x0023_node2>
    </transitions>
</jcr:root>

cq:generatingPage property is referring to /conf/global/settings/workflow/models/practice-content-workflow-model/jcr:content node.

node1 step is mapped with com.javadoubts.core.workflow.PracticeCustomWorkflowProcess workflow process step.

crx/de view of above XML file:

Note: This XML is responsible for execution of workflow process step at runtime. 

2. Below XML is of type cq:Page and used only for authoring workflow process steps.

Note: Below XML doesn’t comes in picture while executing workflow over content or asset at run time.

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" 
          xmlns:cq="http://www.day.com/jcr/cq/1.0" 
          xmlns:jcr="http://www.jcp.org/jcr/1.0" 
          xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="cq:Page">
    <jcr:content
        cq:designPath="/libs/settings/wcm/designs/default"
        cq:template="/libs/cq/workflow/templates/model"
        jcr:primaryType="cq:PageContent"
        jcr:title="Practice Content Workflow Model"
        sling:resourceType="cq/workflow/components/pages/model">
        <flow
            jcr:primaryType="nt:unstructured"
            sling:resourceType="foundation/components/parsys">
            <process
                jcr:created="{Date}2022-11-11T16:08:35.726+05:30"
                jcr:createdBy="admin"
                jcr:primaryType="nt:unstructured"
                jcr:title="Process"
                sling:resourceType="cq/workflow/components/model/process"/>
        </flow>
    </jcr:content>
</jcr:root>

crx/de view of above XML file:

Pass value from one workflow process step to another

Follow below implementation to pass value from one process step to another.

1. Update PracticeCustomWorkflowProcess.java class to set value in data map.

package com.javadoubts.core.workflow;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@Component(service = WorkflowProcess.class,
        property = {"process.label=Practice Custom Workflow Process" })
public class PracticeCustomWorkflowProcess implements WorkflowProcess{

    protected final Logger logger = LoggerFactory.getLogger(PracticeCustomWorkflowProcess.class);

    public void execute(WorkItem workItem, WorkflowSession wfSession,
        MetaDataMap metaDataMap) throws WorkflowException {
            logger.error("PracticeCustomWorkflowProcess called >>>>>>>>");

            workItem.getWorkflow().getMetaDataMap().put("user", "Mike");
            workItem.getWorkflow().getMetaDataMap().put("Age", "28");
    }
}

2. Create one more PracticeSecondWorkflowProcess.java class to access data map value passed from above process step as shown below.

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@Component(service = WorkflowProcess.class,
        property = {"process.label=Practice Second Workflow Process" })
public class PracticeSecondWorkflowProcess implements WorkflowProcess{

    protected final Logger logger = LoggerFactory.getLogger(PracticeSecondWorkflowProcess.class);

    public void execute(WorkItem workItem, WorkflowSession wfSession,
        MetaDataMap metaDataMap) throws WorkflowException {
        String name = workItem.getWorkflow().getMetaDataMap().get("user", String.class);
        String age = workItem.getWorkflow().getMetaDataMap().get("Age", String.class);

        logger.error("Name >>>>>>> {}", name);
        logger.error("Age >>>>>>> {}", age);
    }
}

3. Check the below Handler Advance checkbox. It will proceed to next step automatically once execution of current process step is done.

4. Add one more Process Step in existing workflow model created ealier as part of this tutorial. It will load PracticeSecondWorkflowProcess.java process step.

5. Configure above highlighted process step and add newly created Practice Second Workflow Process process step.

6. Click on Sync button will pick latest configurations from workflow model while execution.

7. Open any page to Start Workflow as shown below.

8. Select workflow created earlier as shown below and click on Start Workflow to start workflow execution.

9. It will call execute() method from PracticeCustomWorkflowProcess.java and PracticeSecondWorkflowProcess.java class one after another on click of Start Workflow button.

It will print below logs in error.log file.

Trigger Workflow using backend API

  1. Update earlier model to have only one Process Step as PracticeCustomWorkflowProcess.java class.

2. Create below workflow process step java class to access data map values.

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@Component(service = WorkflowProcess.class,
        property = {"process.label=Practice Custom Workflow Process" })
public class PracticeCustomWorkflowProcess implements WorkflowProcess{

    protected final Logger logger = LoggerFactory.getLogger(PracticeCustomWorkflowProcess.class);

    public void execute(WorkItem workItem, WorkflowSession wfSession,
        MetaDataMap metaDataMap) throws WorkflowException {

        logger.error("PracticeCustomWorkflowProcess called >>>>>>>>");

        String pathInfo = workItem.getWorkflow().getMetaDataMap().get("pathInfo", String.class);
        logger.error("pathInfo >>>>>>> {}", pathInfo);
    }
}

3. Create below GET Servlet java class to trigger workflow created as part of step2 using workflow model.

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 org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Servlet;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component(
    service=Servlet.class,
    property={
        Constants.SERVICE_DESCRIPTION + "=Custom Servlet",
        "sling.servlet.methods=" + HttpConstants.METHOD_GET,
        "sling.servlet.paths=" + "/bin/wfservlet"
    }
)
public class CallWorkflowServletByPath extends SlingAllMethodsServlet {

    private static final long serialVersionUID = 1L;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected void doGet(final SlingHttpServletRequest req,
        final SlingHttpServletResponse resp) throws IOException {
        try {
            logger.error("Entering CallWorkflowServletByPath >>>>>>>>>>>>>>>>>");

            // Workflow model to get call as part of workflow API
            final String model = "/var/workflow/models/practice-content-workflow-model";

            // Content path on which workflow will get trigger
            final String payloadContentPath = "/content/practice/us/en/test";

            final ResourceResolver resolver = req.getResourceResolver();
            final WorkflowSession workflowSession = resolver.adaptTo(WorkflowSession.class);

            // Create workflow model using mode path
            final WorkflowModel workflowModel = workflowSession.getModel(model);

            final WorkflowData workflowData = workflowSession.newWorkflowData("JCR_PATH", payloadContentPath);

            // Pass value to workflow
            final Map<String, Object> workflowMetadata = new HashMap<>();
            workflowMetadata.put("pathInfo", req.getPathInfo());

            // Trigger practice-content-workflow-model workflow
            workflowSession.startWorkflow(workflowModel, workflowData, workflowMetadata);

            logger.error("Exiting CallWorkflowServletByPath >>>>>>>>>>>>>>>>>");
        } catch (WorkflowException e) {
            e.printStackTrace();
        }

        resp.setStatus(SlingHttpServletResponse.SC_OK);
        resp.setContentType("application/json;charset=UTF-8");
        resp.getWriter().print("{\"response message\" : \" Workflow Called\"}");
    }
}

4. Hit servlet URL directly in browser and check for error.log file.

It will print below logs under error.log file.

Imran Khan

Specialist Master (Architect) with a passion for cutting-edge technologies like AEM (Adobe Experience Manager) and a proven track record of delivering high-quality software solutions.

  • Languages: Java, Python
  • Frameworks: J2EE, Spring, Struts 2.0, Hibernate
  • Web Technologies: React, HTML, CSS
  • Analytics: Adobe Analytics
  • Tools & Technologies: IntelliJ, JIRA

🌐 LinkedIn

📝 Blogs

📧 Imran Khan