Developing Workflows

This page describes the main elements when developing workflows. If needed refer to the information on using workflows.

There are many code samples to help you get started quickly.

A workflow is made of steps. Automated steps, also called process steps, can be defined by using either an ECMA script or a service (a Java class in a bundle). Rules for OR split can be defined with ECMA scripts. Services can be developed to listen to special workflow events and perform tasks according to the business logic. The Java API, the main workflow objects and the REST API are described. The last section explains how to change the workflow specific log level to efficiently debug the code.

New and Noteworthy in 5.4

Step Components

CQ 5.4 introduces a new workflow editor built on the well known concept of components. Every process step is an own component in the sidekick. It allows to build step specific and user friendly edit dialogs, especially for configuring process arguments. You can have a look at the edit dialog of the new Create Web Enabled Image process step component. Instead of inputting the process arguments as a one single string, the user is presented a form with dedicated fields for the arguments.

There is also a generic process step component called Process Step that allows to use custom process step implementations without building a step component. Please refer to Creating a custom step component  for more information on how to create your own custom step component.

Dynamic Participant Step

While it was possible to create dynamic participant steps before, there is since 5.4 a step component called Dynamic Participant Step that allows to select custom participant chooser implementations for easy integration into the workflow model. 

See Defining a participant step: with a Java class  for more information on how to create your own custom participant chooser step.

API Changes

The step components allow to create more flexible process arguments than just a single string like until 5.3. And because it is possible to define multi valued arguments, the existing API changed to reflect this flexiblity. So API changes are mainly around passing and accessing process arguments, also known as meta data in the API. 

The following interfaces are declared deprecated:

  • com.day.cq.workflow.exec.JavaProcess
  • com.day.cq.workflow.exec.JavaProcessExec
  • com.day.cq.workflow.exec.ParticipantChooser

And replaced by the following new interfaces:

  • com.day.cq.workflow.exec.WorkflowProcess
  • com.day.cq.workflow.exec.ParticipantStepChooser
  • com.day.cq.workflow.metadata.MetaDataMap

Deprecated API means it is still available in 5.4 and also backward compatible, but it will probably be removed in future releases. So you are encouraged to use the new API and migrate existing custom process steps if any.

MetaDataMap

This new interface replaces the previous string array to pass meta data (process arguments) into the new step execution method. It is basically a map and works like the Sling ValueMap. See Working with process arguments for more details.

Overview of the Main Workflow Objects

This section describes the main workflow objects.

Model

Is made of WorkflowNodes and WorkflowTransitions. The transitions connect the nodes and define the "flow". The Model has always a start node and an end node.

Workflow models are versioned. Running Workflow Instances keep the initial workflow model version that is set when the workflow is started.

Step

There are different types of workflow steps: 

  • Participant (User/Group) 
  • Process (Script, Java method call) 
  • Container (Sub Workflow) 
  • OR Split/Join 
  • AND Split/Join

All the steps share the following common properties: Autoadvance and Timeout alerts (scriptable).

Transition

Defines the link between two consecutive steps.

It is possible to apply rules.

WorkItem

Is the "there is a task identifier" and is put into the respective inbox.

A workflow instance can have one or many WorkItems at the same time (depending on the workflow model).

The WorkItem references the workflow instance.

In the repository the WorkItem is stored below the workflow instance.

Payload

References the resource that has to be advanced through a workflow.

The payload implementation references a resource in the repository (by either a path or an UUID) or a resource by a URL or by a serialized java object. Referencing a resource in the repository is very flexible and in conjunction with sling very productive: for example the referenced node could be rendered as a form.

Lifecycle

Is created when starting a new workflow (by choosing the respective workflow model and defining the payload) and ends when the end node is processed. 

The following actions are possible on a workflow instance:  

  • Terminate
  • Suspend
  • Resume
  • Restart

Completed and terminated instances are archived.

Inbox

Each logged in user has its own workflow inbox in which the assigned WorkItems are accessible.

The WorkItems are assigned either to the user itself or to the group to which he belongs.

Overview of the APIs: Java, ECMA and REST

Workflow objects can be accessed through the following APIs: the Java, ECMA and REST APIs.

