activiti5.9调研总结(更新集成mysql数据库bug解决方案-2013.04.07)

3 篇文章 0 订阅
1 篇文章 0 订阅

        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

关于集成mysql问题:

        这个问题是yangwen8314反馈的,错误提示如下:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'ID_' in order clause is ambiguous
        这个错误信息是说:列"ID_"在order子句里面是模糊不清的.什么意思那?其实就是说在代码中使用了order by ID_ 进行排序 但是没有指明ID_列是哪张表的,因为sql语句里面包含不止一张表而且几张表都有ID_列.

        知道了错误原因,我开始调试程序,发现是代码中在查询历史流程的任务节点时报的错:

  <%
	      HistoricTaskInstanceQuery httq = ProcessCustomService.getHistoryService().createHistoricTaskInstanceQuery();
	      httq=httq.processFinished().processInstanceId(hpi.getId());
	      httq=httq.orderByTaskId().asc();
   		List<HistoricTaskInstance> list = httq.list();
	       
	      %>
	      <td><%=list.get(0).getAssignee()%></td>
	      <td><%=hpi.getStartTime() %></td>
	      <td><%=hpi.getEndTime() %></td>
             代码红色不分引起的错误.

        我加入了activiti的源码进行调试,调试到HistoricTaskInstanceManager extends AbstractHistoricManager 的findHistoricTaskInstancesByQueryCriteria方法:

  @SuppressWarnings("unchecked")
  public List<HistoricTaskInstance> findHistoricTaskInstancesByQueryCriteria(HistoricTaskInstanceQueryImpl historicTaskInstanceQuery, Page page) {
    if (historyLevel>ProcessEngineConfigurationImpl.HISTORYLEVEL_NONE) {
      return getDbSqlSession().selectList("selectHistoricTaskInstancesByQueryCriteria", historicTaskInstanceQuery, page);
    }
    return Collections.EMPTY_LIST;
  }
  

        由于activiti使用的是ibatis持久化,所以这里很容易就理解这里调用了在xml中定义的一个叫做selectHistoricTaskInstancesByQueryCriteria的查询语句.我在activiti-engine-5.9.jar中找到了这个定义:


        文件内容如下:

<?xml version="1.0" encoding="UTF-8" ?>

