Using jBPM 6 to create workflows

November 14, 2023

Jahia works with a business process engine to manage its workflows. The business process engine is mainly used for publication operations, which usually require user validation and interaction with external systems. However, you can use the process engine for other operations related to Jahia or for a purely business-oriented workflow.

Jahia is bundled and configured with the jBPM process engine.

Workflow declaration

Workflow processes must be deployed into the process engine and declared in Jahia to be used by site administrators and end-users. See Using jBPM for information about how to create and deploy processes with the jBPM process engine.

Once the workflow is available in the process engine, you must register it with the workflow service. You can do so by creating a org.jahia.services.workflow.WorklowTypeRegistration spring bean in the module. This bean has to declare multiple properties:

  • definition
    The key of the process definition as declared in jBPM.
  • type
    The type of workflow, such as publish, unpublish, or another name. Each workflow must be associated with a workflow type. Only one workflow per type can be associated with a node. For example, if multiple publication workflows exist, one must be chosen at a specific level.
  • forms
    A mapping of task names to task data node types. If a node type is associated with a task, a task data node is created under the task and is available for modification by the user completing the task. The task data can be filled with task input parameters, and its values can also be mapped with output parameters to the process variables. A form based on that node type displays to the user when they complete the task in My Dashboard in Edit mode. In the My tasks components, the task data node displays with a taskData view of this type.
  • permissions
    An optional mapping between workflow actions and Jahia permissions. The map contains the workflow action as the key and the permission full path as the value. A user must be part of a role with the permission to execute the workflow action (see Roles).
    Note that you can base the permission name on a custom variable that has been set previously in the workflow context. For example, in the translationWorkflow, the  jcr:modifyProperties_default_$translateTo permission is replaced by jcr:modifyProperties_default_en if the translateTo variable has been set to en. If no permission is defined for a task, a specific permission is automatically created.
  • canBeUsedForDefault
    Specifies if the workflow definition is available by default on all nodes without having to setup a workflow rule.
  • defaultPriority
    Specifies that if multiple workflows have canBeUsedForDefault, the workflow with the highest priority applies.
    <bean class="org.jahia.services.workflow.WorklowTypeRegistration">
        <property name="type" value="unpublish"/>
        <property name="definition" value="1-step-unpublication"/>
        <property name="forms">
            <map>
                <entry key="start" value="jnt:simpleWorkflow"/>
                <entry key="unpublish" value="jnt:simpleWorkflow"/>
            </map>
        </property>
    </bean>

Using jBPM 6

For more information about jBPM, see http://www.jboss.org/drools/documentation .

jBPM configuration

The jBPM provider configuration file is the following:

 WEB-INF/etc/spring/workflow/applicationcontext-jBPM.xml

This file contains references to other services, the path used by jBPM to get the process definitions, and the location of the jBPM config file.

The JBPMModuleProcessLoader bean, which is loaded in the context of every module, defines where the system looks for the process definition files. This bean is defined in:

 WEB-INF/etc/spring/modules-applicationcontext-registry.xml

Declaring a workflow in jBPM

As you can see in the jBPM configuration file, Jahia uses a wildcard classpath lookup to search for the BPMN workflow process definition files. This means that you can simply put your module in the proper package (by default under org.jahia.modules) in a file with the .bpmn2 extension and it will be picked up when Jahia starts. To be available in Jahia, the process must also be declared in a configuration file. For more information see Workflow declaration.

jBPM forms

jBPM allows you to associate a form with a task. In Jahia, this form is defined by a simple node type. This node type creates the form and the data is stored like a standard JCR node in the process engine. Forms are associated to tasks by a mapping in the workflow registration. For more information, see Workflow declaration.

Here the "request" task uses the "jnt:translationRequest" as a form.

     <bpmn2:userTask id="UserTask_2" name="request">
        ...

The jnt:translationRequest type is defined in a CND file with:

[jnt:translationRequest] > mix:title
- request (string, richtext)
- translateTo (string, choicelist[siteLocales])

The mapping is done during the workflow registration.

<bean name="translationWorkflow" class="org.jahia.services.workflow.WorklowTypeRegistration">
    ...
    <property name="forms">
        <map>
            <entry key="start" value="jnt:translationRequest"/>
            <entry key="request" value="jnt:translationRequest"/>
        </map>
    </property>
</bean>

The execution of this task displays a form in all the engine automatically that the user needs to complete. Data entered in this form can later be used in other work items.

To use and render your form in HTML pages using tasks modules, you need to define a view of that form. To be used by the tasks module, the view must be named taskData. Here's an example from the workflowRemotePublicationForm.taskData.jsp file :

<%@ taglib prefix="template" uri="http://www.jahia.org/tags/templateLib" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="jcr" uri="http://www.jahia.org/tags/jcr" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<template:tokenizedForm>
    <c:url value='${url.base}${currentNode.path}' var="url"/>
    <form id="taskDataForm_${currentNode.parent.identifier}" method="post" action="${url}">
        <input type="hidden" name="jcrMethodToCall" value="put"/>
        <div>
            <jcr:propertyInitializers node="${currentNode}" name="remotePublicationToExecute" var="values"/>
            <fmt:message key="jnt_workflowRemotePublicationForm.remotePublicationToExecute"/> :
            <select name="remotePublicationToExecute">
                <c:forEach items="${values}" var="value">
                    <option value="${value.value.string}">${value.displayName}</option>
                </c:forEach>
            </select>
        </div>
    </form>
