activiti是jbpm4进化而来,team leader 也没换人,所以activiti和jbpm4的api有这将近60%的相同,官方网站是activiti.org. 上手来说比较简单.
activiti自我感触
activiti也支持了bpmn2.0,而且在 支持的基础上,做了大量的自定义以activiti前缀开头的扩展,目的是让你用着更方便,一般来说,bpmn2.0里面配置复杂的,或者实现复杂,或者没实现的,activiti都有相应的拓展. 带来的问题也显而易见,大量的拓展,对与以后版本的升级带了的困扰
activiti在junit 测试方面提供了大量的支持,我不知道他们为什么话这么多功夫搞junit,很多我想要的功能在userguide 和activiti in action(一般官方推荐的书)里面都没有提到,有40%的activiti jar包里面的api没有提到,但是反而官方提供的例子里面都是用这些没有提到的api实现的,这就导致了我学习activiti时,遇到了很多困扰.在官方提供的javadoc里面仅仅包涵了60%的api 我不知道activiti团队是什么意思,也许是觉得这些api不够稳定,如果不够稳定,他们也没有给提供什么解决方案,例如:流程图显示,api里面明明有一个类可以生成,偏偏不告诉你,自由流的实现也是,很多都是我通过反编译官方提供的例子(activiti-explorer)才知道的.然我太费解了.
activiti的持久化(persistence)用的是mybatis,效率真的不是很高,可能activiti的团队的sql很烂吧,在查询所有任务时,往往超乎你的想想.
activiti的eclipse的插件 让我也很费解,user guide 上说 创建的流程文件必须是bpmn20.xml,但是用这个插件创建的东西都是bpmn结尾的,而且不能指定这个插件默认编辑bpmn20.xml结尾的文件,让我操作起来很不爽,但是总体来说,这个插件其他地方用起来还不错,比jbpm4的插件好了不止100倍,不论是bpmn2.0标准的流程元素,还是activiti拓展的流程元素,这些元素的所有属性,监听器都可以直接在属性页面配置.还是很不错的.
activiti对spring的集成还是很到位的,在userguide里面占了一定的篇幅.,而且专门提供了一个spring使用的流程引擎:SpringProcessEngineConfiguration,集成起来还是比较方便的.user guide上有说的很详细了,我不多说了。
我做了一个请假流程的小例子(貌似,学习语言的例子是Hello world,我见过的流程的例子都是请假流程。。。),这是我的流程图。
主要逻辑很简单,申请人需要填写申请单,字段有:请假天数(day),请假类型(病假,事假),请假原因。然后病假类型走经理审批线路,事假类型走人力审批线路。
病假线路中,部门经理审批完成之后,如果请假天数大于3天,需要走总监审批,如果,小于等于3天,直接结束,发送通知邮件。
事假线路中,财务审批完成之后,也是如果请假天数大于3天,需要走老板审批,如果,小于等于3天,直接结束,发送通知邮件。
每个节点的审批人都是分别是:申请人:随便,经理审批:jingli,部门经理审批:bumen,总监审批:zongjian,人力审批:人力,财务审批:caiwu,老板审批:boss。
这里我也是封装了一个类,ProcessCustomService,这个类是我在另外一篇博客中看到的,当然是不能运行的代码,我修改完善,有加了我很多自己的东西。下面是我修改后的代码:
package com.netqin.kingviker;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
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.history.HistoricActivityInstance;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.process.ProcessDefinitionImpl;
import org.activiti.engine.impl.pvm.process.TransitionImpl;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.lang.StringUtils;
/**
* 流程操作核心类<br>
* 此核心类主要处理:流程通过、驳回、转办、中止、挂起等核心操作<br>
*
*
*/
public class ProcessCustomService{
private static ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
private static RepositoryService repositoryService = processEngine.getRepositoryService();
private static RuntimeService runtimeService = processEngine.getRuntimeService();
private static TaskService taskService = processEngine.getTaskService();
private static FormService formService = processEngine.getFormService();
private static HistoryService historyService = processEngine.getHistoryService();
/**
* 驳回流程
*
* @param taskId
* 当前任务ID
* @param activityId
* 驳回节点ID
* @param variables
* 流程存储参数
* @throws Exception
*/
public static void backProcess(String taskId, String activityId,
Map<String, Object> variables) throws Exception {
if (StringUtils.isEmpty(activityId)) {
throw new Exception("驳回目标节点ID为空!");
}
// 查找所有并行任务节点,同时驳回
List<Task> taskList = findTaskListByKey(findProcessInstanceByTaskId(
taskId).getId(), findTaskById(taskId).getTaskDefinitionKey());
for (Task task : taskList) {
commitProcess(task.getId(), variables, activityId);
}
}
/**
* 取回流程
*
* @param taskId
* 当前任务ID
* @param activityId
* 取回节点ID
* @throws Exception
*/
public static void callBackProcess(String taskId, String activityId)
throws Exception {
if (StringUtils.isEmpty(activityId)) {
throw new Exception("目标节点ID为空!");
}
// 查找所有并行任务节点,同时取回
List<Task> taskList = findTaskListByKey(findProcessInstanceByTaskId(
taskId).getId(), findTaskById(taskId).getTaskDefinitionKey());
for (Task task : taskList) {
commitProcess(task.getId(), null, activityId);
}
}
/**
* 清空指定活动节点流向
*
* @param activityImpl
* 活动节点
* @return 节点流向集合
*/
private static List<PvmTransition> clearTransition(ActivityImpl activityImpl) {
// 存储当前节点所有流向临时变量
List<PvmTransition> oriPvmTransitionList = new ArrayList<PvmTransition>();
// 获取当前节点所有流向,存储到临时变量,然后清空
List<PvmTransition> pvmTransitionList = activityImpl
.getOutgoingTransitions();
for (PvmTransition pvmTransition : pvmTransitionList) {
oriPvmTransitionList.add(pvmTransition);
}
pvmTransitionList.clear();
return oriPvmTransitionList;
}
/**
* @param taskId
* 当前任务ID
* @param variables
* 流程变量
* @param activityId
* 流程转向执行任务节点ID<br>
* 此参数为空,默认为提交操作
* @throws Exception
*/
private static void commitProcess(String taskId, Map<String, Object> variables,
String activityId) throws Exception {
if (variables == null) {
variables = new HashMap<String, Object>();
}
// 跳转节点为空,默认提交操作
if (StringUtils.isEmpty(activityId)) {
taskService.complete(taskId, variables);
} else {// 流程转向操作
turnTransition(taskId, activityId, variables);
}
}
/**
* 中止流程(特权人直接审批通过等)
*
* @param taskId
*/
public static void endProcess(String taskId) throws Exception {
ActivityImpl endActivity = findActivitiImpl(taskId, "end");
commitProcess(taskId, null, endActivity.getId());
}
/**
* 根据流入任务集合,查询最近一次的流入任务节点
*
* @param processInstance
* 流程实例
* @param tempList
* 流入任务集合
* @return
*/
private static ActivityImpl filterNewestActivity(ProcessInstance processInstance,
List<ActivityImpl> tempList) {
while (tempList.size() > 0) {
ActivityImpl activity_1 = tempList.get(0);
HistoricActivityInstance activityInstance_1 = findHistoricUserTask(
processInstance, activity_1.getId());
if (activityInstance_1 == null) {
tempList.remove(activity_1);
continue;
}
if (tempList.size() > 1) {
ActivityImpl activity_2 = tempList.get(1);
HistoricActivityInstance activityInstance_2 = findHistoricUserTask(
processInstance, activity_2.getId());
if (activityInstance_2 == null) {
tempList.remove(activity_2);
continue;
}
if (activityInstance_1.getEndTime().before(
activityInstance_2.getEndTime())) {
tempList.remove(activity_1);
} else {
tempList.remove(activity_2);
}
} else {
break;
}
}
if (tempList.size() > 0) {
return tempList.get(0);
}
return null;
}
/**
* 根据任务ID和节点ID获取活动节点 <br>
*
* @param taskId
* 任务ID
* @param activityId
* 活动节点ID <br>
* 如果为null或"",则默认查询当前活动节点 <br>
* 如果为"end",则查询结束节点 <br>
*
* @return
* @throws Exception
*/
private static ActivityImpl findActivitiImpl(String taskId, String activityId)
throws Exception {
// 取得流程定义
ProcessDefinitionEntity processDefinition = findProcessDefinitionEntityByTaskId(taskId);
// 获取当前活动节点ID
if (StringUtils.isEmpty(activityId)) {
activityId = findTaskById(taskId).getTaskDefinitionKey();
}
// 根据流程定义,获取该流程实例的结束节点
if (activityId.toUpperCase().equals("END")) {
for (ActivityImpl activityImpl : processDefinition.getActivities()) {
List<PvmTransition> pvmTransitionList = activityImpl
.getOutgoingTransitions();
if (pvmTransitionList.isEmpty()) {
return activityImpl;
}
}
}
// 根据节点ID,获取对应的活动节点
ActivityImpl activityImpl = ((ProcessDefinitionImpl) processDefinition)
.findActivity(activityId);
return activityImpl;
}
/**
* 根据当前任务ID,查询可以驳回的任务节点
*
* @param taskId
* 当前任务ID
*/
public static List<ActivityImpl> findBackAvtivity(String taskId) throws Exception {
List<ActivityImpl> rtnList = iteratorBackActivity(taskId, findActivitiImpl(taskId,
null), new ArrayList<ActivityImpl>(),
new ArrayList<ActivityImpl>());
return reverList(rtnList);
}
/**
* 查询指定任务节点的最新记录
*
* @param processInstance
* 流程实例
* @param activityId
* @return
*/
private static HistoricActivityInstance findHistoricUserTask(
ProcessInstance processInstance, String activityId) {
HistoricActivityInstance rtnVal = null;
// 查询当前流程实例审批结束的历史节点
List<HistoricActivityInstance> historicActivityInstances = historyService
.createHistoricActivityInstanceQuery().activityType("userTask")
.processInstanceId(processInstance.getId()).activityId(
activityId).finished()
.orderByHistoricActivityInstanceEndTime().desc().list();
if (historicActivityInstances.size() > 0) {
rtnVal = historicActivityInstances.get(0);
}
return rtnVal;
}
/**
* 根据当前节点,查询输出流向是否为并行终点,如果为并行终点,则拼装对应的并行起点ID
*
* @param activityImpl
* 当前节点
* @return
*/
private static String findParallelGatewayId(ActivityImpl activityImpl) {
List<PvmTransition> incomingTransitions = activityImpl
.getOutgoingTransitions();
for (PvmTransition pvmTransition : incomingTransitions) {
TransitionImpl transitionImpl = (TransitionImpl) pvmTransition;
activityImpl = transitionImpl.getDestination();
String type = (String) activityImpl.getProperty("type");
if ("parallelGateway".equals(type)) {// 并行路线
String gatewayId = activityImpl.getId();
String gatewayType = gatewayId.substring(gatewayId
.lastIndexOf("_") + 1);
if ("END".equals(gatewayType.toUpperCase())) {
return gatewayId.substring(0, gatewayId.lastIndexOf("_"))
+ "_start";
}
}
}
return null;
}
/**
* 根据任务ID获取流程定义
*
* @param taskId
* 任务ID
* @return
* @throws Exception
*/
public static ProcessDefinitionEntity findProcessDefinitionEntityByTaskId(
String taskId) throws Exception {
// 取得流程定义
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
.getDeployedProcessDefinition(findTaskById(taskId)
.getProcessDefinitionId());
if (processDefinition == null) {
throw new Exception("流程定义未找到!");
}
return processDefinition;
}
/**
* 根据任务ID获取对应的流程实例
*
* @param taskId
* 任务ID
* @return
* @throws Exception
*/
public static ProcessInstance findProcessInstanceByTaskId(String taskId)
throws Exception {
// 找到流程实例
ProcessInstance processInstance = runtimeService
.createProcessInstanceQuery().processInstanceId(
findTaskById(taskId).getProcessInstanceId())
.singleResult();
if (processInstance == null) {
throw new Exception("流程实例未找到!");
}
return processInstance;
}
/**
* 根据任务ID获得任务实例
*
* @param taskId
* 任务ID
* @return
* @throws Exception
*/
private static TaskEntity findTaskById(String taskId) throws Exception {
TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(
taskId).singleResult();
if (task == null) {
throw new Exception("任务实例未找到!");
}
return task;
}
/**
* 根据流程实例ID和任务key值查询所有同级任务集合
*
* @param processInstanceId
* @param key
* @return
*/
private static List<Task> findTaskListByKey(String processInstanceId, String key) {
return taskService.createTaskQuery().processInstanceId(
processInstanceId).taskDefinitionKey(key).list();
}
/**
* 迭代循环流程树结构,查询当前节点可驳回的任务节点
*
* @param taskId
* 当前任务ID
* @param currActivity
* 当前活动节点
* @param rtnList
* 存储回退节点集合
* @param tempList
* 临时存储节点集合(存储一次迭代过程中的同级userTask节点)
* @return 回退节点集合
*/
private static List<ActivityImpl> iteratorBackActivity(String taskId,
ActivityImpl currActivity, List<ActivityImpl> rtnList,
List<ActivityImpl> tempList) throws Exception {
// 查询流程定义,生成流程树结构
ProcessInstance processInstance = findProcessInstanceByTaskId(taskId);
// 当前节点的流入来源
List<PvmTransition> incomingTransitions = currActivity
.getIncomingTransitions();
// 条件分支节点集合,userTask节点遍历完毕,迭代遍历此集合,查询条件分支对应的userTask节点
List<ActivityImpl> exclusiveGateways = new ArrayList<ActivityImpl>();
// 并行节点集合,userTask节点遍历完毕,迭代遍历此集合,查询并行节点对应的userTask节点
List<ActivityImpl> parallelGateways = new ArrayList<ActivityImpl>();
// 遍历当前节点所有流入路径
for (PvmTransition pvmTransition : incomingTransitions) {
TransitionImpl transitionImpl = (TransitionImpl) pvmTransition;
ActivityImpl activityImpl = transitionImpl.getSource();
String type = (String) activityImpl.getProperty("type");
/**
* 并行节点配置要求:<br>
* 必须成对出现,且要求分别配置节点ID为:XXX_start(开始),XXX_end(结束)
*/
if ("parallelGateway".equals(type)) {// 并行路线
String gatewayId = activityImpl.getId();
String gatewayType = gatewayId.substring(gatewayId
.lastIndexOf("_") + 1);
if ("START".equals(gatewayType.toUpperCase())) {// 并行起点,停止递归
return rtnList;
} else {// 并行终点,临时存储此节点,本次循环结束,迭代集合,查询对应的userTask节点
parallelGateways.add(activityImpl);
}
} else if ("startEvent".equals(type)) {// 开始节点,停止递归
return rtnList;
} else if ("userTask".equals(type)) {// 用户任务
tempList.add(activityImpl);
} else if ("exclusiveGateway".equals(type)) {// 分支路线,临时存储此节点,本次循环结束,迭代集合,查询对应的userTask节点
currActivity = transitionImpl.getSource();
exclusiveGateways.add(currActivity);
}
}
/**
* 迭代条件分支集合,查询对应的userTask节点
*/
for (ActivityImpl activityImpl : exclusiveGateways) {
iteratorBackActivity(taskId, activityImpl, rtnList, tempList);
}
/**
* 迭代并行集合,查询对应的userTask节点
*/
for (ActivityImpl activityImpl : parallelGateways) {
iteratorBackActivity(taskId, activityImpl, rtnList, tempList);
}
/**
* 根据同级userTask集合,过滤最近发生的节点
*/
currActivity = filterNewestActivity(processInstance, tempList);
if (currActivity != null) {
// 查询当前节点的流向是否为并行终点,并获取并行起点ID
String id = findParallelGatewayId(currActivity);
if (StringUtils.isEmpty(id)) {// 并行起点ID为空,此节点流向不是并行终点,符合驳回条件,存储此节点
rtnList.add(currActivity);
} else {// 根据并行起点ID查询当前节点,然后迭代查询其对应的userTask任务节点
currActivity = findActivitiImpl(taskId, id);
}
// 清空本次迭代临时集合
tempList.clear();
// 执行下次迭代
iteratorBackActivity(taskId, currActivity, rtnList, tempList);
}
return rtnList;
}
/**
* 还原指定活动节点流向
*
* @param activityImpl
* 活动节点
* @param oriPvmTransitionList
* 原有节点流向集合
*/
private static void restoreTransition(ActivityImpl activityImpl,
List<PvmTransition> oriPvmTransitionList) {
// 清空现有流向
List<PvmTransition> pvmTransitionList = activityImpl
.getOutgoingTransitions();
pvmTransitionList.clear();
// 还原以前流向
for (PvmTransition pvmTransition : oriPvmTransitionList) {
pvmTransitionList.add(pvmTransition);
}
}
/**
* 反向排序list集合,便于驳回节点按顺序显示
*
* @param list
* @return
*/
private static List<ActivityImpl> reverList(List<ActivityImpl> list) {
List<ActivityImpl> rtnList = new ArrayList<ActivityImpl>();
// 由于迭代出现重复数据,排除重复
for (int i = list.size(); i > 0; i--) {
if (!rtnList.contains(list.get(i - 1)))
rtnList.add(list.get(i - 1));
}
return rtnList;
}
/**
* 转办流程
*
* @param taskId
* 当前任务节点ID
* @param userCode
* 被转办人Code
*/
public static void transferAssignee(String taskId, String userCode) {
taskService.setAssignee(taskId, userCode);
}
/**
* 流程转向操作
*
* @param taskId
* 当前任务ID
* @param activityId
* 目标节点任务ID
* @param variables
* 流程变量
* @throws Exception
*/
private static void turnTransition(String taskId, String activityId,
Map<String, Object> variables) throws Exception {
// 当前节点
ActivityImpl currActivity = findActivitiImpl(taskId, null);
// 清空当前流向
List<PvmTransition> oriPvmTransitionList = clearTransition(currActivity);
// 创建新流向
TransitionImpl newTransition = currActivity.createOutgoingTransition();
// 目标节点
ActivityImpl pointActivity = findActivitiImpl(taskId, activityId);
// 设置新流向的目标节点
newTransition.setDestination(pointActivity);
// 执行转向任务
taskService.complete(taskId, variables);
// 删除目标节点新流入
pointActivity.getIncomingTransitions().remove(newTransition);
// 还原以前流向
restoreTransition(currActivity, oriPvmTransitionList);
}
public static InputStream getImageStream(String taskId) throws Exception{
ProcessDefinitionEntity pde = findProcessDefinitionEntityByTaskId(taskId);
InputStream imageStream = ProcessDiagramGenerator.generateDiagram(
pde, "png",
runtimeService.getActiveActivityIds(findProcessInstanceByTaskId(taskId).getId()));
return imageStream;
}
public static FormService getFormService() {
return formService;
}
public static HistoryService getHistoryService() {
return historyService;
}
public static ProcessEngine getProcessEngine() {
return processEngine;
}
public static RepositoryService getRepositoryService() {
return repositoryService;
}
public static RuntimeService getRuntimeService() {
return runtimeService;
}
public static TaskService getTaskService() {
return taskService;
}
}
用的几个主要的功能函数,一个是驳回函数:backProcess(),一个是查询可以驳回的节点列表:findBackAvtivity(),还有一个就是生成流程图:getImageStream(),主要的就用到这么几个,有兴趣的可以研究一下,到如包之后代码是可以编译通过的,
在生成流程图的时候,会有一个乱码问题,这个问题网上也有了解决方案,是生成流程图时使用的字体是西方字体导致的,将下面这个类放入项目就可以解决:
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.engine.impl.bpmn.diagram;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.util.IoUtil;
import org.activiti.engine.impl.util.ReflectUtil;
/**
* Represents a canvas on which BPMN 2.0 constructs can be drawn.
*
* Some of the icons used are licenced under a Creative Commons Attribution 2.5
* License, see http://www.famfamfam.com/lab/icons/silk/
*
* @see ProcessDiagramGenerator
* @author Joram Barrez
*/
public class ProcessDiagramCanvas {
protected static final Logger LOGGER = Logger.getLogger(ProcessDiagramCanvas.class.getName());
// Predefined sized
protected static final int ARROW_WIDTH = 5;
protected static final int CONDITIONAL_INDICATOR_WIDTH = 16;
protected static final int MARKER_WIDTH = 12;
// Colors
protected static Color TASK_COLOR = new Color(255, 255, 204);
protected static Color BOUNDARY_EVENT_COLOR = new Color(255, 255, 255);
protected static Color CONDITIONAL_INDICATOR_COLOR = new Color(255, 255, 255);
protected static Color HIGHLIGHT_COLOR = Color.RED;
// Strokes
protected static Stroke THICK_TASK_BORDER_STROKE = new BasicStroke(3.0f);
protected static Stroke GATEWAY_TYPE_STROKE = new BasicStroke(3.0f);
protected static Stroke END_EVENT_STROKE = new BasicStroke(3.0f);
protected static Stroke MULTI_INSTANCE_STROKE = new BasicStroke(1.3f);
// icons
protected static int ICON_SIZE = 16;
protected static Image USERTASK_IMAGE;
protected static Image SCRIPTTASK_IMAGE;
protected static Image SERVICETASK_IMAGE;
protected static Image RECEIVETASK_IMAGE;
protected static Image SENDTASK_IMAGE;
protected static Image MANUALTASK_IMAGE;
protected static Image TIMER_IMAGE;
protected static Image ERROR_THROW_IMAGE;
protected static Image ERROR_CATCH_IMAGE;
// icons are statically loaded for performace
static {
try {
USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/user.png"));
SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/script.png"));
SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/service.png"));
RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/receive.png"));
SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/send.png"));
MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/manual.png"));
TIMER_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/timer.png"));
ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/error_throw.png"));
ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/error_catch.png"));
} catch (IOException e) {
LOGGER.warning("Could not load image for process diagram creation: " + e.getMessage());
}
}
protected int canvasWidth = -1;
protected int canvasHeight = -1;
protected int minX = -1;
protected int minY = -1;
protected BufferedImage processDiagram;
protected Graphics2D g;
protected FontMetrics fontMetrics;
protected boolean closed;
/**
* Creates an empty canvas with given width and height.
*/
public ProcessDiagramCanvas(int width, int height) {
this.canvasWidth = width;
this.canvasHeight = height;
this.processDiagram = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
this.g = processDiagram.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setPaint(Color.black);
Font font = new Font("瀹嬩綋", Font.BOLD, 11);
g.setFont(font);
this.fontMetrics = g.getFontMetrics();
}
/**
* Creates an empty canvas with given width and height.
*
* Allows to specify minimal boundaries on the left and upper side of the
* canvas. This is useful for diagrams that have white space there (eg
* Signavio). Everything beneath these minimum values will be cropped.
*
* @param minX
* Hint that will be used when generating the image. Parts that fall
* below minX on the horizontal scale will be cropped.
* @param minY
* Hint that will be used when generating the image. Parts that fall
* below minX on the horizontal scale will be cropped.
*/
public ProcessDiagramCanvas(int width, int height, int minX, int minY) {
this(width, height);
this.minX = minX;
this.minY = minY;
}
/**
* Generates an image of what currently is drawn on the canvas.
*
* Throws an {@link ActivitiException} when {@link #close()} is already
* called.
*/
public InputStream generateImage(String imageType) {
if (closed) {
throw new ActivitiException("ProcessDiagramGenerator already closed");
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
// Try to remove white space
minX = (minX <= 5) ? 5 : minX;
minY = (minY <= 5) ? 5 : minY;
BufferedImage imageToSerialize = processDiagram;
if (minX >= 0 && minY >= 0) {
imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5);
}
ImageIO.write(imageToSerialize, imageType, out);
} catch (IOException e) {
throw new ActivitiException("Error while generating process image", e);
} finally {
IoUtil.closeSilently(out);
}
return new ByteArrayInputStream(out.toByteArray());
}
/**
* Closes the canvas which dissallows further drawing and releases graphical
* resources.
*/
public void close() {
g.dispose();
closed = true;
}
public void drawNoneStartEvent(int x, int y, int width, int height) {
drawStartEvent(x, y, width, height, null);
}
public void drawTimerStartEvent(int x, int y, int width, int height) {
drawStartEvent(x, y, width, height, TIMER_IMAGE);
}
public void drawStartEvent(int x, int y, int width, int height, Image image) {
g.draw(new Ellipse2D.Double(x, y, width, height));
if (image != null) {
g.drawImage(image, x, y, width, height, null);
}
}
public void drawNoneEndEvent(int x, int y, int width, int height) {
Stroke originalStroke = g.getStroke();
g.setStroke(END_EVENT_STROKE);
g.draw(new Ellipse2D.Double(x, y, width, height));
g.setStroke(originalStroke);
}
public void drawErrorEndEvent(int x, int y, int width, int height) {
drawNoneEndEvent(x, y, width, height);
g.drawImage(ERROR_THROW_IMAGE, x + 3, y + 3, width - 6, height - 6, null);
}
public void drawCatchingEvent(int x, int y, int width, int height, Image image) {
// event circles
Ellipse2D outerCircle = new Ellipse2D.Double(x, y, width, height);
int innerCircleX = x + 3;
int innerCircleY = y + 3;
int innerCircleWidth = width - 6;
int innerCircleHeight = height - 6;
Ellipse2D innerCircle = new Ellipse2D.Double(innerCircleX, innerCircleY, innerCircleWidth, innerCircleHeight);
Paint originalPaint = g.getPaint();
g.setPaint(BOUNDARY_EVENT_COLOR);
g.fill(outerCircle);
g.setPaint(originalPaint);
g.draw(outerCircle);
g.draw(innerCircle);
g.drawImage(image, innerCircleX, innerCircleY, innerCircleWidth, innerCircleHeight, null);
}
public void drawCatchingTimerEvent(int x, int y, int width, int height) {
drawCatchingEvent(x, y, width, height, TIMER_IMAGE);
}
public void drawCatchingErroEvent(int x, int y, int width, int height) {
drawCatchingEvent(x, y, width, height, ERROR_CATCH_IMAGE);
}
public void drawSequenceflow(int srcX, int srcY, int targetX, int targetY, boolean conditional) {
Line2D.Double line = new Line2D.Double(srcX, srcY, targetX, targetY);
g.draw(line);
drawArrowHead(line);
if (conditional) {
drawConditionalSequenceFlowIndicator(line);
}
}
public void drawSequenceflowWithoutArrow(int srcX, int srcY, int targetX, int targetY, boolean conditional) {
Line2D.Double line = new Line2D.Double(srcX, srcY, targetX, targetY);
g.draw(line);
if (conditional) {
drawConditionalSequenceFlowIndicator(line);
}
}
public void drawArrowHead(Line2D.Double line) {
int doubleArrowWidth = 2 * ARROW_WIDTH;
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0, 0);
arrowHead.addPoint(-ARROW_WIDTH, -doubleArrowWidth);
arrowHead.addPoint(ARROW_WIDTH, -doubleArrowWidth);
AffineTransform transformation = new AffineTransform();
transformation.setToIdentity();
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
transformation.translate(line.x2, line.y2);
transformation.rotate((angle - Math.PI / 2d));
AffineTransform originalTransformation = g.getTransform();
g.setTransform(transformation);
g.fill(arrowHead);
g.setTransform(originalTransformation);
}
public void drawConditionalSequenceFlowIndicator(Line2D.Double line) {
int horizontal = (int) (CONDITIONAL_INDICATOR_WIDTH * 0.7);
int halfOfHorizontal = horizontal / 2;
int halfOfVertical = CONDITIONAL_INDICATOR_WIDTH / 2;
Polygon conditionalIndicator = new Polygon();
conditionalIndicator.addPoint(0, 0);
conditionalIndicator.addPoint(-halfOfHorizontal, halfOfVertical);
conditionalIndicator.addPoint(0, CONDITIONAL_INDICATOR_WIDTH);
conditionalIndicator.addPoint(halfOfHorizontal, halfOfVertical);
AffineTransform transformation = new AffineTransform();
transformation.setToIdentity();
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
transformation.translate(line.x1, line.y1);
transformation.rotate((angle - Math.PI / 2d));
AffineTransform originalTransformation = g.getTransform();
g.setTransform(transformation);
g.draw(conditionalIndicator);
Paint originalPaint = g.getPaint();
g.setPaint(CONDITIONAL_INDICATOR_COLOR);
g.fill(conditionalIndicator);
g.setPaint(originalPaint);
g.setTransform(originalTransformation);
}
public void drawTask(String name, int x, int y, int width, int height) {
drawTask(name, x, y, width, height, false);
}
protected void drawTask(String name, int x, int y, int width, int height, boolean thickBorder) {
Paint originalPaint = g.getPaint();
g.setPaint(TASK_COLOR);
// shape
RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
g.fill(rect);
g.setPaint(originalPaint);
if (thickBorder) {
Stroke originalStroke = g.getStroke();
g.setStroke(THICK_TASK_BORDER_STROKE);
g.draw(rect);
g.setStroke(originalStroke);
} else {
g.draw(rect);
}
// text
if (name != null) {
String text = fitTextToWidth(name, width);
int textX = x + ((width - fontMetrics.stringWidth(text)) / 2);
int textY = y + ((height - fontMetrics.getHeight()) / 2) + fontMetrics.getHeight();
g.drawString(text, textX, textY);
}
}
protected String fitTextToWidth(String original, int width) {
String text = original;
// remove length for "..."
int maxWidth = width - 10;
while (fontMetrics.stringWidth(text + "...") > maxWidth && text.length() > 0) {
text = text.substring(0, text.length() - 1);
}
if (!text.equals(original)) {
text = text + "...";
}
return text;
}
public void drawUserTask(String name, int x, int y, int width, int height) {
drawTask(name, x, y, width, height);
g.drawImage(USERTASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE, null);
}
public void drawScriptTask(String name, int x, int y, int width, int height) {
drawTask(name, x, y, width, height);
g.drawImage(SCRIPTTASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE, null);
}
public void drawServiceTask(String name, int x, int y, int width, int height) {
drawTask(name, x, y, width, height);
g.drawImage(SERVICETASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE, null);
}
public void drawReceiveTask(String name, int x, int y, int width, int height) {
drawTask(name, x, y, width, height);
g.drawImage(RECEIVETASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE, null);
}
public void drawSendTask(String name, int x, int y, int width, int height) {
drawTask(name, x, y, width, height);
g.drawImage(SENDTASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE, null);
}
public void drawManualTask(String name, int x, int y, int width, int height) {
drawTask(name, x, y, width, height);
g.drawImage(MANUALTASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE, null);
}
public void drawExpandedSubProcess(String name, int x, int y, int width, int height) {
RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
g.draw(rect);
String text = fitTextToWidth(name, width);
g.drawString(text, x + 10, y + 15);
}
public void drawCollapsedSubProcess(String name, int x, int y, int width, int height) {
drawCollapsedTask(name, x, y, width, height, false);
}
public void drawCollapsedCallActivity(String name, int x, int y, int width, int height) {
drawCollapsedTask(name, x, y, width, height, true);
}
protected void drawCollapsedTask(String name, int x, int y, int width, int height, boolean thickBorder) {
// The collapsed marker is now visualized separately
drawTask(name, x, y, width, height, thickBorder);
}
public void drawCollapsedMarker(int x, int y, int width, int height) {
// rectangle
int rectangleWidth = MARKER_WIDTH;
int rectangleHeight = MARKER_WIDTH;
Rectangle rect = new Rectangle(x + (width - rectangleWidth) / 2, y + height - rectangleHeight - 3, rectangleWidth, rectangleHeight);
g.draw(rect);
// plus inside rectangle
Line2D.Double line = new Line2D.Double(rect.getCenterX(), rect.getY() + 2, rect.getCenterX(), rect.getMaxY() - 2);
g.draw(line);
line = new Line2D.Double(rect.getMinX() + 2, rect.getCenterY(), rect.getMaxX() - 2, rect.getCenterY());
g.draw(line);
}
public void drawActivityMarkers(int x, int y, int width, int height, boolean multiInstanceSequential, boolean multiInstanceParallel, boolean collapsed) {
if (collapsed) {
if (!multiInstanceSequential && !multiInstanceParallel) {
drawCollapsedMarker(x, y, width, height);
} else {
drawCollapsedMarker(x - MARKER_WIDTH / 2 - 2, y, width, height);
if (multiInstanceSequential) {
drawMultiInstanceMarker(true, x + MARKER_WIDTH / 2 + 2, y, width, height);
} else if (multiInstanceParallel) {
drawMultiInstanceMarker(false, x + MARKER_WIDTH / 2 + 2, y, width, height);
}
}
} else {
if (multiInstanceSequential) {
drawMultiInstanceMarker(false, x, y, width, height);
} else if (multiInstanceParallel) {
drawMultiInstanceMarker(true, x, y, width, height);
}
}
}
public void drawGateway(int x, int y, int width, int height) {
Polygon rhombus = new Polygon();
rhombus.addPoint(x, y + (height / 2));
rhombus.addPoint(x + (width / 2), y + height);
rhombus.addPoint(x + width, y + (height / 2));
rhombus.addPoint(x + (width / 2), y);
g.draw(rhombus);
}
public void drawParallelGateway(int x, int y, int width, int height) {
// rhombus
drawGateway(x, y, width, height);
// plus inside rhombus
Stroke orginalStroke = g.getStroke();
g.setStroke(GATEWAY_TYPE_STROKE);
Line2D.Double line = new Line2D.Double(x + 10, y + height / 2, x + width - 10, y + height / 2); // horizontal
g.draw(line);
line = new Line2D.Double(x + width / 2, y + height - 10, x + width / 2, y + 10); // vertical
g.draw(line);
g.setStroke(orginalStroke);
}
public void drawExclusiveGateway(int x, int y, int width, int height) {
// rhombus
drawGateway(x, y, width, height);
int quarterWidth = width / 4;
int quarterHeight = height / 4;
// X inside rhombus
Stroke orginalStroke = g.getStroke();
g.setStroke(GATEWAY_TYPE_STROKE);
Line2D.Double line = new Line2D.Double(x + quarterWidth + 3, y + quarterHeight + 3, x + 3 * quarterWidth - 3, y + 3 * quarterHeight - 3);
g.draw(line);
line = new Line2D.Double(x + quarterWidth + 3, y + 3 * quarterHeight - 3, x + 3 * quarterWidth - 3, y + quarterHeight + 3);
g.draw(line);
g.setStroke(orginalStroke);
}
public void drawInclusiveGateway(int x, int y, int width, int height) {
// rhombus
drawGateway(x, y, width, height);
int diameter = width / 2;
// circle inside rhombus
Stroke orginalStroke = g.getStroke();
g.setStroke(GATEWAY_TYPE_STROKE);
Ellipse2D.Double circle = new Ellipse2D.Double(((width - diameter) / 2) + x, ((height - diameter) / 2) + y, diameter, diameter);
g.draw(circle);
g.setStroke(orginalStroke);
}
public void drawMultiInstanceMarker(boolean sequential, int x, int y, int width, int height) {
int rectangleWidth = MARKER_WIDTH;
int rectangleHeight = MARKER_WIDTH;
int lineX = x + (width - rectangleWidth) / 2;
int lineY = y + height - rectangleHeight - 3;
Stroke orginalStroke = g.getStroke();
g.setStroke(MULTI_INSTANCE_STROKE);
if (sequential) {
g.draw(new Line2D.Double(lineX, lineY, lineX + rectangleWidth, lineY));
g.draw(new Line2D.Double(lineX, lineY + rectangleHeight / 2, lineX + rectangleWidth, lineY + rectangleHeight / 2));
g.draw(new Line2D.Double(lineX, lineY + rectangleHeight, lineX + rectangleWidth, lineY + rectangleHeight));
} else {
g.draw(new Line2D.Double(lineX, lineY, lineX, lineY + rectangleHeight));
g.draw(new Line2D.Double(lineX + rectangleWidth / 2, lineY, lineX + rectangleWidth / 2, lineY + rectangleHeight));
g.draw(new Line2D.Double(lineX + rectangleWidth, lineY, lineX + rectangleWidth, lineY + rectangleHeight));
}
g.setStroke(orginalStroke);
}
public void drawHighLight(int x, int y, int width, int height) {
Paint originalPaint = g.getPaint();
Stroke originalStroke = g.getStroke();
g.setPaint(HIGHLIGHT_COLOR);
g.setStroke(THICK_TASK_BORDER_STROKE);
RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
g.draw(rect);
g.setPaint(originalPaint);
g.setStroke(originalStroke);
}
}
具体的代码,请看我上传的例子,下载地址:http://download.csdn.net/detail/qq413041153/4547911