<!--
  ~ 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.
  -->

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.activiti.engine.impl.persistence.entity.HistoricTaskInstanceEntity">
  
  <!-- HISTORIC TASK INSTANCE INSERT -->
  
  <insert id="insertHistoricTaskInstance" parameterType="org.activiti.engine.impl.persistence.entity.HistoricTaskInstanceEntity">
      insert into ACT_HI_TASKINST (
        ID_,
        PROC_DEF_ID_,
        PROC_INST_ID_,
        EXECUTION_ID_,
        NAME_,
        PARENT_TASK_ID_,
        DESCRIPTION_,
        OWNER_,
        ASSIGNEE_,
        START_TIME_,
        END_TIME_,
        DURATION_,
        DELETE_REASON_,
        TASK_DEF_KEY_,
        PRIORITY_,
        DUE_DATE_
      ) values (
        #{id ,jdbcType=VARCHAR},
        #{processDefinitionId, jdbcType=VARCHAR},
        #{processInstanceId, jdbcType=VARCHAR},
        #{executionId, jdbcType=VARCHAR},
        #{name ,jdbcType=VARCHAR},
        #{parentTaskId ,jdbcType=VARCHAR},
        #{description ,jdbcType=VARCHAR},
        #{owner ,jdbcType=VARCHAR},
        #{assignee ,jdbcType=VARCHAR},
        #{startTime, jdbcType=TIMESTAMP},
        #{endTime, jdbcType=TIMESTAMP},
        #{durationInMillis ,jdbcType=BIGINT},
        #{deleteReason ,jdbcType=VARCHAR},
        #{taskDefinitionKey ,jdbcType=VARCHAR},
        #{priority, jdbcType=INTEGER},
        #{dueDate, jdbcType=TIMESTAMP}
      )
  </insert>

  <!-- HISTORIC TASK INSTANCE UPDATE -->
  
  <update id="updateHistoricTaskInstance" parameterType="org.activiti.engine.impl.persistence.entity.HistoricTaskInstanceEntity">
    update ACT_HI_TASKINST set
      EXECUTION_ID_ = #{executionId, jdbcType=VARCHAR},
      NAME_ = #{name, jdbcType=VARCHAR},
      PARENT_TASK_ID_ = #{parentTaskId, jdbcType=VARCHAR},
      DESCRIPTION_ = #{description, jdbcType=VARCHAR},
      OWNER_ = #{owner, jdbcType=VARCHAR},
      ASSIGNEE_ = #{assignee, jdbcType=VARCHAR},
      END_TIME_ = #{endTime, jdbcType=TIMESTAMP},
      DURATION_ = #{durationInMillis ,jdbcType=BIGINT},
      DELETE_REASON_ = #{deleteReason ,jdbcType=VARCHAR},
      TASK_DEF_KEY_ = #{taskDefinitionKey ,jdbcType=VARCHAR},
      PRIORITY_ = #{priority, jdbcType=INTEGER},
      DUE_DATE_ = #{dueDate, jdbcType=TIMESTAMP}
    where ID_ = #{id}
  </update>

  <!-- HISTORIC TASK INSTANCE DELETE -->
  
  <delete id="deleteHistoricTaskInstance">
    delete from ACT_HI_TASKINST where ID_ = #{historicTaskInstanceId}
  </delete>

  <!-- HISTORIC TASK INSTANCE RESULT MAP -->

  <resultMap id="historicTaskInstanceResultMap" type="org.activiti.engine.impl.persistence.entity.HistoricTaskInstanceEntity">
    <id property="id" column="ID_" jdbcType="VARCHAR" />
    <result property="processDefinitionId" column="PROC_DEF_ID_" jdbcType="VARCHAR" />
    <result property="processInstanceId" column="PROC_INST_ID_" jdbcType="VARCHAR" />
    <result property="executionId" column="EXECUTION_ID_" jdbcType="VARCHAR" />
    <result property="name" column="NAME_" jdbcType="VARCHAR" />
    <result property="parentTaskId" column="PARENT_TASK_ID_" jdbcType="VARCHAR" />
    <result property="description" column="DESCRIPTION_" jdbcType="VARCHAR" />
    <result property="owner" column="OWNER_" jdbcType="VARCHAR" />
    <result property="assignee" column="ASSIGNEE_" jdbcType="VARCHAR" />
    <result property="startTime" column="START_TIME_" jdbcType="TIMESTAMP" />
    <result property="endTime" column="END_TIME_" jdbcType="TIMESTAMP" />
    <result property="durationInMillis" column="DURATION_" jdbcType="BIGINT" />
    <result property="deleteReason" column="DELETE_REASON_" jdbcType="VARCHAR" />
    <result property="taskDefinitionKey" column="TASK_DEF_KEY_" jdbcType="VARCHAR" />
    <result property="priority" column="PRIORITY_" jdbcType="INTEGER" />
    <result property="dueDate" column="DUE_DATE_" jdbcType="TIMESTAMP" />
  </resultMap>

  <!-- HISTORIC TASK INSTANCE SELECT -->
  
  <select id="selectHistoricTaskInstance" resultMap="historicTaskInstanceResultMap">
    select * from ACT_HI_TASKINST where ID_ = #{historicTaskInstanceId}
  </select>
  
  <select id="selectHistoricTaskInstanceIdsByProcessInstanceId" resultType="string" parameterType="string" >
    select ID_ 
    from ACT_HI_TASKINST 
    where PROC_INST_ID_ = #{processInstanceId}
  </select>

  <select id="selectHistoricTaskInstancesByQueryCriteria" parameterType="org.activiti.engine.impl.HistoricTaskInstanceQueryImpl" resultMap="historicTaskInstanceResultMap">
    select *
    <include refid="selectHistoricTaskInstancesByQueryCriteriaSql"/>
    <if test="orderBy != null">
      order by ${orderBy}
    </if>
  </select>
  
  <select id="selectHistoricTaskInstanceCountByQueryCriteria" parameterType="org.activiti.engine.impl.HistoricTaskInstanceQueryImpl" resultType="long">
    select count(*)
    <include refid="selectHistoricTaskInstancesByQueryCriteriaSql"/>
  </select>
  
  <sql id="selectHistoricTaskInstancesByQueryCriteriaSql">
    from ACT_HI_TASKINST HTI
    <if test="processFinished || processUnfinished">
      inner join ACT_HI_PROCINST HPI ON HTI.PROC_INST_ID_ = HPI.ID_
    </if>
    <if test="processDefinitionKey != null || processDefinitionName != null">
      inner join ACT_RE_PROCDEF D on HTI.PROC_DEF_ID_ = D.ID_
    </if>
    <where>
      <if test="taskId != null">
        HTI.ID_ = #{taskId}
      </if>
      <if test="processDefinitionId != null">
        and HTI.PROC_DEF_ID_ = #{processDefinitionId}
      </if>
      <if test="processDefinitionKey != null">
        and D.KEY_ = #{processDefinitionKey}
      </if>
      <if test="processDefinitionName != null">
        and D.NAME_ = #{processDefinitionName}
      </if>
      <if test="processInstanceId != null">
        and HTI.PROC_INST_ID_ = #{processInstanceId}
      </if>
      <if test="taskDefinitionKey != null">
        and HTI.TASK_DEF_KEY_ = #{taskDefinitionKey}
      </if>
      <if test="executionId != null">
        and HTI.EXECUTION_ID_ = #{executionId}
      </if>
      <if test="taskName != null">
        and HTI.NAME_ = #{taskName}
      </if>
      <if test="taskNameLike != null">
        and HTI.NAME_ like #{taskNameLike}
      </if>
      <if test="taskParentTaskId != null">
        and HTI.PARENT_TASK_ID_ = #{taskParentTaskId}
      </if>
      <if test="taskDescription != null">
        and HTI.DESCRIPTION_ = #{taskDescription}
      </if>
      <if test="taskDescriptionLike != null">
        and HTI.DESCRIPTION_ like #{taskDescriptionLike}
      </if>
      <if test="taskDeleteReason != null">
        and HTI.DELETE_REASON_ = #{taskDeleteReason}
      </if>
      <if test="taskDeleteReasonLike != null">
        and HTI.DELETE_REASON_ like #{taskDeleteReasonLike}
      </if>
      <if test="taskOwner != null">
        and HTI.OWNER_ = #{taskOwner}
      </if>
      <if test="taskOwnerLike != null">
        and HTI.OWNER_ like #{taskOwnerLike}
      </if>
      <if test="taskAssignee != null">
        and HTI.ASSIGNEE_ = #{taskAssignee}
      </if>
      <if test="taskAssigneeLike != null">
        and HTI.ASSIGNEE_ like #{taskAssigneeLike}
      </if>
      <if test="taskPriority != null">
        and HTI.PRIORITY_ = #{taskPriority}
      </if>
      <if test="unfinished">
        and HTI.END_TIME_ is null
      </if>
      <if test="finished">
        and HTI.END_TIME_ is not null
      </if>
      <if test="processFinished">
        and HPI.END_TIME_ is not null
      </if>
      <if test="processUnfinished">
        and HPI.END_TIME_ is null
      </if>
      <if test="dueDate != null">
        and HTI.DUE_DATE_ = #{dueDate}
      </if>
      <if test="dueBefore != null">
        and HTI.DUE_DATE_ < #{dueBefore}
      </if>
      <if test="dueAfter != null">
        and HTI.DUE_DATE_ > #{dueAfter}
      </if>
      <foreach collection="variables" index="index" item="var">
        and exists (
          select HD.ID_ 
          from ACT_HI_DETAIL HD 
          where
            HD.TYPE_ = 'VariableUpdate' 
            and HD.NAME_ =  #{var.name}
            <choose>
              <when test="var.taskVariable">
                and HD.TASK_ID_ = HTI.ID_
                and HD.REV_ = (select max(HDM.REV_) from ACT_HI_DETAIL HDM where HDM.TASK_ID_ = HTI.ID_ and HDM.VAR_TYPE_ = #{var.type} and HDM.NAME_ = #{var.name} and HDM.TYPE_ = 'VariableUpdate')
                and HD.TIME_ = (select max(HDM.TIME_) from ACT_HI_DETAIL HDM where HDM.TASK_ID_ = HTI.ID_ and HDM.VAR_TYPE_ = #{var.type} and HDM.NAME_ = #{var.name} and HDM.TYPE_ = 'VariableUpdate')
              </when>
              <otherwise>
                and HD.PROC_INST_ID_ = HTI.PROC_INST_ID_ and HD.TASK_ID_ is null
                and HD.REV_ = (select max(HDM.REV_) from ACT_HI_DETAIL HDM where HDM.PROC_INST_ID_ = HTI.PROC_INST_ID_ and HDM.TASK_ID_ is null and HDM.VAR_TYPE_ = #{var.type} and HDM.NAME_ = #{var.name} and HDM.TYPE_ = 'VariableUpdate')
                and HD.TIME_ = (select max(HDM.TIME_) from ACT_HI_DETAIL HDM where HDM.PROC_INST_ID_ = HTI.PROC_INST_ID_ and HDM.TASK_ID_ is null and HDM.VAR_TYPE_ = #{var.type} and HDM.NAME_ = #{var.name} and HDM.TYPE_ = 'VariableUpdate')
              </otherwise>
            </choose>  
            <if test="!var.type.equals('null')">
              and HD.VAR_TYPE_ = #{var.type}
            </if>
            <!-- Variable value -->
            <if test="var.textValue != null && var.longValue == null && var.doubleValue == null">
              and HD.TEXT_ =  #{var.textValue}
            </if>
            <if test="var.textValue2 != null">
              and HD.TEXT2_ = #{var.textValue2}
            </if>
            <if test="var.longValue != null">
              and HD.LONG_ = #{var.longValue}
            </if>
            <if test="var.doubleValue != null">
              and HD.DOUBLE_ = #{var.doubleValue}
            </if>
            <!-- Null variable type -->
            <if test="var.textValue == null && var.textValue2 == null && var.longValue == null && var.doubleValue == null">
              and HD.TEXT_ is null and HD.TEXT2_ is null and HD.LONG_ is null and HD.DOUBLE_ is null and HD.BYTEARRAY_ID_ is null
            </if>
        )
      </foreach>
    </where>
  </sql>

</mapper>
             我标红的部分就是代码需要调用的sql语句,这里的排序 order by ${orderBy}  如果传进来的参数没有指定表前缀就会报错,因为在selectHistoricTaskInstancesByQueryCriteriaSql里面包含了不止一张表,而且都有ID_字段.我在调试的时候定位传过来的参数,没有发现表前缀,如下:


        到了这里,已经发现了问题的所在,很明显这是activiti官方的一个bug. 5.10版本我测试过没有修复这个bug,5.11之后api接口调整,我就没有测试.

解决方案:

        一,我尝试直接修改jar包里面的xml中定义的sql语句,没有成功.

        二,修改源代码,重新生成jar包.在HistoricTaskInstanceQueryImpl中的orderByTaskID()方法:

 public HistoricTaskInstanceQueryImpl orderByTaskId() {
    orderBy(HistoricTaskInstanceQueryProperty.HISTORIC_TASK_INSTANCE_ID);
    return this;
  }
             直接修改HISTORIC_TASK_INSTANCE_ID的值,因为HistoricTaskInstanceQueryProperty是对应taskinstance的实体类所以修改它对别的类没有影响.直接修改,:

  public static final HistoricTaskInstanceQueryProperty HISTORIC_TASK_INSTANCE_ID = new HistoricTaskInstanceQueryProperty("ID_");

 public static final HistoricTaskInstanceQueryProperty HISTORIC_TASK_INSTANCE_ID = new HistoricTaskInstanceQueryProperty("HTI.ID_");
             直接将ID_改成HTI.ID_即可,测试已经成功.暂时未发现bug.

        这里是我自己修改源代码后重新编程的jar包:http://download.csdn.net/detail/qq413041153/5229697

        三,升级activiti,我查看官方源码在5.11版本中修改了sql实现,修复了此bug,但是由于api接口变更,项目升级需要非一番周折,大家自己取舍.





  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 23
    评论
activiti-engine.jar是Activiti工作流引擎的核心库文件。Activiti是一个开源的工作流引擎,支持BPMN 2.0标准,提供了一套完整的工作流管理解决方案activiti-engine.jar包含了Activiti引擎的所有必要组件和功能。 activiti-engine.jar中包含了各种核心类和接口,用于处理工作流相关的业务逻辑。它提供了流程定义的解析、创建和管理功能,允许用户根据BPMN规范创建具体的流程定义。同时,它还支持流程实例的创建、启动和管理,可以对流程实例进行暂停、恢复、挂起和终止等操作。activiti-engine.jar还提供了任务处理的功能,可以创建任务,分配任务给具体参与者,并对任务进行处理和跟踪。此外,它还支持流程变量的传递和管理,可以在流程中存储和获取相关据。 activiti-engine.jar还包含了工作流引擎的管理和监控功能。它提供了对工作流引擎的配置和初始化,可以设置引擎的各种参和属性。它还支持引擎的持久化和事务管理,可以将流程定义、流程实例和任务等信息存储到数据库中,并保证相关操作的一致性和可靠性。通过activiti-engine.jar,用户可以获取工作流引擎的运行状态,监控和统计流程的执行情况。 总之,activiti-engine.jar是实现Activiti工作流引擎的核心库文件,提供了一套完整的工作流管理解决方案,包括流程定义的创建和管理、流程实例的启动和管理、任务的处理和跟踪,以及引擎的配置和监控等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值