AEM Workflows : How to Implement Parallel Approvals

Workflow

What is Workflow in AEM?

Workflows enable you to automate Experience Manager activities. Workflows consist of a series of steps that are executed in a specific order. Each step performs a distinct activity such as activating a page or sending an email message. Workflows can interact with assets in the repository, user accounts, and Experience Manager services. Therefore, workflows can coordinate complicated activities that involve any aspect of Experience Manager.

Business Use case

The core business use case is, as an author I should be able to add ‘n’ number of reviewers (AEM Users) to the workflow, and every user must approve or deny the request to complete the workflow.

The Challenge

Two challenges found to achieve this use case.

  1. If you assign the step to the group, then the workflow internally assigns this step to all the members under the group. This step will be completed automatically as and when any one of the members completes the step. But the business use case here is, all the members must approve the content before it moves to the next step for Activation/Deactivation.
  2. At a time you can assign one group or a user to a participant step. Out-of-the-box Workflow capabilities in AEM do not allow for dynamically setting approvers at run-time. The workflow model requires that a group be selected as part of the model.

How was this Solved?

Two approaches for solving the problem.

  1. Parallel Approval
  2. Serial Approval (Next Article ..)

Parallel Approval

As the names implies,”Parallel”: All the members configured should get notified via email & AEM notifications. Each member can approve the step in any order, but the workflow should be completed once all the members approved. To achieve this, we need to create two workflow models as below.

  • Parent Workflow Model
  • Child Workflow Model

Parent Workflow Model

Step 1: Configure “Dialog DynamicParticipantStep” with Multifield as the type to configure a group of user’s as the reviewer’s

<email
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
    required="{Boolean}true"
    fieldLabel="Configure Users">
    <field
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/authorizable/autocomplete"
        required="{Boolean}true"
        forceSelection="{Boolean}true"
        selector="user"
        name="./jcr:content/metadata/email"/>
</email>

Step 2: Save configured users under workflow metaData

Step 3: Notify reviewers by email using Custom Process Step.

Step 4: Create the Child Workflows by Iterating the configured users saved under metaData. Two meta properties are key here.

  1. ASSIGNEE – Custom Property which we will be used to assign the child workflow to this user (DynamicParticipantStep)
  2. parenentInstancePath – This path will be used to poll the reviewer’s actions(Approve/Deny) to the parent workflow.
for (String userId : approverList) {
     MetaDataMap mdp = workflowData.getMetaDataMap();
     mdp.put(ProjectConstants.ASSIGNEE,userId);
     mdp.put("parentInstacePath",instanceId)
     session.startWorkflow(wfModel, childWfData, mdp);
}

Step 5: Create a step using ParticipantStep and assign it to the ‘admin’ as a user. This will hold the step here until all the reviewers approve or deny the step by anyone of the approver.

Step 6: Create OR split

Step 7: DENY (Left Side) – This step will be executed If anyone of the reviewer denies the request and will send the ‘Acknowledgment’ notification to the initiator and re-initiate the workflow from the start (Notify User’s)

Step 8: APPROVE (Right Side) – This step will be executed only when all the reviewers approved the request.

Child Workflow Model

 

Step 1: Create “DynamicParticipantStep” as a first step. This step should read “assignee” property under metadata and return as a participant.

Step 2: OR Split

Step 3: DENY (left) – Auto-Advance Step.

Step 4: Approve (Right) – Auto-Advance Step.

Step 5: Create a Poll Status custom process step. Based on the action(Approve/Deny) Poll Status Custom Process step will be called and updates the status to the parent workflow node. Below is the sample code snippet to update the parent workflow node & complete the workflow (Parallel Approval Check – Parent).

Code Snippet: Updates Parent Workflow Node

Session session = resourceResolver.adaptTo(Session.class);
           WorkflowSession wfSession = UtilityMethods.getWorkflowSession(session,workflowService);
           parentWfInstance = wfSession.getWorkflow(parentInstancePath);
           String[] approverList = parentWfInstance.getWorkflowData().getMetaDataMap().get(ProjectConstants.WORKFLOW_APPROVAL_USER_LIST, String[].class);
           String[] approverStatusList = parentWfInstance.getWorkflowData().getMetaDataMap().get(ProjectConstants.WORKFLOW_APPROVAL_STATUS_LIST, String[].class);
           int incrementCount = 0;
           for (String assignee : approverList) {
               if (StringUtils.equalsIgnoreCase(assignee,currentAssignee)) {
                   if(StringUtils.equalsIgnoreCase(action,ProjectConstants.WORKFLOW_APPROVE)) {
                       approverStatusList[incrementCount] = ProjectConstants.WORKFLOW_YES;
                   }
  
                   if(StringUtils.equalsIgnoreCase(action,ProjectConstants.WORKFLOW_DENY)) {
                       approverStatusList[incrementCount2] = ProjectConstants.WORKFLOW_NO;
                   }
                   break;
               }
               incrementCount++;
           }
parentWfInstance.getWorkflowData().getMetaDataMap().put(ProjectConstants.WORKFLOW_APPROVAL_STATUS_LIST, approverStatusList);
wfSession.updateWorkflowData(parentWfInstance, parentWfInstance.getWorkflowData());

Code Snippet: Complete Parent Workflow from Child Workflow  

Session session = resourceResolver.adaptTo(Session.class);
WorkflowSession parentWfSession = UtilityMethods.getWorkflowSession(session,workflowService);
parentWfInstance = parentWfSession.getWorkflow(parentInstancePath);
String[] approverStatusList = parentWfInstance.getWorkflowData().getMetaDataMap().get(ProjectConstants.WORKFLOW_APPROVAL_STATUS_LIST, String[].class);
if(checkIfAllReviewersAreApproved(approverStatusList) || StringUtils.equalsIgnoreCase(currentAction,ProjectConstants.WORKFLOW_DENY)){
    for(WorkItem workItem :parentWfInstance.getWorkItems()){
        if (StringUtils.equalsIgnoreCase(currentAction, ProjectConstants.WORKFLOW_DENY)) {
            parentWfSession.complete(workItem, parentWfSession.getRoutes(workItem).get(0)); // Deny Route
        }else{
            parentWfSession.complete(workItem, parentWfSession.getRoutes(workItem).get(1)); // Approve Route
        }
    }
}

 

Conclusion

As one can see, the business case of having approvals set up as dynamic users at run-time was solved by implementing AEM workflows in a customized fashion. Although Out-of-the-Box workflows provide a certain amount of functionality, they had to be extended in order to solve what appears to be a seemingly common need. We hope you find this information was helpful!

1
Leave a Reply

avatar
1 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
AEMUser Recent comment authors
  Subscribe  
Newest Oldest Most voted
Notify of
AEMUser
Guest
AEMUser

Great Post