of all the activiti eclipse...

activiti5.9调研总结 - 推酷
activiti5.9调研总结
& & & & activiti是jbpm4进化而来,team leader 也没换人,所以activiti和jbpm4的api有这将近60%的相同,官方网站是
. 上手来说比较简单.
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.
import java.io.InputS
import java.util.ArrayL
import java.util.HashM
import java.util.L
import java.util.M
import org.activiti.engine.FormS
import org.activiti.engine.HistoryS
import org.activiti.engine.ProcessE
import org.activiti.engine.ProcessE
import org.activiti.engine.RepositoryS
import org.activiti.engine.RuntimeS
import org.activiti.engine.TaskS
import org.activiti.engine.history.HistoricActivityI
import org.activiti.engine.impl.RepositoryServiceI
import org.activiti.engine.impl.bpmn.diagram.ProcessDiagramG
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionE
import org.activiti.engine.impl.persistence.entity.TaskE
import org.activiti.engine.impl.pvm.PvmT
import org.activiti.engine.impl.pvm.process.ActivityI
import org.activiti.engine.impl.pvm.process.ProcessDefinitionI
import org.activiti.engine.impl.pvm.process.TransitionI
import org.activiti.engine.runtime.ProcessI
import org.activiti.engine.task.T
import mons.lang.StringU
* 流程操作核心类&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 oriPvmTransitionL
* @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)) {
plete(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
流入任务集合
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);
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);
if (activityInstance_1.getEndTime().before(
activityInstance_2.getEndTime())) {
tempList.remove(activity_1);
tempList.remove(activity_2);
if (tempList.size() & 0) {
return tempList.get(0);
* 根据任务ID和节点ID获取活动节点 &br&
* @param taskId
* @param activityId
活动节点ID &br&
如果为null或&&,则默认查询当前活动节点 &br&
如果为&end&,则查询结束节点 &br&
* @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 activityI
// 根据节点ID,获取对应的活动节点
ActivityImpl activityImpl = ((ProcessDefinitionImpl) processDefinition)
.findActivity(activityId);
return activityI
* 根据当前任务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
private static HistoricActivityInstance findHistoricUserTask(
ProcessInstance processInstance, String activityId) {
HistoricActivityInstance rtnVal =
// 查询当前流程实例审批结束的历史节点
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 rtnV
* 根据当前节点,查询输出流向是否为并行终点,如果为并行终点,则拼装对应的并行起点ID
* @param activityImpl
private static String findParallelGatewayId(ActivityImpl activityImpl) {
List&PvmTransition& incomingTransitions = activityImpl
.getOutgoingTransitions();
for (PvmTransition pvmTransition : incomingTransitions) {
TransitionImpl transitionImpl = (TransitionImpl) pvmT
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&;
* 根据任务ID获取流程定义
* @param taskId
* @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 processD
* 根据任务ID获取对应的流程实例
* @param taskId
* @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 processI
* 根据任务ID获得任务实例
* @param taskId
* @throws Exception
private static TaskEntity findTaskById(String taskId) throws Exception {
TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(
taskId).singleResult();
if (task == null) {
throw new Exception(&任务实例未找到!&);
* 根据流程实例ID和任务key值查询所有同级任务集合
* @param processInstanceId
* @param key
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) pvmT
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 rtnL
} else {// 并行终点,临时存储此节点,本次循环结束,迭代集合,查询对应的userTask节点
parallelGateways.add(activityImpl);
} else if (&startEvent&.equals(type)) {// 开始节点,停止递归
return rtnL
} 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 rtnL
* 还原指定活动节点流向
* @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
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 rtnL
* 转办流程
* @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);
// 执行转向任务
plete(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 imageS
public static FormService getFormService() {
return formS
public static HistoryService getHistoryService() {
return historyS
public static ProcessEngine getProcessEngine() {
return processE
public static RepositoryService getRepositoryService() {
return repositoryS
public static RuntimeService getRuntimeService() {
return runtimeS
public static TaskService getTaskService() {
return taskS
用的几个主要的功能函数,一个是驳回函数: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.
import java.awt.BasicS
import java.awt.C
import java.awt.F
import java.awt.FontM
import java.awt.Graphics2D;
import java.awt.I
import java.awt.P
import java.awt.P
import java.awt.R
import java.awt.RenderingH
import java.awt.S
import java.awt.geom.AffineT
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedI
import java.io.ByteArrayInputS
import java.io.ByteArrayOutputS
import java.io.IOE
import java.io.InputS
import java.util.logging.L
import javax.imageio.ImageIO;
import org.activiti.engine.ActivitiE
import org.activiti.engine.impl.util.IoU
import org.activiti.engine.impl.util.ReflectU
* 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 /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;
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);
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
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 processD
protected Graphics2D
protected FontMetrics fontM
* Creates an empty canvas with given width and height.
public ProcessDiagramCanvas(int width, int height) {
this.canvasWidth =
this.canvasHeight =
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
public InputStream generateImage(String imageType) {
if (closed) {
throw new ActivitiException(&ProcessDiagramGenerator already closed&);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Try to remove white space
minX = (minX &= 5) ? 5 : minX;
minY = (minY &= 5) ? 5 : minY;
BufferedImage imageToSerialize = processD
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();
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);
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);
g.draw(rect);
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 =
// 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 + &...&;
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);
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);
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));
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);
已发表评论数()
&&登&&&陆&&
已收藏到推刊!
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见

我要回帖

更多关于 activiti eclipse 的文章

 

随机推荐