</template:tokenizedForm>

Assigning jBPM tasks

jBPM needs to know how to assign new tasks to users when the task is created. Depending on the roles assigned to the node, the task is not assigned to the same users and groups. Jahia automatically assigns the task to the users or groups who have the correct permissions when the task is created. Subsequent changes to the roles are not visible for existing tasks.

Using jBPM customs work items handlers

At one point of the process, the process may need to interact with Jahia services. For example, a publication workflow needs to publish some nodes after a validation. There are multiple ways to do this with jBPM, but the simplest way is to create custom handlers. A few handlers are provided with Jahia to lock or unlock nodes and publish or unpublish nodes. They are located in org.jahia.services.workflow.jbpm.custom.*.

Defining your WorkItem handler

http://docs.jboss.org/jbpm/v6.0.1/userguide/jBPMDomainSpecificProcesses.html#sec.workitemhandler.overview

Handlers must be registered in your module spring file with a name, which is used in the BPMN files in the drools:taskName property of a task. For example, the following handler can be defined:

    <bean id="unlockWH" parent="abstractWH" class="org.jahia.services.workflow.jbpm.custom.UnlockWorkItemHandler">
        <property name="name" value="Unlock node"/>
    </bean>

And the handler is used in a process by the following task:

    <bpmn2:task id="Task_16" drools:taskName="Unlock node" name="Unlock the node">

Sharing data between tasks

You can share variables between tasks using Input/Output variables. On Eclipse, when editing a task, just switch to I/O Parameters and define your Input and Output variables. For example, if your task requires workspace and nodeIds as inputs and needs to define a valid boolean as output, you should have the following configuration:

IOCustomWorkItemHandler.png

In the .bpmn2 file, it is represented as:

<bpmn2:task id="Task_3" name="Call a custom workitem handler" drools:taskName="Custom work item handler">
  <bpmn2:incoming>SequenceFlow_5</bpmn2:incoming>
  <bpmn2:outgoing>SequenceFlow_6</bpmn2:outgoing>
  <bpmn2:ioSpecification id="InputOutputSpecification_5">
    <bpmn2:dataInput id="DataInput_3" itemSubjectRef="ItemDefinition_9" name="workspace"/>
    <bpmn2:dataInput id="DataInput_4" itemSubjectRef="ItemDefinition_278" isCollection="true" name="nodeIds"/>
    <bpmn2:dataOutput id="DataOutput_1" itemSubjectRef="ItemDefinition_603" name="valid"/>
    <bpmn2:inputSet id="InputSet_5" name="Input Set 5">
      <bpmn2:dataInputRefs>DataInput_3</bpmn2:dataInputRefs>
      <bpmn2:dataInputRefs>DataInput_4</bpmn2:dataInputRefs>
    </bpmn2:inputSet>
    <bpmn2:outputSet id="OutputSet_5" name="Output Set 5">
      <bpmn2:dataOutputRefs>DataOutput_1</bpmn2:dataOutputRefs>
    </bpmn2:outputSet>
  </bpmn2:ioSpecification>
  <bpmn2:dataInputAssociation id="DataInputAssociation_3">
    <bpmn2:sourceRef>workspace</bpmn2:sourceRef>
    <bpmn2:targetRef>DataInput_3</bpmn2:targetRef>
  </bpmn2:dataInputAssociation>
  <bpmn2:dataInputAssociation id="DataInputAssociation_4">
    <bpmn2:sourceRef>nodeIds</bpmn2:sourceRef>
    <bpmn2:targetRef>DataInput_4</bpmn2:targetRef>
  </bpmn2:dataInputAssociation>
  <bpmn2:dataOutputAssociation id="DataOutputAssociation_1">
    <bpmn2:sourceRef>DataOutput_1</bpmn2:sourceRef>
    <bpmn2:targetRef>valid</bpmn2:targetRef>
  </bpmn2:dataOutputAssociation>
</bpmn2:task>

Using the WorkItem input variable, you can retrieve input data or set output data in your WorkItemHandler:

@Override
public void executeWorkItem(WorkItem workItem, WorkItemManager workItemManager) {
   // Retrieving input data
   List<String> nodeIds = (List<String>) workItem.getParameter("nodeIds");
   String workspace = (String) workItem.getParameter("workspace");

   /*
   ... Your code here ...
   */

   Boolean result = false;

   // Setting output data
   workItem.getResults().put("valid", result);
   
   workItemManager.completeWorkItem(workItem.getId(), null);
}

Internationalizing workflow labels

You can create an optional resource bundle file along with the bpmn file to internationalize the name and labels of the process. The resource bundle must be named with the key of the jBPM process. For example, if the process has the "translation-workflow" key, the file must be named translation-workflow.properties. This file can include resources for process name, actions, and action outcomes. You can also specify a key for completed tasks by adding ".completed" to the task key.

