本家 : http://www.activiti.org/userguide/
1. Introduction †
2. Getting Started †
- Tutorial
- Java6 + Tomcat + Activiti-Explorer
- Demo database setup flag
- Activiti Explorer や Rest API では、後述する標準の activiti.cfg.xml の代わりに db.properties, engine.properties でアプリケーションの設定を行う
- WEB-INF/classes/db.properties
db=h2
jdbc.driver=org.h2.Driver
jdbc.url=jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000
jdbc.username=sa
jdbc.password=
- WEB-INF/classes/engine.properties
# demo data properties
create.demo.users=true
create.demo.definitions=true
create.demo.models=true
create.demo.reports=true
# engine properties
engine.schema.update=true
engine.activate.jobexecutor=false
engine.asyncexecutor.enabled=true
engine.asyncexecutor.activate=true
engine.history.level=full
# email properties
#engine.email.enabled=true
#engine.email.host=localhost
#engine.email.port=1025
3. Configuration †
- 独自の Workflow アプリ ⇒ ProcessEngine? を使ってワークフローを操作
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
- デフォルト設定では Classpath にある activiti.cfg.xml または activiti-context.xml の設定に基づいて ProcessEngine? が作られる
(設定内容をプログラム内で明示的に設定することも可。後述)
- activity.cfg.xml
<beans xmlns="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">
<!-- 3.2.processEngineConfiguration
Stand alone app : org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration
Test (Use in-memory h2) : org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration
Spring integration : org.activiti.spring.SpringProcessEngineConfiguration
Stand alone app with JTA : org.activiti.engine.impl.cfg.JtaProcessEngineConfiguration
-->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
<!-- 3.3-3.8.database connection
support h2, musql, oracle, postgres, db2, mssql
-->
<property name="jdbcUrl" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
<property name="jdbcDriver" value="org.h2.Driver" />
<property name="jdbcUsername" value="sa" />
<property name="jdbcPassword" value="" />
<!-- Optional database connection properties * The unit of XXXTime is millisec *
<property name="jdbcMaxActiveConnections" value="10" />
<property name="jdbcMaxIdleConnections" value="" />
<property name="jdbcMaxCheckoutTime" value="20000" />
<property name="jdbcMaxWaitTime" value="20000" />
-->
<!-- Schema Update
false (default) : If the db schema version != activiti lib version then abend
true : If the db schema version != activiti lib version then update db schema
create-drop : Always create and drop schema
<property name="databaseSchemaUpdate" value="false" />
-->
<!-- 3.9-3.11 Job Executor
By default, the JobExecutor is activated when the process engine boots.
By default, the AsyncExecutor is not enabled and the JobExecutor is used
due to legacy reasons.
It?'s however recommended to use the new AsyncExecutor instead.
-->
<!--
<property name="jobExecutorActivate" value="false" />
<property name="asyncExecutorEnabled" value="true" />
<property name="asyncExecutorActivate" value="true" />
-->
<!-- 3.12. Mail Configuration (for email task)
|======================|=======================|
|property name | Default value |
|======================|=======================|
|mailServerHost | localhost |
|mailServerPort | 25 |
|mailServerDefaultFrom | activiti@activiti.org |
|mailServerUsername | (not set) |
|mailServerPassword | (not set) |
|mailServerUseSSL | (not set) |
|mailServerUseTLS | (not set) |
|======================|=======================|
-->
<property name="mailServerHost" value="localhost" />
<property name="mailServerPort" value="50025" />
<!-- 3.13. History (for logging task execution)
<property name="history" value="audit" />
-->
<!-- 3.15. Cache (default is no limit)
<property name="processDefinitionCacheLimit" value="10" />
-->
</bean>
</beans>
- Spring の bean.xml 形式だが、文法を借りているだけ。Activiti に Spring が必須ではない
- JNDI から Datasource を lookup して利用することも可能
- Activiti Explorer、 Activity Rest API は、db.properties で接続先を設定するようになっている。→ 内部的には、その他の ProcessEngine 作成方法
- 設定ファイルを明示する
ProcessEngine processEngine
= ProcessEngineConfiguration
.createProcessEngineConfigurationFromInputStream(inputStream).buildProcessEngine()
- プログラム中で設定を行う (Activiti-Explorer、Activiti-Rest はこのやり方)
- デフォルト設定
ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration();
- デフォルト設定に個別設定を追加
ProcessEngine processEngine
= ProcessEngineConfiguration
.createStandaloneInMemProcessEngineConfiguration()
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE)
.setJdbcUrl("jdbc:h2:mem:my-own-db;DB_CLOSE_DELAY=1000")
.setAsyncExecutorEnabled(true)
.setAsyncExecutorActivate(false)
.buildProcessEngine();
3.16. Loggin †
3.18. Event handlers †
- ProcessEngine? の起動・停止、JOB の開始・終了イベントをフックできる
- 3.18.6. Supported event types
- EventListener? は ProcessEngine? のインスタンスのもちもの。DBを共有して同じJOBを二つの ProcessEneigne? をまたいで実行した場合、それぞれの EventLister? はそれぞれの ProcessEngine? (の実行環境にある activiti.cfg.xml) に定義されているもの
- EventListener? 実装
- インタフェース org.activiti.engine.delegate.event.ActivitiEventListener? を実装する
- 3.18.1. Event listener implementation
- EventListener? 定義
- activiti.cfg.xml に定義する
- 全イベントを拾う 3.18.2. Configuration and setup
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
...
<property name="eventListeners">
<list>
<bean class="org.activiti.engine.example.MyEventListener" />
</list>
</property>
</bean>
- 特定のイベントだけを拾う
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
...
<property name="typedEventListeners">
<map>
<entry key="JOB_EXECUTION_SUCCESS,JOB_EXECUTION_FAILURE" >
<list>
<bean class="org.activiti.engine.example.MyJobEventListener" />
</list>
</entry>
</map>
</property>
</bean>
- 処理中でイベントリスナを設定 3.18.3. Adding listeners at runtime
- 特定のプロセスだけ拾う 3.18.4. Adding listeners to process definitions
4. The Activiti API †
4.1~4.3 ワークフローを実行するコンソールアプリ †
開発環境 †
- https://eclipse.org/ 最新版(2015時点)のMarsでOK
- plugin
- BPMN を GUI で編集するために Activiti BPMN 2.0 designer が必要ないのであれば Eclipse でなくてもいい。プログラム本体を作るだけなら Java の Maven Project が扱えればどんな IDE でもいい。
サンプルプロジェクト †
activiti.cfg.xml †
ここでは、テスト用の Table をロードする設定(StandaloneInMemProcessEngineConfiguration?)を使う
<beans xmlns="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">
<!-- 3.2.processEngineConfiguration
Stand alone app : org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration
Test (Use in-memory h2) : org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration
Spring integration : org.activiti.spring.SpringProcessEngineConfiguration
Stand alone app with JTA : org.activiti.engine.impl.cfg.JtaProcessEngineConfiguration
-->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
<!-- 3.3-3.8.database connection
support h2, musql, oracle, postgres, db2, mssql
-->
<property name="jdbcUrl" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
<property name="jdbcDriver" value="org.h2.Driver" />
<property name="jdbcUsername" value="sa" />
<property name="jdbcPassword" value="" />
<!-- Optional database connection properties * The unit of XXXTime is millisec *
<property name="jdbcMaxActiveConnections" value="10" />
<property name="jdbcMaxIdleConnections" value="" />
<property name="jdbcMaxCheckoutTime" value="20000" />
<property name="jdbcMaxWaitTime" value="20000" />
-->
<!-- Schema Update
false (default) : If the db schema version != activiti lib version then abend
true : If the db schema version != activiti lib version then update db schema
create-drop : Always create and drop schema
<property name="databaseSchemaUpdate" value="false" />
-->
<!-- 3.9-3.11 Job Executor
By default, the JobExecutor is activated when the process engine boots.
By default, the AsyncExecutor is not enabled and the JobExecutor is used
due to legacy reasons.
It?'s however recommended to use the new AsyncExecutor instead.
-->
<!--
<property name="jobExecutorActivate" value="false" />
<property name="asyncExecutorEnabled" value="true" />
<property name="asyncExecutorActivate" value="true" />
-->
<!-- 3.12. Mail Configuration (for email task)
|======================|=======================|
|property name | Default value |
|======================|=======================|
|mailServerHost | localhost |
|mailServerPort | 25 |
|mailServerDefaultFrom | activiti@activiti.org |
|mailServerUsername | (not set) |
|mailServerPassword | (not set) |
|mailServerUseSSL | (not set) |
|mailServerUseTLS | (not set) |
|======================|=======================|
-->
<property name="mailServerHost" value="localhost" />
<property name="mailServerPort" value="50025" />
<!-- 3.13. History (for logging task execution)
<property name="history" value="audit" />
-->
<!-- 3.15. Cache (default is no limit)
<property name="processDefinitionCacheLimit" value="10" />
-->
</bean>
</beans>
テスト用BPMN (VacationRequest?.bpmn20.xml) †
ActivitiExplorerのデモアプリ で使った休暇申請のワークフローを /src/main/resources/org/activiti/test に配置
Java コンソールアプリ †
BMMNの配備(DB格納) → プロセス開始 (休暇申請) → 否決 → 再申請
の流れをやってみる
package com.snail.exam;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyProcess {
private static final Logger log = LoggerFactory.getLogger(MyProcess.class);
public static void main(String[] args) {
try {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// ----- 4.3.1. Deploying the process
log.info("--- #1. Deploying the process");
RepositoryService repositoryService = processEngine.getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource("org/activiti/test/VacationRequest.bpmn20.xml")
.deploy();
log.info("Number of process definitions {}", repositoryService.createProcessDefinitionQuery().count());
for (ProcessDefinition p : repositoryService.createProcessDefinitionQuery().list()) {
log.info("PROCESS DEF [id={},name={},key={}]", p.getId(), p.getName(), p.getKey());
}
// ----- 4.3.2. Starting a process instance
log.info("--- #2. Starting a process instance");
// VacationRequest.bpmn20.xml L3-10
// --------------------------------------------------------------------------
// <process id="vacationRequest" name="Vacation request" isExecutable="true">
// ^^^^^^^^^^^^^^^
// <startEvent id="request" activiti:initiator="employeeName">
// ^^^^^^^*1 ^^^^^^^^^^^^ $employeeName = the user name
// <extensionElements>
// <activiti:formProperty id="numberOfDays" name="Number of days" type="long" required="true">
// ^^^^^^^^^^^^ ^^^^
// </activiti:formProperty>
// <activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" type="date"
// ^^^^^^^^^ ^^^^
// datePattern="dd-MM-yyyy hh:mm" required="true"></activiti:formProperty>
// <activiti:formProperty id="vacationMotivation" name="Motivation" type="string">
// ^^^^^^^^^^^^^^^^^^ ^^^^^^
// </activiti:formProperty>
// </extensionElements>
// </startEvent>
// <activiti:formProperty>
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employeeName", "Kermit");
variables.put("numberOfDays", new Integer(4));
variables.put("startDate", DateUtils.parseDate("1999-12-31", "yyyy-MM-dd"));
variables.put("vacationMotivation", "I'm really tired!");
// the process to run
// id : <process id="vacationRequest">
// arguments : <activiti:formProperty>
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);
log.info("Number of process instances: " + runtimeService.createProcessInstanceQuery().count());
for (ProcessInstance p : runtimeService.createProcessInstanceQuery().list()) {
log.info("PROCESS INSTANCE [id={},pid={},pname={},pkey={}]"
, p.getId()
, p.getProcessDefinitionId()
, p.getProcessDefinitionName()
, p.getProcessDefinitionKey());
}
// ----- 4.3.3. Completing tasks (Reject Request)
// VacationRequest.bpmn20.xml L11-21
// --------------------------------------------------------------------------
// <sequenceFlow id="flow1" sourceRef="request" targetRef="handleRequest"></sequenceFlow>
// ^^^^^^^*1 ^^^^^^^^^^^^^*2
// <userTask id="handleRequest" name="Handle vacation request" activiti:candidateGroups="management">
// ^^^^^^^^^^^^^*2 ^^^^^^^^^^
// <documentation>${employeeName} would like to take ${numberOfDays} day(s) of vacation
// (Motivation: ${vacationMotivation}).</documentation>
// <extensionElements>
// <activiti:formProperty id="vacationApproved" name="Do you approve this vacation" type="enum"
// ^^^^^^^^^^^^^^^^
// required="true">
// <activiti:value id="true" name="Approve"></activiti:value>
// <activiti:value id="false" name="Reject"></activiti:value>
// </activiti:formProperty>
// <activiti:formProperty id="managerMotivation" name="Motivation" type="string"></activiti:formProperty>
// ^^^^^^^^^^^^^^^^^
// </extensionElements>
// </userTask>
log.info("--- #3. Completing tasks (Reject Request)");
// Fetch all tasks for the management group
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
if (task.getProcessDefinitionId().startsWith("vacationRequest")){
if (task.getTaskDefinitionKey().equals("handleRequest")) {
// Description is <documentation>.
log.info("TASK REJECT REQ [{}]", task.getDescription());
// Do task (reject application)
Map<String, Object> taskVariables = new HashMap<String, Object>();
taskVariables.put("vacationApproved", "false");
taskVariables.put("managerMotivation", "We have a tight deadline!");
taskService.complete(task.getId(), taskVariables);
}
}
}
// ----- 4.3.3. Completing tasks (Adjust rejected request)
// VacationRequest.bpmn20.xml L22-23
// --------------------------------------------------------------------------
// <sequenceFlow id="flow2" sourceRef="handleRequest" targetRef="requestApprovedDecision"></sequenceFlow>
// ^^^^^^^^^^^^^*2 ^^^^^^^^^^^^^^^^^^^^^^^*3
// <exclusiveGateway id="requestApprovedDecision" name="Request approved?"></exclusiveGateway>
// ^^^^^^^^^^^^^^^^^^^^^^^*3
//
// VacationRequest.bpmn20.xml L30-35
// --------------------------------------------------------------------------
// <sequenceFlow id="flow5" name="denied" sourceRef="requestApprovedDecision" targetRef="adjustVacationRequestTask">
// ^^^^^^^^^^^^^^^^^^^^^^^*3 ^^^^^^^^^^^^^^^^^^^^^^^^^*4
// <conditionExpression xsi:type="tFormalExpression"><![CDATA[${vacationApproved == 'false'}]]>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// </conditionExpression>
// </sequenceFlow>
// <userTask id="adjustVacationRequestTask" name="Adjust vacation request" activiti:assignee="${employeeName}">
// ^^^^^^^^^^^^^^^^^^^^^^^^^*4
// <documentation>Your manager has disapproved your vacation request for ${numberOfDays} days.
// Reason: ${managerMotivation}</documentation>
log.info("--- #4. Completing tasks (Adjust rejected request)");
taskService = processEngine.getTaskService();
tasks = taskService.createTaskQuery().active().list();
for (Task task : tasks) {
if (task.getProcessDefinitionId().startsWith("vacationRequest")){
if (task.getTaskDefinitionKey().equals("adjustVacationRequestTask")) {
// Description is <documentation>.
log.info("ADJUST REJECT REQ [{}]", task.getDescription());
}
}
}
} catch (Throwable th) {
log.error("ERROR", th);
}
}
}
- 実行ログ
16-03-24 00:24:21 [INFO ] Initializing process engine using configuration 'file:/Users/atsushi/EclipseWorkspace/ActivitExam/target/classes/activiti.cfg.xml'
16-03-24 00:24:21 [INFO ] initializing process engine for resource file:/Users/atsushi/EclipseWorkspace/ActivitExam/target/classes/activiti.cfg.xml
16-03-24 00:24:21 [INFO ] Loading XML bean definitions from resource loaded through InputStream
16-03-24 00:24:22 [INFO ] performing create on engine with resource org/activiti/db/create/activiti.h2.create.engine.sql
16-03-24 00:24:22 [INFO ] performing create on history with resource org/activiti/db/create/activiti.h2.create.history.sql
16-03-24 00:24:22 [INFO ] performing create on identity with resource org/activiti/db/create/activiti.h2.create.identity.sql
16-03-24 00:24:22 [INFO ] ProcessEngine default created
16-03-24 00:24:22 [INFO ] initialised process engine default
16-03-24 00:24:22 [INFO ] --- #1. Deploying the process
16-03-24 00:24:22 [INFO ] Processing resource org/activiti/test/VacationRequest.bpmn20.xml
16-03-24 00:24:23 [INFO ] Number of process definitions 1
16-03-24 00:24:23 [INFO ] PROCESS DEF [id=vacationRequest:1:4,name=Vacation request,key=vacationRequest]
16-03-24 00:24:23 [INFO ] --- #2. Starting a process instance
16-03-24 00:24:23 [INFO ] Number of process instances: 1
16-03-24 00:24:23 [INFO ] PROCESS INSTANCE [id=5,pid=vacationRequest:1:4,pname=Vacation request,pkey=vacationRequest]
16-03-24 00:24:23 [INFO ] --- #3. Completing tasks (Reject Request)
16-03-24 00:24:23 [INFO ] TASK REJECT REQ [Kermit would like to take 4 day(s) of vacation (Motivation: I'm really tired!).]
16-03-24 00:24:23 [INFO ] --- #4. Completing tasks (Adjust rejected request)
16-03-24 00:24:23 [INFO ] ADJUST REJECT REQ [Your manager has disapproved your vacation request for 4 days.
Reason: We have a tight deadline!]
I got it.
BPMNの配備 (RepositoryService?) †
log.info("--- #1. Deploying the process");
RepositoryService repositoryService = processEngine.getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource("org/activiti/test/VacationRequest.bpmn20.xml")
.deploy()
プロセス開始 (RuntimeService?) †
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employeeName", "Kermit");
variables.put("numberOfDays", new Integer(4));
variables.put("startDate", DateUtils.parseDate("1999-12-31", "yyyy-MM-dd"));
variables.put("vacationMotivation", "I'm really tired!");
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);
- http://activiti.org/javadocs/org/activiti/engine/RuntimeService.html
- プロセスIDを指定してプロセスを開始する。入力値がある場合には Map 形式で投入する
- RuntimeService?.startProcessInstanceByKey?(String processDefinitionId?, Map<String,Object> variables) でプロセスを開始する
- processDefinitionId? : BPMN の process id
<process id="vacationRequest" name="Vacation request" isExecutable="true">
- variables : BPMN の <startEvent> に定義された入力値
<startEvent id="request" activiti:initiator="employeeName">
<extensionElements>
<activiti:formProperty id="numberOfDays" name="Number of days" type="long" required="true"></activiti:formProperty>
<activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" type="date"
datePattern="dd-MM-yyyy hh:mm" required="true"></activiti:formProperty>
<activiti:formProperty id="vacationMotivation" name="Motivation" type="string"></activiti:formProperty>
</extensionElements>
</startEvent>
タスク実行 (TaskService?) †
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
if (task.getProcessDefinitionId().startsWith("vacationRequest")){
if (task.getTaskDefinitionKey().equals("handleRequest")) {
// Description is <documentation>.
log.info("TASK REJECT REQ [{}]", task.getDescription());
// Do task (reject application)
Map<String, Object> taskVariables = new HashMap<String, Object>();
taskVariables.put("vacationApproved", "false");
taskVariables.put("managerMotivation", "We have a tight deadline!");
taskService.complete(task.getId(), taskVariables);
}
}
}
4.3.4.プロセスの Suspend と Activate †
package com.snail.exam;
import java.util.HashMap;
import java.util.Map;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyProcess2 {
private static final Logger log = LoggerFactory.getLogger(MyProcess2.class);
public static void main(String[] args) {
try {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// ----- 4.3.1. Deploying the process
log.info("--- #1. Deploying the process");
RepositoryService repositoryService = processEngine.getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource("org/activiti/test/VacationRequest.bpmn20.xml")
.deploy();
// ----- 4.3.4. Suspending and activating a process
log.info("--- #2. Suspend Process");
repositoryService.suspendProcessDefinitionByKey("vacationRequest");
// ----- 4.3.2. Starting a process instance
log.info("--- #3. Starting a process instance (will fail)");
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employeeName", "Kermit");
variables.put("numberOfDays", new Integer(4));
variables.put("startDate", DateUtils.parseDate("1999-12-31", "yyyy-MM-dd"));
variables.put("vacationMotivation", "I'm really tired!");
// the process to run
// id : <process id="vacationRequest">
// arguments : <activiti:formProperty>
RuntimeService runtimeService = processEngine.getRuntimeService();
try {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);
} catch (ActivitiException e) {
log.error("ERROR", e);
}
// ----- 4.3.4. Suspending and activating a process
log.info("--- #5. Activate Process");
repositoryService.activateProcessDefinitionByKey("vacationRequest");
log.info("--- #6. Starting a process instance (will success)");
try {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);
log.info("{} was started", processInstance.getProcessDefinitionId());
} catch (ActivitiException e) {
log.error("ERROR", e);
}
} catch (Throwable th) {
log.error("ERROR", th);
}
}
}
4.4 Query API †
- タスクの検索
- TaskQuery? と NativeTaskQuery? を使うことができる
- TaskQuery?
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("kermit")
.processVariableValueEquals("orderId", "0815")
.orderByDueDate().asc()
.list();
- NativeTaskQuery?
List<Task> tasks = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}")
.parameter("taskName", "gonzoTask")
.list();
long count = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, "
+ managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")
.count();
- クラス図
- NativeTaskQuery? の列名
- その他の Query
代表的な TaskQuery? によるタスクの検索と同様に、ProcessEngine? から Service を取得して、Service から Query を取得して、検索を行う。
どんな検索ができるかは Javadoc を参照 http://activiti.org/javadocs/org/activiti/engine/query/Query.html
4.5.Variables †
- プロセス変数は ACT_RU_VARIABLE に格納される
- アクセス方法
- BPMN の中の EL 式
- ${myVar}
- ${myBean.myProperty} ⇔ Object も入れられるようだけど、文字列にしといたほうが無難だよね
- 暗黙オブジェクト
${execution} | DelegateExecution? |
${task} | DelegateTask? |
${authenticatedUserId?} | String |
- Java Service Task の execution
JavaDelegate?#execution(DelegateExecution? execution)
- Object execution.getVariable(String variableName);
- void execution.setVariable(String variableName, Object value);
- Map<String,Object> execution.getVariables();
- RuntimeService?
- Object execution.getVariable(String executionId, String variableName);
- void execution.setVariable(String executionId, String variableName, Object value);
- Map<String,Object> execution.getVariables(String executionId);
- TaskSerivce?
- Object execution.getVariable(String taskId, String variableName);
- void execution.setVariable(String taskId, String variableName, Object value);
- Map<String,Object> execution.getVariables(String taskId);
- 変数をたくさん取り出すときには、Map<String,Object> getVariables() を使って一気に取り出す。変数は RDB のテーブルから取り出しているので、一気に読み書きしたほうが効率的
- 変数スコープ
- getVariable() と getVariableLocal?() があって、変数スコープがあるようだけどよう分からん
- こんな BPMN を作ってみた
B,C で setVariablesLocal?() した変数は、D,E で見られなかたりするのかな...と思ったけどそうでもないらしい
- 各 Task では、現在の Variable を表示して、自分のタスク名の変数を setVariable() setVariableLocal?() する
package com.snail.exam;
import java.util.ArrayList;
import java.util.List;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VariableTask implements JavaDelegate {
private static final Logger log = LoggerFactory.getLogger(VariableTask.class);
@Override
public void execute(DelegateExecution execution) throws Exception {
String id = execution.getCurrentActivityName();
log.info(id);
// Dump variables
List<String> keys = new ArrayList<String>(execution.getVariableNames());
keys.sort((o1,o2)->{return o1.compareTo(o2);});
keys.forEach((key)->{
log.info("@{} EXEC SCOPE {} = {}", id, key, execution.getVariable(key));
});
List<String> keysLocal = new ArrayList<String>(execution.getVariableNamesLocal());
keysLocal.sort((o1,o2)->{return o1.compareTo(o2);});
keysLocal.forEach((key)->{
log.info("@{} LOCAL SCOPE {} = {}", id, key, execution.getVariableLocal(key));
});
// Update variables
execution.getVariableNames().forEach((name)->{
execution.setVariable(name, id);
});
execution.getVariableNamesLocal().forEach((name)->{
execution.setVariableLocal(name, id);
});
execution.setVariable(id + "_EXEC", id);
execution.setVariable(id + "_LOCAL", id);
}
}
- 実行結果
16-05-21 23:38:30 [INFO ] Initializing process engine using configuration 'file:/Users/atsushi/EclipseWorkspace/ActivitExam/target/classes/activiti.cfg.xml'
16-05-21 23:38:30 [INFO ] initializing process engine for resource file:/Users/atsushi/EclipseWorkspace/ActivitExam/target/classes/activiti.cfg.xml
16-05-21 23:38:30 [INFO ] Loading XML bean definitions from resource loaded through InputStream
16-05-21 23:38:31 [INFO ] performing create on engine with resource org/activiti/db/create/activiti.h2.create.engine.sql
16-05-21 23:38:31 [INFO ] performing create on history with resource org/activiti/db/create/activiti.h2.create.history.sql
16-05-21 23:38:31 [INFO ] performing create on identity with resource org/activiti/db/create/activiti.h2.create.identity.sql
16-05-21 23:38:31 [INFO ] ProcessEngine default created
16-05-21 23:38:31 [INFO ] initialised process engine default
16-05-21 23:38:31 [INFO ] --- #1. Deploying the process
16-05-21 23:38:31 [INFO ] Processing resource org/activiti/test/VariableProcess.bpmn
16-05-21 23:38:32 [INFO ] TaskA
16-05-21 23:38:32 [INFO ] @TaskA EXEC SCOPE start = Start
16-05-21 23:38:32 [INFO ] @TaskA LOCAL SCOPE start = Start
16-05-21 23:38:32 [INFO ] TaskB
16-05-21 23:38:32 [INFO ] @TaskB EXEC SCOPE TaskA_EXEC = TaskA
16-05-21 23:38:32 [INFO ] @TaskB EXEC SCOPE TaskA_LOCAL = TaskA
16-05-21 23:38:32 [INFO ] @TaskB EXEC SCOPE start = TaskA
16-05-21 23:38:32 [INFO ] TaskC
16-05-21 23:38:32 [INFO ] @TaskC EXEC SCOPE TaskA_EXEC = TaskB
16-05-21 23:38:32 [INFO ] @TaskC EXEC SCOPE TaskA_LOCAL = TaskB
16-05-21 23:38:32 [INFO ] @TaskC EXEC SCOPE TaskB_EXEC = TaskB
16-05-21 23:38:32 [INFO ] @TaskC EXEC SCOPE TaskB_LOCAL = TaskB
16-05-21 23:38:32 [INFO ] @TaskC EXEC SCOPE start = TaskB
16-05-21 23:38:32 [INFO ] TaskD
16-05-21 23:38:32 [INFO ] @TaskD EXEC SCOPE TaskA_EXEC = TaskC
16-05-21 23:38:32 [INFO ] @TaskD EXEC SCOPE TaskA_LOCAL = TaskC
16-05-21 23:38:32 [INFO ] @TaskD EXEC SCOPE TaskB_EXEC = TaskC
16-05-21 23:38:32 [INFO ] @TaskD EXEC SCOPE TaskB_LOCAL = TaskC
16-05-21 23:38:32 [INFO ] @TaskD EXEC SCOPE TaskC_EXEC = TaskC
16-05-21 23:38:32 [INFO ] @TaskD EXEC SCOPE TaskC_LOCAL = TaskC
16-05-21 23:38:32 [INFO ] @TaskD EXEC SCOPE start = TaskC
16-05-21 23:38:32 [INFO ] TaskE
16-05-21 23:38:32 [INFO ] @TaskE EXEC SCOPE TaskA_EXEC = TaskD
16-05-21 23:38:32 [INFO ] @TaskE EXEC SCOPE TaskA_LOCAL = TaskD
16-05-21 23:38:32 [INFO ] @TaskE EXEC SCOPE TaskB_EXEC = TaskD
16-05-21 23:38:32 [INFO ] @TaskE EXEC SCOPE TaskB_LOCAL = TaskD
16-05-21 23:38:32 [INFO ] @TaskE EXEC SCOPE TaskC_EXEC = TaskD
16-05-21 23:38:32 [INFO ] @TaskE EXEC SCOPE TaskC_LOCAL = TaskD
16-05-21 23:38:32 [INFO ] @TaskE EXEC SCOPE TaskD_EXEC = TaskD
16-05-21 23:38:32 [INFO ] @TaskE EXEC SCOPE TaskD_LOCAL = TaskD
16-05-21 23:38:32 [INFO ] @TaskE EXEC SCOPE start = TaskD
16-05-21 23:38:32 [INFO ] TaskF
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskA_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskA_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskB_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskB_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskC_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskC_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskD_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskD_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskE_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE TaskE_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF EXEC SCOPE start = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskA_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskA_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskB_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskB_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskC_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskC_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskD_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskD_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskE_EXEC = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE TaskE_LOCAL = TaskE
16-05-21 23:38:32 [INFO ] @TaskF LOCAL SCOPE start = TaskE
- よくわからない。Local のことは忘れて、プロセス・インスタンのスコープで変数が保持されると思っておいたほうが良さそう
4.7 Unit Test †
public class MyBusinessProcessTest {
@Rule
public ActivitiRule activitiRule = new ActivitiRule();
@Test
@Deployment
public void ruleUsageExample() {
RuntimeService runtimeService = activitiRule.getRuntimeService();
runtimeService.startProcessInstanceByKey("ruleUsage");
TaskService taskService = activitiRule.getTaskService();
Task task = taskService.createTaskQuery().singleResult();
assertEquals("My Task", task.getName());
taskService.complete(task.getId());
assertEquals(0, runtimeService.createProcessInstanceQuery().count());
}
}
@Deployment で BPMN を配備することもできる
@Deplpyment(resources = {"org/activiti/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})
4.9 The process engine in a web application †
@WebServletContextListener
public class ProcessEnginesServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ProcessEngines.init();
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ProcessEngines.destroy();
}
}
コンソールアプリと同じように ProcessEngines?.getDefaultProcessEngine?() で ProcessEngine? を取得するが、アプリ再起動で確実に設定ファイルが反映されるように ServletContextListener? で ProcessEngine?.init(), ProcessEngine?.destroy() を実行する
6. Deployment †
7.標準 BPMN 2.0 †
- <Flow>
- <sequenceFlow> フロー
sorceRef と targetRef に <Event> <Task> の id を設定して、処理の流れを作る
条件分岐は、sequenceFlow の一種
8.Activiti 拡張 BPMN 2.0 †
- BPMN 規格自体に、BPMN を拡張できる仕様が備わっている
8.2.Event †
- Event は、Eclipse Activiti Designer のパレットから Drag&Drop すれば良い
- Event の設定は、Activiti Designer 上の properties で編集すると BPMN (XML) に反映される
- <timerEventDefinition?>
- <?Event> の子要素にすると、指定したタイマーによって Event が発効する
- Note. timers are only fired when the job or async executor is enabled (i.e. jobExecutorActivate? or asyncExecutorActivate? needs to be set to true in the activiti.cfg.xml, since the job and async executor are disabled by default).
- https://en.wikipedia.org/wiki/ISO_8601 (日本語Wikipedia には、Duration、Intervals の記述なし)
- <errorEventDefinition?>
- <startEvent> の子要素にすると、エラーの発生を契機に Event が発効する
- <endEvent> の子要素にすると、その <endEvent> に到達した時に指定されたエラーでフローを終了する
- Java の Exception の発生を検知して <endEvent> に跳ぶのではなく、その <endEvent> に到達した時に指定されたエラーでフローを終了する
- <signalEventDefinition?>
- <messageEventDefinition?>
- メッセージは、特定のプロセスに送られる。
- BPMNのフローからメッセージを送ることはできない
- <startEvent> の子要素にすると、メッセージの受信を契機に Event が発効する
- <intermediateCatchEvent?> の子要素にすると、メッセージ受信を契機にそこからフローが始まる
- プログラムからメッセージを発効する
RuntimeService.messageEventReceived(String messageName, String executionId);
RuntimeService.messageEventReceived(String messageName, String executionId, HashMap<String, Object> processVariables);
- フローをはじめる (メッセージ受信を契機に実行できるフローがあるだけでは、勝手にフローは実行されないことに注意。どこかの Process Engine が実行する必要がある)
ProcessInstance.startProcessInstanceByMessage(String messageName);
ProcessInstance.startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance.startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object> processVariables);
- あるメッセージを契機にはじまるプロセスの一覧 (<startEvent> がメッセージ契機なプロセスの一覧)
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.messageEventSubscription("newCallCenterBooking")
.singleResult();
- あるメッセージを契機に特殊処理が走るプロセスの一覧 (<intermediateCatch> でメッセージを待っているプロセスインスタンスの一覧)
Execution execution = runtimeService.createExecutionQuery()
.messageEventSubscriptionName("paymentReceived")
.variableValueEquals("orderId", message.getOrderId())
.singleResult();
- JMSなどを使って、外部システムのイベントを契機にフローを動かしたい時とかに使う
- <terminateEventDefinition?>
- <endEvnet> の子要素
- 平行して動いているフローが有った場合には強制終了して、プロセスインスタンスを完全に終了させる
- <boundaryEvent>
- Task に張り付いて、Task 内のイベントに基づいてフローを開始する
- <timerEventDefinition?> をつけると、主処理とは別に Task 終了後一定時間してから起動するフローを書ける
- <errorEventDefinition?> をつけると、Task / Sub process があるエラーで終わった場合に起動するフローを書ける
- <signalEventDefinition?> をつけると、当該タスクの実効待中にシグナルを受信した時のフローを書ける
- <messageEventDefinition?> をつけると、当該タスクの実効待中にメッセージを受信した時のフローを書ける
8.3 Sequence Flow †
8.4 Gateways †
- Gateway は、Eclipse Activiti Designer のパレットから Drag&Drop すれば良い
- <exclusiveGateway>
- 排他の分岐
- 行き先の <sequenceFlow> は、<conditionExpression> 子要素を持ち、評価式が true になる最初の <sequenceFlow> に進む (一つの Flow にしか進まない)
- <conditionExpression> の xsi:type は、現時点では tFormalExpression? (EL式) しか設定できない
<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" />
<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1">
<conditionExpression xsi:type="tFormalExpression">${input == 1}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2">
<conditionExpression xsi:type="tFormalExpression">${input == 2}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3">
<conditionExpression xsi:type="tFormalExpression">${input == 3}</conditionExpression>
</sequenceFlow>
- 評価式は Eclipse Activiti Designer で、<sequenceFlow> をクリックして properties ペインで設定できる
この設定から生成される bpmn (xml) は
<sequenceFlow id="flow7" sourceRef="exclusivegateway1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${val > 100}]]></conditionExpression>
</sequenceFlow>
- <parallelGateway>
- <inclusiveGateway>
- <eventBasedGateway?>
8.5 Task †
8.5.1. User Task †
8.5.2. Script Task †
- JSR-223 (Java Scripting API) で、BPMN内に定義された文字列のスクリプトを実行する。
- 例
<scriptTask id="theScriptTask" name="Execute script" scriptFormat="groovy">
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
</scriptTask>
- フラグを立てるくらいなら使ってもいいかもね
- ここに、がっつり処理ロジックを書いてもなかなかテストできないし、実行時に Syntax Error が起きるかもしれんし
- プロセス変数の変更・設定
<script>
def scriptVar = "test123"
execution.setVariable("myVar", scriptVar)
</script>
- groovy を使う場合には、pom.xml に
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.x.x<version>
</dependency>
を追記する必要あり
8.5.3. Java Task †
- Javaコードを実行する
- 例
<serviceTask id="javaService"
name="My Java Service Task"
activiti:class="org.activiti.MyJavaDelegate" />
(Activiti が Spring コンテナ上で動いている場合には、Spring が管理している Bean を lookup したり、その任意のメソッドを実行できる。
JavaSE環境で実行するときには上記のように、クラス名をパッケージ名付きで指定する)
- 呼び出される Java コード
public class MyJavaDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
String var = (String) execution.getVariable("input");
var = var.toUpperCase();
execution.setVariable("input", var);
}
}
- JavaDelegagte? を実装する
- 処理ロジックは void execute(DelegateExecution? execution) に記述する
- DelegateExecution? からプロセススコープの (ACT_RU_VARIABLE テーブルに格納されている) 変数を取得・変更できる cf. 4.5.Variables
- パラメータの Injection
<serviceTask id="javaService" name="Java service invocation"
activiti:class="org.activiti.examples.bpmn.servicetask.ReverseStringsFieldInjected">
<extensionElements>
<activiti:field name="text1">
<activiti:expression>${genderBean.getGenderString(gender)}</activiti:expression>
</activiti:field>
<activiti:field name="text2">
<activiti:expression>Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}</activiti:expression>
</activiti:field>
</ extensionElements>
</ serviceTask>
フィールド変数 text1、text2 に BPMN で指定した値を設定できる
- エラー処理
<serviceTask id="servicetask1" name="Service Task" activiti:class="...">
<extensionElements>
<activiti:mapException errorCode="myErrorCode1"/>
</extensionElements>
</serviceTask>
例外が発生したら、エラーコード myErrorCode1 で終了
- Exception の種類によってエラーコードを変える
<serviceTask id="servicetask1" name="Service Task" activiti:class="...">
<extensionElements>
<activiti:mapException
errorCode="myErrorCode1">org.activiti.SomeException</activiti:mapException>
</extensionElements>
</serviceTask>
- Exception の親クラスを指定 (どの子クラスの例外が起きても、指定されたエラーコードをで終了する)
<serviceTask id="servicetask1" name="Service Task" activiti:class="...">
<extensionElements>
<activiti:mapException errorCode="myErrorCode1"
includeChildExceptions="true">org.activiti.SomeException</activiti:mapException>
</extensionElements>
</serviceTask>
8.5.6. Email Task †
<serviceTask id="mailtask1" name="Mail Task" activiti:type="mail">
<extensionElements>
<activiti:field name="from">
<activiti:string><![CDATA[order-shipping@thecompany.com]]></activiti:string>
</activiti:field>
<activiti:field name="to">
<activiti:expression><![CDATA[foo@example.com]]></activiti:expression>
</activiti:field>
<activiti:field name="subject">
<activiti:string><![CDATA[ENGLISH SUBJECT 日本語の件名]]></activiti:string>
</activiti:field>
<activiti:field name="charset">
<activiti:string><![CDATA[iso-2022-jp]]></activiti:string>
</activiti:field>
<activiti:field name="text">
<activiti:string><![CDATA[ENGLISH MESSAGE
日本語の本文]]></activiti:string>
</activiti:field>
</extensionElements>
</serviceTask>
- activiti.cfg.xml で、SMTP サーバーの設定が必要
- Activiti Explorer / REST API では、WEB-INF/classes/engine.properties
- 設定できるのは
activiti:field name="" | 設定内容 |
to | TO |
from | FROM |
subject | SUBJECT |
cc | CC |
bcc | BCC |
charset | 日本語なら iso-2022-jp やね |
html | HTML Mail 平文+EL式 で指定 |
text | Text Mail 平文+EL式 で指定 |
htmlVar | HTML Mail の全文が格納されているプロセス変数名 |
textVar | Text Mail の全文が格納されているプロセス変数名 |
ignoreException | 例外を無視 |
exceptionVariableName? | ignoreException=true のとき、発生した Exception を格納するプロセス変数名 |
- Eclipse Activiti Designer から設定可能
- 日本語メールもOK (charset = iso-2022-jp にしておけば、あとは (commons-mailが) やってくれる)
日本語部分は、iso-2022-jp (JISコード) の base64 になっている
8.5.8. Camel Task (つかえない) †
8.5.9. Manual Task †
8.5.10. Java Receive Task †
- シグナル待ちタスク
<receiveTask id="waitState" name="wait" />
- シグナル発行
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId())
.activityId("waitState")
.singleResult();
assertNotNull(execution);
runtimeService.signal(execution.getId());
8.5.11. Shell Task (つかえない) †
<serviceTask id="shellEcho" activiti:type="shell" >
<extensionElements>
<activiti:field name="command" stringValue="cmd" />
<activiti:field name="arg1" stringValue="/c" />
<activiti:field name="arg2" stringValue="echo" />
<activiti:field name="arg3" stringValue="EchoTest" />
<activiti:field name="wait" stringValue="true" />
<activiti:field name="outputVariable" stringValue="resultVar" />
</extensionElements>
</serviceTask>
8.15.12.Execution listener †
- プロセス開始、終了時の処理 (event="start/end")
- bpmn
<process id="executionListenersProcess">
...
<extensionElements>
<activiti:executionListener class="com.foo.ExecutionListenerOne" event="start" />
</extensionElements>
...
<startEvent id="theStart">
<sequenceFlow sourceRef="theStart" ...
...
</process>
- com.foo.ExecutionListenerOne?
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.ExecutionListenerExecution;
public class ExecutionListenerOne implements ExecutionListener {
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("variableSetInExecutionListener", "firstValue");
execution.setVariable("eventReceived", execution.getEventName());
}
}
旧バージョン (< 5.3) との互換性のため、org.activiti.engine.impl.pvm.delegate.ExecutionListener? もあるが、そちらは使わない
- イベントハンドラには、ExecutionListner? ではなく、JavaTask? (org.activiti.engine.delegate.JavaDelegate?を継承したクラス) を指定してもよい
- Flow 通過時の処理 (event なし)
- Task開始、終了時の処理 (event="start/end")
- bpmn から executionListener への Field Injection
- bpmn
<process id="executionListenersProcess">
<extensionElements>
<activiti:executionListener class="com.foo.FieldInjectedExecutionListener" event="start">
<activiti:field name="fixedValue" stringValue="Yes, I am " />
<activiti:field name="dynamicValue" expression="${myVar}" />
</activiti:executionListener>
</extensionElements>
...
</process>
- com.foo.FieldInjectedExecutionListener?
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.ExecutionListenerExecution;
import org.activiti.engine.delegate.Expression;
public class FieldInjectedExecutionListener implements ExecutionListener {
private Expression fixedValue;
private Expression dynamicValue;
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("var", fixedValue.getValue(execution).toString() + dynamicValue.getValue(execution).toString());
}
}
旧バージョン (< 5.3) との互換性のため、org.activiti.engine.impl.pvm.delegate.Expression もあるが、そちらは使わない
- bpmn からプロセススコープの変数を Injection できる (ACT_RU_VARIABLE テーブルに格納されているプロセス固有の変数 cf. 4.5.Variable
- Unit Test では、RuntimeService? でプロセスを開始するときに、変数を設定してあげればいい
@Deployment(resources = {"org/activiti/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})
public void testExecutionListenerFieldInjection() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("myVar", "listening!");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess", variables);
Object varSetByListener = runtimeService.getVariable(processInstance.getId(), "var");
assertNotNull(varSetByListener);
assertTrue(varSetByListener instanceof String);
// Result is a concatenation of fixed injected field and injected expression
assertEquals("Yes, I am listening!", varSetByListener);
}
8.5.13. Task listener †
- User Task のイベント処理
- event="create" (タスクが作られたとき)
- event="assignment" (誰かにアサインされたとき)
- event="complete" (完了したとき)
- event="delete" (棄却されたとき)
- bpmn
<process id="executionListenersProcess">
...
<userTask id="myTask" name="My Task" >
<extensionElements>
<activiti:taskListener class="com.foo.MyTaskCreateListener" event="create"/>
</extensionElements>
</userTask>
...
</process>
- com.foo.MyTaskCreateListener?
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
public class MyTaskCreateListener implements TaskListener {
public void notify(DelegateTask delegateTask) {
// Custom logic goes here
}
}
8.5.14 Multi-instance (for each) †
- Task/Subprocessの繰り返し実行
- まぁ、使うとしても JavaTask? だけだろうね
- User Task とかで使うと複雑になりすぎる
- Mail Task にも使えるけど... 大量の嫌がらせメール送るとか ...
- bpmn
縦線は並列実行、横線はシーケンシャル実行
<process id="MultiExam" name="Multi Exam" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<endEvent id="endevent1" name="End"></endEvent>
<serviceTask id="servicetask1" name="Service Task" activiti:class="com.snail.exam.MyJavaTask">
<multiInstanceLoopCharacteristics isSequential="false">
<loopCardinality>10</loopCardinality>
</multiInstanceLoopCharacteristics>
</serviceTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="servicetask1" targetRef="endevent1"></sequenceFlow>
</process>
- <multiInstanceLoopCharacteristics?> の属性、子要素
property | note |
isSequential | true(並列実行可) / false(逐次実行) |
loopCardinality | 繰り返し数 (${}でプロセス変数の参照も可能) |
completionCondition | 終了条件 ${EL式} でプロセス変数を評価する |
- タスク側では、自分が何番目の繰り返しなのかをプロセス変数で参照可能
property | note |
nrOfInstancfes? | インスタンス数 |
nrOfActiveInstances? | 起動中のインスタンス数 |
nrOfCompletedInstances? | 終了したインスタンス数 |
loopCounter | 現在実行中のインスタンス番号 |
- サンプル実装
package com.snail.exam;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MultiInstanceProcess {
private static final Logger log = LoggerFactory.getLogger(MyProcess.class);
public static void main(String[] args) {
try {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource("org/activiti/test/MultiInstance.bpmn")
.deploy();
log.info("Deployed");
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("MultiExam");
log.info("Complete");
} catch (RuntimeException th) {
log.error("ERROR", th);
}
}
}
package com.snail.exam;
import java.util.Map;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyJavaTask implements JavaDelegate {
private static final Logger log = LoggerFactory.getLogger(MyJavaTask.class);
public void execute(DelegateExecution execution) throws Exception {
log.info("This is MyJavaTask");
for(Map.Entry entry : execution.getVariables().entrySet()) {
log.info("{}={}", entry.getKey(), entry.getValue());
}
}
}
実行結果 (10回実行された)
16-05-18 23:49:36 [INFO ] Initializing process engine using configuration 'file:/Users/atsushi/EclipseWorkspace/ActivitExam/target/classes/activiti.cfg.xml'
16-05-18 23:49:36 [INFO ] initializing process engine for resource file:/Users/atsushi/EclipseWorkspace/ActivitExam/target/classes/activiti.cfg.xml
16-05-18 23:49:36 [INFO ] Loading XML bean definitions from resource loaded through InputStream
16-05-18 23:49:37 [INFO ] performing create on engine with resource org/activiti/db/create/activiti.h2.create.engine.sql
16-05-18 23:49:37 [INFO ] performing create on history with resource org/activiti/db/create/activiti.h2.create.history.sql
16-05-18 23:49:37 [INFO ] performing create on identity with resource org/activiti/db/create/activiti.h2.create.identity.sql
16-05-18 23:49:37 [INFO ] ProcessEngine default created
16-05-18 23:49:37 [INFO ] initialised process engine default
16-05-18 23:49:37 [INFO ] Processing resource org/activiti/test/MultiInstance.bpmn
16-05-18 23:49:37 [INFO ] Deployed
16-05-18 23:49:37 [INFO ] This is MyJavaTask
16-05-18 23:49:37 [INFO ] nrOfActiveInstances=10
16-05-18 23:49:37 [INFO ] loopCounter=0
16-05-18 23:49:37 [INFO ] nrOfInstances=10
16-05-18 23:49:37 [INFO ] nrOfCompletedInstances=0
16-05-18 23:49:38 [INFO ] This is MyJavaTask
16-05-18 23:49:38 [INFO ] nrOfActiveInstances=9
16-05-18 23:49:38 [INFO ] loopCounter=1
16-05-18 23:49:38 [INFO ] nrOfInstances=10
16-05-18 23:49:38 [INFO ] nrOfCompletedInstances=1
16-05-18 23:49:38 [INFO ] This is MyJavaTask
16-05-18 23:49:38 [INFO ] nrOfActiveInstances=8
16-05-18 23:49:38 [INFO ] loopCounter=2
16-05-18 23:49:38 [INFO ] nrOfInstances=10
16-05-18 23:49:38 [INFO ] nrOfCompletedInstances=2
16-05-18 23:49:38 [INFO ] This is MyJavaTask
16-05-18 23:49:38 [INFO ] nrOfActiveInstances=7
16-05-18 23:49:38 [INFO ] loopCounter=3
16-05-18 23:49:38 [INFO ] nrOfInstances=10
16-05-18 23:49:38 [INFO ] nrOfCompletedInstances=3
16-05-18 23:49:38 [INFO ] This is MyJavaTask
16-05-18 23:49:38 [INFO ] nrOfActiveInstances=6
16-05-18 23:49:38 [INFO ] loopCounter=4
16-05-18 23:49:38 [INFO ] nrOfInstances=10
16-05-18 23:49:38 [INFO ] nrOfCompletedInstances=4
16-05-18 23:49:38 [INFO ] This is MyJavaTask
16-05-18 23:49:38 [INFO ] nrOfActiveInstances=5
16-05-18 23:49:38 [INFO ] loopCounter=5
16-05-18 23:49:38 [INFO ] nrOfInstances=10
16-05-18 23:49:38 [INFO ] nrOfCompletedInstances=5
16-05-18 23:49:38 [INFO ] This is MyJavaTask
16-05-18 23:49:38 [INFO ] nrOfActiveInstances=4
16-05-18 23:49:38 [INFO ] loopCounter=6
16-05-18 23:49:38 [INFO ] nrOfInstances=10
16-05-18 23:49:38 [INFO ] nrOfCompletedInstances=6
16-05-18 23:49:38 [INFO ] This is MyJavaTask
16-05-18 23:49:38 [INFO ] nrOfActiveInstances=3
16-05-18 23:49:38 [INFO ] loopCounter=7
16-05-18 23:49:38 [INFO ] nrOfInstances=10
16-05-18 23:49:38 [INFO ] nrOfCompletedInstances=7
16-05-18 23:49:38 [INFO ] This is MyJavaTask
16-05-18 23:49:38 [INFO ] nrOfActiveInstances=2
16-05-18 23:49:38 [INFO ] loopCounter=8
16-05-18 23:49:38 [INFO ] nrOfInstances=10
16-05-18 23:49:38 [INFO ] nrOfCompletedInstances=8
16-05-18 23:49:38 [INFO ] This is MyJavaTask
16-05-18 23:49:38 [INFO ] nrOfActiveInstances=1
16-05-18 23:49:38 [INFO ] loopCounter=9
16-05-18 23:49:38 [INFO ] nrOfInstances=10
16-05-18 23:49:38 [INFO ] nrOfCompletedInstances=9
16-05-18 23:49:38 [INFO ] Complete
8.6. Sub-Processes and Call Activities †
- ひとかたまりの Activiti を Sub-Process としてまとめることができる
- Activiti Explorer では、[Container] から SubProcess? と EventSubProcess? を選ぶことができる
- 通常の sub process は、Task と同じように扱うことができる。sub process 全体にエラーイベントを貼り付けることもできる
- event sub process は、Process 中のどこかで発生したイベントを契機に開始する
- 現時点では sub process にする意味はあまりない。
9. Form †
- Activiti Explorer は、bpmn 上の User Task や Start Event からユーザ入力画面を作ってくれる
- User Task や Start Event には、入力項目を指定できる
<startEvent>
<extensionElements>
<activiti:formProperty id="numberOfDays" name="Number of days"
value="${numberOfDays}" type="long" required="true"/>
<activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)"
value="${startDate}" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" />
<activiti:formProperty id="vacationMotivation" name="Motivation"
value="${vacationMotivation}" type="string" />
</extensionElements>
</userTask>
property | note |
id | key項目。入力値は id をキーにしてプロセススコープの変数として保持される ((ACT_RU_VARIABLE テーブルに格納される) |
name | 表示するラベル |
type | "string","long","enum","date","boolean" |
value | 初期値。プロセススコープの変数 (ACT_RU_VARIABLE テーブルに格納されているプロセス固有の変数) を EL式で参照可能 |
readable | "true"/"false" |
writable | "true"/"false" |
required | "true"/"false" |
- type="enum" の時には次のように候補を定義する
<userTask id="usertask1" name="User Task">
<extensionElements>
<activiti:formProperty id="sex" name="YOUR SEX" type="enum">
<activiti:value id="male" name="Male" />
<activiti:value id="female" name="Female" />
</activiti:formProperty>
</extensionElements>
</userTask>
GUI は Activiti Explorer 前提なら From の話はここまでで OK
- 自前のワークフローGUIを作りたい時、bpmn 上の Form 入出力定義から動的に画面を作りたくなるだろう
- FormService? にはそのための API がある
StartFormData FormService.getStartFormData(String processDefinitionId)
TaskFormdata FormService.getTaskFormData(String taskId)
- StartFromData? を検索するキーは、processDefinitionId? (プロセス定義のID)。TaskFormData? を検索するキーは taskId (実行中のタスクのID)。考えて見れば当たり前。
- StartFormData?.getFormProperties?()、TaskFromData?.getFormProperties?() で List<FormProperty?> が返ってくる
public interface FormProperty {
FormService#submitStartFormData(String, java.util.Map)}
String getId();
String getName();
FormType getType();
String getValue();
boolean isReadable();
boolean isWritable();
boolean isRequired();
}
- <startEvent> や <userTask> に定義できる入出力項目 <activiti:formProperty> の属性と同じね
- 入出力項目の型は FormType? 型で返される FormType?.getName() を使えば型名を文字列で取得できる
- string (org.activiti.engine.impl.form.StringFormType?)
- long (org.activiti.engine.impl.form.LongFormType?)
- enum (org.activiti.engine.impl.form.EnumFormType?)
- date (org.activiti.engine.impl.form.DateFormType?)
- boolean (org.activiti.engine.impl.form.BooleanFormType?)
- Vacation Request を読み込むサンプル
package com.snail.exam;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.form.FormProperty;
import org.activiti.engine.form.StartFormData;
import org.activiti.engine.form.TaskFormData;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FormProcess {
private static final Logger log = LoggerFactory.getLogger(FormProcess.class);
public static void main(String[] args) {
try {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// ----- 4.3.1. Deploying the process
log.info("--- #1. Deploying the process");
RepositoryService repositoryService = processEngine.getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource("org/activiti/test/VacationRequest.bpmn20.xml")
.deploy();
// ----- 9. Forms (Read Start Form)
ProcessDefinition processDef = processEngine.getRepositoryService().createProcessDefinitionQuery()
.processDefinitionKey("vacationRequest").singleResult();
log.info("Vaction Request PID={}", processDef.getId());
StartFormData startForm = processEngine.getFormService().getStartFormData(processDef.getId());
for (FormProperty prop : startForm.getFormProperties()) {
log.info("id={}, name={}, type={}, value={}, readable={}, required={}, writable={}, "
, prop.getId(), prop.getName(), prop.getType(), prop.getValue()
, prop.isReadable(), prop.isRequired(), prop.isWritable());
}
// ----- 4.3.2. Starting a process instance
log.info("--- #3. Starting a process instance");
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employeeName", "Kermit");
variables.put("numberOfDays", new Integer(4));
variables.put("startDate", DateUtils.parseDate("1999-12-31", "yyyy-MM-dd"));
variables.put("vacationMotivation", "I'm really tired!");
// the process to run
// id : <process id="vacationRequest">
// arguments : <activiti:formProperty>
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);
// Fetch all tasks for the management group
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().processDefinitionKeyLike("vacationRequest%").list();
for (Task task : tasks) {
TaskFormData taskForm = processEngine.getFormService().getTaskFormData(task.getId());
for (FormProperty prop : taskForm.getFormProperties()) {
log.info("id={}, name={}, type={}, value={}, readable={}, required={}, writable={}, "
, prop.getId(), prop.getName(), prop.getType(), prop.getValue()
, prop.isReadable(), prop.isRequired(), prop.isWritable());
}
// Do task (reject application)
Map<String, Object> taskVariables = new HashMap<String, Object>();
taskVariables.put("vacationApproved", "false");
taskVariables.put("managerMotivation", "We have a tight deadline!");
taskService.complete(task.getId(), taskVariables);
}
} catch (Throwable th) {
log.error("ERROR", th);
}
}
}
- 実行結果
16-05-19 01:22:08 [INFO ] Initializing process engine using configuration 'file:/Users/atsushi/EclipseWorkspace/ActivitExam/target/classes/activiti.cfg.xml'
16-05-19 01:22:08 [INFO ] initializing process engine for resource file:/Users/atsushi/EclipseWorkspace/ActivitExam/target/classes/activiti.cfg.xml
16-05-19 01:22:08 [INFO ] Loading XML bean definitions from resource loaded through InputStream
16-05-19 01:22:09 [INFO ] performing create on engine with resource org/activiti/db/create/activiti.h2.create.engine.sql
16-05-19 01:22:09 [INFO ] performing create on history with resource org/activiti/db/create/activiti.h2.create.history.sql
16-05-19 01:22:09 [INFO ] performing create on identity with resource org/activiti/db/create/activiti.h2.create.identity.sql
16-05-19 01:22:09 [INFO ] ProcessEngine default created
16-05-19 01:22:09 [INFO ] initialised process engine default
16-05-19 01:22:09 [INFO ] --- #1. Deploying the process
16-05-19 01:22:09 [INFO ] Processing resource org/activiti/test/VacationRequest.bpmn20.xml
16-05-19 01:22:10 [INFO ] Vaction Request PID=vacationRequest:1:4
16-05-19 01:22:10 [INFO ] id=numberOfDays, name=Number of days, type=org.activiti.engine.impl.form.LongFormType@6bb75258,
value=null, readable=true, required=true, writable=true,
16-05-19 01:22:10 [INFO ] id=startDate, name=First day of holiday (dd-MM-yyy), type=org.activiti.engine.impl.form.DateFormType@c260bdc,
value=null, readable=true, required=true, writable=true,
16-05-19 01:22:10 [INFO ] id=vacationMotivation, name=Motivation, type=org.activiti.engine.impl.form.StringFormType@75e01201, value=null,
readable=true, required=false, writable=true,
16-05-19 01:22:10 [INFO ] --- #3. Starting a process instance
16-05-19 01:22:10 [INFO ] id=vacationApproved, name=Do you approve this vacation, type=org.activiti.engine.impl.form.EnumFormType@47dbb1e2,
value=null, readable=true, required=true, writable=true,
16-05-19 01:22:10 [INFO ] id=managerMotivation, name=Motivation, type=org.activiti.engine.impl.form.StringFormType@75e01201,
value=null, readable=true, required=false, writable=true,
11.History †
13. Activiti Explorer 14. Modeler †
→ Java Activiti Explorer
15. REST API †
→ Java Activiti REST API
Java#Activiti