Article summary

Summary

Discusses how to invoke a custom workflow from an Experience Manager page. Also discusses how to approve the payload using an OR Split step. This article also describes how to use the Workkflow API to obtain the user whom approved the workflow item and uses DS Annotations to create the custom workflow step as opposed to Felix SRC annotations. 

A special thank you to Ratna Kumar Kotla, a member of the AEM community, for testing this article and ensuring it works. Also, thank you to Arun Patidar, a top AEM Community member, for contributing code used in this article. 

Digital Marketing Solution(s) Adobe Experience Manager (Adobe CQ)
Audience
Developer (intermediate)
Required Skills
Java, OSGi, Maven
Tested On  Adobe Experience Manager 6.4

Introduction

You can develop a custom Adobe Experience Manager workflow and invoke the workflow from an Experience Manager web page. That is, you can specify information required by the workflow such as the payload and invoke the workflow from a page within an Experience Manager site. In this example, assume that the use case is as follows:

  • Invoke a Servlet from a web page
  • Pass in an asset location and email address
  • Invoke the Workflow from an AEM Servlet
  • The Workflow gets approval for the payload (an asset)
  • An OR Split is used within the approval workflow and routes to different branches.
    If approved, a property on the asset is modified. 
  • If not approved, the asset is not modified and a log messaged is created

The following illustration shows the workflow with the OR Split step that is required to approve an asset payload.

 

Model
A Workflow model with an OR Split

In this article, a web page invokes the AEM Sling Servlet by using an AJAX call.  

Form
An AEM page that invokes an AEM Sling Servlet

In this example, the web page passes two values to the AEM Servlet. 

1 - The location of the AEM Asset that represents the payload for the workflow. This corresponds to an image in the AEM DAM. 

2 - An email address that is used to send a message if the asset is approved. 

The web page uses an AJAX call to invoke the AEM Servlet which is shown later in this development article. Also, the Experience Manager Workflow API is used to dynamically invoke the workflow. 

Create an Experience Manager System User

Create an Experience Manager System User that can access JCR data required for the workflow. The system user account must have privileges to interact with the workflow.

To create an Experience Manager System user, perform these tasks. 

1. Open http://localhost:4502/crx/explorer/index.jsp.

2. Login as admin.

3. Click User Administration.

4. Click Create System User. Name the user data (data is used in this article).

5. Set the UserId

6. Click Save

7.  Access the user page at http://localhost:4502/useradmin. 

8.  Select the data user.

9. From the right-hand pane, select the Permissions tab. 

10. Expand the content tab and then select permissions to access workflows. 

11. Click all the checkboxes that represent the permissions (click the top row in permissions for this example). 

12. Click the Save button located in the top menu bar (located above the Path heading).  

Configure the Sling Mapper Service

The next step is to configure the Apache Sling Service User Mapper service by adding a new entry. Enter the following value:

InvokeWF.core:datawrite=data

where:

  • InvokeWF.core – is the Bundle-SymbolicName value of the OSGi bundle this is developed in the upcoming sections of this article.
  • datawrite – the name of the sub service (you reference this value in a Java Map object)
  • data – the system user account with data privileges for workflows 

The following illustration shows an entry for this service. 

sling2
An entry in the Apache Sling Service Mapping Service

To create an entry in the Apache Sling Mapper service, perform these tasks:

1. Go to the Apache Sling Mapper service at http://localhost:4502/system/console/configMgr. 

2. Click OSGI, Configurations. 

2. Scroll to an entry named Apache Sling Service User Mapper Service.

3. Enter the value InvokeWF.core:datawrite=data.

4. Click Save. 

Create an Experience Manager 13 archetype project

You can create an Experience Manager archetype project by using the Maven archetype plugin. In this example, assume that the working directory is C:\AdobeCQ. 

ProjectA
An Experience Manager Maven Archetype 13 project

To create an Experience Manager archetype project, perform these steps:

