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