This section gives an overview of the different APIs. For a complete description of:

The following table points to the appropriate Java objects when developing workflows:

Features Objects
Accessing a workflow
WorkflowService
WorkflowSession
Executing a workflow
WorkflowSession
Workflow
WorkItem
WorkflowData
Querying a workflow
WorkflowSession
WorkItem
Workflow
Managing a workflow model
WorkflowModel
WorkflowNode
WorkflowTransition
WorkflowSession

The following code snippets help you to programmatically access the most used workflow objects and methods. The REST API is shown by using curl, a command line tool that sends requests to a server and manages its responses.

Getting the Workflow Service

In an OSGI service or in an OSGI component:

/** @scr.reference policy="static" */
private WorkflowService workflowService;

Java: in a JSP script or in a servlet:

SlingHttpServletRequest slingReq = (SlingHttpServletRequest) request;
WorkflowService workflowService = sling.getService(WorkflowService.class);
Session session = slingReq.getResourceResolver().adaptTo(Session.class);

ECMAScript:

var slingReq = request;
var workflowService = sling.getService(Packages.com.day.cq.workfow.WorkflowService.class);
var session = slingReq.getResourceResolver().adaptTo(Packages.javax.jcr.Session.class);

Getting a Workflow Session

The WorkflowService (OSGI service) provides a method in order to get a WorkflowSession.

Java:

WorkflowSession wfSession = workflowService.getWorkflowSession(session);

ECMAScript:

var wfSession = workflowService.getWorkflowSession(session);

Creating, Reading or Deleting Workflow Models

Java:

WorkflowModel model = wfSession.createNewModel(name);
WorkflowModel model = wfSession.deleteModel(id);
WorkflowModel model = wfSession.getModel(id);

ECMAScript:

var model = wfSession.createNewModel(name);
var model = wfSession.deleteModel(id);
var model = wfSession.getModel(id);

REST:

# creating the model called "name"
curl -u admin:admin -d "title=name" http://localhost:4502/etc/workflow/models

# deleting the model by its id
curl -u admin:admin -X DELETE http://localhost:4502/etc/workflow/models/{id}

# getting the model by its id
curl -u admin:admin http://localhost:4502/etc/workflow/models/{id}

Managing Workflow Instances

Java:

// starting a workflow
WorkflowModel model = wfSession.getModel(workflowId);
WorkflowData wfData = wfSession.newWorkflowData("JCR_PATH", repoPath);
wfSession.startWorkflow(model, wfData);

// querying and managing a workflow
Workflow workflows[] workflows = wfSession.getWorkflows(“RUNNING“);
Workflow workflow= wfSession.getWorkflow(id);
wfSession.suspendWorkflow(workflow);
wfSession.resumeWorkflow(workflow);
wfSession.terminateWorkflow(workflow);

ECMAScript:

// starting a workflow
var model = wfSession.getModel(workflowId);
var wfData = wfSession.newWorkflowData("JCR_PATH", repoPath);
wfSession.startWorkflow(model, wfData);

// querying and managing a workflow
var workflows = wfSession.getWorkflows(“RUNNING“);
var workflow= wfSession.getWorkflow(id);
wfSession.suspendWorkflow(workflow);
wfSession.resumeWorkflow(workflow);
wfSession.terminateWorkflow(workflow);

REST:

# starting a workflow
curl -d "model={id}&payloadType={type}&payload={payload}" http://localhost:4502/etc/workflow/instances
# for example:
curl -u admin:admin -d "model=/etc/workflow/models/name&payloadType=JCR_PATH&payload=/content/geometrixx/en/services" http://localhost:4502/etc/workflow/instances

# listing the instances
curl -u admin:admin http://localhost:4502/etc/workflow/instances/

# suspending a workflow
curl -d "state=SUSPENDED" http://localhost:4502/etc/workflow/instances/{id}
# for example:
curl -u admin:admin -d "state=SUSPENDED" http://localhost:4502/etc/workflow/instances/2009-12-09/name_1_1260365650960705000

# resuming a workflow
curl -d "state=RUNNING" http://localhost:4502/etc/workflow/instances/{id}
# for example:
curl -u admin:admin -d "state=RUNNING" http://localhost:4502/etc/workflow/instances/2009-12-09/name_1_1260365650960705000

