How to create Activiti Parallel Tasks with Spring JPA + Spring Boot Example

This tutorial shows you a Spring Boot example which is integrated with Activiti Parallel Tasks and Spring JPA.

Related Articles:
Introduction to Activiti – a Java BPM Engine
How to start Activiti + Spring JPA with Spring Boot

I. Technology

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.1.RELEASE
– Spring Boot: 1.5.3.RELEASE

II. Overview

1. Goal

To build a Spring Boot application that helps to manage process as below:
activiti-parallel-spring-boot

– We will create a group of persons and use Spring JPA to store their information.
– Then we assign:
+ Check Hardware and Do Meeting to John.
+ Check Software and Check Firmware to David.
+ Write Report to Katherin.

– John’s tasks and David’s tasks can be handled in parallel. David doesn’t need to wait for any task which is assigned to John be done.
– Katherin is the assignee of Write Report task only if Check Hardware, Check Software and Check Firmware are done.

– After report is done, ‘notification’ message will be shown in the System Console.

2. Project Structure

activiti-parallel-spring-boot-structure

– Process definition will be written under XML format in process/process.bpmn20.xml file.
MyService provides service methods for Controller such as: startProcess(), getTasks(), completeTask(). Those functions can be done with help of some @Autowired objects which are instances of these classes:
+ RepositoryService
+ RuntimeService
+ TaskService
+ PersonRepository
PersonRepository is an interface extends JpaRepository for implementing repository methods on Person entities.

SpringParallelActivitiApplication class contains Bean that calls MyService‘s creating persons method when starting the application.
MyController is a REST Controller which has request mapping methods for RESTful requests such as: /process, /tasks, /completetask.

pom.xml contains dependencies for:
+ Activiti Spring Boot Starter Basic, Activiti Spring Boot Starter JPA
+ Spring Boot Starter Web
+ H2 database
+ Groovy: scripting engine for running Script Task in process.

Notes: We must add Groovy Jar file (groovy-all-2.4.10.jar) to make it work.

3. Step to do

We will follow these steps to make things done:
– Create Spring Boot project
– Define Process
– Create DataModel Class and JPA Repository Interface
– Create Service Class
– Create Application Class
– Create Controller
– Run & Check Result

III. Practice

1. Create Spring Maven project

– Using Spring Tool Suite to create Project and add Dependencies to pom.xml file:

<dependency>
	<groupId>org.codehaus.groovy</groupId>
	<artifactId>groovy-all</artifactId>
</dependency>

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
</dependency>

<dependency>
	<groupId>org.activiti</groupId>
	<artifactId>activiti-spring-boot-starter-basic</artifactId>
	<version>5.22.0</version>
</dependency>

<dependency>
	<groupId>org.activiti</groupId>
	<artifactId>activiti-spring-boot-starter-jpa</artifactId>
	<version>5.22.0</version>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

– Add Groovy Jar file (groovy-all-2.4.10.jar) as Referenced Library.

2. Define Process

