Preface

This manual describes the CUBA Platform Business Process Management add-on.

Target Audience

This manual is intended for developers building applications using CUBA platform. It is assumed that the reader is familiar with the Developer’s Manual.

Additional Materials

This guide, as well as any other CUBA platform documentation, is available at https://www.cuba-platform.com/documentation.

CUBA business process management add-on is based on the Activiti framework, therefore general knowledge on the framework is beneficial. See http://www.activiti.org to learn more about Activiti. Processes are defined according to the BPMN 2.0 notation, so it is assumed that the reader is familiar with the notation. More information about BPMN 2.0 specification can be found at http://www.bpmn.org.

Feedback

If you have any suggestions for improving this manual, feel free to report issues in the source repository on GitHub. If you see a spelling or wording mistake, a bug or inconsistency, don’t hesitate to fork the repo and fix it. Thank you!

1. Quick Start

In this chapter, we will create a small project to demonstrate how to use the business processes add-on. As an illustration, we will implement a contract approval process software.

Usually, an approval process involves the following steps:

  • A user creates the Contract object, defines actors and initiates an approval process.

  • An actor with the Controller role receives the task to validate an attached contract.

  • If the validation is passed then the contract is transferred to the users with the Manager role assigned, otherwise the process will be terminated with Not valid state.

  • After the associated managers approve or reject the contract it turns, respectively, into the Approved or Not approved state.

1.1. Creating the Project

  1. Create a new project in CUBA Studio as described in the Creating a New Project section of the CUBA Studio User Guide:

    • Project name: bpm-demo

    • Project namespace: demo

    • Root package: com.company.bpmdemo

  1. Open the Project Properties editor in CUBA Studio: click CUBA > Project Properties main menu item. Add the bpm application component in the App components list. Confirm when Studio suggests recreating Gradle scripts. Studio will download the required sources and binary artifacts. If your project is not synchronized with Gradle files automatically, click refresh_button button in the Gradle tool window.

  2. Create the database on the local HyperSQL server: select option CUBA > Create database in the main menu. The database name is the same as project namespace by default.

  3. Run the application: click the run_button button next to the selected CUBA Application configuration in the main toolbar. The link in the Runs at…​ section of the CUBA project tree will help to open the application in a web browser directly from Studio.

    The username and password are admin / admin.

    The running application contains two main menu items (Administration and Help), as well as security and administration subsystems functionality.

1.2. Creating the Data Model

Let’s create a Contract entity class.

  1. In the Data Model section of the CUBA project tree click New > Entity. The New CUBA Entity dialog window will appear.

  2. Enter the name of the entity class – Contract – in the Entity name field and click OK button. The Entity Designer page will be displayed in the workspace.

  3. Using the Entity Designer add attributes:

    • number of the type String

    • date of the type Date

    • state of the type String

Go to the Instance name editor and add the number attribute to the Name pattern attributes.

Contract entity creation is now complete.

1.3. Creating Standard Screens

Now we will create screens for contracts.

Select the Contract entity in the Data Model section of the CUBA project tree and select New > Screen in the context menu to create standard screens for viewing and editing Contract instances. After that, the template browser page will appear.

Select Entity browser and editor screens in the list of available templates and click Next.

All fields in this dialog are already populated with default values, there is no need to change them. Click Finish.

The screen files will appear in the Screens section of the Generic UI tree section:

  • contract-browse.xml – browser screen descriptor;

  • ContractBrowse – browser screen controller;

  • contract-edit.xml – editor screen descriptor;

  • ContractEdit – editor screen controller.

1.4. Creating ApprovalHelper Bean

Create the ApprovalHelper bean as described in the Creating Managed Beans section of the CUBA Studio User Guide.

Replace its content with the following code:

package com.company.bpmdemo.core;

import org.springframework.stereotype.Component;
import com.company.bpmdemo.entity.Contract;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Transaction;

import javax.inject.Inject;
import java.util.UUID;

@Component(ApprovalHelper.NAME)
public class ApprovalHelper {
    public static final String NAME = "demo_ApprovalHelper";

    @Inject
    private Persistence persistence;

    public void updateState(UUID entityId, String state) {
        try (Transaction tx = persistence.getTransaction()) {
            Contract contract = persistence.getEntityManager().find(Contract.class, entityId);
            if (contract != null) {
                contract.setState(state);
            }
            tx.commit();
        }
    }
}

The updateState() method of the ApprovalHelper bean will be invoked from the contract approval process for setting a contract state.

Method parameters:

  • entityId – contract entity identifier;

  • state – contract state.

1.5. Creating the Database and Running the Application

Click CUBA > Generate Database Scripts in the main menu to create the database tables. After that, Database Scripts page will open.

Click Save and close button to save the generated scripts.

To run update scripts, stop the running application by clicking the stop_button button in the Debug tool window, then select CUBA > Update database.

Now let’s see how the created screens look in the actual application. Click the run_button button in the main toolbar.

Open the application in a web browser at http://localhost:8080/app. You can also open the running application in the web browser using the Runs At…​ CUBA tree section.

1.6. Creating the Process

In this section, we will create and deploy the process.

1.6.1. Creating the Process model

The final version of the process model will look like this:

ProcessFull
Figure 1. Process Full

Let’s take a look at the steps needed to create the model.

In the web interface of the running application, open the BPM > Process Models screen and click Create. Enter Contract approval for the model name and click OK. Model Editor will be opened in a new browser tab.

Tip

A notification with the clickable link is shown in case of creating or copying the process model. In case of clicking the Edit button, the process model editor is shown in a new tab.

Select the Process roles property in the model properties panel. The Process roles edit window will be opened.

ProcessRolesProperty
Figure 2. Process Roles Property

Two types of actors participate in the process: a manager and a controller. Create two roles: Controller and Manager.

ProcessRolesEditor
Figure 3. Process Roles Editor

Drag and drop the Start event node from the Start Events group to the workspace.

We need to display a form to select process actors when the process starts. Select the start event node. Select the Start form in its properties panel – a form selection window will appear. Select Standard form in the Form name field. Then add two form parameters:

  • procActorsVisible with true value indicates that a table for process actors selection will be displayed on the form.

  • attachmentsVisible with true value indicates that a table for uploading attachments will be displayed on the form.

StartForm
Figure 4. Start Form

Add the User task node from the Activities group to the model. Name it Validation.