1. Open the command prompt and go to your working directory (for example, C:\AdobeCQ).

2. Run the following Maven command:

mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeGroupId=com.adobe.granite.archetypes -DarchetypeArtifactId=aem-project-archetype -DarchetypeVersion=13 -DarchetypeCatalog=https://repo.adobe.com/nexus/content/groups/public/

3. When prompted, specify the following information:

  • groupId - InvokeWF
  • artifactId - InvokeWF
  • version - 1.0-SNAPSHOT
  • package - com.aem.community.wf
  • appsFolderName - InvokeWF
  • artifactName - InvokeWF
  • componentGroupName - InvokeWF
  • contentFolderName - InvokeWF
  • cssId - InvokeWF
  • packageGroup - InvokeWF
  • siteName -InvokeWF

4. WHen prompted, specify Y.

5. Once done, you will see a message like:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:42 min
[INFO] Finished at: 2016-04-25T14:34:19-04:00
[INFO] Final Memory: 16M/463M
[INFO] ------------------------------------------------------------------------

6. Change the working directory to InvokeWF and then enter the following command.

mvn eclipse:eclipse

After you run this command, you can import the project into Eclipse as discussed in the next section.

Note:

If you have not setup Maven, see this article Creating an Adobe Experience Manager 6.4 Project using Adobe Maven Archetype 13.

Add Java files to the Maven project using Eclipse 

To make it easier to work with the Maven generated project, import it into the Eclipse development environment, as shown in the following illustration.

Project
Eclipse Import Project Dialog

Note:

Do not worry about the errors reported in Eclipse. It does not read the POM file where the APIs are resolved. You build the bundle with Maven. Eclipse is used to edit the Java files and the POM file.

The next step is to add four Java files to the com.aem.community.wf.core package:

  • ApproveStep - a custom workflow step that is executed if the asset is approved
  • DenyStep - a custom workflow step that is executed if the asset is denied
  • ModNode - a Java Interface that exposed a method named updateNode
  • ModNodeImpl - the implementation class that uses the JCR API to update node properties

 

Note:

You also have to update the SimpleServlet class which is shown later in this section. This class invokes the Experience Manager workflow by using the Workflow API. 

ApproveStep

The ApproveStep class represents a custom workflow step that implements WorkflowProcess. If successful, this step updates a node property named approve to true located here: 

<digital asset in DAM>/jcr:content/metadata

To update the node, a @Reference annotation creates an instance of ModNode.

This class also uses the Workflow API to retrieve the user whom approved the workflow. This application logic is in the getUserWhomApproved method. 

The following Java code represents this class. 

package com.aem.community.wf.core;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
    
 
import javax.jcr.Session;
import javax.jcr.Node; 
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
     
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
     
import javax.jcr.Repository; 
import javax.jcr.SimpleCredentials; 
import javax.jcr.Node; 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import com.day.cq.dam.api.Asset; 
import java.util.Collections;
      
import org.apache.jackrabbit.commons.JcrUtils;
     
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
 
     
import javax.jcr.Session;
import javax.jcr.Node; 
import org.osgi.framework.Constants;
   
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.WorkflowData;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import com.adobe.granite.workflow.exec.HistoryItem;
 
//AssetManager
import com.day.cq.dam.api.AssetManager; 
 
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream ; 
import java.io.OutputStream ; 
import java.io.ByteArrayInputStream ; 
import java.io.FileOutputStream ; 
   
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
   
    
//Sling Imports
import org.apache.sling.api.resource.ResourceResolverFactory ; 
import org.apache.sling.api.resource.ResourceResolver; 
import org.apache.sling.api.resource.Resource; 
import com.day.cq.wcm.api.Page; 
    
 
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
 
import com.adobe.granite.workflow.model.WorkflowNode;
import com.adobe.granite.workflow.exec.WorkflowData;
 
 
@Component(service=WorkflowProcess.class, property = {"process.label=Good Process"})
 