# terminating a workflow
curl -d "state=ABORTED" http://localhost:4502/etc/workflow/instances/{id}
# for example:
curl -u admin:admin -d "state=ABORTED" http://localhost:4502/etc/workflow/instances/2009-12-09/name_1_1260365650960705000

Managing Work Items

Java:

// querying work items
WorkItem[] workItems = wfSession.getActiveWorkItems();
WorkItem workItem = wfSession.getWorkItem(id);

// getting routes
List<Route> routes = wfSession.getRoutes(workItem);

// delegating
List<Authorizable> delegatees = wfSession.getDelegatees(workItem);
wfSession.delegateWorkItem(workItem, delegatees.get(0));

// completing or advancing to the next step
wfSession.complete(workItem, routes.get(0));

ECMAScript:

// querying work items
var workItems = wfSession.getActiveWorkItems();
var workItem = wfSession.getWorkItem(id);

// getting routes
var routes = wfSession.getRoutes(workItem);

// delegating
var delegatees = wfSession.getDelegatees(workItem);
wfSession.delegateWorkItem(workItem, delegatees.get(0));

// completing or advancing to the next step
wfSession.complete(workItem, routes.get(0));

REST:

# listing the work items
curl -u admin:admin http://localhost:4502/bin/workflow/inbox

# delegating
curl -d "item={item}&delegatee={delegatee}" http://localhost:4502/bin/workflow/inbox
# For example: curl -u admin:admin -d "item=/etc/workflow/instances/2010-01-11/test1_1263223169820641000/workItems/node1_etc_workflow_instances_2010-01-11_test1_1263223169820641000&delegatee=author" http://localhost:4502/bin/workflow/inbox

# completing or advancing to the next step
curl -d "item={item}&route={route}" http://localhost:4502/bin/workflow/inbox
# For example:
curl -d "item=/etc/workflow/instances/2010-01-11/test1_1263223169820641000/workItems/node1_etc_workflow_instances_2010-01-11_test1_1263223169820641000&route=233123169" http://localhost:4502/bin/workflow/inbox

Working with Process Arguments

Since the 5.4 version, process arguments can be easily accessed. This section describes how to access the MetaDataMap object passed into the API methods, to get access to the process arguments.

Note

This section describes how to work with arguments for process steps, but the same applies for dynamic participant choosers as well.

Using the Process Step Component

Using the Process Step component allows to add a single argument string in the components edit dialog. This argument string is then available to the arguments map through the PROCESS_ARGS key. 

For example, setting the arguments argument1, argument2 as shown in the screenshot below:

file

The arguments are available through the arguments map as follows:

//reading arguments from Process Component
String argument = args.get("PROCESS_ARGS", "default value");

//equal will be true
boolean equal = argument.equals("argument1, argument2");

Note

It is in the callers responsability to extract multiple arguments from the single argument string, as for example comma-separated.

Using a Custom Step Component

Using a custom step component gives you control over the arguments that are put on the arguments map through the argument keys. Let's look at the Create Web Enabled Image component as an example of a custom step component. This component offers a more user-friendly way of setting arguments, as shown below:

file

The same arguments had to be encoded in a singe argument string until 5.4, looking like:

dimension:300:200", "skip:image/gif", "skip:image/jpeg"

Since 5.4 the arguments are available in the implementation as follows:


//get the width as string, defaulting to '100'
String width = args.get("WIDTH", "100");

//make an attempt to implicitly convert to an long
Long width = args.get("WIDTH", Long.class);

//accessing multi value arguments
String[] skipList = args.get("SKIP", String[].class);

Using Both the Process Step Component and a Custom Step Component

You can also design your process implementation to be used with the Process Component and a custom component at the same time. It is then up to the implementation to find out how the process is called and treat the arguments accordingly. You can find out if the process is called from the Process Component by testing for the PROCESS_ARGS key. The following snippet should give you an idea:

 
String processArgs = metaData.get("PROCESS_ARGS", String.class);
//called from the Process Step component
if (processArgs != null && !processArgs.equals("")) {
//process the single arguments string
}
//called from a custom component
else {
//access individual arguments accordingly
}