ModelValidationNode
Figure 5. Model Validation Node

Select this node and assign the controller value to the Process role property at the properties panel. This way we define that the task will be assigned to a process actor with the controller role.

SelectProcRoleForValidation
Figure 6. Select ProcRole For Validation

Next, select the Task outcomes property. The window for task outcomes edit will be opened. Outcomes define possible user actions when users receive tasks. Create two outcomes: Valid and Not valid. Define the Standard form for both outcomes. Add form parameter commentRequired=true for the Not valid outcome, because we want to make a user add a comment in case of invalid contract.

OutcomesForValidation
Figure 7. Outcomes For Validation

Depending on the controller’s decision we have to send the contract to managers approval or to finish the process with the Not valid state. The Exclusive gateway node from the Gateways group is used to control the process flow. Add it to the workspace and then add two more elements: Script task with Set 'Not valid' state name and User task with Approval name. Name the flow to the Script task as Not valid and the flow to the User task as Valid.

ModelValidationExclGateway
Figure 8. Model Validation Exclusive Gateway

Select the Not valid flow. Expand the Flow outcome dropdown list from the properties panel. It shows outcomes from the tasks before the gateway. Select the Not valid value.

NotValidFlowOutcome
Figure 9. Not Valid Flow Outcome

Now, if the Not valid outcome is selected, a transition to this flow will be performed.

The Valid flow should be marked as the default flow (if no other flows condition are true). Select the Valid flow and tick the Default flow property.

Warning

There must be no value selected in the Flow outcome dropdown list for the flow marked as default.

Next, select the Exclusive gateway and open its Flow order property editor. Make sure that the Not valid flow is on the first place in the list. Change the flows sequence if necessary.

ValidationFlowOrder
Figure 10. Validation Flow Order

Let’s move on to the Set 'Not valid' state node. We need to set the state property of the Contract entity to the Not valid value. Select the node. Set the Script format property value to groovy. Click on the Script property field – the script editor will be opened. Copy and paste the following code there:

def em = persistence.getEntityManager()
def contract = em.find(com.company.bpmdemo.entity.Contract.class, entityId)
contract.setState('Not valid')

You can use process variables and persistence and metadata platform objects (see Developer’s Manual) in scripts. The entityId variable is created on process start and stores an identifier of the linked entity.

After the contract state is changed, the process should be finished. Let’s add the End event node from the End events group to the workspace and connect the node with the Set 'Not valid' state.

Let’s go back to the Approval task. Define the manager process role for it as we did for the first task. In order for the task to be assigned to many managers simultaneously, set its Multi-instance type property to Parallel.

ApprovalMutlInstanceType
Figure 11. Multi-instance Approval Type

Create two task outcomes: Approve and Reject (Task outcomes property). For both outcomes, set the Standard form form and set commentRequired parameter to true for the Reject outcome.

After the approval is completed, either Approved or Not approved status should be assigned to the contract depending on the approval result. Add an Exclusive gateway node after the Approval task. Add two Service task after the exclusive gateway: Set 'Approved' state and Set 'Not approved' state. They will do the same thing as the Script task we have added earlier, but in a different way: by invoking a Spring bean method. Name the flow to the Set 'Approved' state as Approved, and the flow to the Set 'Not approved' state as Not approved.

ModelWithApproval
Figure 12. Model With Approval

Select the Not approved flow node and select the Reject value in the Flow outcome list. Now if at least one of the managers performs the Reject action then this outcome will be initiated. Select the Approved flow node and check the Default flow checkbox. This means that if no other flow is initiated then this flow will be used.

Set the flow order for Exclusive gateway as we did for the previous one. Select Exclusive gateway and open the Flow order property editor. Not approved should be processed first.

ApprovalFlowOrder
Figure 13. Approval Flow Order

Let’s go back to the Service task. Select the Set 'Approved' state node and set its Expression property to the following value:

${demo_ApprovalHelper.updateState(entityId, 'Approved')}

Apply the following script for the Set 'Not approved' state:

${demo_ApprovalHelper.updateState(entityId, 'Not approved')}

The Activiti engine is integrated with the Spring framework, so we can access Spring managed beans by their names. entityId is a process variable that stores an identifier of the contract which is linked to the process. Its value is set on the process start.

Connect both service tasks with the End event and click the save button. The model is ready, and we can move on to the model deployment.

ProcessFull
Figure 14. Process Model

1.6.2. Process Model Deployment

The model deployment process consists of the following steps:

  • Generating a process XML in BPMN 2.0 notation from the model.

  • Deploying the process to Activiti engine internal tables.

  • Creating a ProcDefinition object that relates to the Activiti process.

  • Creating ProcRole objects for process roles defined in the model.

Select the model in the list on the Process Models screen. Press the Deploy button. The model deployment window will be displayed. The model is deployed for the first time, so the Create new process option is selected. You will be able to deploy the model to existing processes for further model changes. Click OK. The process definition is created.

DeployModelScreen
Figure 15. Deploy Model Screen

Open the screen BPM > Process Definitions. Open the Contract approval item for editing. The Code field value is contractApproval. Remember this value – we will use it to identify the process definition later in this chapter.

ProcDefinitionEdit
Figure 16. ProcDefinition Edit

1.7. Adapting Screens to the Process

In this section, we will add an ability to work with the contract approval process to the contract editor screen.

1.7.1. Contract Editor Screen Layout

Open the contract-edit.xml screen in Studio and completely replace its content with the following code:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="msg://editorCaption"
        focusComponent="form"
        messagesPack="com.company.bpmdemo.web.contract">
    <data>
        <instance id="contractDc"
                  class="com.company.bpmdemo.entity.Contract"
                  view="_local">
            <loader id="contractDl"/>
        </instance>
        <collection id="procAttachmentsDc"
                    class="com.haulmont.bpm.entity.ProcAttachment"
                    view="procAttachment-browse">
            <loader id="procAttachmentsDl">
                <query><![CDATA[select e from bpm$ProcAttachment e
                                where e.procInstance.entity.entityId  = :entityId
                                order by e.createTs]]>
                </query>
            </loader>
        </collection>
    </data>
    <dialogMode height="600"
                width="800"/>
    <layout expand="editActions"
            spacing="true">
        <form id="form"
              dataContainer="contractDc">
            <column width="250px">
                <textField id="numberField"
                           property="number"/>
                <dateField id="dateField"
                           property="date"/>
                <textField id="stateField"
                           property="state"/>
            </column>
        </form>
        <groupBox id="procActionsBox"
                  caption="msg://process"
                  spacing="true"
                  width="AUTO"
                  orientation="vertical">
            <fragment id="procActionsFragment"
                      screen="bpm_ProcActionsFragment"/>
        </groupBox>
        <groupBox caption="msg://attachments"
                  height="300px"
                  spacing="true"
                  width="700px">
            <table id="attachmentsTable"
                   dataContainer="procAttachmentsDc"
                   height="100%"
                   width="100%">
                <columns>
                    <column id="file.name"/>
                    <column id="author"/>
                    <column id="type"/>
                    <column id="comment"
                            maxTextLength="50"/>
                </columns>
            </table>
        </groupBox>
        <hbox id="editActions"
              spacing="true">
            <button action="windowCommitAndClose"/>
            <button action="windowClose"/>
        </hbox>
    </layout>
