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
Contents
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:
– 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
– 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.
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, Long> { 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<String, Object> variables = new HashMap<String, Object>(); 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<Task> 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<Task> 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. |
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]]
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.