activiti learning - regular user task jump (return, free jump function)


Through the understanding of the previous articles, at present we have enough knowledge to jump to the regular userTask. In the "Chinese process", rejection, free jump and so on are all realized by this way. It can be said that this is also an important part of the activiti process.

activiti itself does not provide an api for rejection and free jump. Maybe in the thinking of foreigners, there is no backward or random jump in the process, which must be strictly followed by the connection. We have to do this manually. But some of the more difficult problems are:

  • If the entity management class is called one by one to modify the database records, it is necessary to thoroughly understand the execution of the process virtual machine and the database changes during the process operation, which is too complex;
  • Even if it is clear that such a delicate operation, in case of any omission or some small problems in logical judgment, the whole process may be broken.

In order to solve the above problems, we must adjust our thinking and use some of the existing APIs. We should try our best to use them instead of struggling step by step.



Through< activiti learning (21) - source code analysis of process virtual machine (3) - from entering to leaving userTask >In this article, we have a general understanding of how to enter and leave userTask. The process of process jump is actually to leave the current userTask and enter the specified userTask.

Let's see what we need to do to leave userTask in the next article:

  1. Set the process variable of execution;
  2. Trigger task listener complete event and global event forwarder TASK_COMPLETED event;
  3. Add act_ Ru_ Identity link table record;
  4. Delete the current task;
  5. Leave the current node and press the connection to go to the next node.

You can see that we can't let it perform step 5, otherwise the process will run to the next node instead of jumping freely. So we can't do it task.complete , to implement the first four steps, and then enter the specified node.

Fortunately, when entering the specified node, we don't need to do much. According to the previous article, we directly call execution.performOperation(TRANSITION_CREATE_SCOPE). It should be noted that it is necessary to call execution.setActivity() set the specified activitiImpl as the current activity and call execution.setTransition(null), it's OK. Another, more convenient, call executionEntity.executeActivity(), set the parameter to the specified node.

Specific implementation

If we want to use entity management class to operate database records, it is better to use command class because its insertion, modification and so on are to cache. At the end of the command class, the cache is automatically refreshed to the database through the callback. New command class

public class CommonJumpCmd implements Command {

	private String taskId;
	private Map<String, Object> variables;
	private String desActivityId;