Scripts and Process Arguments

Within a script for a Process Step component, arguments are available through the args object.

When creating a custom step component, the object metaData is available in a script. This object is limited to single string argument. 

Creating a Custom Step Component

This section walks you through the steps of creating a custom step component. It shows how to create a step component that allows to set a few arguments, and a process step that writes the values of the arguments into the log file:

  1. Copy the noopprocess at /libs/cq/workflow/components/workflow/noopprocess to /apps/cq/workflow/components/workflow/loggerprocess
  2. Change the jcr:title and jcr:description properties of /apps/cq/workflow/components/workflow/loggerprocess accordingly.
  3. Go to the cq:formParamters node at /apps/cq/workflow/components/workflow/loggerprocess/cq:editConfig/cq:formParameters
  4. Change the jcr:title and jcr:description properties accordingly.
  5. Change the PROCESS property to the step implementation: let's assume com.day.cq.workflow.impl.process.LoggerProcess
  6. Change the title property of the dialog node at /apps/cq/workflow/components/workflow/loggerprocess/dialog
  7. Change the path property of the processargs node at /apps/cq/workflow/components/workflow/loggerprocess/dialog/items/tabs/items/processargs and set it to /apps/cq/workflow/components/workflow/loggerprocess/processargs.infinity.json
  8. Add the argument widgets below /apps/cq/workflow/components/workflow/loggerprocess/processargs/items/arguments/items
  9. Add a text field widget and set the name property to ./metaData/argSingle
  10. Add a multifield widget and set the name property to ./metaData/argMulti
The Logger Process component is added to the Sidekick as follows:

file

Add the Logger Process to your workflow model and open the edit dialog. It looks similiar to the following:

file

Let's continue by creating the com.day.cq.workflow.impl.process.LoggerProcess implementation. See here for details about creating a Java based process step:

package com.day.cq.workflow.impl.process;

import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.exec.WorkflowData;
import com.day.cq.workflow.exec.WorkflowProcess;
import com.day.cq.workflow.metadata.MetaDataMap;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import java.util.Arrays;

/**
* Sample workflow process that logs arguments into the logfile.
*/
@Component
@Service
@Properties({
@Property(name = Constants.SERVICE_DESCRIPTION, value = "Logger process implementation."),
@Property(name = Constants.SERVICE_VENDOR, value = "Adobe"),
@Property(name = "process.label", value = "Logger Process")})
public class LoggerProcess implements WorkflowProcess {

private static final Logger log = LoggerFactory.getLogger(LoggerProcess.class);

public void execute(WorkItem item, WorkflowSession session, MetaDataMap args) throws WorkflowException {
String singleValue = args.get("argSingle", "not set");
String[] multiValue = args.get("argMulti", new String[]{"not set"});

log.info("---> Single Value: {}", singleValue);
log.info("---> Multi Value: {}", Arrays.toString(multiValue));
}
}

Custom Step Components for Participant Choosers

To create a custom dynamic participant chooser component, the same process described in the Creating a Custom Step Component section  applies except that at step 5, the property to reference the participant chooser implementation needs to be called DYNAMIC_PARTICIPANT instead of PROCESS.

Defining a Participant Step: with a Java class


To define a participant step as an OSGI service component (Java bundle):

  1. The OSGI component needs to implement the ParticipantStepChooser interface with its getParticipant() method. See the example code below. 
    Create the bundle and deploy it into the OSGI container. Refer to the documentation about creating a bundle with CRXDE LiteCRXDE or Eclipse.
    Note: The package name needs to be added to the <Private-Package> section of the maven-bundle-plugin configuration.
  2. Add the SCR property "chooser.label" and set the value as you please. This will be the name as which your participant chooser is listed, using the Dynamic Participant Step component. See the example below.
  3. In the CQ Workflow console, add the dynamic participant step to the workflow using the generic Dynamic Participant Step component.
  4. In the edit dialog select the Participant Chooser tab and select your chooser implementation.
  5. If  you use arguments in your code set the Process Arguments. For this example: /content/geometrixx/en.
  6. Save the changes.