public class ApproveStep implements WorkflowProcess 
{
         
        
/** Default log. */
protected final Logger log = LoggerFactory.getLogger(this.getClass());
        
//Inject a MessageGatewayService 
//Inject a Sling ResourceResolverFactory
@Reference
private ResourceResolverFactory resolverFactory;


@Reference
private com.aem.community.wf.core.ModNode theNode;  
  
private Session session;
        
public void execute(WorkItem item, WorkflowSession wfsession,MetaDataMap args) throws WorkflowException {
            
try
{
    log.info("**** Here in execute method");    //ensure that the execute method is invoked    
            
    //Get the Assets from the file system for a test
    WorkflowNode myNode = item.getNode(); 
      
    String myTitle = myNode.getTitle(); //returns the title of the workflow step
      
    log.info("**** The title is "+myTitle);  
      
    WorkflowData workflowData = item.getWorkflowData(); //gain access to the payload data
    String path = workflowData.getPayload().toString();//Get the path of the asset
      
      
    //Get only the name of the asset - including the ext
    int index = path.lastIndexOf("/");
    String fileName = path.substring(index + 1);
        
   String approveUser =   getUserWhomApproved(wfsession,item);
   
   
   //Get the user whom approved the workflow
   log.info("**** This asset was accepted " +fileName +" and approved by "+approveUser);  
   
   theNode.updateNode(path) ; 
   
   
}
    
    catch (Exception e)
    {
    e.printStackTrace()  ; 
    }
 }

//Gets the User from approves the payload
 private String getUserWhomApproved(WorkflowSession wfsession,WorkItem item)
 {
	 	
	 try{
	 List<HistoryItem> historyList = wfsession.getHistory(item.getWorkflow());

	  int listSize = historyList.size();

	  // log.info("listSize = {}", listSize);

	  HistoryItem lastItem = historyList.get(listSize - 1);

	  String lastComment = lastItem.getComment();

	  String lastAction = lastItem.getAction();

	  String lastUser = lastItem.getUserId();
	  return lastUser; 
	 }
	    
	    catch (Exception e)
	    {
	    e.printStackTrace()  ; 
	    }
	  return "error  - no user" ; 
	  
	 
 }	 
 
  

}

DenyStep

The DenyStep class represents another custom workflow step that represents the route if the asset is rejected. This class simply logs a message to the log file. 

The following Java code represents this class. 

package com.aem.community.wf.core;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
    
 
import javax.jcr.Session;
import javax.jcr.Node; 
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
     
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
     
import javax.jcr.Repository; 
import javax.jcr.SimpleCredentials; 
import javax.jcr.Node; 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import com.day.cq.dam.api.Asset; 
import java.util.Collections;
      
import org.apache.jackrabbit.commons.JcrUtils;
     
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
 
     
import javax.jcr.Session;
import javax.jcr.Node; 
import org.osgi.framework.Constants;
   
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.WorkflowData;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
 
//AssetManager
import com.day.cq.dam.api.AssetManager; 
 
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream ; 
import java.io.OutputStream ; 
import java.io.ByteArrayInputStream ; 
import java.io.FileOutputStream ; 
   
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
   
    
//Sling Imports
import org.apache.sling.api.resource.ResourceResolverFactory ; 
import org.apache.sling.api.resource.ResourceResolver; 
import org.apache.sling.api.resource.Resource; 
import com.day.cq.wcm.api.Page; 
    
 
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
 
import com.adobe.granite.workflow.model.WorkflowNode;
import com.adobe.granite.workflow.exec.WorkflowData;
 
 
@Component(service=WorkflowProcess.class, property = {"process.label=Deny Process"})
 