	public CommonJumpCmd(String taskId, Map<String, Object> variables, String desActivityId) {
		this.taskId = taskId;
		this.variables = variables;
		this.desActivityId = desActivityId;

	public Object execute(CommandContext commandContext) {
		TaskEntityManager taskEntityManager = Context.getCommandContext().getTaskEntityManager();
		TaskEntity taskEntity = taskEntityManager.findTaskById(taskId);
		ExecutionEntity executionEntity = taskEntity.getExecution();
		// Set process variables

		// Trigger the execute listener complete event

		// Trigger global event forwarder TASK_COMPLETED event
		if (Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
					.createEntityWithVariablesEvent(ActivitiEventType.TASK_COMPLETED, this, variables, false));

		// Add act_ru_identitylink table record. The routine process instance id is the same as the execution id
		if (Authentication.getAuthenticatedUserId() != null && executionEntity.getProcessInstanceId() != null) {
			executionEntity.involveUser(Authentication.getAuthenticatedUserId(), IdentityLinkType.PARTICIPANT);

		// Delete task
		taskEntityManager.deleteTask(taskEntity, TaskEntity.DELETE_REASON_COMPLETED, false);

		String processDefinitionId = executionEntity.getProcessDefinitionId();
		ProcessDefinitionEntity processDefinitionEntity = Context.getProcessEngineConfiguration().getDeploymentManager()
		ActivityImpl desActivityimpl = processDefinitionEntity.findActivity(desActivityId);


		return null;

The code is not complicated, and the corresponding notes are also written. Of course, for the sake of simplicity, some exceptions are not handled here, such as whether to throw an exception if the incoming taskId and desActivityId cannot find the corresponding record.

Although the code of CommonJumpCmd is not complicated and the logic is clear, there are several pits that need attention:

  • For the 35 line deleteTask method, you must use void deleteTask(TaskEntity task, String deleteReason, boolean cascade) instead of void deleteTask(String taskId, String deleteReason, boolean cascade). If the latter is used, an exception will be thrown because it determines whether the executionId of the TaskEntity corresponding to the taskId is empty. Otherwise, the task cannot be deleted.
  • Line 38 queries ProcessDefinitionEntity through findDeployedProcessDefinitionById of the deployment manager. An intuitive idea might be to use the ProcessDefinitionEntityManager to query ProcessDefinitionEntity. But when you use the latter to find out ProcessDefinitionEntity, it is possible to return null when 40 lines of findActivity are found. Why? The reason is that ActivityImpl is generated by object resolution, while findProcessDefinitionById of ProcessDefinitionEntityManager will not be resolved. If the process engine does not parse the object before executing the function, the return must be null. The findDeployedProcessDefinitionById of the deployment manager calls object resolution, so this problem does not exist.

Let's test it and build a flow chart commonJump.bpmn

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="" xmlns:xsi="" xmlns:xsd="" xmlns:activiti="" xmlns:bpmndi="" xmlns:omgdc="" xmlns:omgdi="" typeLanguage="" expressionLanguage="" targetNamespace="">
  <process id="commonJump" name="Common Jump" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="usertask1" activiti:assignee="${assignee}"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="usertask2" activiti:assignee="${assignee}"></userTask>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <userTask id="usertask3" name="usertask3" activiti:assignee="${assignee}"></userTask>
    <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
    <userTask id="usertask4" name="usertask4" activiti:assignee="${assignee}"></userTask>
    <sequenceFlow id="flow4" sourceRef="usertask3" targetRef="usertask4"></sequenceFlow>
    <endEvent id="endevent2" name="End"></endEvent>
    <sequenceFlow id="flow6" sourceRef="usertask4" targetRef="endevent2"></sequenceFlow>
  <bpmndi:BPMNDiagram id="BPMNDiagram_commonJump">
    <bpmndi:BPMNPlane bpmnElement="commonJump" id="BPMNPlane_commonJump">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="150.0" y="220.0"></omgdc:Bounds>
      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
        <omgdc:Bounds height="55.0" width="105.0" x="230.0" y="210.0"></omgdc:Bounds>
      <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
        <omgdc:Bounds height="55.0" width="105.0" x="380.0" y="210.0"></omgdc:Bounds>
      <bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
        <omgdc:Bounds height="55.0" width="105.0" x="380.0" y="300.0"></omgdc:Bounds>
      <bpmndi:BPMNShape bpmnElement="usertask4" id="BPMNShape_usertask4">
        <omgdc:Bounds height="55.0" width="105.0" x="230.0" y="300.0"></omgdc:Bounds>
      <bpmndi:BPMNShape bpmnElement="endevent2" id="BPMNShape_endevent2">
        <omgdc:Bounds height="35.0" width="35.0" x="150.0" y="310.0"></omgdc:Bounds>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="185.0" y="237.0"></omgdi:waypoint>
        <omgdi:waypoint x="230.0" y="237.0"></omgdi:waypoint>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="335.0" y="237.0"></omgdi:waypoint>
        <omgdi:waypoint x="380.0" y="237.0"></omgdi:waypoint>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="432.0" y="265.0"></omgdi:waypoint>
        <omgdi:waypoint x="432.0" y="300.0"></omgdi:waypoint>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="380.0" y="327.0"></omgdi:waypoint>
        <omgdi:waypoint x="335.0" y="327.0"></omgdi:waypoint>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="230.0" y="327.0"></omgdi:waypoint>
        <omgdi:waypoint x="185.0" y="327.0"></omgdi:waypoint>

The flow chart is very simple, that is, four ordinary usertasks are serialized. Then we deploy and launch. The deployment process document path and startup process id are different for different readers:

public void deploy() {
	RepositoryService repositoryService = pe.getRepositoryService();
	DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
	InputStream inputStream = null;
	try {
		inputStream = App.class.getClassLoader().getResource("bpmn/jump/commonJump.bpmn").openStream();
		deploymentBuilder.addInputStream("commonJump.bpmn", inputStream);"commonJumpDeployment");
		Deployment deployment = deploymentBuilder.deploy();
		System.out.println("Deployment complete");
	} catch (IOException e) {

public void startProcessByIdWithVar() {
	RuntimeService runtimeService = pe.getRuntimeService();
	Map<String, Object> vars = new HashMap<String, Object>();
	vars.put("assignee", "Zhang San");
	ProcessInstance pi = runtimeService.startProcessInstanceById("commonJump:1:4", vars);
	System.out.println("Process definition ID: " + pi.getProcessInstanceId());
	System.out.println("Process instance ID: " + pi.getId());


View act at this time_ Ru_ Execution table:

act_ru_task table:


Next, we call free jump to userTask3 to try the effect:

public void commonJump() {
	String taskId = "2505";
	Map<String, Object> variables = new HashMap<String, Object>();
	variables.put("assignee", "Li Si");
	String desActivityId = "usertask3";
	CommonJumpCmd commonJumpCmd = new CommonJumpCmd(taskId, variables, desActivityId);
	ServiceImpl service = (ServiceImpl)pe.getRepositoryService();
	CommandExecutor commandExecutor = service.getCommandExecutor();

View act after execution_ Ru_ Execution table, now it has jumped to userTask3:

Look at act again_ Ru_ Task table:

All the above shows that the process has normally jumped to userTask3, and other variable tables and history tables can be viewed by themselves, which will not be shown here.


After passing the normal task.complete Submit the process to the userTask4 node:

public void completeTaskWithVar() {
	Map<String, Object> vars = new HashMap<String, Object>();
	vars.put("assignee", "Wang Wu");
	TaskService taskService = pe.getTaskService();
	taskService.complete("5002", vars);
	System.out.println("Submit complete");

View act_ru_execution table and act_ Ru_ There is no exception in the task table


Next, we want to roll back the process to userTask3:

public void commonJump() {
	String taskId = "7502";
	Map<String, Object> variables = new HashMap<String, Object>();
	variables.put("assignee", "Zhao Liu");
	String desActivityId = "usertask3";
	CommonJumpCmd commonJumpCmd = new CommonJumpCmd(taskId, variables, desActivityId);
	ServiceImpl service = (ServiceImpl)pe.getRepositoryService();
	CommandExecutor commandExecutor = service.getCommandExecutor();

View act_ru_execution table and act_ru_task table, successfully returned to userTask3 without exception.



This article completes the general jump of userTask, but it is not suitable for multi instance userTask and branch node. The multi instance situation and branch situation will be implemented after the analysis of the process virtual machine. The routine process jump can satisfy the use of many simple processes. In addition, through this practice, we have more experience on how to disassemble the call of virtual machine.

Tags: Database Java xml encoding

Posted on Mon, 08 Jun 2020 23:03:55 -0400 by Democreous