The java methods, respectively the classes that implement the executable Java method are registered as OSGI services, enabling you to add methods at anytime during runtime.

package com.mycompany.test;

import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.ParticipantStepChooser;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.exec.WorkflowData;
import com.day.cq.workflow.metadata.MetaDataMap;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Constants;

/**
* Sample dynamic participant step that determines the participant based on a path given as argument.
*/
@Component
@Service
@Properties({
@Property(name = Constants.SERVICE_DESCRIPTION, value = "A sample implementation of a dynamic participant chooser."),
@Property(name = Constants.SERVICE_VENDOR, value = "Adobe"),
@Property(name = ParticipantStepChooser.SERVICE_PROPERTY_LABEL, value = "Sample Participant Chooser")})
public class MyDynamicParticipant implements ParticipantStepChooser {

private static final String TYPE_JCR_PATH = "JCR_PATH";

public String getParticipant(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap args) throws WorkflowException {
WorkflowData workflowData = workItem.getWorkflowData();
if (workflowData.getPayloadType().equals(TYPE_JCR_PATH)) {
String path = workflowData.getPayload().toString();
String pathFromArgument = args.get("PROCESS_ARGS", String.class);
if (pathFromArgument != null && path.startsWith(pathFromArgument)) {
return "admin";
}
}
return "administrators";
}
}

Defining a Participant Step: with an ECMA Script

To define a participant step as an ECMA script:

  1. Create the script (for example with CRXDE Lite) and save it in the repository under /etc/workflow/scripts.
    For example: /etc/workflow/scripts/myDynamicParticipant.ecma
  2. As you can see in the following code samples, a typical script starts with the workItem to get a workflow data object. It then checks for the relevant criteria and returns the name of the dynamically chosen participant.
  3. By adding the jcr:title property to the jcr:content node of your script in the repository, you can define the title under which the script get's listed in the Dynamic Participant Step component. To add the jcr:title property you must first add the mix:title mixin to the jcr:content node.
  4. In the CQ Workflow console, add the dynamic participant step to the workflow using the generic Dynamic Participant Step component.
  5. In the edit dialog select the Participant Chooser tab and select your chooser implementation.
  6. Save the changes.

The scripts are located in the jcr repository and executed from there.

The following objects are available within ECMA scripts:

The following script sample help you get started.

function getParticipant() {
var workflowData = workItem.getWorkflowData();
if (workflowData.getPayloadType() == "JCR_PATH") {
var path = workflowData.getPayload().toString();
if (path.indexOf("/content/geometrixx/en") == 0) {
return "admin";
} else {
return "administrators";
}
}
}

Defining a Process Step: with a Java Class

To define a process step as an OSGI service component (Java bundle):

  1. Create the bundle and deploy it into the OSGI container. Refer to the documentation about creating a bundle with CRXDE LiteCRXDE or Eclipse.
    Note 1: the OSGI component needs to implement the WorkflowProcess interface with its execute() method. See the example code below.
    Note 2: The package name needs to be added to the <Private-Package> section of the maven-bundle-plugin configuration.
  2. Add the SCR property "process.label"  and set the value as you please. This will be the name which your process step is listed as when using the generic Process Step component. See the example below.
  3. In the CQ Workflow console, add the process step to the workflow using the generic Process Step component.
  4. In the edit dialog, go to the Process tab and select your process implementation. 
  5. If you use arguments in your code, set the Process Arguments . For example: false.
  6. Save the changes.

The java methods, respectively the classes that implement the executable Java method are registered as OSGI services, enabling you to add methods at anytime during runtime.

The following OSGI component adds the property approved to the page content node when the payload is a page:

package com.mycompany.test;

import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.exec.WorkflowData;
import com.day.cq.workflow.exec.WorkflowProcess;
import com.day.cq.workflow.metadata.MetaDataMap;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Constants;

import javax.jcr.Node;
import javax.jcr.RepositoryException;

