activiti5.9调研总结

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值