public class DenyStep implements WorkflowProcess 
{
         
        
/** Default log. */
protected final Logger log = LoggerFactory.getLogger(this.getClass());
        
//Inject a MessageGatewayService 
//Inject a Sling ResourceResolverFactory
@Reference
private ResourceResolverFactory resolverFactory;
  
private Session session;
        
public void execute(WorkItem item, WorkflowSession wfsession,MetaDataMap args) throws WorkflowException {
            
try
{
    log.info("**** Here in execute method");    //ensure that the execute method is invoked    
            
    //Get the Assets from the file system for a test
    WorkflowNode myNode = item.getNode(); 
      
    String myTitle = myNode.getTitle(); //returns the title of the workflow step
      
    log.info("**** The title is "+myTitle);  
      
    WorkflowData workflowData = item.getWorkflowData(); //gain access to the payload data
    String path = workflowData.getPayload().toString();//Get the path of the asset
      
      
    //Get only the name of the asset - including the ext
    int index = path.lastIndexOf("/");
    String fileName = path.substring(index + 1);
        
           
   log.info("**** This asset was rejected " +fileName );  
}
    
    catch (Exception e)
    {
    e.printStackTrace()  ; 
    }
 }
  

}

ModNode Interface

The ModNode interface exposes a method named updateNode that accepts a string parameter that represents the path of the node. The following Java code represents this interface. 

package com.aem.community.wf.core;

public interface ModNode {
	
	public void updateNode(String path) ; 
	
	

}

ModNodeImpl

The ModNodeImpl class represents an Experience Manager service that updates the payload (the assets metadata node). It updates the approve property on the metadata node to true by using the JCR API. The following Java code represents this class.  

package com.aem.community.wf.core;


import org.w3c.dom.Document;
import org.w3c.dom.Element;
    
 
import javax.jcr.Session;
import javax.jcr.Node; 
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
     
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
     
import javax.jcr.Repository; 
import javax.jcr.SimpleCredentials; 
import javax.jcr.Node; 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import com.day.cq.dam.api.Asset; 
import java.util.Collections;
      
import org.apache.jackrabbit.commons.JcrUtils;
     
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
 
     
import javax.jcr.Session;
import javax.jcr.Node; 
import org.osgi.framework.Constants;
   
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.WorkflowData;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
 
//AssetManager
import com.day.cq.dam.api.AssetManager; 
 
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream ; 
import java.io.OutputStream ; 
import java.io.ByteArrayInputStream ; 
import java.io.FileOutputStream ; 
   
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
   
    
//Sling Imports
import org.apache.sling.api.resource.ResourceResolverFactory ; 
import org.apache.sling.api.resource.ResourceResolver; 
import org.apache.sling.api.resource.Resource; 
import com.day.cq.wcm.api.Page; 
    
 
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
 
import com.adobe.granite.workflow.model.WorkflowNode;
import com.adobe.granite.workflow.exec.WorkflowData;

//Use the NODE API
import javax.jcr.Node; 

import java.util.Map; 
import java.util.HashMap;
 
 
@Component

public class ModNodeImpl implements ModNode{
	
	@Reference
	private ResourceResolverFactory resolverFactory;
	
	  
	private Session session;
	
	/** Default log. */
	protected final Logger log = LoggerFactory.getLogger(this.getClass());
	
	public void updateNode(String path)
	{
		Map<String, Object> param = new HashMap<String, Object>();
        param.put(ResourceResolverFactory.SUBSERVICE, "datawrite");
        ResourceResolver resolver = null;
           
        try {
                      
            //Invoke the adaptTo method to create a Session used to create a QueryManager
            resolver = resolverFactory.getServiceResourceResolver(param);
            session = resolver.adaptTo(Session.class);
		    
		 
		    //Create a node that represents the root node
		    Node root = session.getRootNode(); 
		    
		    
		    String metaPath = path +"/jcr:content/metadata"  ;
		    
		    
		    log.info("**** ABOUT TO GET PATH"); 
		    
		  //Remove the first / char - JCR API does not like that
		    String newPath = path.replaceFirst("/", "");   
		    
		    String finalPath = newPath+"/jcr:content/metadata" ;
		     
		    //This returns the metadata node under the asset node
		    Node rcontent = root.getNode(finalPath);
		    
		    String ttt = rcontent.getPath(); 
		    
		    log.info("**** This meta path is " + ttt); 
		    
		    rcontent.setProperty("xmpRights:UsageTerms", "Approved"); 
		    
		    session.save(); 
		    session.logout();
		}
		catch (Exception e)
		{
			e.printStackTrace()  ;
		}
	}

}