/**
* Sample workflow process that sets an <code>approve</code> property to the payload based on the process argument value.
*/
@Component
@Service
@Properties({
@Property(name = Constants.SERVICE_DESCRIPTION, value = "A sample workflow process implementation."),
@Property(name = Constants.SERVICE_VENDOR, value = "Adobe"),
@Property(name = "process.label", value = "My Sample Workflow Process")})
public class MyProcess implements WorkflowProcess {

private static final String TYPE_JCR_PATH = "JCR_PATH";

public void execute(WorkItem item, WorkflowSession session, MetaDataMap args) throws WorkflowException {
WorkflowData workflowData = item.getWorkflowData();
if (workflowData.getPayloadType().equals(TYPE_JCR_PATH)) {
String path = workflowData.getPayload().toString() + "/jcr:content";
try {
Node node = (Node) session.getSession().getItem(path);
if (node != null) {
node.setProperty("approved", readArgument(args));
session.getSession().save();
}
} catch (RepositoryException e) {
throw new WorkflowException(e.getMessage(), e);
}
}
}

private boolean readArgument(MetaDataMap args) {
String argument = args.get("PROCESS_ARGS", "false");
return argument.equalsIgnoreCase("true");
}
}

Note

If the process fails three times in a row, an item is placed in the Inbox of the workflow administrator to inform him.

Defining a Process Step: with an ECMA Script

To define a process step as an ECMA script:

  1. Create the script (for example with CRXDE Lite) and save it in the repository under /etc/workflow/scripts.
    As you can see in the following code samples, a typical script starts with the workItem to get a workflow data object. It then checks the payload type, gets the payload node, sets a property and saves the node.
  2. By adding the jcr:title property to the jcr:content node of your script in the repository, you can define the title under which the script get's listed in the Process Step component. To add the jcr:title property you must first add the mix:title mixin to the jcr:content node.
  3. In the CQ Workflow console, add the process step to the workflow using the generic Process Step component.
  4. In the edit dialog, go to the Process tab and select your process implementation.
  5. Save the changes

The scripts are located in the JCR repository and executed from there.

The following objects are available within ECMA scripts:

The following script samples help you get started.

The following script adds the property approved to the payload.

var workflowData = workItem.getWorkflowData();
if (workflowData.getPayloadType() == "JCR_PATH") {
var path = workflowData.getPayload().toString();
var node = workflowSession.getSession().getItem(path);
node.setProperty("approved", args[0] == "true" ? true : false);
node.save();
}

The following script checks if the payload is an image (png file), creates a black and white image out of it and saves it as a sibling node.

var workflowData = workItem.getWorkflowData();
if (workflowData.getPayloadType() == "JCR_PATH") {
var path = workflowData.getPayload().toString();
var node = workflowSession.getSession().getRootNode().getNode(path.substring(1));
if (node.isNodeType("nt:file") && node.getProperty("jcr:content/jcr:mimeType").getString().indexOf("image/") == 0) {
var is = node.getProperty("jcr:content/jcr:data").getStream();
var layer = new Packages.com.day.image.Layer(is);
layer.grayscale();
var parent = node.getParent();
var gn = parent.addNode("grey" + node.getName(), "nt:file");
var content = gn.addNode("jcr:content", "nt:resource");
content.setProperty("jcr:mimeType","image/png");
var cal = Packages.java.util.Calendar.getInstance();
content.setProperty("jcr:lastModified",cal);
var f = Packages.java.io.File.createTempFile("test",".png");
var tout = new Packages.java.io.FileOutputStream(f);
layer.write("image/png", 1.0, tout);
var fis = new Packages.java.io.FileInputStream(f);
content.setProperty("jcr:data", fis);
parent.save();
tout.close();
fis.close();
is.close();
f.deleteOnExit();
}
}

Note

If the process fails three times in a row, an item is placed in the Inbox of the workflow administrator to inform him.

Extending the DAM Delete Asset Workflow to Deactivate the Asset

In the default configuration an asset is deleted from /content/dam but not deactivated when its original file is deleted in /var/dam (for example by using WebDAV). To trigger the deactivation of the asset when its original file is deleted in /var/dam, add the following ECMA script based process step after the Delete asset step in the DAM Delete Asset Workflow:

log.info("executing script deactivate_asset.ecma now...");
log.info("Payload: " + workItem.getWorkflowData().getPayload());

