/* 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.rest.diagram.services;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
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.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.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@RestController
public class ProcessInstanceHighlightsResource {
@Autowired
private RuntimeService runtimeService;
@Autowired
private RepositoryService repositoryService;
@Autowired
private HistoryService historyService;
protected ObjectMapper objectMapper = new ObjectMapper();
@RequestMapping(value="/process-instance/{processInstanceId}/highlights", method = RequestMethod.GET, produces = "application/json")
public ObjectNode getHighlighted(@PathVariable String processInstanceId) {
ObjectNode responseJSON = objectMapper.createObjectNode();
responseJSON.put("processInstanceId", processInstanceId);
ArrayNode activitiesArray = objectMapper.createArrayNode();
ArrayNode flowsArray = objectMapper.createArrayNode();
try {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());
responseJSON.put("processDefinitionId", processInstance.getProcessDefinitionId());
List<String> highLightedActivities = runtimeService.getActiveActivityIds(processInstanceId);
List<String> highLightedFlows = getHighLightedFlows(processDefinition, processInstanceId);
for (String activityId : highLightedActivities) {
activitiesArray.add(activityId);
}
for (String flow : highLightedFlows) {
flowsArray.add(flow);
}
} catch (Exception e) {
e.printStackTrace();
}
responseJSON.put("activities", activitiesArray);
responseJSON.put("flows", flowsArray);
return responseJSON;
}
/**
* 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();
LinkedList<HistoricActivityInstance> hisActInstList = new LinkedList<HistoricActivityInstance>();
hisActInstList.addAll(historicActivityInstances);
getHighlightedFlows(processDefinition.getActivities(), hisActInstList, highLightedFlows);
return highLightedFlows;
}
/**
* getHighlightedFlows
*
* 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") >= 0){
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") < 0){
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")>=0){
boundaryTrans.addAll(subActivity.getOutgoingTransitions());
}
}
return boundaryTrans;
}
/**
* find out single activity's highlighted flowIds
*
* @param activity
* @param hisActInstList
* @param isExclusive if true only return one flowId(Such as exclusiveGateway, BoundaryEvent On Task)
* @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()) > 0)) {
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;
}
}