7.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 theActivitiEventListener
.
The sample uses the Task execution – 1 process 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.
@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 Metadata metadata;
@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());
}
private ProcActor createProcActor(String procRoleCode, ProcInstance procInstance, User user) {
ProcActor initiatorProcActor = metadata.create(ProcActor.class);
initiatorProcActor.setUser(user);
ProcRole initiatorProcRole = bpmEntitiesService.findProcRole(PROCESS_CODE, procRoleCode, View.MINIMAL);
initiatorProcActor.setProcRole(initiatorProcRole);
initiatorProcActor.setProcInstance(procInstance);
return initiatorProcActor;
}
/** * 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.
/** * 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.
To open this window click somewhere in the modeler, click the Show advanced properties link and then go with the Event listeners property.