var workflowData = workItem.getWorkflowData();
if (workflowData.getPayloadType() == "JCR_PATH") {
var path = workflowData.getPayload().toString();
var damManager = sling.getService(Packages.com.day.cq.dam.api.DamManager);
var assetPath = damManager.createMetadataPath(path);
log.info("assetPath: " + assetPath);
var replicator = sling.getService(Packages.com.day.cq.replication.Replicator);
var session = workflowSession.getSession();
replicator.replicate(session, Packages.com.day.cq.replication.ReplicationActionType.DEACTIVATE, assetPath);
}

Listening to Workflow Events

This section shows you through an example how to define a service that listens to workflow events and performs tasks when a specific workflow event happens.

package com.mycompany.test;

import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.apache.sling.event.EventUtil;
import org.apache.sling.event.JobProcessor;
import org.apache.sling.jcr.api.SlingRepository;
import org.osgi.service.event.Event;
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.WorkItem;
import com.day.cq.workflow.exec.Workflow;

/**
* The <code>WorkflowEventCatcher</code> is listening to workflow events
*
* @scr.component metatype="false" immediate="true"
* @scr.service interface="org.osgi.service.event.EventHandler"
*
* @scr.property name="event.topics"
* valueRef="com.day.cq.workflow.event.WorkflowEvent.EVENT_TOPIC"
*/
public class WorkflowEventCatcher implements EventHandler, JobProcessor {

/** the logger */
private static final Logger log =
LoggerFactory.getLogger(WorkflowEventCatcher.class);

/** @scr.reference policy="static" */
private WorkflowService workflowService;

/** @scr.reference policy="static" */
private SlingRepository repository;

public void handleEvent(Event event) {
EventUtil.processJob(event, this);
}

public boolean process(Event event) {
log.debug("Received event of topic: " + event.getTopic());
String topic = event.getTopic();
WorkflowSession wfSession = getWorkflowSession();
try {
if (topic.equals(WorkflowEvent.EVENT_TOPIC)) {
Object eventType = event.getProperty(WorkflowEvent.EVENT_TYPE);
String instanceId =
(String) event.getProperty(WorkflowEvent.WORKFLOW_INSTANCE_ID);
Workflow instance = null;
try {
instance = (instanceId != null) ? wfSession.getWorkflow(instanceId) : null;
} catch (WorkflowException we) {
log.warn("Unable to load workflow instance", we);
}
if (instance != null) {
// workflow instance events
if (eventType.equals(WorkflowEvent.WORKFLOW_STARTED_EVENT) ||
eventType.equals(WorkflowEvent.WORKFLOW_RESUMED_EVENT) ||
eventType.equals(WorkflowEvent.WORKFLOW_SUSPENDED_EVENT)) {
// your code comes here...
} else if (
eventType.equals(WorkflowEvent.WORKFLOW_ABORTED_EVENT) ||
eventType.equals(WorkflowEvent.WORKFLOW_COMPLETED_EVENT)) {
// your code comes here...
}
// workflow node event
if (eventType.equals(WorkflowEvent.NODE_TRANSITION_EVENT)) {
WorkItem currentItem =
(WorkItem) event.getProperty(WorkflowEvent.WORK_ITEM);
// your code comes here...
}
}
}
} finally {
if (wfSession != null) {
wfSession.logout();
}
}
return true;
}

//----------< helper >-----------------------------------------------------
private WorkflowSession getWorkflowSession() {
try {
Session adminSession = repository.loginAdministrative(null);
return workflowService.getWorkflowSession(adminSession);
} catch (RepositoryException re) {
log.error("RepositoryException: " + re);
}
return null;
}
}

Defining a Rule for an OR Split

OR splits allow you to introduce conditional processing paths into your workflow. To define an OR rule, proceed as follows:

  1. Create the two scripts and save them in the repository, for example under /apps/myapp/workflow/scripts.
    Note: the scripts must have a function check() that returns a boolean.
  2. In the CQ Workflow console, add the OR split to the workflow.
  3. Edit the properties of one branch of the OR split.
  4. Define the Default Route by setting the Value to true.
  5. As Rule, set the path to the script. For example: /apps/myapp/workflow/scripts/myscript1.ecma.
  6. Edit the properties of the other branch of the OR split.
  7. As Rule, set the path to the other script. For example: /apps/myapp/workflow/scripts/myscript2.ecma.
  8. Set the properties of the steps in each branch. Make sure the User/Group is set.