Sending mail with jBPM

You can send mail using the Send mail task handler provided by Jahia. You can define a task as shown in the following example :

   <bpmn2:task id="Task_13" drools:taskName="Send mail" name="Send Mail">

This task requires the following input variables to send mail:

  • user
    The user initiating the workflow
  • currentUser
    The user who executed the last task
  • templateKey
    The name of the mail template to use
  • workflow
    The workflow definition
  • nodeIds
    The list of nodes on which the workflow has been started
  • locale
    The locale on which the workflow has been started
  • workspace
    The workspace on which the workflow has been started

The templateKey input defines which template to use to send mail. Mail templates are found, like workflows, in a path specified in the JBPMModuleProcessLoader bean. By default, all resources in the org.jahia.modules package with the .mail extension are taken. The following example, start-publication-template.mail, defines mail that is sent when a publication workflow starts.

to: #if ($userNotificationEmail) $userNotificationEmail #end
to-users: assignableFor(review)
subject: Publication request by ${PrincipalViewHelper.getFullName($currentUser)} for $site.getDisplayableName()
text: Hello,

A publication workflow with one validation step has been started on ${date.format("short","short",$submissionDate,$locale)} by ${currentUser.getName()}, on the following items :
#foreach( $node in $nodes )
    #if( $velocityCount <= 10)
    - #if( $node.getDisplayableName().length() > 100) ${node.getDisplayableName().substring(0, 100)}... #{else} $node.getDisplayableName() #end
    #end
#end

html: Hello,
<br>
<br>
<div>
    ${PrincipalViewHelper.getFullName($currentUser)} has requested the publication of $publicationCount item(s) for $site.getDisplayableName() ($site.getServerName()) on
    ${date.format("short","short",$submissionDate,$locale)}:
</div>

#set( $fileCheck = "/$site.getSiteKey()/files/" )
#set( $contentCheck = "/$site.getSiteKey()/contents/" )
#set( $replace = "/sites/$site.getSiteKey()")
<div>
    <ul>
        <li>Language: $locale</li>
        <li>Publication title: $workflowTitle</li>
        <li>
            Publication items:
            <ul>
                #foreach( $entry in $publications.entrySet() )
                    #if( $entry.value.size() > 0 )
                        <li>
                            $entry.key
                            <ul>
                                #foreach( $nodeInfo in $entry.value )
                                    #set( $node = $nodeInfo.get("node") )
                                ##Determine the "open in" link (Open in Edit Mode OR Open in Content Media Manager)
                                    #if ( $site.getSiteKey() == "systemsite" || $node.getPath().indexOf($fileCheck) != -1 || $node.getPath().indexOf($contentCheck) != -1 )
                                        #define( $openIn )
                                        ##Determine which route will be used
                                            #if ( $node.getPath().indexOf($fileCheck) != -1 )
                                                #set( $prefix = "$cmmPrefix/browse-files")
                                            #{else}
                                                #set( $prefix = "$cmmPrefix/browse")
                                            #end
                                        #**
                                         We need to use the folder path when opening CMM. Check if this node is a folder,
                                         otherwise get the parent node which will be a folder.
                                         *#
                                            #if( $node.isNodeType("nt:folder") )
                                                #set( $contentNodePath = "$prefix$node.getPath().replace($replace, '')" )
                                            #{else}
                                                #set( $contentNodePath = "$prefix$node.getParent().getPath().replace($replace, '')" )
                                            #end
                                            <a href="$contentNodePath">Open in Content and Media Manager</a>
                                        #end
                                    #else
                                        #define( $openIn )
                                            #set( $displayablePath = $nodeInfo.get("displayablePath") )
                                            #define( $previewLink ) - <a href="${previewPrefix}${displayablePath}.html">Preview</a> #end
                                            <a href="${editPrefix}${displayablePath}.html">Open in Edit Mode</a>
                                        #end
                                    #end
                                    <li>
                                        ${nodeInfo.get("displayableName")} (${nodeInfo.get("type")}) ${openIn} #if( $previewLink ) $previewLink #end
                                    </li>
                                #end
                            </ul>
                        </li>
                    #end
                #end
                #if ($publicationCount > 10) <li>...</li> #end
            </ul>
        </li>
    </ul>
    <br>
</div>
##Display any comments that are apart of this workflow
#if ( $comments )
<div>
    <p>
        <strong>Comments</strong>
    </p>
    <ul>
        #foreach( $comment in $comments )
            <li>
                $comment.get("userName") on ${date.format("default", "default", $comment.get("time"), $locale)}
                <br/>
                $comment.get("comment")
            </li>
        #end
    </ul>
</div>
#end

The template contains both text and HTML versions of the mail and uses velocity language to get dynamic information. You can use another script language (as defined by the JSR 223) by using the key language in the template file. The following variables are bound when executing the script: bundle, user, currentUser, date, submissionDate, locale, workspace, and nodes.

You can also declare one template per locale by suffixing the template name by language code. For example, start-publication-template.fr.mail is used when the workflow locale is French.