Under src/main/resources, create processes folder and add simple-process.bpmn20.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
	xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
	<process id="fixSystemProcess" name="Fix System Process"
		isExecutable="true">
		<startEvent id="thestart" name="Start"></startEvent>
		<sequenceFlow id="flow1" sourceRef="thestart" targetRef="fork"></sequenceFlow>
		<parallelGateway id="fork" name="Parallel Gateway - In"></parallelGateway>

		<sequenceFlow sourceRef="fork" targetRef="domeetingtask"></sequenceFlow>
		<userTask id="domeetingtask" name="Do Meeting"
			activiti:assignee="${hardwareChecker.name}">
			<documentation>
				Make a Meeting.
			</documentation>
		</userTask>
		<sequenceFlow sourceRef="domeetingtask" targetRef="endmeetingtask"></sequenceFlow>
		<endEvent id="endmeetingtask" name="Meeting Ending"></endEvent>

		<sequenceFlow sourceRef="fork" targetRef="hardwaretask"></sequenceFlow>
		<userTask id="hardwaretask" name="Check Hardware"
			activiti:assignee="${hardwareChecker.name}">
			<documentation>
				Check hardware of the System.
			</documentation>
		</userTask>
		<sequenceFlow sourceRef="hardwaretask" targetRef="join"></sequenceFlow>

		<sequenceFlow sourceRef="fork" targetRef="softwaretask"></sequenceFlow>
		<userTask id="softwaretask" name="Check Software"
			activiti:assignee="${softwareChecker.name}">
			<documentation>
				Check software of the System.
			</documentation>
		</userTask>
		<sequenceFlow sourceRef="softwaretask" targetRef="firmwaretask"></sequenceFlow>
		<userTask id="firmwaretask" name="Check Firmware"
			activiti:assignee="${softwareChecker.name}">
			<documentation>
				Check firmware of the System.
			</documentation>
		</userTask>
		<sequenceFlow sourceRef="firmwaretask" targetRef="join"></sequenceFlow>

		<parallelGateway id="join" name="Parallel Gateway - OUT"></parallelGateway>
		<sequenceFlow sourceRef="join" targetRef="usertaskreport"></sequenceFlow>
		<userTask id="usertaskreport" name="Write Report"
			activiti:assignee="${reporter.name}">
			<documentation>
				Write report for system checking.
			</documentation>
		</userTask>
		<sequenceFlow sourceRef="usertaskreport" targetRef="notification"></sequenceFlow>

		<scriptTask id="notification" scriptFormat="groovy">
			<script>
				println 'Send Report and finish Process.'
			</script>
		</scriptTask>

		<sequenceFlow sourceRef="notification" targetRef="theend"></sequenceFlow>

		<endEvent id="theend" name="End" />
	</process>
</definitions>

We use ${hardwareChecker.name}, ${softwareChecker.name}, ${reporter.name} expressions and activiti:assignee attribute for assigning task.

3. Create DataModel Class and JPA Repository Interface


package com.javasampleapproach.paractiviti.model;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
	@Id
	@GeneratedValue
	private Long id;

	private String name;

	private Date birthDate;

	public Person() {
	}

	public Person(String name, Date birthDate) {
		this.name = name;
		this.birthDate = birthDate;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Date getBirthDate() {
		return birthDate;
	}

	public void setBirthDate(Date birthDate) {
		this.birthDate = birthDate;
	}

}

package com.javasampleapproach.paractiviti.repo;

import org.springframework.data.jpa.repository.JpaRepository;

import com.javasampleapproach.paractiviti.model.Person;

public interface PersonRepository extends JpaRepository {

	Person findByName(String name);

}

4. Create Service Class


package com.javasampleapproach.paractiviti.service;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.javasampleapproach.paractiviti.model.Person;
import com.javasampleapproach.paractiviti.repo.PersonRepository;

@Service
@Transactional
public class MyService {
	@Autowired
	private RuntimeService runtimeService;

	@Autowired
	private TaskService taskService;

	@Autowired
	private PersonRepository personRepository;

	public void startProcess(String[] assignees) {

		Map variables = new HashMap();

		Person hardwareChecker = personRepository.findByName(assignees[0]);
		Person softwareChecker = personRepository.findByName(assignees[1]);
		Person reporter = personRepository.findByName(assignees[2]);

		variables.put("hardwareChecker", hardwareChecker);
		variables.put("softwareChecker", softwareChecker);
		variables.put("reporter", reporter);

		runtimeService.startProcessInstanceByKey("fixSystemProcess", variables);
	}

	public List getTasks(String assignee) {
		return taskService.createTaskQuery().taskAssignee(assignee).list();
	}

	public void completeTask(String taskId) {
		taskService.complete(taskId);
	}

	public void createPersons() {
		if (personRepository.findAll().size() == 0) {

			personRepository.save(new Person("John", new Date()));
			personRepository.save(new Person("David", new Date()));
			personRepository.save(new Person("Katherin", new Date()));
		}
	}
}

5. Create Application Class


package com.javasampleapproach.paractiviti;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.javasampleapproach.paractiviti.service.MyService;

