Activiti - Business process engine
Activiti framework (Java) - a description of the flow of tasks on XML (bpm) and the management of this process. Here I will describe the basic basic concepts and how to build simple business processes.
The basic concept of Activiti is a process (process) and a task (task). The process is all tasks related to each other by directional flows and branches.
I will touch upon such aspects:
Movement on flows goes steps from a task to a task, each such step suspends the process execution waiting for input data and the task execution, all intermediate actions are saved in the database.
Where, what to take I will point below. Let's start with a simple example - the program development process, which consists of writing code and testing. Below is a process diagram.
![image](https://habrastorage.org/webt/1a/ed/-y/1aed-y18ukw-khkvxmobgmj-uhw.jpeg)
This is all a process, it has the ID, Name and other characteristics.
![image](https://habrastorage.org/webt/tq/_p/ok/tq_pokqaqcfczuln4yb5bmmn5aw.jpeg)
It has:
Start of the process, two tasks “Develop” and “Test”, one branch (gateway) and end of the process. In words, everything happens like this:
Activiti consists of some set of services.
Here are the main ones:
But it all starts with the configuration and file - activiti.cfg.xml.
Here from this
If you don’t use your configuration, then Activiti will deploy the database in memory of H2 itself, which doesn’t suit me, but my favorite Oracle is quite possible to connect different databases.
Here is my configuration
Change the values in "property name = jdbc *" and connect another database.
Project structure
![](https://habrastorage.org/webt/p7/8t/ve/p78tveh3lroieaug_gxi4rgocbw.jpeg)
The presence in the POM of the maven-assembly-plugin plugin will make it possible to build (package) a jar with dependencies and run
jdbc driver for Oracle installed in the local maven repository
For this process, we define 4 actions: bpm loading, process start, development and testing. Each action will have a corresponding parameter: deploy, start, develop, test.
Scripts for the database are taken from
activiti-get-started
there in the folder \ activiti-6.0.0 \ activiti-6.0.0 \ database \ create - the scripts for creating the database
Prepare users and roles:
Create users and groups, developer and tester, respectively.
In the database, all tables are separated by the corresponding services and have the prefixes
ACT_RE_ *: repository.
ACT_RU_ *: runtime.
ACT_ID_ *: identity.
ACT_HI _ *: history
and so forth.
After creating the users of can be found here
![image](https://habrastorage.org/webt/im/90/tz/im90tzvrwm0_dw9oay3pqycb9iw.jpeg)
Our tasks in the description designated by the respective groups (CandidateGroup), because such a task group Develop - programmers
![image](https://habrastorage.org/webt/wo/ra/ev/woraev4lkoihnajqwd0rddofyho.jpeg)
And so the first thing we do place the database «MyProcess.bpmn», run the program with the team deploy
Further we start the process start
After the delpoy and start process, the corresponding entries will appear in the database.
The
![image](https://habrastorage.org/webt/m4/xr/yj/m4xryjtrcbz9gyqubchiyu9me_s.jpeg)
Runtime repository , which task is assigned to
![image](https://habrastorage.org/webt/yg/a_/qn/yga_qnxt71huohfontg6_i-zxi8.jpeg)
whom it is assigned to.
![image](https://habrastorage.org/webt/7n/ag/n2/7nagn2fdirsq4fvqrkmda7zfcry.jpeg)
In the code it looks like this (the full code will be lower):
deploy
start
develop
After this, you can proceed to the development task.
In the Develop task, one variable “issue” is defined.
![image](https://habrastorage.org/webt/jj/2g/-g/jj2g-gn7wcl2_whiazhldkzfxjy.jpeg)
After processing the variables with the FormService, the task is executed
![image](https://habrastorage.org/webt/ve/p5/wy/vep5wyje62bgymdwfrcehqvg-zw.jpeg)
For the Develop task, you will be prompted to enter a variable.
In the historical table, you can see the variables and values of the task, the process.
![image](https://habrastorage.org/webt/ju/b2/e1/jub2e1hhqs4bppcpuqs6litqcpu.jpeg)
Thus, the process after the task Develop will stop on it, the state will be saved in the database.
In general, the cycle looks like this:
Request a task for the executor
Variable definition
Task execution
Checking the end of the process
After each task execution, the process is suspended until the new task is completed.
So after developing Develop, let's move on to the Test task, here we will also be prompted to enter the variable “devResult” - the result of the development (it didn't work out quite correctly, even before the start of Test, we enter the result), and then the result will be branching or ending (Ok) or again on the development (No), see the process diagram.
![image](https://habrastorage.org/webt/74/fd/-e/74fd-e0k_mognaoo3eolnftmex8.jpeg)
In this case, the development, etc. If you now request tasks for a developer, they will be, but not for testing.
Modify the project using Spring
Add dependencies to POM
DemoActiviti class is now so
I use a mixed model - when part of the beans are described in the xml configuration (@ImportResource ("classpath: activiti.cfg.xml")), and the other is defined through annotations.
Now Spring is responsible for the configuration, it can be seen
Add for SpringBoot standard command line processing, in the form of components
Which will process all those commands, I will not implement them all, everything is simple, I will show two: test and develop. And add a service to process them.
In the CommandLine Autowir component, the DemoService service, and in it the Activiti services already prepared by Spring
We collect, run as before from the command line.
If you want to use task execution from the Web, then we connect the REST API.
SpringBoot by default will provide the embedded Tomcat server, and then the matter of technology.
In POM, to what is, add spring web dependency
We delete the CommandLine component, now everything will be delivered via the URL via HTTP. Add RestController:
We execute the same commands, slightly modified the responses of the DemoService service, which is Autowire in the controller.
we test using curl, here's the result:
![image](https://habrastorage.org/webt/ig/hr/li/ighrlit0unzave5idkbo-nwh1z8.jpeg)
I changed the port for Tomcat to 8081 in application.properties
server.port = 8081
There are many constructions in Activiti, for example, the launch of scheduled tasks is a “TimerStartEvent”. In order for Job to start being executed in the configuration, you need to specify
I will return to the initial project, where Activiti is used in its pure form.
In the DemoActiviti class I will leave support for only two commands: deploy and start. I will do a new process.
![image](https://habrastorage.org/webt/cv/3i/dc/cv3idc8pjyywdo2p5kfwzucp5ky.jpeg)
After the process starts, it will switch to a timer that will schedule the “Develop” task. The timer schedule will be - run every 10 seconds., Cron expression - “0/10 * * * *?”.
![image](https://habrastorage.org/webt/5f/57/-l/5f57-lfbt26ref3zfnz0eoljz9u.jpeg)
Let’s deploy the new process as before, then start the process (start). All - the task is performed every 10 seconds.
The Activiti component - ServiceTask, which can be specified as the implementation of the Java class, is selected as the task.
![image](https://habrastorage.org/webt/yg/wi/pb/ygwipb_bxqq5n9iy4rrli69ohdi.jpeg)
In the table in the database (select * from ACT_RU_TIMER_JOB t), you can see the
![image](https://habrastorage.org/webt/y-/-u/ql/y--uqlqlsbfw7rllzzh3a3bp5gc.jpeg)
activity of the Job, in the DUEDATE_ field there will be the time of the next launch.
In the execution history, the variable “issue” from Delegate will be fixed.
![image](https://habrastorage.org/webt/5b/gr/iz/5bgrizfuld0u-u9g7qyu516npcc.jpeg)
There is still a lot left overboard: Events, Listener, JPA, etc., maybe I’ll come back to them. Activiti Eclipse Designer
Materials
The basic concept of Activiti is a process (process) and a task (task). The process is all tasks related to each other by directional flows and branches.
I will touch upon such aspects:
- - Activiti in pure form
- - Users, Roles
- - Connecting SpringBoot
- - REST API
- - Job and Delegate
Movement on flows goes steps from a task to a task, each such step suspends the process execution waiting for input data and the task execution, all intermediate actions are saved in the database.
Where, what to take I will point below. Let's start with a simple example - the program development process, which consists of writing code and testing. Below is a process diagram.
![image](https://habrastorage.org/webt/1a/ed/-y/1aed-y18ukw-khkvxmobgmj-uhw.jpeg)
This is all a process, it has the ID, Name and other characteristics.
![image](https://habrastorage.org/webt/tq/_p/ok/tq_pokqaqcfczuln4yb5bmmn5aw.jpeg)
It has:
Start of the process, two tasks “Develop” and “Test”, one branch (gateway) and end of the process. In words, everything happens like this:
- we load the description of bpm
- we start the process
- after the start we immediately get into the task Develop
- after the execution of Develop, it goes into testing and, based on the test result, the process ends or returns to development again.
Activiti consists of some set of services.
Here are the main ones:
- RepositoryService: manages the loading of process descriptions
- RuntimeService: starts processes
- TaskService: performs tasks
- FormService: access to task variables
- HistoryService: access to the history of the process
- IdentityService: Users and Roles
Activiti in pure form
But it all starts with the configuration and file - activiti.cfg.xml.
Here from this
ProcessEngineConfiguration cfg = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
If you don’t use your configuration, then Activiti will deploy the database in memory of H2 itself, which doesn’t suit me, but my favorite Oracle is quite possible to connect different databases.
Here is my configuration
activiti.cfg.xml
<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"><propertyname="jdbcUrl"value="jdbc:oracle:thin:@localhost:1521:xe" /><propertyname="jdbcDriver"value="oracle.jdbc.driver.OracleDriver" /><propertyname="jdbcUsername"value="BPM" /><propertyname="jdbcPassword"value="1" /><!-- Database configurations --><propertyname="databaseSchemaUpdate"value="false" /><propertyname="asyncExecutorActivate"value="false" /><!-- mail server configurations --><propertyname="mailServerPort"value="5025" /></bean></beans>
Change the values in "property name = jdbc *" and connect another database.
Project structure
![](https://habrastorage.org/webt/p7/8t/ve/p78tveh3lroieaug_gxi4rgocbw.jpeg)
POM
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>DemoActiviti</groupId><artifactId>DemoActiviti</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.activiti</groupId><version>6.0.0</version><artifactId>activiti-spring-boot-starter-integration</artifactId></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.21</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.21</version></dependency><dependency><groupId>com.oracle</groupId><artifactId>ojdbc6</artifactId><version>11.2.0</version></dependency></dependencies><build><plugins><!-- Maven Assembly Plugin --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>2.4.1</version><configuration><!-- get all project dependencies --><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><!-- MainClass in mainfest make a executable jar --><archive><manifest><mainClass>com.example.DemoActiviti</mainClass></manifest></archive></configuration><executions><execution><id>make-assembly</id><!-- bind to the packaging phase --><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><target>1.8</target></configuration></plugin></plugins></build></project>
The presence in the POM of the maven-assembly-plugin plugin will make it possible to build (package) a jar with dependencies and run
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar
jdbc driver for Oracle installed in the local maven repository
mvn install:install-file -Dfile={Path/to/your/ojdbc6.jar}
-DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0 -Dpackaging=jar
log4j
log4j.rootLogger=WARN, ACT
log4j.appender.ACT=org.apache.log4j.ConsoleAppender
log4j.appender.ACT.layout=org.apache.log4j.PatternLayout
log4j.appender.ACT.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n
For this process, we define 4 actions: bpm loading, process start, development and testing. Each action will have a corresponding parameter: deploy, start, develop, test.
Scripts for the database are taken from
activiti-get-started
there in the folder \ activiti-6.0.0 \ activiti-6.0.0 \ database \ create - the scripts for creating the database
Users, Roles
Prepare users and roles:
Identity
publicclassDemoActiviti{
privatestaticfinal String DEV_PROCESS = "devProcess";
publicstaticvoidmain(String[] args){
Locale.setDefault(Locale.ENGLISH);
ProcessEngineConfiguration cfg = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
ProcessEngine processEngine = cfg.buildProcessEngine();
createIdentity(processEngine, "programmer", "programmers");
createIdentity(processEngine, "tester", "testers");
}
publicstaticvoidcreateIdentity(ProcessEngine processEngine, String userName, String userGroup){
IdentityService identityService = processEngine.getIdentityService();
String userId = userName + "Id";
if (identityService.createUserQuery().userId(userId).count() == 0) {
User user = identityService.newUser(userName);
user.setId(userId);
user.setEmail(userName + "@gmail.com");
identityService.saveUser(user);
System.out.println("user created success fully");
}
String groupId = userGroup + "Id";
if (identityService.createGroupQuery().groupId(groupId).count() == 0) {
Group group = identityService.newGroup(userGroup);
group.setName(userGroup);
group.setId(groupId);
identityService.saveGroup(group);
System.out.println("group created success fully");
}
if (identityService.createGroupQuery().groupId(groupId).list().size() > 0) {
identityService.createMembership(userId, groupId);
System.out.println("user to group success fully");
}
}
}
Create users and groups, developer and tester, respectively.
In the database, all tables are separated by the corresponding services and have the prefixes
ACT_RE_ *: repository.
ACT_RU_ *: runtime.
ACT_ID_ *: identity.
ACT_HI _ *: history
and so forth.
After creating the users of can be found here
![image](https://habrastorage.org/webt/im/90/tz/im90tzvrwm0_dw9oay3pqycb9iw.jpeg)
Our tasks in the description designated by the respective groups (CandidateGroup), because such a task group Develop - programmers
![image](https://habrastorage.org/webt/wo/ra/ev/woraev4lkoihnajqwd0rddofyho.jpeg)
And so the first thing we do place the database «MyProcess.bpmn», run the program with the team deploy
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar deploy
Further we start the process start
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar start
After the delpoy and start process, the corresponding entries will appear in the database.
The
![image](https://habrastorage.org/webt/m4/xr/yj/m4xryjtrcbz9gyqubchiyu9me_s.jpeg)
Runtime repository , which task is assigned to
![image](https://habrastorage.org/webt/yg/a_/qn/yga_qnxt71huohfontg6_i-zxi8.jpeg)
whom it is assigned to.
![image](https://habrastorage.org/webt/7n/ag/n2/7nagn2fdirsq4fvqrkmda7zfcry.jpeg)
In the code it looks like this (the full code will be lower):
deploy
deployment = repositoryService.createDeployment()
.addClasspathResource("processes/MyProcess.bpmn").deploy()
start
ProcessInstance myProcess = runtimeService.startProcessInstanceByKey(DEV_PROCESS);
develop
After this, you can proceed to the development task.
java -jar DemoActiviti-1.0-SNAPSHOT-jar-with-dependencies.jar develop
// Задачи для разработчика
tasks = taskService.createTaskQuery().taskCandidateGroup("programmers").list();
In the Develop task, one variable “issue” is defined.
![image](https://habrastorage.org/webt/jj/2g/-g/jj2g-gn7wcl2_whiazhldkzfxjy.jpeg)
After processing the variables with the FormService, the task is executed
for (Task task : tasks) {
System.out.println("Task:" + task.getTaskDefinitionKey() + ", id=" + task.getId());
FormData formData = formService.getTaskFormData(task.getId());
Map<String, Object> variables = new HashMap<String, Object>();
// переменные задачиfor (FormProperty formProperty : formData.getFormProperties()) {
System.out.println("Enter varName <" + formProperty.getName() +">:");
String value = scanner.nextLine();
variables.put(formProperty.getId(), value);
}
// выполняем задачу
taskService.complete(task.getId(), variables);
System.out.println("Task complete success:" + task.getTaskDefinitionKey());
}
![image](https://habrastorage.org/webt/ve/p5/wy/vep5wyje62bgymdwfrcehqvg-zw.jpeg)
For the Develop task, you will be prompted to enter a variable.
In the historical table, you can see the variables and values of the task, the process.
![image](https://habrastorage.org/webt/ju/b2/e1/jub2e1hhqs4bppcpuqs6litqcpu.jpeg)
Thus, the process after the task Develop will stop on it, the state will be saved in the database.
In general, the cycle looks like this:
Request a task for the executor
tasks = taskService.createTaskQuery().taskCandidateGroup("...").list();
Variable definition
Map<String, Object> variables = new HashMap<String, Object>();
...
variables.put("var_1", value);
Task execution
taskService.complete(task.getId(), variables);
Checking the end of the process
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstance.getId()).singleResult();
if (processInstance != null && !processInstance.isEnded())
After each task execution, the process is suspended until the new task is completed.
So after developing Develop, let's move on to the Test task, here we will also be prompted to enter the variable “devResult” - the result of the development (it didn't work out quite correctly, even before the start of Test, we enter the result), and then the result will be branching or ending (Ok) or again on the development (No), see the process diagram.
![image](https://habrastorage.org/webt/74/fd/-e/74fd-e0k_mognaoo3eolnftmex8.jpeg)
In this case, the development, etc. If you now request tasks for a developer, they will be, but not for testing.
Program code
package com.example;
import org.activiti.engine.*;
import org.activiti.engine.form.FormData;
import org.activiti.engine.form.FormProperty;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
publicclassDemoActiviti{
privatestaticfinal String DEV_PROCESS = "devProcess";
publicstaticvoidmain(String[] args){
Locale.setDefault(Locale.ENGLISH);
ProcessEngineConfiguration cfg = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
ProcessEngine processEngine = cfg.buildProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
String mode = StringUtils.EMPTY;
if (args.length > 0) {
mode = args[0];
}
System.out.println("Processes mode: " + mode);
Deployment deployment;
if ("deploy".equals(mode)) {
deployment = repositoryService.createDeployment()
.addClasspathResource("processes/MyProcess.bpmn").deploy();
System.out.println("deploy process success");
System.exit(0);
} else {
List<Deployment> myProcesses = repositoryService.createDeploymentQuery()
.processDefinitionKey(DEV_PROCESS).list();
deployment = myProcesses.get(myProcesses.size()-1);
System.out.println("get process success:" + deployment.getId());
}
//
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance;
if ("start".equals(mode)){
ProcessInstance myProcess = runtimeService.startProcessInstanceByKey(DEV_PROCESS);
System.out.println("start process success:" + myProcess.getName() +", id="+ myProcess.getId());
System.exit(0);
}
processInstance = runtimeService.createProcessInstanceQuery().deploymentId(deployment.getId()).singleResult();
TaskService taskService = processEngine.getTaskService();
FormService formService = processEngine.getFormService();
List<Task> tasks = new ArrayList<>();
if ("develop".equals(mode)) {
System.out.println("develop mode");
// получить задачи для разработчика
tasks = taskService.createTaskQuery().taskCandidateGroup("programmers").list();
if (tasks.isEmpty()) {
System.out.println("Задач на разработку нет");
System.exit(0);
}
}
if ("test".equals(mode)) {
System.out.println("test mode");
// получить задачи для тестирования
tasks = taskService.createTaskQuery().taskCandidateGroup("testers").list();
if (tasks.isEmpty()) {
System.out.println("Задач на тестирование нет");
System.exit(0);
}
}
Scanner scanner = new Scanner(System.in);
if (processInstance != null && !processInstance.isEnded()) {
System.out.println("tasks count: [" + tasks.size() + "]");
for (Task task : tasks) {
System.out.println("Task:" + task.getTaskDefinitionKey() + ", id=" + task.getId());
FormData formData = formService.getTaskFormData(task.getId());
Map<String, Object> variables = new HashMap<String, Object>();
// переменные задачиfor (FormProperty formProperty : formData.getFormProperties()) {
System.out.println("Enter varName <" + formProperty.getName() +">:");
String value = scanner.nextLine();
variables.put(formProperty.getId(), value);
}
// выполняем задачу
taskService.complete(task.getId(), variables);
System.out.println("Task complete success:" + task.getTaskDefinitionKey());
}
// Re-query the process instance, making sure the latest state is available//processInstance = runtimeService.createProcessInstanceQuery()// .processInstanceId(processInstance.getId()).singleResult();
}
}
}
SpringBoot connection
Modify the project using Spring
Add dependencies to POM
POM with SpringBoot
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.3.1.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><dependencies><dependency><groupId>org.activiti</groupId><artifactId>activiti-spring-boot-starter-basic</artifactId><version>6.0.0</version></dependency><dependency><groupId>org.activiti</groupId><version>6.0.0</version><artifactId>activiti-spring-boot-starter-integration</artifactId></dependency>
....
DemoActiviti class is now so
DemoActiviti - SpringBootApplication
@SpringBootApplication@ImportResource("classpath:activiti.cfg.xml")
publicclassDemoActiviti{
publicstaticvoidmain(String[] args){
Locale.setDefault(Locale.ENGLISH);
SpringApplication.run(DemoActiviti.class, args);
}
}
I use a mixed model - when part of the beans are described in the xml configuration (@ImportResource ("classpath: activiti.cfg.xml")), and the other is defined through annotations.
activiti.cfg.xml - spring
<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"><beanid="dataSource"class="org.springframework.jdbc.datasource.SimpleDriverDataSource"><propertyname="url"value="jdbc:oracle:thin:@localhost:1521:xe" /><propertyname="driverClass"value="oracle.jdbc.driver.OracleDriver" /><propertyname="username"value="BPM" /><propertyname="password"value="1" /></bean><beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><propertyname="dataSource"ref="dataSource" /></bean><beanid="processEngineConfiguration"class="org.activiti.spring.SpringProcessEngineConfiguration"><propertyname="dataSource"ref="dataSource" /><propertyname="transactionManager"ref="transactionManager" /><propertyname="databaseSchemaUpdate"value="true" /><propertyname="asyncExecutorActivate"value="false" /></bean></beans>
Now Spring is responsible for the configuration, it can be seen
bean id="processEngineConfiguration"class="org.activiti.spring.SpringProcessEngineConfiguration"
Add for SpringBoot standard command line processing, in the form of components
Commandline
@ComponentpublicclassCommandLineimplementsCommandLineRunner{
@Autowiredprivate DemoService demoService;
publicvoidrun(String... args){
if ("test".equals(args[0])) {
demoService.startTest();
} elseif ("develop".equals(args[0])) {
demoService.startDevelop();
}
}
}
Which will process all those commands, I will not implement them all, everything is simple, I will show two: test and develop. And add a service to process them.
Demoservice
@ServicepublicclassDemoService{
@Autowiredprivate TaskService taskService;
@Autowiredprivate FormService formService;
publicvoidstartTest(){
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("testers").list();
if (tasks.isEmpty()) {
System.out.println("Задач на тестирование нет");
return;
}
processTasks(tasks);
}
publicvoidstartDevelop(){
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("develop").list();
if (tasks.isEmpty()) {
System.out.println("Задач на разработку нет");
return;
}
processTasks(tasks);
}
privatevoidprocessTasks(List<Task> tasks){
Scanner scanner = new Scanner(System.in);
for (Task task : tasks) {
...... тут как и ранее, выше
}
In the CommandLine Autowir component, the DemoService service, and in it the Activiti services already prepared by Spring
@AutowiredprivateTaskService taskService;
We collect, run as before from the command line.
If you want to use task execution from the Web, then we connect the REST API.
REST API
SpringBoot by default will provide the embedded Tomcat server, and then the matter of technology.
In POM, to what is, add spring web dependency
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
We delete the CommandLine component, now everything will be delivered via the URL via HTTP. Add RestController:
RestController
@RestControllerpublicclassDemoRestController{
@Autowiredprivate DemoService demoService;
@RequestMapping(value="/test", method= RequestMethod.GET,
produces= {MediaType.APPLICATION_JSON_VALUE})
public List<String> startTest(@RequestParam String devResult){
List<String> strings = demoService.startTest(devResult);
return strings;
}
@RequestMapping(value="/develop", method= RequestMethod.GET,
produces= MediaType.APPLICATION_JSON_VALUE)
public List<String> startDevelop(@RequestParam String issue){
List<String> strings = demoService.startDevelop(issue);
return strings;
}
@RequestMapping(value="/start", method= RequestMethod.GET,
produces= MediaType.APPLICATION_JSON_VALUE)
public List<String> startProcess(){
List<String> strings = demoService.startDevProcess();
return strings;
}
}
We execute the same commands, slightly modified the responses of the DemoService service, which is Autowire in the controller.
Demoservice
@ServicepublicclassDemoService{
@Autowiredprivate TaskService taskService;
@Autowiredprivate FormService formService;
@Autowiredprivate RuntimeService runtimeService;
public List<String> startTest(String devResult){
List<String> results = new ArrayList<>();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("testers").list();
if (tasks.isEmpty()) {
results.add("The tasks for testing are not");
return results;
}
Object issue = runtimeService.getVariables(tasks.get(0).getProcessInstanceId()).get("issue");
processTasks(tasks, devResult);
results.add("Task N " + issue + " - tested, result=" + devResult);
return results;
}
public List<String> startDevelop(String issue){
List<String> results = new ArrayList<>();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("programmers").list();
if (tasks.isEmpty()) {
results.add("There are no development tasks");
return results;
}
processTasks(tasks, issue);
Object mIssue = runtimeService.getVariables(tasks.get(0).getProcessInstanceId()).get("issue");
results.add("Task N " + mIssue + " - taken in the develop");
return results;
}
public List<String> startDevProcess(){
List<String> results = new ArrayList<>();
ProcessInstance myProcess = runtimeService.startProcessInstanceByKey("devProcess");
results.add("The process is started #"+myProcess.getId());
return results;
}
privatevoidprocessTasks(List<Task> tasks, String param){
for (Task task : tasks) {
FormData formData = formService.getTaskFormData(task.getId());
Map<String, Object> variables = new HashMap<>();
// переменные задачиfor (FormProperty formProperty : formData.getFormProperties()) {
variables.put(formProperty.getId(), param);
}
// выполняем задачу
taskService.complete(task.getId(), variables);
}
}
}
we test using curl, here's the result:
![image](https://habrastorage.org/webt/ig/hr/li/ighrlit0unzave5idkbo-nwh1z8.jpeg)
I changed the port for Tomcat to 8081 in application.properties
server.port = 8081
Activiti job
There are many constructions in Activiti, for example, the launch of scheduled tasks is a “TimerStartEvent”. In order for Job to start being executed in the configuration, you need to specify
property name="asyncExecutorActivate" value="true"
(see activiti.cfg.xml), then the java process will remain running and will check the schedule and run tasks. I will return to the initial project, where Activiti is used in its pure form.
In the DemoActiviti class I will leave support for only two commands: deploy and start. I will do a new process.
![image](https://habrastorage.org/webt/cv/3i/dc/cv3idc8pjyywdo2p5kfwzucp5ky.jpeg)
After the process starts, it will switch to a timer that will schedule the “Develop” task. The timer schedule will be - run every 10 seconds., Cron expression - “0/10 * * * *?”.
![image](https://habrastorage.org/webt/5f/57/-l/5f57-lfbt26ref3zfnz0eoljz9u.jpeg)
Let’s deploy the new process as before, then start the process (start). All - the task is performed every 10 seconds.
The Activiti component - ServiceTask, which can be specified as the implementation of the Java class, is selected as the task.
![image](https://habrastorage.org/webt/yg/wi/pb/ygwipb_bxqq5n9iy4rrli69ohdi.jpeg)
class DemoDelegate
publicclassDemoDelegateimplementsJavaDelegate{
@Overridepublicvoidexecute(DelegateExecution execution){
Date now = new Date();
execution.setVariable("issue", now.toString());
System.out.println("job start="+now);
}
}
In the table in the database (select * from ACT_RU_TIMER_JOB t), you can see the
![image](https://habrastorage.org/webt/y-/-u/ql/y--uqlqlsbfw7rllzzh3a3bp5gc.jpeg)
activity of the Job, in the DUEDATE_ field there will be the time of the next launch.
In the execution history, the variable “issue” from Delegate will be fixed.
select * from ACT_HI_VARINST t
![image](https://habrastorage.org/webt/5b/gr/iz/5bgrizfuld0u-u9g7qyu516npcc.jpeg)
code for DemoActiviti c Job
publicclassDemoActiviti{
privatestaticfinal String DEV_PROCESS = "devProcessJob";
publicstaticvoidmain(String[] args){
Locale.setDefault(Locale.ENGLISH);
ProcessEngineConfiguration cfg = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
ProcessEngine processEngine = cfg.buildProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
String mode = StringUtils.EMPTY;
if (args.length > 0) {
mode = args[0];
}
System.out.println("Processes mode: " + mode);
Deployment deployment;
if ("deploy".equals(mode)) {
deployment = repositoryService.createDeployment()
.addClasspathResource("processes/MyProcessJob.bpmn").deploy();
System.out.println("deploy process success");
System.exit(0);
} else {
List<Deployment> myProcesses = repositoryService.createDeploymentQuery()
.processDefinitionKey(DEV_PROCESS).list();
deployment = myProcesses.get(myProcesses.size()-1);
System.out.println("get process success:" + deployment.getId());
}
//
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance;
if ("start".equals(mode)){
ProcessInstance myProcess = runtimeService.startProcessInstanceByKey(DEV_PROCESS);
System.out.println("start process success:" + myProcess.getName() +", id="+ myProcess.getId());
}
}
}
There is still a lot left overboard: Events, Listener, JPA, etc., maybe I’ll come back to them. Activiti Eclipse Designer
Materials
devProcess bpmn
<?xml version="1.0" encoding="UTF-8"?><definitionsxmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:activiti="http://activiti.org/bpmn"xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"typeLanguage="http://www.w3.org/2001/XMLSchema"expressionLanguage="http://www.w3.org/1999/XPath"targetNamespace="http://www.activiti.org/test"><processid="devProcess"name="Dev process"isExecutable="true"><startEventid="startevent1"name="Start"activiti:initiator="programmerId"></startEvent><userTaskid="develop"name="Develop"activiti:candidateGroups="programmers"><extensionElements><activiti:formPropertyid="issue"name="issue"type="string"required="true"></activiti:formProperty></extensionElements></userTask><sequenceFlowid="flow1"sourceRef="startevent1"targetRef="develop"></sequenceFlow><userTaskid="test"name="Test"activiti:candidateGroups="testers"><extensionElements><activiti:formPropertyid="devResult"name="devResult"type="string"default="No"required="true"></activiti:formProperty></extensionElements></userTask><sequenceFlowid="flow2"sourceRef="develop"targetRef="test"></sequenceFlow><exclusiveGatewayid="gateway"name="Exclusive Gateway"default="flowNo"></exclusiveGateway><sequenceFlowid="flow3"sourceRef="test"targetRef="gateway"></sequenceFlow><sequenceFlowid="flowOk"name="Ok"sourceRef="gateway"targetRef="endevent1"><conditionExpressionxsi:type="tFormalExpression"><![CDATA[${devResult == "Ok"}]]></conditionExpression></sequenceFlow><sequenceFlowid="flowNo"name="No"sourceRef="gateway"targetRef="develop"></sequenceFlow><endEventid="endevent1"name="End"></endEvent></process></definitions>
devProcessJob bpmn
<?xml version="1.0" encoding="UTF-8"?><definitionsxmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:activiti="http://activiti.org/bpmn"xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"typeLanguage="http://www.w3.org/2001/XMLSchema"expressionLanguage="http://www.w3.org/1999/XPath"targetNamespace="http://www.activiti.org/test"><processid="devProcessJob"name="Dev process Job"isExecutable="true"><startEventid="startevent"name="Start"activiti:initiator="programmerId"></startEvent><sequenceFlowid="flow1"sourceRef="startevent"targetRef="timerstartevent"></sequenceFlow><endEventid="endevent"name="End"></endEvent><startEventid="timerstartevent"name="Timer start"><extensionElements><activiti:formPropertyid="issue"name="issue"type="string"></activiti:formProperty></extensionElements><timerEventDefinition><timeCycle>0/10 * * * * ?</timeCycle></timerEventDefinition></startEvent><sequenceFlowid="flow2"sourceRef="timerstartevent"targetRef="servicetask1"></sequenceFlow><sequenceFlowid="flow3"sourceRef="servicetask1"targetRef="endevent"></sequenceFlow><serviceTaskid="servicetask1"name="Develop"activiti:class="com.example.DemoDelegate"></serviceTask></process></definitions>