SimpleServlet

The SimpleServlet class represents the Sling Servlet that uses the Workflow API to invoke the workflow. In this example, the workflow is named approveasset is referenced here:

/var/workflow/models/approveasset

Another aspect to note is how to pass a value to an AEM workflow. By default, the only value you can pass to the workflow by using the API is the payload. In this example, its the JCR path of the asset. 

You cannot pass a value like an email address to the workflow by using the API. To ensure that the workflow has access to this value, the email address is updated on the metadata nodes's email property. This way, each time this workflow is invoked, the email property on the metadata is updated with the email property. This value can be retrieved by using the JCR API and used within the workflow. 

The following code represents the SimpleSerlet class located in the com.aem.community.wf.core.servlets package.

package com.aem.community.wf.core.servlets;

import org.apache.sling.api.SlingHttpServletRequest;

import java.util.Map; 
import java.util.HashMap;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.api.resource.ValueMap;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;


//AEM Workflow APIs
//import com.aem.community.wf.core.Object;
//import com.aem.community.wf.core.String;
import com.day.cq.workflow.model.WorkflowModel ; 
import com.day.cq.workflow.WorkflowService ; 
import com.day.cq.workflow.WorkflowSession; 
import com.day.cq.workflow.exec.WorkflowData; 

import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.jcr.api.SlingRepository;
    
import javax.jcr.Session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
   

//Use the NODE API
import javax.jcr.Node; 
 
/**
 * A Servlet that invokes an AEM Workflow
 */
@Component(service=Servlet.class,
        property={
                Constants.SERVICE_DESCRIPTION + "=Simple Demo Servlet",
                "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                "sling.servlet.paths="+ "/bin/invokeWF"
           })
public class SimpleServlet extends SlingSafeMethodsServlet {

    private static final long serialVersionUid = 1L;
    
    /** Default log. */
    protected final Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Reference
    private WorkflowService workflowService;
  
    private Session session;
      
    @Reference
    private ResourceResolverFactory resolverFactory;
    
    

    @Override
    protected void doGet(final SlingHttpServletRequest req,
            final SlingHttpServletResponse resp) throws ServletException, IOException {
        
    	
    	Map<String, Object> param = new HashMap<String, Object>();
        param.put(ResourceResolverFactory.SUBSERVICE, "datawrite");
        ResourceResolver resolver = null;
           
        try {
                      
            //Invoke the adaptTo method to create a Session used to create a QueryManager
            resolver = resolverFactory.getServiceResourceResolver(param);
            session = resolver.adaptTo(Session.class);
        
            log.info("IN SERVET") ; 
    	
            //Need to read the parameters from the AJAX operation 
            String assetPath = req.getParameter("assetPath");
            String email = req.getParameter("email");
        
        
            log.info("IN SERVET - asset path "+assetPath) ; 
            log.info("IN SERVET - email "+email) ;
              
            //Create a workflow session 
        WorkflowSession wfSession = workflowService.getWorkflowSession(session);
                    
       //String workflowName = "/etc/workflow/models/approveasset-"; //need to update with real WF
        
        String workflowName = "/var/workflow/models/approveasset"; //etc/workflow/models/approveasset-
        
        // Get the workflow model
       WorkflowModel wfModel = wfSession.getModel(workflowName); 
                    
        // Get the workflow data
        // The first param in the newWorkflowData method is the payloadType.  Just a fancy name to let it know what type of workflow it is working with.
       WorkflowData wfData = wfSession.newWorkflowData("JCR_PATH", assetPath);
                    
       
       //Before we Send Payload to the Workflow - we need to update email prop on the Payload node
       updateEmailProp(assetPath, email);
         
       
       // Run the Workflow.
        wfSession.startWorkflow(wfModel, wfData);
        
        //Return the JSON formatted data
        resp.getWriter().write("Servlet invoked");
        
        
        session.save(); 
	    session.logout();
        
    	}
    	catch(Exception e)
    	{
    	    e.printStackTrace();
    	}
    
    }
    
    
    