The following objects are available within ECMA scripts:

The following sample script returns true if the node is a JCR_PATH located under /content/geometrixx/en:

function check() {
if (workflowData.getPayloadType() == "JCR_PATH") {
var path = workflowData.getPayload().toString();
var node = jcrSession.getItem(path);

if (node.getPath().indexOf("/content/geometrixx/en") >= 0) {
return true;
} else {
return false;
}
} else {
return false;
}
}

Managing Workflow Packages

Workflow packages can be passed to a workflow for processing. They are defined, on a specialized page, as a list of links to the required resources (for example, links to pages and assets).

The workflow script must be programmed to process the resources listed in the package, but not the page defining the package itself. For example, the workflow scripts com.day.cq.wcm.workflow.process.ActivatePageProcess and com.day.cq.wcm.workflow.process.DeactivatePageProcess will activate/deactivate all resources listed in the package.

You can develop your own workflow scripts to process such a package, for example:

/**
* @see com.day.cq.workflow.exec.JavaProcess#execute(com.day.cq.workflow.exec.WorkItem, com.day.cq.workflow.WorkflowSession)
*/
public void execute(WorkItem workItem, WorkflowSession workflowSession) throws Exception {
Session session = workflowSession.getSession();
WorkflowData data = workItem.getWorkflowData();
String path = null;
String type = data.getPayloadType();
if (type.equals(TYPE_JCR_PATH) && data.getPayload() != null) {
String payloadData = (String) data.getPayload();
if (session.itemExists(payloadData)) {
path = payloadData;
}
} else if (data.getPayload() != null && type.equals(TYPE_JCR_UUID)) {
Node node = session.getNodeByUUID((String) data.getPayload());
path = node.getPath();
}

// CUSTOMIZED CODE IF REQUIRED....

if (path != null) {
// check for resource collection
ResourceCollection rcCollection = ResourceCollectionUtil.getResourceCollection((Node)admin.getItem(path), rcManager);
// get list of paths to replicate (no resource collection: size == 1
// otherwise size >= 1
List<String> paths = getPaths(path, rcCollection);
for (String aPath: paths) {

// CUSTOMIZED CODE....

}
} else {
log.warn("Cannot process because path is null for this " + "workitem: " + workItem.toString());
}
}

/**
* helper
*/
private List<String> getPaths(String path, ResourceCollection rcCollection) {
List<String> paths = new ArrayList<String>();
if (rcCollection == null) {
paths.add(path);
} else {
log.debug("ResourceCollection detected " + rcCollection.getPath());
// this is a resource collection. the collection itself is not
// replicated. only its members
try {
List<Node> members = rcCollection.list(new String[]{"cq:Page", "dam:Asset"});
for (Node member: members) {
String mPath = member.getPath();
paths.add(mPath);
}
} catch(RepositoryException re) {
log.error("Cannot build path list out of the resource collection " + rcCollection.getPath());
}
}
return paths;
}

Debugging and Testing Workflows

Refer to the section Debugging to start the CQ server in debug mode.

You can display Debug messages related to workflows in the log files:

  1. In your browser, go to the Apache Felix Console and select Configuration.
  2. As Factory Configurations select Factory Apache Sling Logging Logger Configuration. Click Create.
  3. Set the Log Level to Debug.
  4. Set the Logger to com.day.cq.workflow.
  5. Click Save.
  6. When the development is finished, set the Log Level back to Info.

Test a new workflow as follows:

  1. Open the Workflow console; for example http://localhost:4502/libs/cq/workflow/content./console.html
  2. In the Models tab, right-click the model and select Start.
  3. Define the Payload and click OK.
  4. If one of the steps is a User Step, click the Inbox tab, select the step and perform the desired action.
  5. While running the workflow, monitor the log files.

Note

It is a good practice when debugging a workflow to test the workflow with a  payload type that is different than the one for which it has been developed. For example, if you intend your workflow to deal with Assets, test it by setting a Page as payload and make sure that it does not throw errors.