轉自:http://www.tuicool.com/articles/FJzMzmJ
最近又開始忙活工作流的相關工作了,第一次接觸工作流也就是我在埃森哲的第一個項目,也是最後一個項目。那時候用的是日本的一整套解決方案好像叫-iMart。而進入金山工作後第二個項目也是和工作流密切相關的項目,那時候才接觸到了這個開源工作流引擎-Activiti。那時候也是第一次真正了解這些東西。隻是從金山離職後回到現在的公司,沒想到還要用這些東西,好像又回到了去年的這個時間。
這一次需要重新內建這個引擎,以插件包的方式。其實工作量不大,因為Activiti已經自己封裝的很好了,完全可以在日常開發中直接引用它的Service。閑話少說,這次解決工作流流程圖問題。簡而言之,在任何一條已啟動的流程執行個體檢視流程狀态,用流程圖檔的形式展示一下。
提供的Service方法如下:
/**
* 擷取目前任務流程圖
*
* @param processInstanceId
* @return
*/
@Override
public InputStream generateDiagram(String processInstanceId) {
//方法中用到的參數是流程執行個體ID,其實TaskId也可以轉為這個。調用taskService查詢即可。
Command<InputStream> cmd = new ProcessInstanceDiagramCmd(processInstanceId, runtimeService, repositoryService, processEngine, historyService);
return managementService.executeCommand(cmd);
}
而ProcessInstanceDiagramCmd采用了Activiti的指令模式。就是繼承Command接口,其實這個地方完全可以使用純Service方法去搞定,我之是以這麼寫,還是受到了國内臨遠大師的影響。
本次繪制的流程圖分為兩種情況:1、流程執行個體還未執行完畢,也就是流程還沒有結束,還有運作的任務。2、已經執行完畢的流程,流程已經進入了流程曆史。不管屬于以上哪種情況的流程圖都會繪制流程走向。
具體代碼如下:
import com.google.common.collect.Lists;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.activiti.spring.ProcessEngineFactoryBean;
import java.io.InputStream;
import java.util.*;
/**
* 根據流程執行個體ID生成流程圖
*
* @author Chen Zhiguo
*/
public class ProcessInstanceDiagramCmd implements Command<InputStream> {
protected String processInstanceId;
private RuntimeService runtimeService;
private RepositoryService repositoryService;
private ProcessEngineFactoryBean processEngine;
private HistoryService historyService;
public ProcessInstanceDiagramCmd(String processInstanceId, RuntimeService runtimeService, RepositoryService repositoryService, ProcessEngineFactoryBean processEngine, HistoryService historyService) {
this.processInstanceId = processInstanceId;
this.runtimeService = runtimeService;
this.repositoryService = repositoryService;
this.processEngine = processEngine;
this.historyService = historyService;
}
public InputStream execute(CommandContext commandContext) {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId).singleResult();
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (processInstance == null && historicProcessInstance == null) {
return null;
}
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService
.getProcessDefinition(processInstance == null ? historicProcessInstance.getProcessDefinitionId() : processInstance.getProcessDefinitionId());
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
List<String> activeActivityIds = Lists.newArrayList();
if (processInstance != null) {
activeActivityIds = runtimeService.getActiveActivityIds(processInstance.getProcessInstanceId());
} else {
activeActivityIds.add(historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("endEvent")
.singleResult().getActivityId());
}
// 使用spring注入引擎請使用下面的這行代碼
Context.setProcessEngineConfiguration(processEngine.getProcessEngineConfiguration());
List<String> highLightedFlows = getHighLightedFlows(processDefinition, processInstanceId);
InputStream imageStream = new DefaultProcessDiagramGenerator().generateDiagram(bpmnModel, "png", activeActivityIds,
highLightedFlows);
return imageStream;
}
/**
* getHighLightedFlows
*
* @param processDefinition
* @param processInstanceId
* @return
*/
private List<String> getHighLightedFlows(ProcessDefinitionEntity processDefinition, String processInstanceId) {
List<String> highLightedFlows = new ArrayList<String>();
// List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
// .processInstanceId(processInstanceId)
// //order by startime asc is not correct. use default order is correct.
// //.orderByHistoricActivityInstanceStartTime().asc()/*.orderByActivityId().asc()*/
// .list();
//上面注釋掉的代碼是官方的rest方法中提供的方案,可是我在實際測試中有Bug出現,是以做了一下修改。注意下面List内容的排序會影響流程走向紅線丢失的問題
List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime().orderByHistoricActivityInstanceEndTime().asc().list();
LinkedList<HistoricActivityInstance> hisActInstList = new LinkedList<HistoricActivityInstance>();
hisActInstList.addAll(historicActivityInstances);
getHighlightedFlows(processDefinition.getActivities(), hisActInstList, highLightedFlows);
return highLightedFlows;
}
/**
* getHighlightedFlows
* <p/>
* code logic:
* 1. Loop all activities by id asc order;
* 2. Check each activity's outgoing transitions and eventBoundery outgoing transitions, if outgoing transitions's destination.id is in other executed activityIds, add this transition to highLightedFlows List;
* 3. But if activity is not a parallelGateway or inclusiveGateway, only choose the earliest flow.
*
* @param activityList
* @param hisActInstList
* @param highLightedFlows
*/
private void getHighlightedFlows(List<ActivityImpl> activityList, LinkedList<HistoricActivityInstance> hisActInstList, List<String> highLightedFlows) {
//check out startEvents in activityList
List<ActivityImpl> startEventActList = new ArrayList<ActivityImpl>();
Map<String, ActivityImpl> activityMap = new HashMap<String, ActivityImpl>(activityList.size());
for (ActivityImpl activity : activityList) {
activityMap.put(activity.getId(), activity);
String actType = (String) activity.getProperty("type");
if (actType != null && actType.toLowerCase().indexOf("startevent") >= ) {
startEventActList.add(activity);
}
}
//These codes is used to avoid a bug:
//ACT-1728 If the process instance was started by a callActivity, it will be not have the startEvent activity in ACT_HI_ACTINST table
//Code logic:
//Check the first activity if it is a startEvent, if not check out the startEvent's highlight outgoing flow.
HistoricActivityInstance firstHistActInst = hisActInstList.getFirst();
String firstActType = (String) firstHistActInst.getActivityType();
if (firstActType != null && firstActType.toLowerCase().indexOf("startevent") < ) {
PvmTransition startTrans = getStartTransaction(startEventActList, firstHistActInst);
if (startTrans != null) {
highLightedFlows.add(startTrans.getId());
}
}
while (!hisActInstList.isEmpty()) {
HistoricActivityInstance histActInst = hisActInstList.removeFirst();
ActivityImpl activity = activityMap.get(histActInst.getActivityId());
if (activity != null) {
boolean isParallel = false;
String type = histActInst.getActivityType();
if ("parallelGateway".equals(type) || "inclusiveGateway".equals(type)) {
isParallel = true;
} else if ("subProcess".equals(histActInst.getActivityType())) {
getHighlightedFlows(activity.getActivities(), hisActInstList, highLightedFlows);
}
List<PvmTransition> allOutgoingTrans = new ArrayList<PvmTransition>();
allOutgoingTrans.addAll(activity.getOutgoingTransitions());
allOutgoingTrans.addAll(getBoundaryEventOutgoingTransitions(activity));
List<String> activityHighLightedFlowIds = getHighlightedFlows(allOutgoingTrans, hisActInstList, isParallel);
highLightedFlows.addAll(activityHighLightedFlowIds);
}
}
}
/**
* Check out the outgoing transition connected to firstActInst from startEventActList
*
* @param startEventActList
* @param firstActInst
* @return
*/
private PvmTransition getStartTransaction(List<ActivityImpl> startEventActList, HistoricActivityInstance firstActInst) {
for (ActivityImpl startEventAct : startEventActList) {
for (PvmTransition trans : startEventAct.getOutgoingTransitions()) {
if (trans.getDestination().getId().equals(firstActInst.getActivityId())) {
return trans;
}
}
}
return null;
}
/**
* getBoundaryEventOutgoingTransitions
*
* @param activity
* @return
*/
private List<PvmTransition> getBoundaryEventOutgoingTransitions(ActivityImpl activity) {
List<PvmTransition> boundaryTrans = new ArrayList<PvmTransition>();
for (ActivityImpl subActivity : activity.getActivities()) {
String type = (String) subActivity.getProperty("type");
if (type != null && type.toLowerCase().indexOf("boundary") >= ) {
boundaryTrans.addAll(subActivity.getOutgoingTransitions());
}
}
return boundaryTrans;
}
/**
* find out single activity's highlighted flowIds
*
* @param pvmTransitionList
* @param hisActInstList
* @param isParallel
* @return
*/
private List<String> getHighlightedFlows(List<PvmTransition> pvmTransitionList, LinkedList<HistoricActivityInstance> hisActInstList, boolean isParallel) {
List<String> highLightedFlowIds = new ArrayList<String>();
PvmTransition earliestTrans = null;
HistoricActivityInstance earliestHisActInst = null;
for (PvmTransition pvmTransition : pvmTransitionList) {
String destActId = pvmTransition.getDestination().getId();
HistoricActivityInstance destHisActInst = findHisActInst(hisActInstList, destActId);
if (destHisActInst != null) {
if (isParallel) {
highLightedFlowIds.add(pvmTransition.getId());
} else if (earliestHisActInst == null || (earliestHisActInst.getId().compareTo(destHisActInst.getId()) > )) {
earliestTrans = pvmTransition;
earliestHisActInst = destHisActInst;
}
}
}
if ((!isParallel) && earliestTrans != null) {
highLightedFlowIds.add(earliestTrans.getId());
}
return highLightedFlowIds;
}
private HistoricActivityInstance findHisActInst(LinkedList<HistoricActivityInstance> hisActInstList, String actId) {
for (HistoricActivityInstance hisActInst : hisActInstList) {
if (hisActInst.getActivityId().equals(actId)) {
return hisActInst;
}
}
return null;
}
}
我遇到的坑:因為測試使用的是JUNIT跑的測試用例,是以真個流程在同一秒就結束了,是以造成流程曆史節點開始時間都一樣,排序錯亂,流程圖繪制出現短線的情況。