</window>

The screen contains some fields for contract editing, a fragment for displaying process actions and a table with process attachments.

1.7.2. Contract Editor Controller

Open the ContractEdit screen controller and replace its content with the following code:

package com.company.bpmdemo.web.contract;

import com.haulmont.bpm.entity.ProcAttachment;
import com.haulmont.bpm.gui.procactionsfragment.ProcActionsFragment;
import com.haulmont.cuba.gui.app.core.file.FileDownloadHelper;
import com.haulmont.cuba.gui.components.Table;
import com.haulmont.cuba.gui.model.CollectionLoader;
import com.haulmont.cuba.gui.model.InstanceContainer;
import com.haulmont.cuba.gui.model.InstanceLoader;
import com.haulmont.cuba.gui.screen.*;
import com.company.bpmdemo.entity.Contract;

import javax.inject.Inject;

@UiController("demo_Contract.edit")
@UiDescriptor("contract-edit.xml")
@EditedEntityContainer("contractDc")
public class ContractEdit extends StandardEditor<Contract> {
    private static final String PROCESS_CODE = "contractApproval";

    @Inject
    private CollectionLoader<ProcAttachment> procAttachmentsDl;

    @Inject
    private InstanceContainer<Contract> contractDc;

    @Inject
    protected ProcActionsFragment procActionsFragment;

    @Inject
    private Table<ProcAttachment> attachmentsTable;

    @Inject
    private InstanceLoader<Contract> contractDl;

    @Subscribe
    private void onBeforeShow(BeforeShowEvent event) {
        contractDl.load();
        procAttachmentsDl.setParameter("entityId",contractDc.getItem().getId());
        procAttachmentsDl.load();
        procActionsFragment.initializer()
                .standard()
                .init(PROCESS_CODE, getEditedEntity());

        FileDownloadHelper.initGeneratedColumn(attachmentsTable, "file");
    }
}

Let’s examine the controller code in details.

The ProcessActionsFragment is the fragment that displays process actions available to the user at the moment. On initialization, the fragment searches for a related ProcInstance object by two parameters: process code and entity instance. If the ProcInstance object is not found then a new instance is created, and the fragment displays the start process button. If the related process instance is found then the fragment searches for an uncompleted process task for the current user and displays buttons for process task actions. See ProcActionsFragment for details.

In the contract editor, the process actions fragment initialization is performed in the onBeforeShow() by the invocation of the init(PROCESS_CODE, getEditedEntity()). The PROCESS_CODE stores a process code (contractApproval – we saw this value during the model deployment, see Process Model Deployment). The second argument getEditedEntity() is the current edited contract.

The standard() initialization does the following:

  • initializes standard process actions predicates that will be invoked before the process is started and before process tasks are completed. The predicates commit the entity editor.

  • initializes standard listeners that will show notifications like "Process started" or "Task completed" and refresh the procActionsFragment after the process is started or process task is completed.

1.8. Working With the Application

Hot deploy mechanism is enabled in Studio by default, so all changes in screens should already be applied to the running application. If hot deploy is disabled, restart the server using the CUBA > Restart Application Server menu command.

1.8.1. Creating Users

In order to demonstrate the process, create three users in the Administration > Users screen:

  • login: norman, First name: Tommy, Last name: Norman

  • login: roberts, First name: Casey, Last name: Roberts

  • login: pierce, First name: Walter, Last name: Pierce

1.8.2. Creating a Contract and Starting the Process

  1. Open the Application > Contracts screen and create a new contract. Fill in the Number and Date fields and click Save.

  2. Click Start process button, the start process form should appear. During the model creation, we defined the Standard form with attributes procActorsVisible=true and attachmentsVisible=true for the Start event node. That’s why now we see the form with the process actors and attachments tables.

  3. Enter a comment and add actors: the controller is norman and the two managers are pierce and roberts.

  4. Add an attachment by using an Upload button from the attachments table.

    StartProcessForm
    Figure 17. Start Process Form
  5. Press OK. Now the process is started.

1.8.3. Controller Validation Stage

Log in as norman.

When the process reaches the User task node, a ProcTask object is created. This object is linked to the particular process actor. The BPM add-on has a screen for displaying uncompleted tasks for the current user: BPM > Process Tasks.

ProcTaskBrowse
Figure 18. ProcTask Browse

We see that the user norman has one uncompleted task Validation of the Contract approval process. Select it and click the Open entity editor button. The contract edit screen will appear:

ContractEditValidation
Figure 19. Contract Edit Validation

The current user (norman) has an uncompleted task (ProcTask), so the procActionsFragment displays available process actions. When we were defining the Validation UserTask node, we set two possible outcomes: Valid and Not valid. That’s why two buttons are added to the procActionsFragment.

Click the Valid button and enter a comment in the opened window:

ValidationCompleteForm
Figure 20. Validation Complete Form

Click OK.

After the successful validation, the contract should go to the parallel approval by managers.

1.8.4. Manager Approval Stage

Log in as the pierce user.

Open the list of tasks for the current user: BPM > Process tasks. You can see the Approval task.

TaskListApproval
Figure 21. Task List Approval

Select the task and this time click the Open process instance button. The system screen for working with the process instance will be opened.

ProcInstanceEditApproval
Figure 22. ProcInstance Edit Approval

It displays the information about the process start time, initiator, attachments list, actors and process instance tasks list. The screen also allows you to open a linked entity editor and execute a process action.

