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接口变更,项目升级需要非一番周折,大家自己取舍.