RepositoryService主要用于对Activiti中的流程存储的相关数据进行操作,这些操作包括对流程存储数据的管理、流程部署以及对流程的基本操作等。
流程文件部署
RepositoryService负责对流程文件的部署以及流程的定义进行管理,不管是JBPM还是Activiti等工作流引擎,都会产生流程文件,工作流引擎需要对这些文件进行管理,这些文件包括流程描述文件、流程图等。
在Activiti中,如果需要对这些资源文件进行操作(包括添加、删除、查询等),可以使用RepositoryService提供的API。这些文件数据被保存在ACT_GE_BYTEARRAY表中,对应的实体为ResourceEntityImpl。
设置用户图片同样会向ACT_GE_BYTEARRAY表中写入数据,但是当数据为用户图片时,对应的实体是ByteArrayEntityImpl。ResourceEntityImpl与ByteArrayEntityImpl区别如下。
➢ ByteArrayEntityImpl对应的数据有版本管理数据,而ResourceEntityImpl则没有。
➢ ResourceEntityImpl 会设置 ACT_GE_BYTEARRAY 表的 GENERATED_字段值,而ByteArrayEntityImpl在进行保存时,该值为null。
Deployment对象
Deployment对象是一个接口,一个Deployment实例表示一条ACT_RE_DEPLOYMENT表的数据,同样,Deployment也遵循Activiti的实体命名规则,子接口为DeploymentEntity,实现类为DeploymentEntityImpl。如果要对属性进行修改,需要调用DeploymentBuilder提供的方法,Deployment只提供了一系列getter方法。DeploymentEntityImpl中包含以下映射属性。
➢ id:主键,对应ACT_RE_DEPLOYMENT表的ID_列。
➢ name:部署名称,对应ACT_RE_DEPLOYMENT表的NAME_列。
➢ deploymentTime:部署时间,对应DEPLOY_TIME_列。
➢ category:部署的类别,对应CATEGORY_列。
➢ tenantId:在云时代,同一个软件有可能被多个租户所使用,因此 Activiti 在部署、流程定义等数据中都预留了tenantId字段。
➢ key:为部署设置键属性,保存在KEY_列。
DeploymentBuilder对象
对流程文件进行部署,需要使用DeploymentBuilder对象,获取该对象,可以调用RepositoryService的createDeployment方法,
DeploymentBuilder中包含了多个addXXX方法,可以用于为部署添加资源,这些方法有:
➢ addClasspathResource(String resource):添加classpath下的资源文件。
➢ addInputStream(String resourceName,InputStream):添加输入流资源。
➢ addString(String resourceName,String text):添加字符串资源。
➢ addZipInputStream(ZipInputStream inputStream):添加zip压缩包资源。
➢ addBpmnModel(String resourceName,BpmnModel bpmnModel):解析BPMN模型对象,并作为资源保存。
➢ addBytes(String resourceName,byte[] bytes):添加字节资源。
除此之外,还提供了修改部署信息的方法,例如key、name、category等。下面将讲述这些方法的使用。
添加输入流资源
在DeploymentEntityImpl类中,使用一个Map来维护资源,表示一次部署中会有多个资源,就是我们平常所说的一对多关系。调用DeploymentBuilder的addInputStream方法,实际上就是往DeploymentEntityImpl的Map里面添加元素,Map的key是资源名称,value是解析InputStream后获得的byte数组。
使用addInputStream方法添加部署流程图资源。
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.DeploymentBuilder;
/**
* 调用DeploymentBuilder的AddInputStream方法
*
* @author yangenxiong
*
*/
public class AddInputStream {
public static void main(String[] args) throws Exception {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 第一个资源输入流
InputStream is1 = new FileInputStream(new File(
"resource/artifact/flow_inputstream1.png"));
// 第二个资源输入流
InputStream is2 = new FileInputStream(new File(
"resource/artifact/flow_inputstream1.png"));
// 创建DeploymentBuilder实例
DeploymentBuilder builder = repositoryService.createDeployment();
// 为DeploymentBuilder添加资源输入流
builder.addInputStream("inputA", is1);
builder.addInputStream("inputB", is2);
// 执行部署方法
builder.deploy();
}
}
添加classpath资源
与addInputStream方法类似,addClasspathResource方法也是往部署实体的Map里面添加元素,但不同的是,addClasspathResource方法会得到当前的ClassLoader对象。调用getResourceAsStream方法将指定的classpath下的资源文件转换为InputStream,再调用addInputStream方法。
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.DeploymentBuilder;
/**
* 调用DeploymentBuilder的AddClasspathResource方法
* @author yangenxiong
*
*/
public class AddClasspathResource {
public static void main(String[] args) {
//创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
//创建DeploymentBuilder实例
DeploymentBuilder builder = repositoryService.createDeployment();
//添加classpath下的资源
builder.addClasspathResource("artifact/classpath.png");
//执行部署(写入到数据库中)
builder.deploy();
}
}
使用addClasspathResource方法将一份图片文件保存到ACT_GE_BYTEARRAY表中。需要注意的是,使用addClasspathResource方法并不需要指定名称参数,Activiti会使用其传入的路径作为资源的名称。
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.DeploymentBuilder;
/**
* 调用DeploymentBuilder的AddString方法
* @author yangenxiong
*
*/
public class AddString {
public static void main(String[] args) {
//创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
//创建DeploymentBuilder实例
DeploymentBuilder builder = repositoryService.createDeployment();
//添加String资源
builder.addString("test", "this is string method");
//执行部署(写入到数据库中)
builder.deploy();
}
}
添加压缩包资源
在实际应用中,可能需要将多个资源部署到流程引擎中,如果这些关联的资源被放在一个压缩包(zip包)中,则可以使用DeploymentBuilder提供的addZipInputStream方法直接部署压缩包,该方法会遍历压缩包内的全部文件,然后将这些文件转换为byte数组,写到资源表中。使用addZipInputStream方法添加资源。
import java.io.File;
import java.io.FileInputStream;
import java.util.zip.ZipInputStream;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.DeploymentBuilder;
/**
* 调用DeploymentBuilder的AddZipInputStream方法
* @author yangenxiong
*
*/
public class AddZipInputStream {
public static void main(String[] args) throws Exception {
//创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
//创建DeploymentBuilder实例
DeploymentBuilder builder = repositoryService.createDeployment();
//获取zip文件的输入流
FileInputStream fis = new FileInputStream(new File("resource/artifact/ZipInputStream.zip"));
//读取zip文件,创建ZipInputStream对象
ZipInputStream zi = new ZipInputStream(fis);
//添加Zip压缩包资源
builder.addZipInputStream(zi);
//执行部署(写入到数据库中)
builder.deploy();
}
}
添加BPMN模型资源
DeploymentBuilder提供了一个addBpmnModel方法,可传入BPMN规范的模型(BpmnModel类)来进行部署
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.EndEvent;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.StartEvent;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.DeploymentBuilder;
public class AddBpmnModel {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
// 创建DeploymentBuilder实例
DeploymentBuilder builder = repositoryService.createDeployment();
builder.addBpmnModel("MyCodeProcess", createProcessModel())
.name("MyCodeDeploy").deploy();
}
private static BpmnModel createProcessModel() {
// 创建BPMN模型对象
BpmnModel model = new BpmnModel();
org.activiti.bpmn.model.Process process = new org.activiti.bpmn.model.Process();
model.addProcess(process);
process.setId("myProcess");
process.setName("My Process");
// 开始事件
StartEvent startEvent = new StartEvent();
startEvent.setId("startEvent");
process.addFlowElement(startEvent);
// 用户任务
UserTask userTask = new UserTask();
userTask.setName("User Task");
userTask.setId("userTask");
process.addFlowElement(userTask);
// 结束事件
EndEvent endEvent = new EndEvent();
endEvent.setId("endEvent");
process.addFlowElement(endEvent);
// 添加流程顺序
process.addFlowElement(new SequenceFlow("startEvent", "userTask"));
process.addFlowElement(new SequenceFlow("userTask", "endEvent"));
return model;
}
}
修改部署信息
使用DeploymentBuilder的name、key、category、tenanId方法可以设置部署属性,保存后会将数据保存到ACT_RE_DEPLOYMENT表的对应字段中。
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.DeploymentBuilder;
/**
* 使用DeploymentBuilder的name方法
* @author yangenxiong
*
*/
public class Name {
public static void main(String[] args) {
//创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
//创建DeploymentBuilder实例
DeploymentBuilder builder = repositoryService.createDeployment();
//设置各个属性
builder.name("crazyit").tenantId("tenanId").key("myKey").category("myCategory");
//执行部署(写入到数据库中)
builder.deploy();
}
}
过滤重复部署
进行了第一次部署后,资源没有发生变化而再次进行部署,同样会将部署数据写入数据库中,想避免这种情况,可以调用DeploymentBuilder的enableDuplicateFiltering方法,该方法仅将DeploymentBuilder的isDuplicateFilterEnabled属性设置为true。在执行deploy方法时,如果发现该值为true,则根据部署对象的名称去查找最后一条部署记录,如果发现最后一条部署记录与当前需要部署的记录一致,则不会重复部署。
搭建环境
创建activiti.cfg.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="root" />
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
创建log4j.properties配置文件
# Set root category priority to INFO and its only appender to CONSOLE.
log4j.rootCategory=INFO, CONSOLE
#log4j.rootCategory=INFO, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1} - %m%n
log4j.logger.org.apache=INFO
log4j.logger.org.htmlparser=INFO
log4j.logger.org.htmlparser=INFO
log4j.logger.com.angus=INFO
创建DuplicateFilter.txt
abc
创建DuplicateFilterB.txt
abc
测试
package org.crazyit.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
/**
*
* @author yangenxiong
*
*/
public class DuplicateFilter {
/**
* @param args
*/
public static void main(String[] args) {
//创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
//创建第一个部署对象
DeploymentBuilder builderA = repositoryService.createDeployment();
builderA.addClasspathResource("artifact/DuplicateFilter.txt");
builderA.name("DuplicateFilterA");
builderA.deploy();
//由于资源一样,并且调用了enableDuplicateFiltering方法,因此不会再写入到数据库中
DeploymentBuilder builderB = repositoryService.createDeployment();
builderB.addClasspathResource("artifact/DuplicateFilter.txt");
builderB.name("DuplicateFilterA");
builderB.enableDuplicateFiltering();
builderB.deploy();
//由于资源发生变化,即使调用了enableDuplicateFiltering方法,也会写到数据库中
DeploymentBuilder builderC = repositoryService.createDeployment();
builderC.addClasspathResource("artifact/DuplicateFilterB.txt");
builderC.name("DuplicateFilterA");
builderC.enableDuplicateFiltering();
builderC.deploy();
}
}
取消部署时的验证
默认情况下,在部署时会对流程的XML文件进行验证,包括验证是否符合BPMN 2.0的规范、定义的流程是否可执行。如果XML文件不符合规范或者定义的流程不可执行,那么将会在部署时抛出异常。如果想跳过这两个验证,可以调用DeploymentBuilder的disableSchemaValidation与disableBpmnValidation方法。
创建bpmnError.bpmn.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="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">
<process id="vacationProcess" name="vacation" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="User Task"></userTask>
<startEvent id="startevent2" name="Start"></startEvent>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="startevent2"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_vacationProcess">
<bpmndi:BPMNPlane bpmnElement="vacationProcess" id="BPMNPlane_vacationProcess">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="50.0" y="122.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="60.0" width="100.0" x="190.0" y="110.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="startevent2" id="BPMNShape_startevent2">
<omgdc:Bounds height="35.0" width="35.0" x="370.0" y="122.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="85.0" y="139.0"></omgdi:waypoint>
<omgdi:waypoint x="158.0" y="139.0"></omgdi:waypoint>
<omgdi:waypoint x="190.0" y="140.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="290.0" y="140.0"></omgdi:waypoint>
<omgdi:waypoint x="370.0" y="139.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
创建xmlError.bpmn.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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">
<process id="vacationProcess" name="vacation">
<userTask id="usertask1" name="User Task"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
<startEvent id="startevent1" name="Start"></startEvent>
<sequenceFlow id="flow3" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
</process>
<abc></abc>
<bpmndi:BPMNDiagram id="BPMNDiagram_vacationProcess">
<bpmndi:BPMNPlane bpmnElement="vacationProcess"
id="BPMNPlane_vacationProcess">
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55" width="105" x="310" y="160"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35" width="35" x="490" y="170"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="startevent1"
id="BPMNShape_startevent1">
<omgdc:Bounds height="35" width="35" x="170" y="170"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="415" y="187"></omgdi:waypoint>
<omgdi:waypoint x="490" y="187"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="205" y="187"></omgdi:waypoint>
<omgdi:waypoint x="310" y="187"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
测试
package org.crazyit.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.DeploymentBuilder;
public class DisableValidation {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 部署一份错误的xml文件,不会报错
DeploymentBuilder builderA = repositoryService.createDeployment();
builderA.addClasspathResource("bpmn/xmlError.bpmn")
.disableSchemaValidation().deploy();
// 部署一份不可执行bpmn文件,不会报错
DeploymentBuilder builderB = repositoryService.createDeployment();
builderB.addClasspathResource("bpmn/bpmnError.bpmn")
.disableBpmnValidation().deploy();
}
}
流程定义的管理
这里所说的流程定义管理是指由RepositoryService提供的一系列对流程定义的控制,包括中止流程定义、激活流程定义和设置流程权限等。
ProcessDefinition对象
ProcessDefinition对象是一个接口,一个ProcessDefinition实例表示一条流程定义数据,在Activiti中,它的实现类为ProcessDefinitionEntityImpl,对应的数据表为ACT_RE_PROCDEF。ProcessDefinition接口有以下方法。
➢ getCategory:返回流程定义的category属性,对应CATEGORY_字段的值。
➢ getDeploymentId:返回部署的 id,这个流程定义是由哪次部署产生的,对应字段为DEPLOYMENT_ID_。
➢ getDescription:返回流程定义的描述。
➢ getDiagramResourceName:如果流程定义有流程图的话,将返回流程图对应的资源名称。
➢ getEngineVersion:返回流程引擎版本,当前无须关心该方法。
➢ getId:返回流程定义主键。
➢ getKey:返回流程定义的名称,此名称唯一。
➢ getName:返回流程定义显示的名称。
➢ getResourceName:在部署时,会将流程定义的XML文件存到资源表中,该方法返回资源的名称。
➢ getTenantId:返回租户ID。
➢ getVersion:返回流程定义的版本号,对应VERSION_字段,注意并不是revision属性。
➢ hasGraphicalNotation:该流程定义文件是否有流程图的XML元素。
➢ hasStartFormKey:流程的开始事件中是否存在activiti:formKey的定义。
➢ isSuspended:是否为中断状态,SUSPENSION_STATE_字段值为1表示激活状态,值为2则表示中断状态。
流程部署
流程部署实际上就是将流程的描述文件写入数据库中,Activiti在获取资源文件后,会对其后缀进行解析,如果后缀名为“BPMN 20.xml”或者“bpmn”,则它们均会被看作流程描述文件,交由Activiti的Deployer(部署流程描述文件实现类为BpmnDeployer)进行部署。
创建processDeploy.bpmn.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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">
<process id="vacationProcess" name="vacation">
<userTask id="usertask1" name="User Task"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
<startEvent id="startevent1" name="Start"></startEvent>
<sequenceFlow id="flow3" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_vacationProcess">
<bpmndi:BPMNPlane bpmnElement="vacationProcess"
id="BPMNPlane_vacationProcess">
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55" width="105" x="310" y="160"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35" width="35" x="490" y="170"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="startevent1"
id="BPMNShape_startevent1">
<omgdc:Bounds height="35" width="35" x="170" y="170"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="415" y="187"></omgdi:waypoint>
<omgdi:waypoint x="490" y="187"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="205" y="187"></omgdi:waypoint>
<omgdi:waypoint x="310" y="187"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
流程部署测试
package org.crazyit.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
/**
* 部署流程文件
* @author yangenxiong
*
*/
public class ProcessDeploy {
public static void main(String[] args) {
//创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
//创建DeploymentBuilder实例
DeploymentBuilder builder = repositoryService.createDeployment();
builder.addClasspathResource("bpmn/processDeploy.bpmn").deploy();
}
}
流程图部署测试代码
package org.crazyit.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
/**
* 部署流程图
*
* @author yangenxiong
*
*/
public class DeployDiagram {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
//部署流程描述文件与流程图
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("bpmn/diagram.bpmn")
.addClasspathResource("bpmn/diagram.png").deploy();
//查询流程定义实体
ProcessDefinition def = repositoryService.createProcessDefinitionQuery()
.deploymentId(dep.getId()).singleResult();
// 输出结果为 bpmn/diagram.vacationProcess.png
System.out.println(def.getDiagramResourceName());
}
}
流程图自动生成
如果在部署时我们不提供流程图,但在流程定义的XML文件中保存了BPMN流程图的元素,则Activiti会自动生成流程图,并保存到资源表中。如果不希望Activiti帮我们生成流程图,则可以在流程引擎配置文件中加入以下属性:
package org.crazyit.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
public class NoGenDiagram {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 部署流程描述文件与流程图
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("bpmn/noGenDiagram.bpmn").deploy();
// 查询流程定义实体
ProcessDefinition def = repositoryService
.createProcessDefinitionQuery().deploymentId(dep.getId())
.singleResult();
// 输出结果为 bpmn/diagram.vacationProcess.png
System.out.println("自动生成流程图:" + def.getDiagramResourceName());
// 读取不生成流程图的配置文件
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("noGenDiagram.cfg.xml");
ProcessEngine noGenEngine = config.buildProcessEngine();
RepositoryService noGenService = noGenEngine.getRepositoryService();
Deployment noGenDep = noGenService.createDeployment()
.addClasspathResource("bpmn/noGenDiagram.bpmn").deploy();
ProcessDefinition noGenDef = noGenService
.createProcessDefinitionQuery().deploymentId(noGenDep.getId())
.singleResult();
// 输出结果为null
System.out.println("不生成流程图,查询资源为:" + noGenDef.getDiagramResourceName());
}
}
中止与激活流程定义
RepositoryService中提供了多个中止与激活流程定义的方法,可以将流程定义的数据置为中止与激活状态。其中有多个suspendProcessDefinitionById、suspendProcessDefinitionByKey重载的中止方法,也有多个activateProcessDefinitionById、activateProcessDefinitionByKey重载的激活方法。以activateProcessDefinitionById方法为例,有两个重载方法。
➢ activateProcessDefinitionById(String processDefinitionId):根据流程定义的id激活流程定义。
➢ activateProcessDefinitionById(String processDefinitionId,boolean activateProcessInstances,Date activationDate):在某个时间激活流程定义,需要注意的是activateProcessInstances参数,如果为true,则该流程定义下的流程实例,也会被激活。
本身流程定义就没有所谓的中止与激活的概念(流程才有),这里所说的中止与激活,只是流程定义的数据状态设置为中止状态和激活状态。流程定义文件一旦被部署,那么对应的流程定义数据状态为激活,可以调用RepositoryService的中止流程定义的方法改变其状态。RepositoryService提供了两个中止流程定义的方法。
➢ suspendProcessDefinitionById(String processDefinitionId):根据流程ID中止流程定义。
➢ suspendProcessDefinitionByKey(String processDefinitionKey):根据流程定义文件中的process 节点的 id 属性中止流程定义,也可以看作根据 ACT_RE_PROCDEF 表中的KEY_字段值中止流程定义。
当流程定义被中止后,如果想激活流程定义,同样可以使用RepositoryService中提供的激活流程定义的方法,方法描述如下。
➢ activateProcessDefinitionById(String processDefinitionId):根据流程ID激活流程定义。
➢ activateProcessDefinitionByKey(String processDefinitionKey):根据流程的key激活流程定义,与中止流程定义的suspendProcessDefinitionByKey方法一致。
创建suspendProcessDef.bpmn.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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">
<process id="vacationProcess" name="vacation">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="Write Vacation"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
</definitions>
创建activiti.cfg.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="root" />
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
创建log4j.properties配置文件
# Set root category priority to INFO and its only appender to CONSOLE.
log4j.rootCategory=INFO, CONSOLE
#log4j.rootCategory=INFO, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1} - %m%n
log4j.logger.org.apache=INFO
log4j.logger.org.htmlparser=INFO
log4j.logger.org.htmlparser=INFO
log4j.logger.com.angus=INFO
测试
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
/**
* 中断流程
* @author yangenxiong
*
*/
public class SuspendProcessDef {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 部署流程描述文件
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("bpmn/suspendProcessDef.bpmn")
.deploy();
//查询流程定义实体
ProcessDefinition def = repositoryService.createProcessDefinitionQuery()
.deploymentId(dep.getId()).singleResult();
// 调用suspendProcessDefinitionById中止流程定义
repositoryService.suspendProcessDefinitionById(def.getId());
// 调用activateProcessDefinitionById激活流程定义
repositoryService.activateProcessDefinitionById(def.getId());
// 调用suspendProcessDefinitionByKey中止流程定义
repositoryService.suspendProcessDefinitionByKey(def.getKey());
// 调用activateProcessDefinitionByKey激活流程定义
repositoryService.activateProcessDefinitionByKey(def.getKey());
}
}
流程定义缓存配置
为了减少数据的查询,提升流程引擎性能,Activiti本身对某些常用的数据做了缓存。例如在解析完流程定义的XML文件后,会将流程定义缓存到一个Map中,key为流程定义的id(数据库的ID_字段),value为封装好的缓存对象。默认情况下,不需要进行配置,流程引擎会进行缓存的工作。
创建default-cache.bpmn.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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">
<process id="testCache" name="testCache">
<userTask id="usertask1" name="User Task"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
<startEvent id="startevent1" name="Start"></startEvent>
<sequenceFlow id="flow3" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_vacationProcess">
<bpmndi:BPMNPlane bpmnElement="vacationProcess"
id="BPMNPlane_vacationProcess">
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55" width="105" x="310" y="160"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35" width="35" x="490" y="170"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="startevent1"
id="BPMNShape_startevent1">
<omgdc:Bounds height="35" width="35" x="170" y="170"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="415" y="187"></omgdi:waypoint>
<omgdi:waypoint x="490" y="187"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="205" y="187"></omgdi:waypoint>
<omgdi:waypoint x="310" y="187"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
创建activiti.cfg.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="root" />
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
创建日志log4j.properties
# Set root category priority to INFO and its only appender to CONSOLE.
log4j.rootCategory=INFO, CONSOLE
#log4j.rootCategory=INFO, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1} - %m%n
log4j.logger.org.apache=INFO
log4j.logger.org.htmlparser=INFO
log4j.logger.org.htmlparser=INFO
log4j.logger.com.angus=INFO
创建my-cache.cfg.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="root" />
<property name="databaseSchemaUpdate" value="true" />
<property name="processDefinitionCache">
<ref bean="myCache" />
</property>
</bean>
<bean id="myCache" class="org.crazyit.activiti.MyCacheBean"></bean>
</beans>
创建use-limit.cfg.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="root" />
<property name="databaseSchemaUpdate" value="true"/>
<property name="processDefinitionCacheLimit" value="2" />
</bean>
</beans>
测试类
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
/**
* 测试默认情况下的缓存
*
* @author yangenxiong
*
*/
public class DefaultCache {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
ProcessEngineConfigurationImpl config = (ProcessEngineConfigurationImpl) engine
.getProcessEngineConfiguration();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 进行10次部署
for (int i = 0; i < 10; i++) {
repositoryService.createDeployment()
.addClasspathResource("bpmn/default-cache.bpmn")
.name("dep_" + i).key("key_" + i).deploy();
}
// 获取缓存
DefaultDeploymentCache cache = (DefaultDeploymentCache) config
.getProcessDefinitionCache();
// 遍历缓存,输出Map中的key
for (Iterator it = cache.cache.keySet().iterator(); it.hasNext();) {
String key = (String) it.next();
System.out.println(key);
}
}
}
先进行10次部署,然后读取缓存对象,获取Map属性并输出内容。需要注意以下细节:
➢ 为了测试获取了缓存的Map,代码清单7-15的包与DefaultDeploymentCache的包是一致的,换言之,Map是protected的。
➢ DefaultDeploymentCache、ProcessEngineConfigurationImpl等实现类不应出现在业务代码中,尽量使用它们的接口。
由于缓存没有限制,因此部署了10次,缓存(Map)中就有10个元素,如果想限制缓存数量,可以在流程引擎的配置文件中使用以下配置:
import java.util.Iterator;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
/**
* 测试默认情况下的缓存
*
* @author yangenxiong
*
*/
public class UserLimitCache {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngineConfigurationImpl config =
(ProcessEngineConfigurationImpl) ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("use-limit.cfg.xml");
ProcessEngine engine = config.buildProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 进行10次部署
for (int i = 0; i < 10; i++) {
repositoryService.createDeployment()
.addClasspathResource("bpmn/default-cache.bpmn")
.name("dep_" + i).key("key_" + i).deploy();
}
// 获取缓存
DefaultDeploymentCache cache = (DefaultDeploymentCache) config
.getProcessDefinitionCache();
// 遍历缓存,输出Map中的key
for (Iterator it = cache.cache.keySet().iterator(); it.hasNext();) {
String key = (String) it.next();
System.out.println(key);
}
}
}
自定义缓存
如果想自己实现缓存,可以新建一个Java类,实现DeploymentCache接口,该接口主要有增、删、改、查方法,用于操作缓存数据。
自定义缓存类
import java.util.HashMap;
import java.util.Map;
import org.activiti.engine.impl.persistence.deploy.DeploymentCache;
public class MyCacheBean<T> implements DeploymentCache<T> {
public Map<String, T> cache;
public MyCacheBean() {
cache = new HashMap<String, T>();
}
public T get(String id) {
return cache.get(id);
}
public boolean contains(String id) {
return cache.containsKey(id);
}
public void add(String id, T object) {
cache.put(id, object);
}
public void remove(String id) {
cache.remove(id);
}
public void clear() {
cache.clear();
}
}
测试类
package org.crazyit.activiti;
import java.util.Iterator;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
public class MyCacheTest {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngineConfigurationImpl config =
(ProcessEngineConfigurationImpl) ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("my-cache.cfg.xml");
ProcessEngine engine = config.buildProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 进行10次部署
for (int i = 0; i < 10; i++) {
repositoryService.createDeployment()
.addClasspathResource("bpmn/default-cache.bpmn")
.name("dep_" + i).key("key_" + i).deploy();
}
// 获取缓存
MyCacheBean cache = (MyCacheBean) config
.getProcessDefinitionCache();
// 遍历缓存,输出Map中的key
for (Iterator it = cache.cache.keySet().iterator(); it.hasNext();) {
String key = (String) it.next();
System.out.println(key);
}
}
}
流程定义权限
对于一个用户或者用户组是否能操作(查看)某个流程定义的数据,需要进行相应的流程定义权限设置。在Activiti中,并没有对流程定义的权限进行检查,而是提供一种反向的方法,让调用者去管理这些权限数据,然后提供相应的API让使用人决定哪种数据可以被查询。
设置流程定义的用户权限
在默认情况下,所有用户(用户组)均可以根据流程定义创建流程实例(启动流程),可以使用Activiti提供的API来设置启动流程的权限数据(设置启动流程的候选用户)。Activiti提供了设置流程权限数据的API,可以调用这些API来管理这些权限数据,但是,需要注意的是,Activiti本身并不会对权限进行检查,而是提供了相应的权限查询接口,让开发者决定展示何种数据给使用者,从而达到权限控制的作用。例如提供了根据用户来获取其有权限的流程定义,根据流程定义获取有权限的用户组或者用户。Activiti的这种设计,使得在开发流程权限功能时开发人员可以有更灵活的选择。
RepositoryService中提供了addCandidateStarterUser方法,给流程定义与用户绑定权限,该方法实际上是向一个中间表中加入数据,表示流程与用户之间的关系。addCandidateStarterUser方法的第一个参数为流程定义ID,第二个参数为用户的ID。
package org.crazyit.activiti;
import org.activiti.engine.IdentityService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.identity.User;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
/**
* 开始流程用户权限
*
* @author yangenxiong
*
*/
public class UserCandidate {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 得到身份服务组件
IdentityService identityService = engine.getIdentityService();
// 部署流程描述文件
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("bpmn/candidateUser.bpmn").deploy();
// 查询流程定义实体
ProcessDefinition def = repositoryService
.createProcessDefinitionQuery().deploymentId(dep.getId())
.singleResult();
// 写入用户数据
creatUser(identityService, "user1", "angus", "young", "abc@163.com",
"123");
creatUser(identityService, "user2", "angus2", "young2", "abc@163.com",
"123");
creatUser(identityService, "user3", "angus3", "young3", "abc@163.com",
"123");
// 设置用户组与流程定义的关系(设置权限)
repositoryService.addCandidateStarterUser(def.getId(), "user1");
repositoryService.addCandidateStarterUser(def.getId(), "user2");
}
// 创建用户方法
static void creatUser(IdentityService identityService, String id,
String first, String last, String email, String passwd) {
// 使用newUser方法创建User实例
User user = identityService.newUser(id);
// 设置用户的各个属性
user.setFirstName(first);
user.setLastName(last);
user.setEmail(email);
user.setPassword(passwd);
// 使用saveUser方法保存用户
identityService.saveUser(user);
}
}
创建activiti.cfg.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="root" />
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
创建candidateUser.bpmn.xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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">
<process id="vacationProcess" name="vacation">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="User Task"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_vacationProcess">
<bpmndi:BPMNPlane bpmnElement="vacationProcess"
id="BPMNPlane_vacationProcess">
<bpmndi:BPMNShape bpmnElement="startevent1"
id="BPMNShape_startevent1">
<omgdc:Bounds height="35" width="35" x="160" y="170"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55" width="105" x="280" y="160"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35" width="35" x="450" y="170"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="195" y="187"></omgdi:waypoint>
<omgdi:waypoint x="280" y="187"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="385" y="187"></omgdi:waypoint>
<omgdi:waypoint x="450" y="187"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
IdentityLink对象
一个IdentityLink实例表示一种身份数据与流程数据绑定的关系,此处所说的身份数据包括用户组和用户数据,流程数据包括流程定义、流程任务等数据。IdentityLink是一个接口,与其他数据库映射实体一样,其对应的实现类为IdentityLinkEntityImpl,对应的数据表为ACT_RU_IDENTITYLINK。它包含以下映射的属性。
➢ id:主键,对应ID_字段。
➢ type:数据类型,对应TYPE_字段,Activiti为该字段提供了5个值,分别为assignee、candidate、starter、participant和owner。本章中为用户或者用户组绑定流程定义时,该值为candidate,表示创建流程实例的请求者。
➢ groupId:绑定关系中的用户组ID,对应GROUP_ID_字段。
➢ userId:绑定关系中的用户ID,对应USER_ID_字段。
➢ taskId:绑定关系中的流程任务ID,对应TASK_ID_字段。
➢ processDefId:绑定关系中的流程定义ID,对应PROC_DEF_ID_字段。
在此需要注意的是,ACT_RU_IDENTITYLINK表中还有一个REV_字段,IdentityLink-EntityImpl并没有为该字段做相应的属性映射,在实体与数据表映射的配置文件中,该字段的值被设置为1。
查询权限数据
前面介绍了如何设置流程定义的权限,那么当需要使用这些权限数据时,则可以使用IdentityService中提供的获取流程角色权限数据的方法来获取这些数据,例如根据用户获取该用户有权限启动的流程定义,根据流程定义获取有权限申请的用户和用户组数据。这些获取权限数据的方法分布在各个不同的服务组件中,如果要根据流程定义获取有权限申请的角色数据,则需要使用IdentityService;如果要根据用户或者用户组得到相应的流程定义数据,则需要使用RepositoryService。
import java.util.List;
import org.activiti.engine.IdentityService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.identity.Group;
import org.activiti.engine.identity.User;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.task.IdentityLink;
/**
* 查询流程定义的权限数据
*
* @author yangenxiong
*
*/
public class CandidateQuery {
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 得到身份服务组件
IdentityService identityService = engine.getIdentityService();
// 部署流程描述文件
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("bpmn/candidateQuery.bpmn").deploy();
// 添加2个用户
creatUser(identityService, "user1", "张三", "张三", "mail1", "123");
creatUser(identityService, "user2", "李四", "李四", "mail2", "123");
// 添加2个用户组
createGroup(identityService, "group1", "经理组", "manager");
createGroup(identityService, "group2", "员工组", "employee");
// 查询流程定义
ProcessDefinition def = repositoryService
.createProcessDefinitionQuery().deploymentId(dep.getId())
.singleResult();
// 设置权限数据
repositoryService.addCandidateStarterGroup(def.getId(), "group1");
repositoryService.addCandidateStarterGroup(def.getId(), "group2");
repositoryService.addCandidateStarterUser(def.getId(), "user1");
repositoryService.addCandidateStarterUser(def.getId(), "user2");
// 根据用户查询用权限的流程定义
List<ProcessDefinition> defs = repositoryService
.createProcessDefinitionQuery().startableByUser("user1").list();
System.out.println("用户张三有权限的流程定义为:");// 结果为1
for (ProcessDefinition dft : defs) {
System.out.println(" " + dft.getName());
}
// 根据流程定义查询用户组数据
List<Group> groups = identityService.createGroupQuery()
.potentialStarter(def.getId()).list();
System.out.println("以下用户组对流程定义有权限:");
for (Group group : groups) {
System.out.println(" " + group.getName());
}
// 根据流程定义查询用户数据
List<User> users = identityService.createUserQuery()
.potentialStarter(def.getId()).list();
System.out.println("以下用户对流程定义有权限:");// 结果为2
for (User user : users) {
System.out.println(" " + user.getFirstName());
}
// 根据流程定义查询全部的 IdentityLink(ACT_RU_IDENTITYLINK表) 数据
List<IdentityLink> links = repositoryService
.getIdentityLinksForProcessDefinition(def.getId());
System.out.println("与流程定义相关的数据量: " + links.size());// 结果为4
}
// 将用户组数据保存到数据库中
static void createGroup(IdentityService identityService, String id,
String name, String type) {
// 调用newGroup方法创建Group实例
Group group = identityService.newGroup(id);
group.setName(name);
group.setType(type);
identityService.saveGroup(group);
}
// 创建用户方法
static void creatUser(IdentityService identityService, String id,
String first, String last, String email, String passwd) {
// 使用newUser方法创建User实例
User user = identityService.newUser(id);
// 设置用户的各个属性
user.setFirstName(first);
user.setLastName(last);
user.setEmail(email);
user.setPassword(passwd);
// 使用saveUser方法保存用户
identityService.saveUser(user);
}
}
➢ ProcessDefinitionQuery的startableByUser方法:该方法用于根据用户ID查询该用户有权限启动的流程定义数据。
➢ GroupQuery的potentialStarter方法:根据流程定义ID查询有权限启动的用户组数据。
➢ UserQuery的potentialStarter方法:根据流程定义ID查询有权限启动的用户数据。
➢ RepositoryService的getIdentityLinksForProcessDefinition方法:根据流程定义ID查询与之相关的全部权限数据。
RepositoryService数据查询与删除
使用DeploymentBuilder对象将流程描述文件、流程资源等文件保存到数据库中,本节将讲述如何使用RepositoryService提供的方法管理这些资源数据,这些资源包括部署的文件、流程描述文件、流程图文件等。
查询部署资源
部署资源包括与部署相关的文件、流程描述文件、流程图等,可以使用DeploymentBuilder的addXXX方法将相关的资源文件保存到数据库中,如果需要使用这些文件,则可以调用RepositoryService的getResourceAsStream方法,只需要提供部署(Deployment)ID和资源名称,就可以返回资源的输入流对象
创建GetResource.txt文件
www
package org.crazyit.activiti;
import java.io.InputStream;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
/**
* 查询部署资源
*
* @author yangenxiong
*
*/
public class GetResource {
public static void main(String[] args) throws Exception {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
// 部署一份txt文件
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("artifact/GetResource.txt").deploy();
// 查询资源文件
InputStream is = repositoryService.getResourceAsStream(dep.getId(),
"artifact/GetResource.txt");
// 读取输入流
int count = is.available();
byte[] contents = new byte[count];
is.read(contents);
String result = new String(contents);
//输入结果
System.out.println(result);
}
}
查询流程文件
部署流程描述文件的时候,Activiti会向资源表中写入该文件的内容,即先将其看作一份普通的文件进行解析与数据保存,然后再解析它的文件内容,并生成相应的流程定义数据。与查询部署资源的getResourceAsStream方法一样,RepositoryService提供了一个getProcessModel方法,调用该方法只需要提供流程定义的ID即可返回流程文件的InputStream实例。
创建getProcessModel.bpmn配置文件
创建getProcessModel.bpmn.xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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">
<process id="vacationProcess" name="vacation">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="Write Vacation"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
</process>
</definitions>
查询流程图
在部署一个流程的时候,可以由Activiti生成流程图,也可以由我们提供,可以使用RepositoryService的getProcessDiagram方法返回该流程图的InputStream对象。调用getProcessDiagram方法同样需要提供流程定义ID,根据流程定义数据得到部署数据ID与部署资源名称,再到资源表中查询相应的文件数据返回。
package org.crazyit.activiti;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import javax.imageio.ImageIO;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
/**
* 查询流程图
*
* @author yangenxiong
*
*/
public class GetProcessDiagram {
public static void main(String[] args) throws Exception {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
// 部署一份流程文件与相应的流程图文件
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("bpmn/getProcessDiagram.bpmn")
.addClasspathResource("bpmn/getProcessDiagram.png").deploy();
// 查询流程定义
ProcessDefinition def = repositoryService.createProcessDefinitionQuery()
.deploymentId(dep.getId()).singleResult();
// 查询资源文件
InputStream is = repositoryService.getProcessDiagram(def.getId());
// 将输入流转换为图片对象
BufferedImage image = ImageIO.read(is);
// 保存为图片文件
File file = new File("resource/artifact/result.png");
if (!file.exists()) file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
ImageIO.write(image, "png", fos);
fos.close();
is.close();
}
}
查询部署资源名称
package org.crazyit.activiti;
import java.util.List;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
/**
* 查询部署资源名称
*
* @author yangenxiong
*
*/
public class GetResourceNames {
public static void main(String[] args) throws Exception {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务对象
RepositoryService repositoryService = engine.getRepositoryService();
// 部署一份流程文件与相应的流程图文件
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("bpmn/GetResourceNames.bpmn")
.addClasspathResource("bpmn/GetResourceNames.png").deploy();
// 查询资源文件名称集合
List<String> names = repositoryService.getDeploymentResourceNames(dep.getId());
for (String name : names) {
System.out.println(name);
}
}
}
删除部署资源
在前面章节,使用了RepositoryService的各个方法来查询部署所产生的相关资源数据。如果需要删除这些数据,则可以使用RepositoryService提供的两个删除方法,这两个方法的描述如下。
➢ deleteDeployment(String deploymentId):删除部署数据,不进行级联删除,这里所说的级联删除,是指与该部署相关的流程实例数据的删除。
➢ deleteDeployment(String deploymentId,boolean cascade):是否进行级联删除,由调用者决定。如果cascade参数为false,效果等同于deleteDeployment(String deploymentId)方法;如果cascade为true,则会删除部署相关的流程实例数据。
不管删除部署数据时是否指定级联删除,部署的相关数据均会被删除,包括身份数据(IdentityLink)、流程定义数据(ProcessDefinition)、流程资源(Resource)与部署数据(Deployment)。代码清单7-26示范了如何调用两个删除部署数据的方法。
package org.crazyit.activiti;
import java.util.List;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
/**
* 使用DeploymentQuery的deploymentId方法
* @author yangenxiong
*
*/
public class DeploymentQuery {
/**
* @param args
*/
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 写入5条Deployment数据
Deployment depA = repositoryService.createDeployment().addString("a1", "a1")
.addString("a2", "a2").addString("a3", "a3").name("a").deploy();
Deployment depB = repositoryService.createDeployment().addString("b1", "b1")
.addString("b2", "b2").addString("b3", "b3").name("b").deploy();
Deployment depC = repositoryService.createDeployment().addString("c1", "c1")
.addString("c2", "c2").addString("c3", "c3").name("c").deploy();
Deployment depD = repositoryService.createDeployment().addString("d1", "d1")
.addString("d2", "d2").addString("d3", "d3").name("da").deploy();
Deployment depE = repositoryService.createDeployment().addString("e1", "e1")
.addString("e2", "e2").addString("e3", "e3").name("eb").deploy();
//deploymentId方法
Deployment depAQuery = repositoryService.createDeploymentQuery()
.deploymentId(depA.getId()).singleResult();
System.out.println("根据id查询:" + depAQuery.getName());
//deploymentName方法
Deployment depBQuery = repositoryService.createDeploymentQuery()
.deploymentName("b").singleResult();
System.out.println("查询名称为b:" + depBQuery.getName());
//deploymentNameLike, 模糊查询,结果集为2
List<Deployment> depCQuery = repositoryService.createDeploymentQuery()
.deploymentNameLike("%b%").list();
System.out.println("模糊查询b,结果数量:" + depCQuery.size());
}
}
DeploymentQuery对象
Activiti中的每个查询对象(XXXQuery)均有自己相应的查询方法和排序方法(详细请见第6章),本章的DeploymentQuery对象包括如下方法。
➢ deploymentId(String id):添加ID查询条件,查询ID为参数值的数据记录。
➢ deploymentName(String name):添加名称查询条件,查询Deployment名称为参数值的数据记录。
➢ deploymentNameLike(String name):添加模糊查询条件,查询Deployment名称含有参数值的数据记录。
➢ orderByDeploymentId:设置查询结果按照Deployment的ID进行排序,排序方式由asc与desc方法决定。
➢ orderByDeploymentTime:设置查询结果按照DEPLOY_TIME_字段排序。
➢ orderByDeploymentName:设置查询结果按照Deployment名称排序。
package org.crazyit.activiti;
import java.util.List;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
/**
* 使用DeploymentQuery的deploymentId方法
* @author yangenxiong
*
*/
public class DeploymentQuery {
/**
* @param args
*/
public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
// 写入5条Deployment数据
Deployment depA = repositoryService.createDeployment().addString("a1", "a1")
.addString("a2", "a2").addString("a3", "a3").name("a").deploy();
Deployment depB = repositoryService.createDeployment().addString("b1", "b1")
.addString("b2", "b2").addString("b3", "b3").name("b").deploy();
Deployment depC = repositoryService.createDeployment().addString("c1", "c1")
.addString("c2", "c2").addString("c3", "c3").name("c").deploy();
Deployment depD = repositoryService.createDeployment().addString("d1", "d1")
.addString("d2", "d2").addString("d3", "d3").name("da").deploy();
Deployment depE = repositoryService.createDeployment().addString("e1", "e1")
.addString("e2", "e2").addString("e3", "e3").name("eb").deploy();
//deploymentId方法
Deployment depAQuery = repositoryService.createDeploymentQuery()
.deploymentId(depA.getId()).singleResult();
System.out.println("根据id查询:" + depAQuery.getName());
//deploymentName方法
Deployment depBQuery = repositoryService.createDeploymentQuery()
.deploymentName("b").singleResult();
System.out.println("查询名称为b:" + depBQuery.getName());
//deploymentNameLike, 模糊查询,结果集为2
List<Deployment> depCQuery = repositoryService.createDeploymentQuery()
.deploymentNameLike("%b%").list();
System.out.println("模糊查询b,结果数量:" + depCQuery.size());
}
}