Pay attention to the Tasks table. The previous task Validation has been completed with the Valid outcome, and two new Approval tasks have been created for managers pierce and roberts.

Approve the contract pressing the Approve button.

Then log in as roberts. Open the contract from the list in Application > Contracts.

User roberts has an uncompleted task, so the procActionsFragment displays Approve and Reject actions. Click the Reject button.

CompleteApprovalForm
Figure 23. Approval Form

When we were defining the Reject outcome in the model designer, we set the commentRequired form parameter to true, therefore you see that the comment is required in the task complete form. Enter the comment and press OK.

One of the managers has rejected the contract, so the Not approved state should be assigned to the contract. Let’s check it. Open the contract.

ContractEditNotApproved
Figure 24. Not Approved

The approval process is completed with the Not approved state.

2. Data Model

DataModel
Figure 25. Data Model
Tip

Attributes with names starting with act* prefix are references to the Activiti identifiers.

  • ProcModel – the process model. Model attributes:

    • name – the model name.

    • description – the model description.

    • actModelId – ID of a model from Activiti engine, stored in ACT_RE_MODEL table.

  • ProcDefinition – the process definition. It can be retrieved from the model or loaded directly from an XML file. Entity attributes:

    • name – the process name.

    • code – the process code. It can be used to determine an entity instance from the application code.

    • actId – ID of a process from Activiti. It is required to access the BPMN model (it reads extensionElements)

    • active – defines, whether a new process instance start is allowed for the current ProcDefinition.

    • procRoles – collection of objects that defines process actors.

    • model – the reference to a model.

  • ProcRole – the process role. Objects of this type are created automatically on process deployment. ProcRole defines a process actor type. Entity attributes:

    • name – the role name.

    • code – the role code. It can be used by an application for a role identification.

    • order – the order number. It can be used by an application to display roles in an appropriate order.

    • procDefinition – the reference to a process definition.

  • ProcInstance – the process instance. ProcInstance can be started both with a link to a project entity and without a link. For example, the contract approval process instance can be linked with a Contract entity. Entity attributes:

    • description – the description of a process instance.

    • startDate – the process instance start date.

    • endDate – the process instance end date.

    • startedBy – the user who started a process.

    • active – the indicator that shows if a process was started but hasn’t been completed yet.

    • cancelled – the indicator that shows if a process was canceled.

    • actProcessInstanceId – the identifier of the corresponding ProcessInstance from Activiti.

    • startComment – the comment on process start.

    • cancelComment – the comment on process cancel.

    • entityName – the linked entity name.

    • entityId – the linked entity ID.

    • entityEditorName – the screen name that will be used to edit the linked entity.

    • procTasks – the process tasks collection.

    • procActors – the process actors collection.

    • procAttachments – the process attachments collection.

  • ProcActor – the process participant. The entity defines an executor with a particular role for a process instance. Entity attributes:

    • user – the reference to a user.

    • procInstance – the reference to a process instance.

    • procRole – the reference to a process role.

    • order – the order number. The field is used to define the order of actors for a sequential task for multiple users.

  • ProcTask – the process task. Objects of this type are automatically created when a process reaches the User task node. Entity attributes:

    • name – the task name.

    • startDate – the task start date.

    • claimDate – the claim date. The field is used in the case of a task without an explicit process actor.

    • endDate – the task end date.

    • outcome – the task completion result.

    • comment – the task completion comment.

    • procActor – the executor.

    • actTaskId – the Activiti task ID. This field is used to report the Activiti engine about task completion.

    • actExecutionId – the Activiti execution ID. This field is used for process variables read/write.

    • actTaskDefinitionKey – in a process XML it is an id attribute of the UserTask element. It is used for building the name of a variable that stores the task result ([task_id]_result). See Transitions Depending on Task Outcomes.

    • cancelled – the identifier that shows if the task was completed on process cancelation.

    • candidateUsers – the list of possible process actors for a group task.

    • procInstance – the reference to a process instance.

  • ProcAttachment – the process attachment. Entity attributes:

    • file – the reference to the FileDescriptor.

    • type – the attachment type (ProcAttachmentType).

    • comment – the comment.

    • author – the reference to the author of an attachment.

    • procInstance – the reference to a process instance.

    • procTask – the optional reference to a process task.

  • ProcAttachmentType – the attachment type. Entity attributes:

    • code – the attachment type code.

    • name – the attachment type name.

3. Functionality

The BPM add-on employs the Activiti Engine for execution of business processes. The modified editor from Activiti Explorer is used to model processes using BPMN 2.0 notation. In addition to Activiti framework capabilities, the BPM add-on provides additional functionality which is described in this section. The description of the Activiti framework is out of scope of this manual and can be found at https://www.activiti.org/userguide/.

3.1. BpmActivitiListener