    private void updateEmailProp(String path, String email)
    {
    	
    	try{
    	
        	
    	//Create APp ;ogic to change image NODE PROP
	    //Create a node that represents the root node
	    Node root = session.getRootNode(); 
	    
	    
	    String metaPath = path +"/jcr:content/metadata"  ;
	    
	    
	    log.info("**** ABOUT TO GET PATH"); 
	    
	  //Remove the first / char - JCR API does not like that
	    String newPath = path.replaceFirst("/", "");  
	    
	    log.info("**** NEW PATH IS "+newPath);
	    
	    String finalPath = newPath+"/jcr:content/metadata" ;
	    
	    log.info("**** FINAL PATH IS "+finalPath); 
	     
	    //This returns the metadata node under the asset node
	    Node rcontent = root.getNode(finalPath);
	    
	    rcontent.setProperty("email", email);
	   /*
	    
	    if (rcontent.hasProperty("email") == false)
	   {
		   //create email prop on payload 
		   log.info("**** Created an EMAIL PROP  - email is " +email);
	  	 
	   }
	   else
	   {
		   
	   }
	   */
	   

	    
    	}
    	catch(Exception e)
    	{
    		e.printStackTrace(); 
    	}
    }
    
    
    
    
}

Modify the Maven POM file

Add the following POM dependency to the POM file located at C:\AdobeCQ\InvokeWF.

<dependency>
    <groupId>com.adobe.aem</groupId>
    <artifactId>uber-jar</artifactId>
    <version>6.4.0</version>
    <classifier>apis</classifier>
    <scope>provided</scope>
</dependency>
               
  <dependency>
       <groupId>org.apache.geronimo.specs</groupId>
       <artifactId>geronimo-atinject_1.0_spec</artifactId>
       <version>1.0</version>
       <scope>provided</scope>
   </dependency>

When you add new Java classes under core, you need to modify a POM file to successfully build the OSGi bundle. You modify the POM file located at C:\AdobeCQ\InvokeWF\core. The following code represents this POM file.

<?xml version="1.0" encoding="UTF-8"?>
<!--
 |  Copyright 2017 Adobe Systems Incorporated
 |
 |  Licensed under the Apache License, Version 2.0 (the "License");
 |  you may not use this file except in compliance with the License.
 |  You may obtain a copy of the License at
 |
 |      http://www.apache.org/licenses/LICENSE-2.0
 |
 |  Unless required by applicable law or agreed to in writing, software
 |  distributed under the License is distributed on an "AS IS" BASIS,
 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 |  See the License for the specific language governing permissions and
 |  limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>InvokeWF</groupId>
        <artifactId>InvokeWF</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>InvokeWF.core</artifactId>
    <packaging>bundle</packaging>
    <name>InvokeWF - Core</name>
    <description>Core bundle for InvokeWF</description>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.sling</groupId>
                <artifactId>maven-sling-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <!-- Import any version of javax.inject, to allow running on multiple versions of AEM -->
                        <Import-Package>javax.inject;version=0.0.0,*</Import-Package>
                        <Sling-Model-Packages>
                            com.aem.community.wf.core
                        </Sling-Model-Packages>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- OSGi Dependencies -->
        <dependency>
            <groupId>com.adobe.aem</groupId>
            <artifactId>uber-jar</artifactId>
            <classifier>apis</classifier>
        </dependency>
     
        <dependency>
            <groupId>org.apache.geronimo.specs</groupId>
            <artifactId>geronimo-atinject_1.0_spec</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.cmpn</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.annotation</artifactId>
        </dependency>
        <!-- Other Dependencies -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.jcr</groupId>
            <artifactId>jcr</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.adobe.aem</groupId>
            <artifactId>uber-jar</artifactId>
            <classifier>apis</classifier>
        </dependency>
        <dependency>
            <groupId>org.apache.sling</groupId>
            <artifactId>org.apache.sling.models.api</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
        </dependency>
        <dependency>
            <groupId>junit-addons</groupId>
            <artifactId>junit-addons</artifactId>
        </dependency>
    </dependencies>