@SpringBootApplication
public class SpringParallelActivitiApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringParallelActivitiApplication.class, args);
	}

	@Bean
	public CommandLineRunner init(final MyService myService) {

		return new CommandLineRunner() {
			public void run(String... strings) throws Exception {
				myService.createPersons();
			}
		};

	}
}

6. Create Controller


package com.javasampleapproach.paractiviti.controller;

import java.util.List;

import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.javasampleapproach.paractiviti.service.MyService;

@RestController
public class MyController {
	@Autowired
	private MyService myService;

	@RequestMapping(value = "/process", method = RequestMethod.POST)
	public void startProcessInstance(@RequestBody StartProcessRepresentation startProcessRepresentation) {
		myService.startProcess(startProcessRepresentation.getAssignees());
	}

	@RequestMapping(value = "/tasks/{assignee}")
	public String getTasks(@PathVariable("assignee") String assignee) {
		List tasks = myService.getTasks(assignee);
		return tasks.toString();
	}

	@RequestMapping(value = "/completetask")
	public String completeTask(@RequestParam("id") String id) {
		myService.completeTask(id);
		return "Task with id " + id + " has been completed!";
	}

	static class StartProcessRepresentation {

		private String[] assignees;

		public String[] getAssignees() {
			return assignees;
		}

	}

}

7. Run & Check Result

– Config maven build:
clean install
– Run project with mode Spring Boot App
– Check results:

Request 1: Assign tasks to John,David,Katherin
$ curl -H "Content-Type: application/json" -d '{"assignees": ["John","David","Katherin"]}' http://localhost:8080/process

Request 2: show tasks of David
$ curl http://localhost:8080/tasks/David


[Task[id=19, name=Check Software]]

David has only one task at the present because he hasn’t done Check Software task.

Request 3: show tasks of John
http://localhost:8080/tasks/John


[Task[id=14, name=Do Meeting], Task[id=17, name=Check Hardware]]

John has two tasks in parallel.

Request 4: show tasks of Katherin
http://localhost:8080/tasks/Katherin

[] >> No result.

Request 5.1: complete Check Hardware
$ curl http://localhost:8080/completetask?id=17


Task with id 17 has been completed!

Request 5.2: complete Check Software
$ curl http://localhost:8080/completetask?id=19


Task with id 19 has been completed!

Request 6: show tasks of David
$ curl http://localhost:8080/tasks/David


[Task[id=24, name=Check Firmware]]

Request 7: complete Check Firmware
$ curl http://localhost:8080/completetask?id=24


Task with id 24 has been completed!

Request 8: show tasks of Katherin
$ curl http://localhost:8080/tasks/Katherin


[Task[id=27, name=Write Report]]

Request 7: complete Write Report
$ curl http://localhost:8080/completetask?id=27


Task with id 27 has been completed!

And System Console shows:


Send Report and finish Process.

IV. Source Code

SpringParallelActiviti



By grokonez | May 14, 2017.

Last updated on April 29, 2021.



Related Posts


2 thoughts on “How to create Activiti Parallel Tasks with Spring JPA + Spring Boot Example”

  1. Thanks a lot. It is working fine.

    But the only question I have is, how do we get the id of David as ’19’ particularly ??

    What I am trying to ask is –
    — Request 2: show tasks of David
    — curl http://localhost:8080/tasks/David -> when we request for this url, below is the result. How come everytime it is giving 19 ??
    — [Task[id=19, name=Check Software]]

    1. Hi Charan,

      – How do we get the id of David as ’19’ particularly ??
      => curl http://localhost:8080/tasks/David is a HTTP GET request, so getting id from response is just like getting field from object.

      curl http://localhost:8080/tasks/David -> when we request for this url, below is the result. How come everytime it is giving 19 ??
      => it is NOT giving 19 everytime. We assign Tasks to Assignee and task’s id will be generated by Activiti.

      Regards,
      JSA.

Got Something To Say:

Your email address will not be published. Required fields are marked *

*