The BpmActivitiListener event listener is automatically added to the process when a model is created. BpmActivitiListener implements the ActivitiEventListener interface (see http://www.activiti.org/userguide/#eventDispatcher). The listener is responsible for creating and modifying BPM entities when some process events occur (e.g. a user task entering, process cancellation, task completion, etc). This is the listener that creates ProcTasks objects and sets the endDate value for a ProcInstance.

3.2. Process Roles

Process roles define process actor types, e.g. "operator" or "manager". To open the process roles editor, select the Process roles property in the model properties panel. Information about the roles will be written to the process XML (extensionElements section of the process element) during the model deployment.

Process roles definition:

<process id="testProcess" name="Test process">
    <extensionElements>
         <cuba:procRoles>
            <cuba:procRole name="Manager" code="manager"/>
            <cuba:procRole name="Operator" code="operator"/>
        </cuba:procRoles>
    </extensionElements>
</process>

3.3. Start Process Form

To define a form that will be displayed on the process start, use the Start form property of the Start event node. Read mode about forms at the Process Forms section.

Process start form definition:

<startEvent id="startEvent">
  <extensionElements>
    <cuba:form name="standardProcForm">
      <cuba:param name="procActorsVisible" value="true"></cuba:param>
    </cuba:form>
  </extensionElements>
</startEvent>

3.4. User Task

To define the task assignee, select one of the process roles in the Process role property of the User task node. When a process reaches the User task, process actors with the given role will be found among all the process actors, and the task will be assigned to them.

Process role from a task:

<userTask id="managerApproval" name="Manager approval">
    <extensionElements>
        <cuba:procRole>manager</cuba:procRole>
    </extensionElements>
</process>

If you want the task to be assigned to multiple users, then set Parallel or Sequential value to the Multi-instance type property of the User task node.

There is also an ability to specify the task assignee in the assignee property of the User Task node. The property value may contain a string with an identifier of CUBA user: da8159cc-757f-7f59-6579-7f629ac02f72, a variable that contains a string with a user id: ${varialbeName}, or an expression that invokes the service that returns a user id: ${someService.getSomeUserId()}. Keep in mind, that the procRole property still must be defined. When the process reaches such User Task, a ProcActor instance for the specified user and process role is searched. If it doesn’t exist, a new ProcActor object is created. To specify the assignee in the model designer, select the User Task, click the Show advanced properties link and click into the Assignments property editor. A new dialog will appear, fill the Assignee property there.

Set the Claim allowed property if you don’t want the task to be immediately assigned to the particular user, but to appear in the list of available tasks for the group of users. Then, one of the candidates will be able to claim the task. Task candidates are defined amongst process actors with the corresponding Process role property.

A task without an explicit executor:

<userTask id="managerApproval" name="Manager approval">
    <extensionElements>
        <cuba:claimAllowed>true</cuba:claimAllowed>
    </extensionElements>
</process>

3.5. Task Outcomes

Commonly, a user is expected to make a decision on the task, e.g. to approve or to reject the contract. The next route through the process depends on this decision. The Task outcomes property of the User task node is used to define the list of outcomes. The name and form that should be displayed when an outcome is selected can be defined for each outcome separately. Parameters that should be passed to the form can be defined as well (see Process Forms).

Task outcomes:

<userTask id="managerApproval" name="Manager approval">
    <extensionElements>
        <cuba:outcomes>
            <cuba:outcome name="approve">
                <cuba:form name="standardProcessForm">
                    <cuba:param name="commentRequired">true</cuba:param>
                    <cuba:param name="attachmentsVisible">true</cuba:param>
                </cuba:form>
            </cuba:outcome>
            <cuba:outcome name="reject">
                <cuba:form name="someOtherProcessForm">
                </cuba:form>
            </cuba:outcome>
        </cuba:outcomes>
    </extensionElements>
</process>

3.6. Transitions Depending on Task Outcomes

BPMN 2.0 notation doesn’t provide a way to define multiple outcomes for a User task. To make a process to continue in a required direction, the Exclusive gateway is used. Its outgoing flows have conditions that operate with the results of the preceding task. When a user completes the task, its result is written to the process variable with the name generated as [taskId]_result. The variable type is ProcTaskResult.

Methods of the ProcTaskResult class:

  • count(String outcomeName): int – returns the number of users who completed the task with the given outcome.

  • exists(String outcomeName): boolean – returns true if there are users who completed the task with the given outcome.

The result object of the task completion is used in the Flow condition expression of gateway outgoing flows.

Example

TaskOutcomesExample
Figure 26. Task Outcomes Example

Suppose that the approval task was assigned to multiple users in parallel. There are two outcomes defined for the task: approve and reject. When all users have completed the task, the process goes to the exclusive gateway. We want to implement the following behavior: if anyone chooses the reject option then go to the Rejected flow; if everyone approved the task then go to the Approved flow.

Defining a Condition in a Flow Outcome Field

The simplest way to define the flow condition is to select the name of the previous task outcome in the Flow outcome property of the flow node. The flow will be activated if there was at least one task completion with the selected outcome.

Defining a Complex Condition for the Flow Node

If you need to implement more complex condition for the outcome, you can define it in the Flow condition field. For example, "More than 5 users selected the Reject option" condition looks as follows:

${approval_result.count('reject') > 5}

3.6.1. Flow Order

Please notice that the flow order must be defined. Otherwise, Activiti could process the default flow before the flows with explicit conditions. To define the flow order use the Flow order property of the Exclusive gateway node.

3.7. Script Evaluation

The Script task node is used to evaluate a script. The system analyzes the content of the Script property value. If the value is a valid file path and the file exists, then the script from the file will be executed, otherwise the Script field will be evaluated.

Note that you can use persistence and metadata objects in scripts.

3.8. Middleware Beans Methods Invocation

The Service task node is used to invoke a service method. Activiti engine is integrated with the Spring framework, so you can access middleware beans by their names. To invoke a method of a managed bean use the following expression to the Expression field:

${beanName.methodName(processVarName, 'someStringParam')}

You can use process variables as method arguments, including the variables automatically created on process start (entityId, bpmProcInstanceId, etc. as described in ProcessRuntimeService).

3.9. Completing a Task by Timer

To complete a task after a certain time interval, you should:

  • Add the Boundary timer event node to the task node.

  • Draw the flow from the timer node to another required node.

  • Define an expression for the time interval in the Time duration property of the timer node. For example, PT15M is an expression for 15 minutes interval.

  • Set the Cancel activity property to true. It will cancel the current task when the timer is fired.

  • In the Timer outcome property, define the task outcome that should be used when the task is completed by the timer.

TimerEdit
Figure 27. Timer Edit

Defining an outcome for the timer:

<boundaryEvent id="managerApprovalTimer" cancelActivity="true" attachedToRef="managerApproval">
    <extensionElements>
        <cuba:outcome>approve</cuba:outcome>
    </extensionElements>
</boundaryEvent>
Tip

By default, the Job executor for processing timers is disabled. To enable it, set the application property bpm.activiti.asyncExecutorEnabled = true.

3.10. Localization

A process may contain localized messages that are used to display task or outcomes in the user interface.

To open the localized messages editor, select the Localization property in the model properties panel.

To localize the task name, create a record with the task id as a key.

To localize the task outcome name, create a record with an expression like TASK_ID.OUTCOME_NAME as a key.

To localize the process role name, create a record with the role code as a key.

Localized messages:

<process id="testProcess" name="Test process">
    <extensionElements>
        <cuba:localizations>
            <cuba:localization lang="en">
                <cuba:msg key="key1" value="value1"/>
                <cuba:msg key="key2" value="value2"/>
            </cuba:localization>
            <cuba:localization lang="ru">
                <cuba:msg key="key1" value="value1"/>
                <cuba:msg key="key2" value="value2"/>
            </cuba:localization>
      </cuba:localizations>
    </extensionElements>
</process>

3.11. Submodels

A Sub model node of the Structural group allows using an existing model as a part of a new model. While deploying the process submodel elements are being inserted to the current model, and the process XML is produced from the result of this concatenation.

3.12. Custom Elements in Model Designer

BPM add-on enables creating custom elements for process model designer. Basically, a custom element is ServiceTask that saves the developer from the necessity of typing long expressions for method invocation, like ${app_MyBean.someMethod(argument1, 'argument2')}. Below is an example of custom element creation.

Suppose, there is a middleware bean with the app_DiscountManager name. There is a makeDiscount(BigDecimal discountPercent, UUID entityId) method in the bean. The method updates the contract amount by subtracting the discount.

In this example, we will create a custom model element that will invoke the method. The discount percent will be defined as a parameter of the model element.

Open the model elements editor with the menu item BPM > Model Elements Editor.

Click the Add group button and enter the group name – Discounts.

StencilSetAddGroup
Figure 28. Add Group

Select the created group and click the Add element button.

StencilSetAddStencil
Figure 29. Add Stencil

Enter the following values for element properties:

  • Title: Contract discount

  • Stencil ID: contractDiscount

  • Icon: click the Upload button and select the icon file (optional)

  • Bean name: select the app_DiscountManager

  • Method name: select the makeDiscount

Warning

The Bean name lookup contains only beans that implement an interface. The Method name lookup contains methods of implemented interfaces.

The Method arguments table will contain method arguments. You can change a caption and an argument default value.

Save the elements set by clicking the Save button.

Open the process model editor (BPM > Process Models). There are the Discounts group and the Contract discount element in the elements list. Drag and drop the new element to the model and select it. You’ll see that fields for discount percent and process variable name for entity identifier appeared.

StencilSetModel
Figure 30. Set Model
Tip

entityId is a name of the process variable. This process variable is added automatically to each process that is linked to an entity. The variable stores an entity identifier, you can use it in any method calls.

During the process deployment, a custom element will be transformed to a serviceTask:

<serviceTask id="sid-5C184F22-6071-45CD-AEA9-1792512BBDCE" name="Make discount" activiti:expression="${app_DiscountManager.makeDiscount(10,entityId)}"></serviceTask>

The elements can be exported to a ZIP archive and then restored from the archive. It may be useful when new elements are created on a developer’s machine and then imported to the production server. Import and export are performed with the corresponding buttons on the elements editor screen.

The Reset button removes all custom groups and elements and reverts the elements set to its initial state.

4. Main Services

This section contains only general description of the services. Detailed service methods description is available in Java classes documentation.

4.1. ProcessRepositoryService

It is designed to work with process definitions. The service is used to:

  • load a process definition from XML;

  • undeploy a process from Activiti engine;

  • transform the JSON model to BPMN XML.

To access the service functionality on middleware, use the ProcessRepositoryManager bean.

4.2. ProcessRuntimeService

It is designed to work with process instances. The service methods allow you to:

  • start a process;

  • cancel a process;

  • complete a task;

  • assign a task to the user.

When a process is started, the following process variables are created automatically:

  • bpmProcInstanceIdProcInstance object ID;

  • entityName – linked entity name;

  • entityId – linked entity ID.

To access the service functionality on middleware, use the ProcessRuntimeManager bean.

4.3. ProcessFormService

The service is used to provide information about:

  • task outcomes;

  • forms that should be displayed for outcomes;

  • forms to appear on process start and cancel.

To access the service functionality on middleware, use the ProcessFormManager bean.

4.4. ProcessMessagesService

The service is used to access localized messages which are defined in the process.

To access the service functionality on middleware, use the ProcessMessagesManager bean.

4.5. ModelService

The service is used to create and update models in Activiti internal tables. Also, it works with the JSON representation of the model.

5. UI Components

This section contains a description of user interface components provided by the BPM add-on.

5.1. ProcActionsFragment

The ProcActionsFragment is designed to work with process actions. After the fragment is initialized, the following components will be automatically displayed:

  • the start process button, in case if the process is not started yet;

  • the buttons for task outcomes, in case the process is started and the current user has an active task;

  • the cancel process button;

  • the task information panel (name and creation date).

A predicate can be assigned to each of the process actions in order to check if the action can be performed (e.g. the predicate commits an editor, and if the commit failed, the process action is not performed). The post-action listener can also be defined (e.g. the listener will close the editor and show a notification).

The ProcActionsFragment must be linked with the ProcInstance. The linking is performed during fragment initialization.

An example of fragment initialization:

procActionsFragment.initializer()
        .setBeforeStartProcessPredicate(() -> {
            getScreenData().getDataContext().commit();
            return true;
        })
        .setAfterStartProcessListener(() -> {
            notifications.create()
                    .withCaption(messageBundle.getMessage("processStarted"))
                    .withType(Notifications.NotificationType.HUMANIZED)
                    .show();
        })
        .init(PROCESS_CODE, getEditedEntity());
  • The initializer() method returns an object that is used for fragment initialization.

  • The setBeforeStartProcessPredicate method sets the predicate that will be evaluated before the process start. If the predicate returns false then the process start will be interrupted.

  • The setAfterStartProcessListener method defines a listener that will be invoked after the process start action is performed.

  • The init method has two parameters: process code and entity instance. When this method is invoked, a search for the ProcInstance object that is related with the entity instance and has a reference to the ProcDefinition with the given code is performed. If the ProcInstance exists then the fragment is linked to it, otherwise a new ProcInstance object is created.

The easiest way to initialize the ProcActionsFragment is to use the standard() initializer:

procActionsFragment.initializer()
        .standard()
        .init(PROCESS_CODE, getEditedEntity());

The standard initializer does the following:

  • creates predicates that commit entity editor before start process and complete task actions;

  • creates listeners that show notifications like "Process started" or "Task completed" and refresh the ProcActionsFragment.

Below is the list of methods used for customizing the fragment.

Process life cycle
  • initializer() – returns a new instance of fragment initializer.

  • init() – tries to find the process instance by the specified process code and the entity reference. If the process instance is not found then a new one is created. Then the UI for available actions for the current user and the process instance is initialized.

Process configuration
  • setStartProcessEnabled() – defines whether the process can be started.

  • setCancelProcessEnabled() – defines whether the process can be canceled.

  • setCompleteTaskEnabled() – defines whether the task can be completed.

  • setClaimTaskEnabled() – defines whether the task can be assigned to a user by himself.

  • setTaskInfoEnabled() – defines whether the layout with the localized task name and its start date is enabled.

  • setButtonWidth() – sets the width of the action control button. The default value is 150 px.

  • addActionButton() – allows adding a custom button to the fragment alongside with buttons that were automatically generated.

Predicates
  • setBeforeStartProcessPredicate() – sets the predicate that will be evaluated before the process start. If the predicate returns false then the process start will be interrupted.

  • setBeforeCompleteTaskPredicate() – sets the predicate that will be evaluated before the task completion. If the predicate returns false then the task completion will be interrupted.

  • setBeforeClaimTaskPredicate() – sets the predicate that will be evaluated before the task is claimed to a user. If the predicate returns false then the task assignment will be interrupted.

  • setBeforeCancelProcessPredicate() – sets the predicate that will be evaluated before the task cancellation. If the predicate returns false then the task will not be canceled.

Process and task listeners
  • setAfterStartProcessListener() – defines a listener that will be invoked after the process start action is performed.

  • setAfterCompleteTaskListener() – defines a listener that will be invoked after the task complete action is performed.

  • setAfterClaimTaskListener() – defines a listener that will be invoked after the task claim action is performed.

  • setAfterCancelProcessListener() – defines a listener that will be invoked after the process cancel action is performed.

Variables and parameters suppliers
  • setStartProcessActionProcessVariablesSupplier() – sets the process variables suppliers. Process variable suppliers return a map of process variables that must be added to Activiti process instance on process start.

  • setCompleteTaskActionProcessVariablesSupplier() – sets the process variables suppliers. Process variable suppliers return a map of process variables that must be added to Activiti process instance on task completion.

  • setStartProcessActionScreenParametersSupplier() – sets the process form screen parameters suppliers. These screen parameters suppliers return a map of screen parameters that will be passed to the process form displayed by StartProcessAction.

  • setCompleteTaskActionScreenParametersSupplier() – sets the process form screen parameters suppliers. These screen parameters suppliers return a map of screen parameters that will be passed to the process form displayed by CompleteTaskAction.



5.2. Process Forms

ProcForm Interface

When you declare user task outcomes or the start event node in the model editor, it is possible to set a form that will be displayed to the user. The form class should implement the ProcForm interface.

The methods of the ProcForm interface:

  • getComment(): String – returns the value that will be written to the comment field of the ProcTask object or to the startComment field of ProcInstance if the form is displayed on a process start.

  • getFormResult(): Map<String, Object> – returns a list of objects that will be added to process variables after the form commit.

A List of Forms for a Process Model Designer

A list of forms available in the process model designer is built according to the configuration files that are defined in the bpm.formsConfig application property. To add a new process form, do the following:

  1. Create and register a screen for the form. Screen controller must implement the ProcForm interface.

  2. Create an XML file, e.g. app-bpm-forms.xml, which will contain a description of custom forms, and place it under the src directory of the web or gui module. For example:

    <?xml version="1.0" encoding="UTF-8"?>
    <forms xmlns="http://schemas.haulmont.com/cuba/bpm-forms.xsd">
        <form name="myCustomForm" default="true">
            <param name="someParam" value="hello"/>
            <param name="otherParam"/>
        </form>
    </forms>

    myCustomForm here is a screen id.

    The above configuration also describes available form parameters with their names and default values.

    A form with the default="true" attribute will be used as the default form in the model.

  3. Override the bpm.formsConfig property in the web-app.properties file.

    bpm.formsConfig = bpm-forms.xml app-bpm-forms.xml


6. Examples

6.1. Task Execution Sample

This sample demonstrates the following:

  • How to programmatically create process actors on process start using the ProcActionsFragment;

  • How to pass process variables to the process instance using the ProcActionsFragment;

  • How to get and modify standard process actions created by the ProcActionsFragment (e.g. change "Start process" button caption);

  • How to start a process programmatically without the ProcActionsFragment;

  • How to automatically update the processState field each time the process moves further using the ActivitiEventListener.

The sample uses the Task execution – 1 process model:

TaskExecution1Model
Figure 31. Task Execution Model

In this example, we don’t use the StandardProcForm to assign process actors. We do it with the help of the before start process predicate of the ProcActionsFragment. See the setBeforeStartProcessPredicate() method.

TaskEdit.java
@UiController("bpmsamples$Task.edit")
@UiDescriptor("task-edit.xml")
@EditedEntityContainer("taskDc")
@LoadDataBeforeShow
public class TaskEdit extends StandardEditor<Task> {

    public static final String PROCESS_CODE = "taskExecution-1";

    @Inject
    protected ProcActionsFragment procActionsFragment;

    @Inject
    protected BpmEntitiesService bpmEntitiesService;

    @Inject
    protected ProcessRuntimeService processRuntimeService;

    @Inject
    private MessageBundle messageBundle;

    @Inject
    private Notifications notifications;

    @Inject
    private Messages messages;

    @Inject
    private InstanceLoader<Task> taskDl;

        ...

    /** * Method starts the process without {@link ProcActionsFragment} */
    @Subscribe("startProcessProgrammaticallyBtn")
    private void onStartProcessProgrammaticallyBtnClick(Button.ClickEvent event) {

        commitChanges()
                .then(() -> {
            /*The ProcInstanceDetails object is used for describing a ProcInstance to be created with its proc actors*/
            BpmEntitiesService.ProcInstanceDetails procInstanceDetails = new BpmEntitiesService.ProcInstanceDetails(PROCESS_CODE)
                    .addProcActor("initiator", getEditedEntity().getInitiator())
                    .addProcActor("executor", getEditedEntity().getExecutor())
                    .setEntity(getEditedEntity());

            /*The created ProcInstance will have two proc actors. None of the entities is persisted yet.*/
            ProcInstance procInstance = bpmEntitiesService.createProcInstance(procInstanceDetails);

            /*A map with process variables that must be passed to the Activiti process instance when it is started. This variable is used in the model to make a decision for one of gateways.*/
            HashMap<String, Object> processVariables = new HashMap<>();
            processVariables.put("acceptanceRequired", getEditedEntity().getAcceptanceRequired());

            /*Starts the process. The "startProcess" method automatically persists the passed procInstance with its actors*/
            processRuntimeService.startProcess(procInstance, "Process started programmatically", processVariables);
            notifications.create()
                    .withCaption(messageBundle.getMessage("processStarted"))
                    .withType(Notifications.NotificationType.HUMANIZED)
                    .show();

            /*refresh the procActionsFragment to display complete tasks buttons (if a process task appears for the current user after the process is started)*/
            initProcActionsFragment();
        });
    }

    private void initProcActionsFragment() {
        procActionsFragment.initializer()
                .standard()
                .setBeforeStartProcessPredicate(() -> {
                    /*the predicate creates process actors and sets them to the process instance created by the ProcActionsFragment*/
                    if (commitChanges().getStatus() == OperationResult.Status.SUCCESS) {
                        ProcInstance procInstance = procActionsFragment.getProcInstance();
                        ProcActor initiatorProcActor = createProcActor("initiator", procInstance, getEditedEntity().getInitiator());
                        ProcActor executorProcActor = createProcActor("executor", procInstance, getEditedEntity().getExecutor());
                        Set<ProcActor> procActors = new HashSet<>();
                        procActors.add(initiatorProcActor);
                        procActors.add(executorProcActor);
                        procInstance.setProcActors(procActors);
                        return true;
                    }
                    return false;
                })
                .setStartProcessActionProcessVariablesSupplier(() -> {
                    /*the supplier returns a map with process variables that will be used by the Activiti process*/
                    Map<String, Object> processVariables = new HashMap<>();
                    processVariables.put("acceptanceRequired", getEditedEntity().getAcceptanceRequired());
                    return processVariables;
                })
                .setAfterStartProcessListener(() -> {
                    /*custom listener in addition to the standard behavior refreshes the "taskDs", because the process automatically updates the "processState" field of the "Task" entity.*/
                    notifications.create()
                            .withCaption(messages.getMessage(ProcActionsFragment.class,"processStarted"))
                            .withType(Notifications.NotificationType.HUMANIZED)
                            .show();
                    initProcActionsFragment();
                    taskDl.setEntityId(getEditedEntity().getId());
                    taskDl.load();
                })
                .setAfterCompleteTaskListener(() -> {
                    notifications.create()
                            .withCaption(messages.getMessage(ProcActionsFragment.class,"taskCompleted"))
                            .withType(Notifications.NotificationType.HUMANIZED)
                            .show();
                    initProcActionsFragment();
                    taskDl.setEntityId(getEditedEntity().getId());
                    taskDl.load();
                })
                .init(PROCESS_CODE, getEditedEntity());
    }

    /** * Method demonstrates how to get and modify process actions automatically created by the ProcActionsFragment */
    private void changeStartProcessBtnCaption() {
        StartProcessAction startProcessAction = procActionsFragment.getStartProcessAction();
        if (startProcessAction != null) {
            startProcessAction.setCaption("Start process using ProcActionsFragment");
        }
    }
}

See the setStartProcessActionProcessVariablesSupplier() usage in the TaskEdit.java as an example of how to pass process variables at process start using the ProcActionsFragment. The acceptanceRequired process variable will be used by one of the process gateways to decide whether the task must be accepted by the initiator or the process must be completed.

The changeStartProcessBtnCaption() demonstrates that you can get and modify process actions generated by the ProcActionsFragment. In this method, the standard button caption "Start process" is replaced by the custom one.

The onStartProcessProgrammaticallyBtnClick() method demonstrates how to start a new process instance without the ProcActionsFragment.

The UpdateProcessStateListener.java is an implementation of the org.activiti.engine.delegate.event.ActivitiEventListener. This listener is registered as a process-level listener. It does the following: each time a new process step is reached, the processState field of the related com.company.bpmsamples.entity.Task entity is updated with the current process step name.

UpdateProcessStateListener.java
/** * The listener updates the "processState" field of the {@link HasProcessState} with the name of current BPM process * node. This listener is used in the "taskExecution-1" BPM process */
public class UpdateProcessStateListener implements ActivitiEventListener {

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

    private Metadata metadata;

    public UpdateProcessStateListener() {
        metadata = AppBeans.get(Metadata.class);
    }

    @Override
    public void onEvent(ActivitiEvent event) {
        RuntimeService runtimeService = event.getEngineServices().getRuntimeService();
        String executionId = event.getExecutionId();
        UUID entityId = (UUID) runtimeService.getVariable(executionId, "entityId");
        String entityName = (String) runtimeService.getVariable(executionId, "entityName");
        if (entityId == null) {
            log.error("Cannot update process state. entityId variable is null");
            return;
        }
        if (Strings.isNullOrEmpty(entityName)) {
            log.error("Cannot update process state. entityName variable is null");
            return;
        }
        MetaClass metaClass = metadata.getClass(entityName);
        if (metaClass == null) {
            log.error("Cannot update process state. MetaClass {} not found", entityName);
            return;
        }

        if (!HasProcessState.class.isAssignableFrom(metaClass.getJavaClass())) {
            log.error("{} doesn't implement the HasProcessState");
            return;
        }

        switch (event.getType()) {
            case ACTIVITY_STARTED:
                //activityName is the name of the current element taken from the process model
                String activityName = ((ActivitiActivityEvent) event).getActivityName();
                if (!Strings.isNullOrEmpty(activityName)) {
                    updateProcessState(metaClass, entityId, activityName);
                }
                break;
        }
    }

    /** * Method updates the process state of the entity linked with the process instance */
    private void updateProcessState(MetaClass metaClass, UUID entityId, String processState) {
        Persistence persistence = AppBeans.get(Persistence.class);
        try (Transaction tx = persistence.getTransaction()) {
            EntityManager em = persistence.getEntityManager();
            Entity entity = em.find(metaClass.getJavaClass(), entityId);
            if (entity != null) {
                ((HasProcessState) entity).setProcessState(processState);
            } else {
                log.error("Entity {} with id {} not found", metaClass.getName(), entityId);
            }
            tx.commit();
        }
    }

    @Override
    public boolean isFailOnException() {
        return false;
    }
}

That’s how process-level event listeners configuration looks in the process model.

TaskExecution1UpdateProcessStateListener
Figure 32. Process State Listener

To open this window click somewhere in the modeler, click the Show advanced properties link and then go with the Event listeners property.

Appendix A: Application properties

bpm.activiti.asyncExecutorEnabled

Possible values: true or false. Defines whether Job Executor for timers and asynchronous tasks is enabled. The default value is false.

. . .