</project>

Build the OSGi bundle using Maven

To build the OSGi bundle by using Maven, perform these steps:

  1. Open the command prompt and go to the C:\AdobeCQ\InvokeWF.
  2. Run the following maven command: mvn -PautoInstallPackage install.
  3. The OSGi component can be found in the following folder: C:\AdobeCQ\InvokeWF\core\target. The file name of the OSGi component is InvokeWF.core-1.0-SNAPSHOT.jar.

The command -PautoInstallPackage automatically deploys the OSGi bundle to AEM.

View the Active OSGi bundle

After you deploy the OSGi bundle, you can see it in the Apache Felix Web Console.

OSGi
OSGi bundle

View your OSGi bundle by performing these steps:

  1. Login to Adobe Apache Felix Web Console at http://server:port/system/console/bundles (default admin user = admin with password= admin).
  2. Click the Bundles tab, sort the bundle list by Id, and note the Id of the last bundle.

Create an AEM Workflow that uses the custom workflow step that approves an asset

In this step, create an AEM workflow in the Touch UI environment that approves the asset and uses uses an OR Split step. You also use the ApproveStep and DenyStep custom steps.  

 

Model
An AEM Workflow that approves a digital asset

To create a workflow that deletes content and logs a message using the custom step, perform the following tasks:

1. Click the Hammer icon in the main AEM view at http://localhost:4502. 

2. Click Workflow on the side menu. 

3. Click Models. 

4. Click the Create button then Create Model

5. Enter ApproveAsset as the workflow title.

6. Open the ApproveAsset workflow by selecting it and clicking Edit. 

 

EditModel
Opening a workflow model

7. As the 1st step, add a Partipant Step and set to admin.

8. As the 2nd step, add the OR Split step. Set to two branches, as shown in this illustration (there is no need to add code for this example). 

 

Branches
The OR Split step

8. Add a Process Step to the right side of the Split. In this step, select Good Process, which corresponds to the label property in the ApproveStep class. 

Approve
Add the Logger custom step

Under the Common tab, specify Approve as the title field. This value is what shows up in the admin email when the admin approves for denies the asset (shown later in this article).

9. Add a Process Step to the left side of the Split. In this step, select Deny Process, which corresponds to the label property in the DenyStep class. 

 

Deny
Touch UI resource types in a custom workflow step dialog

Under the Common tab, specify Reject as the title field. This value is what shows up in the admin email when the admin approves for denies the asset (shown later in this article).

10. Click the Sync button.

Note:

In this above model, you see the email step that uses the messageGatewayService . This is a custom workflow step that sends an email message. This is not implemented in this article. For information about creating a custom email step, see this AEM Community article Creating custom AEM workflow steps that send email messages.  Also, you can read the email property on the metadata node to get the email address (the Servlet in this example adds an email property to this node). To learn how to read a JCR node property, see Programmatically Accessing Adobe CQ Content using the JCR API

Modify the HelloWorld component

Modify the HelloWorld component to make it a form to invoke the Sling Servlet. 

CRXDE
The InvokeWF app in the AEM JCR

The HelloWorld component that invokes the AEM Sling Servlet. 

Hello
The HelloWorld component that uses AJAX to invoke the Sling Servlet

In this example, notice that the 1st field accepts the location of the digital asset to send to the workflow. The second field is the email address to pass to the workflow. When the user clicks the submit button, the workflow is invoked. 

Add the following code to the /apps/InvokeWF/components/content/helloworld/helloworld.html file. 

<sly data-sly-use.clientLib="/libs/granite/sightly/templates/clientlib.html" data-sly-call="${clientLib.js @ categories='InvokeWF.base'}" data-sly-unwrap/>
<div>
    <h4> Invoke an AEM Workflow that approves the Selected Asset</h4>
       <form>    
            <fieldset>
            <legend>Specify Asset and email address:</legend>
            <input class="btmspace-15" type="text" id="assetPath" value="/content/dam/we-retail/en/people/mens/men_1.jpg" placeholder="Name">
            <input class="btmspace-15" type="text" id="email" value="scottm@adobe.com" placeholder="Email address">
            <input type="button" value="Submit"  name="submit" id="submit" value="Submit">
          </fieldset>
        </form>
 
    </div>

Create the ClientLibs folder

In order to perform an AJEX operation to invoke the Sling Servlet, you need to create a ClientLibs folder. Add a cq:ClientLibraryFolder node located at:

/apps/InvokeWF

In this folder, add the following two files:

  • js.txt - references the script.js file
  • script.js - contains JavaScript logic that invokes the servlet by using an AJAX operation

Set these two properties on the clientlibs node: 

  • categories (String[]) - InvokeWF.base
  • dependencies (String[]) - cq.jquery

The following code represents the script.js file. This JS file contains application logic to invoke the AEM Sling Servlet by using AJAX. Notice that two parameters are specified: the path of the digital asset and the emal address. 

$(document).ready(function(){
 
     $('body').hide().fadeIn(1000);
    $('#submit').click(function() {
 
 
 
      //read the values
        var assetPath = $('#assetPath').val(); 
        var myemail = $('#email').val(); 
 

 
        //alert(myemail) ; 
 
 
 
    //Perform an AJAX operation
        $.ajax({
type: 'GET', 
url:'/bin/invokeWF',
data:'assetPath='+ assetPath+'&email='+ myemail,
success: function(msg){
    alert(msg); //display the data returned by the servlet
}
});
 
 
 
 
});
});

Invoke the ApproveAsset Workflow

The final task to perform is to invoke the workflow from the Experience Manager page located at:

http://localhost:4502/editor.html/content/InvokeWF/en.html

Enter a path to an AEM digial Asset (/content/dam/we-retail/en/people/mens/men_1.jpg is used as a default value) and an email address. Click the submit button. 

Next, open the admin message.

wf4
The admin message generated by the first step in the workflow

You will see the first step of the workflow.

men
A message sent to the admin account lets the admin approve or deny the asset

When the Complete button is clicked, a dialog box appears promoting the user to either approve or deny the asset, as shown here. 

Men2
Select a workflow

Note:

The Reject and Approve values lines up with the Title in the Process Step steps in the workflow model. There are two Process steps in the model. One on right side of the split (Approve) and the other on the left side of the split (Reject). 

If the Approve option is selected, the ApproveStep class is invoked. This class changes the xmpRights:UsageTerms property on the metadata node (for example - /content/dam/we-retail/en/people/mens/men_1.jpg/jcr:content/metadata) to Approved

Men5
The Asset property on the metadata node is set to Approved

In addition, a message in the log file provides this information: 

23.07.2018 11:21:54.730 *INFO* [JobHandler: /var/workflow/instances/server0/2018-07-23/approveasset_1:/content/dam/we-retail/en/people/mens/men_1.jpg] com.aem.community.wf.core.ApproveStep **** This asset was accepted men_1.jpg and approved by admin

The following video shows this use case in action. 

 


See also

Congratulations, you have just created an AEM workflow that approves or rejects an asset. 

You can view additional AEM Community generated content:

Join the AEM community at: Adobe Experience Manager Community

 

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  Twitter™ and Facebook posts are not covered under the terms of Creative Commons.

Legal Notices   |   Online Privacy Policy