From 0f29ebfe91c71faf433f3220019df85f4e6372bf Mon Sep 17 00:00:00 2001 From: zhou-hao Date: Fri, 13 Jul 2018 23:01:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B5=81=E7=A8=8B=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hsweb-system-workflow-local/pom.xml | 27 +- ...rocessDefinitionDiagramLayoutResource.java | 498 ++++++++++++++++++ ...rocessDefinitionDiagramLayoutResource.java | 16 +- .../ProcessInstanceDiagramLayoutResource.java | 15 +- .../ProcessInstanceHighlightsResource.java | 249 ++++++++- 5 files changed, 775 insertions(+), 30 deletions(-) create mode 100644 hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/BaseProcessDefinitionDiagramLayoutResource.java diff --git a/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/pom.xml b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/pom.xml index f4e58d3b2..1b2fd7171 100644 --- a/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/pom.xml +++ b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/pom.xml @@ -20,18 +20,6 @@ ${project.version} - - org.flowable - flowable-diagram-rest - ${flowable.version} - - - org.flowable - flowable-common-rest - - - - org.flowable flowable-spring-boot-starter-basic @@ -49,6 +37,7 @@ batik-parser ${batik.version} + org.apache.xmlgraphics batik-transcoder @@ -60,6 +49,7 @@ + org.apache.xmlgraphics batik-bridge @@ -125,16 +115,24 @@ hsweb-system-organizational-api ${project.version} + org.hswebframework.web hsweb-system-organizational-authorization ${project.version} + org.hswebframework.web hsweb-system-dynamic-form-api ${project.version} + + + io.vavr + vavr + + org.hswebframework.web hsweb-system-dynamic-form-starter @@ -142,11 +140,6 @@ test - - io.vavr - vavr - - org.hswebframework.web hsweb-system-authorization-starter diff --git a/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/BaseProcessDefinitionDiagramLayoutResource.java b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/BaseProcessDefinitionDiagramLayoutResource.java new file mode 100644 index 000000000..c60a71f9b --- /dev/null +++ b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/BaseProcessDefinitionDiagramLayoutResource.java @@ -0,0 +1,498 @@ +/* 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.hswebframework.web.workflow.web.diagram; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.activiti.engine.ActivitiException; +import org.activiti.engine.ActivitiObjectNotFoundException; +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.bpmn.behavior.BoundaryEventActivityBehavior; +import org.activiti.engine.impl.bpmn.behavior.CallActivityBehavior; +import org.activiti.engine.impl.bpmn.parser.BpmnParse; +import org.activiti.engine.impl.bpmn.parser.ErrorEventDefinition; +import org.activiti.engine.impl.bpmn.parser.EventSubscriptionDeclaration; +import org.activiti.engine.impl.jobexecutor.TimerDeclarationImpl; +import org.activiti.engine.impl.persistence.entity.ExecutionEntity; +import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; +import org.activiti.engine.impl.pvm.PvmTransition; +import org.activiti.engine.impl.pvm.delegate.ActivityBehavior; +import org.activiti.engine.impl.pvm.process.ActivityImpl; +import org.activiti.engine.impl.pvm.process.Lane; +import org.activiti.engine.impl.pvm.process.LaneSet; +import org.activiti.engine.impl.pvm.process.ParticipantProcess; +import org.activiti.engine.impl.pvm.process.TransitionImpl; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.Execution; +import org.activiti.engine.runtime.ProcessInstance; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +public class BaseProcessDefinitionDiagramLayoutResource { + + @Autowired + private RuntimeService runtimeService; + + @Autowired + private RepositoryService repositoryService; + + @Autowired + private HistoryService historyService; + + public Map getDiagramNode(String processInstanceId, String processDefinitionId) { + + List highLightedFlows = Collections.emptyList(); + List highLightedActivities; + + Map subProcessInstanceMap = new HashMap<>(); + + ProcessInstance processInstance = null; + if (processInstanceId != null) { + processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + if (processInstance == null) { + throw new ActivitiObjectNotFoundException("Process instance could not be found"); + } + processDefinitionId = processInstance.getProcessDefinitionId(); + + List subProcessInstances = runtimeService + .createProcessInstanceQuery() + .superProcessInstanceId(processInstanceId).list(); + + for (ProcessInstance subProcessInstance : subProcessInstances) { + String subDefId = subProcessInstance.getProcessDefinitionId(); + + String superExecutionId = (subProcessInstance).getSuperExecutionId(); + ProcessDefinitionEntity subDef = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(subDefId); + + JSONObject processInstanceJSON = new JSONObject(); + processInstanceJSON.put("processInstanceId", subProcessInstance.getId()); + processInstanceJSON.put("superExecutionId", superExecutionId); + processInstanceJSON.put("processDefinitionId", subDef.getId()); + processInstanceJSON.put("processDefinitionKey", subDef.getKey()); + processInstanceJSON.put("processDefinitionName", subDef.getName()); + + subProcessInstanceMap.put(superExecutionId, processInstanceJSON); + } + } + + if (processDefinitionId == null) { + throw new ActivitiObjectNotFoundException("No process definition id provided"); + } + + ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(processDefinitionId); + + if (processDefinition == null) { + throw new ActivitiException("Process definition " + processDefinitionId + " could not be found"); + } + + JSONObject responseJSON = new JSONObject(); + + // Process definition + Map pdrJSON = getProcessDefinitionResponse(processDefinition); + + if (pdrJSON != null) { + responseJSON.put("processDefinition", pdrJSON); + } + + // Highlighted activities + if (processInstance != null) { + JSONArray activityArray = new JSONArray(); + JSONArray flowsArray = new JSONArray(); + + highLightedActivities = runtimeService.getActiveActivityIds(processInstanceId); + highLightedFlows = getHighLightedFlows(processInstanceId, processDefinition); + + for (String activityName : highLightedActivities) { + activityArray.add(activityName); + } + + for (String flow : highLightedFlows) + flowsArray.add(flow); + + responseJSON.put("highLightedActivities", activityArray); + responseJSON.put("highLightedFlows", flowsArray); + } + + // Pool shape, if process is participant in collaboration + if (processDefinition.getParticipantProcess() != null) { + ParticipantProcess pProc = processDefinition.getParticipantProcess(); + + JSONObject participantProcessJSON = new JSONObject(); + participantProcessJSON.put("id", pProc.getId()); + if (StringUtils.isNotEmpty(pProc.getName())) { + participantProcessJSON.put("name", pProc.getName()); + } else { + participantProcessJSON.put("name", ""); + } + participantProcessJSON.put("x", pProc.getX()); + participantProcessJSON.put("y", pProc.getY()); + participantProcessJSON.put("width", pProc.getWidth()); + participantProcessJSON.put("height", pProc.getHeight()); + + responseJSON.put("participantProcess", participantProcessJSON); + } + + // Draw lanes + + if (processDefinition.getLaneSets() != null && !processDefinition.getLaneSets().isEmpty()) { + JSONArray laneSetArray = new JSONArray(); + for (LaneSet laneSet : processDefinition.getLaneSets()) { + JSONArray laneArray = new JSONArray(); + if (laneSet.getLanes() != null && !laneSet.getLanes().isEmpty()) { + for (Lane lane : laneSet.getLanes()) { + JSONObject laneJSON = new JSONObject(); + laneJSON.put("id", lane.getId()); + if (StringUtils.isNotEmpty(lane.getName())) { + laneJSON.put("name", lane.getName()); + } else { + laneJSON.put("name", ""); + } + laneJSON.put("x", lane.getX()); + laneJSON.put("y", lane.getY()); + laneJSON.put("width", lane.getWidth()); + laneJSON.put("height", lane.getHeight()); + + List flowNodeIds = lane.getFlowNodeIds(); + JSONArray flowNodeIdsArray = new JSONArray(); + for (String flowNodeId : flowNodeIds) { + flowNodeIdsArray.add(flowNodeId); + } + laneJSON.put("flowNodeIds", flowNodeIdsArray); + + laneArray.add(laneJSON); + } + } + JSONObject laneSetJSON = new JSONObject(); + laneSetJSON.put("id", laneSet.getId()); + if (StringUtils.isNotEmpty(laneSet.getName())) { + laneSetJSON.put("name", laneSet.getName()); + } else { + laneSetJSON.put("name", ""); + } + laneSetJSON.put("lanes", laneArray); + + laneSetArray.add(laneSetJSON); + } + + if (laneSetArray.size() > 0) + responseJSON.put("laneSets", laneSetArray); + } + + JSONArray sequenceFlowArray = new JSONArray(); + JSONArray activityArray = new JSONArray(); + + // Activities and their sequence-flows + + for (ActivityImpl activity : processDefinition.getActivities()) { + getActivity(processInstanceId, activity, activityArray, sequenceFlowArray, + processInstance, highLightedFlows, subProcessInstanceMap); + } + + responseJSON.put("activities", activityArray); + responseJSON.put("sequenceFlows", sequenceFlowArray); + + return responseJSON; + } + + private List getHighLightedFlows(String processInstanceId, ProcessDefinitionEntity processDefinition) { + + List highLightedFlows = new ArrayList(); + List historicActivityInstances = historyService + .createHistoricActivityInstanceQuery() + .processInstanceId(processInstanceId) + .orderByHistoricActivityInstanceStartTime().asc().list(); + + List historicActivityInstanceList = new ArrayList(); + for (HistoricActivityInstance hai : historicActivityInstances) { + historicActivityInstanceList.add(hai.getActivityId()); + } + + // add current activities to list + List highLightedActivities = runtimeService.getActiveActivityIds(processInstanceId); + historicActivityInstanceList.addAll(highLightedActivities); + + // activities and their sequence-flows + for (ActivityImpl activity : processDefinition.getActivities()) { + int index = historicActivityInstanceList.indexOf(activity.getId()); + + if (index >= 0 && index + 1 < historicActivityInstanceList.size()) { + List pvmTransitionList = activity + .getOutgoingTransitions(); + for (PvmTransition pvmTransition : pvmTransitionList) { + String destinationFlowId = pvmTransition.getDestination().getId(); + if (destinationFlowId.equals(historicActivityInstanceList.get(index + 1))) { + highLightedFlows.add(pvmTransition.getId()); + } + } + } + } + return highLightedFlows; + } + + private void getActivity(String processInstanceId, ActivityImpl activity, List activityArray, + List sequenceFlowArray, ProcessInstance processInstance, List highLightedFlows, + Map subProcessInstanceMap) { + + JSONObject activityJSON = new JSONObject(); + + // Gather info on the multi instance marker + String multiInstance = (String) activity.getProperty("multiInstance"); + if (multiInstance != null) { + if (!"sequential".equals(multiInstance)) { + multiInstance = "parallel"; + } + } + + ActivityBehavior activityBehavior = activity.getActivityBehavior(); + // Gather info on the collapsed marker + Boolean collapsed = (activityBehavior instanceof CallActivityBehavior); + Boolean expanded = (Boolean) activity.getProperty(BpmnParse.PROPERTYNAME_ISEXPANDED); + if (expanded != null) { + collapsed = !expanded; + } + + Boolean isInterrupting = null; + if (activityBehavior instanceof BoundaryEventActivityBehavior) { + isInterrupting = ((BoundaryEventActivityBehavior) activityBehavior).isInterrupting(); + } + + // Outgoing transitions of activity + for (PvmTransition sequenceFlow : activity.getOutgoingTransitions()) { + String flowName = (String) sequenceFlow.getProperty("name"); + boolean isHighLighted = (highLightedFlows.contains(sequenceFlow.getId())); + boolean isConditional = sequenceFlow.getProperty(BpmnParse.PROPERTYNAME_CONDITION) != null && + !((String) activity.getProperty("type")).toLowerCase().contains("gateway"); + boolean isDefault = sequenceFlow.getId().equals(activity.getProperty("default")) + && ((String) activity.getProperty("type")).toLowerCase().contains("gateway"); + + List waypoints = ((TransitionImpl) sequenceFlow).getWaypoints(); + JSONArray xPointArray = new JSONArray(); + JSONArray yPointArray = new JSONArray(); + for (int i = 0; i < waypoints.size(); i += 2) { // waypoints.size() + // minimally 4: x1, y1, + // x2, y2 + xPointArray.add(waypoints.get(i)); + yPointArray.add(waypoints.get(i + 1)); + } + + JSONObject flowJSON = new JSONObject(); + flowJSON.put("id", sequenceFlow.getId()); + flowJSON.put("name", flowName); + flowJSON.put("flow", "(" + sequenceFlow.getSource().getId() + ")--" + + sequenceFlow.getId() + "-->(" + + sequenceFlow.getDestination().getId() + ")"); + + if (isConditional) + flowJSON.put("isConditional", isConditional); + if (isDefault) + flowJSON.put("isDefault", isDefault); + if (isHighLighted) + flowJSON.put("isHighLighted", isHighLighted); + + flowJSON.put("xPointArray", xPointArray); + flowJSON.put("yPointArray", yPointArray); + + sequenceFlowArray.add(flowJSON); + } + + // Nested activities (boundary events) + JSONArray nestedActivityArray = new JSONArray(); + for (ActivityImpl nestedActivity : activity.getActivities()) { + nestedActivityArray.add(nestedActivity.getId()); + } + + Map properties = activity.getProperties(); + JSONObject propertiesJSON = new JSONObject(); + for (String key : properties.keySet()) { + Object prop = properties.get(key); + if (prop instanceof String) + propertiesJSON.put(key, (String) properties.get(key)); + else if (prop instanceof Integer) + propertiesJSON.put(key, (Integer) properties.get(key)); + else if (prop instanceof Boolean) + propertiesJSON.put(key, (Boolean) properties.get(key)); + else if ("initial".equals(key)) { + ActivityImpl act = (ActivityImpl) properties.get(key); + propertiesJSON.put(key, act.getId()); + } else if ("timerDeclarations".equals(key)) { + ArrayList timerDeclarations = (ArrayList) properties.get(key); + JSONArray timerDeclarationArray = new JSONArray(); + + if (timerDeclarations != null) + for (TimerDeclarationImpl timerDeclaration : timerDeclarations) { + JSONObject timerDeclarationJSON = new JSONObject(); + + timerDeclarationJSON.put("isExclusive", timerDeclaration.isExclusive()); + if (timerDeclaration.getRepeat() != null) + timerDeclarationJSON.put("repeat", timerDeclaration.getRepeat()); + + timerDeclarationJSON.put("retries", String.valueOf(timerDeclaration.getRetries())); + timerDeclarationJSON.put("type", timerDeclaration.getJobHandlerType()); + timerDeclarationJSON.put("configuration", timerDeclaration.getJobHandlerConfiguration()); + //timerDeclarationJSON.put("expression", timerDeclaration.getDescription()); + + timerDeclarationArray.add(timerDeclarationJSON); + } + if (timerDeclarationArray.size() > 0) + propertiesJSON.put(key, timerDeclarationArray); + // TODO: implement getting description + } else if ("eventDefinitions".equals(key)) { + ArrayList eventDefinitions = (ArrayList) properties.get(key); + JSONArray eventDefinitionsArray = new JSONArray(); + + if (eventDefinitions != null) { + for (EventSubscriptionDeclaration eventDefinition : eventDefinitions) { + JSONObject eventDefinitionJSON = new JSONObject(); + + if (eventDefinition.getActivityId() != null) + eventDefinitionJSON.put("activityId", eventDefinition.getActivityId()); + + eventDefinitionJSON.put("eventName", eventDefinition.getEventName()); + eventDefinitionJSON.put("eventType", eventDefinition.getEventType()); + eventDefinitionJSON.put("isAsync", eventDefinition.isAsync()); + eventDefinitionJSON.put("isStartEvent", eventDefinition.isStartEvent()); + eventDefinitionsArray.add(eventDefinitionJSON); + } + } + + if (eventDefinitionsArray.size() > 0) + propertiesJSON.put(key, eventDefinitionsArray); + + // TODO: implement it + } else if ("errorEventDefinitions".equals(key)) { + ArrayList errorEventDefinitions = (ArrayList) properties.get(key); + JSONArray errorEventDefinitionsArray = new JSONArray(); + + if (errorEventDefinitions != null) { + for (ErrorEventDefinition errorEventDefinition : errorEventDefinitions) { + JSONObject errorEventDefinitionJSON = new JSONObject(); + + if (errorEventDefinition.getErrorCode() != null) + errorEventDefinitionJSON.put("errorCode", errorEventDefinition.getErrorCode()); + else + errorEventDefinitionJSON.put("errorCode",null); + + errorEventDefinitionJSON.put("handlerActivityId", + errorEventDefinition.getHandlerActivityId()); + + errorEventDefinitionsArray.add(errorEventDefinitionJSON); + } + } + + if (errorEventDefinitionsArray.size() > 0) + propertiesJSON.put(key, errorEventDefinitionsArray); + } + + } + + if ("callActivity".equals(properties.get("type"))) { + CallActivityBehavior callActivityBehavior = null; + + if (activityBehavior instanceof CallActivityBehavior) { + callActivityBehavior = (CallActivityBehavior) activityBehavior; + } + + if (callActivityBehavior != null) { + propertiesJSON.put("processDefinitonKey", callActivityBehavior.getProcessDefinitonKey()); + + // get processDefinitonId from execution or get last processDefinitonId + // by key + JSONArray processInstanceArray = new JSONArray(); + if (processInstance != null) { + List executionList = runtimeService.createExecutionQuery() + .processInstanceId(processInstanceId) + .activityId(activity.getId()).list(); + if (!executionList.isEmpty()) { + for (Execution execution : executionList) { + processInstanceArray.add(subProcessInstanceMap.get(execution.getId())); + } + } + } + + // If active activities nas no instance of this callActivity then add + // last definition + if (processInstanceArray.size() == 0 && StringUtils.isNotEmpty(callActivityBehavior.getProcessDefinitonKey())) { + // Get last definition by key + ProcessDefinition lastProcessDefinition = repositoryService + .createProcessDefinitionQuery() + .processDefinitionKey(callActivityBehavior.getProcessDefinitonKey()) + .latestVersion().singleResult(); + + // TODO: unuseful fields there are processDefinitionName, processDefinitionKey + if (lastProcessDefinition != null) { + JSONObject processInstanceJSON = new JSONObject(); + processInstanceJSON.put("processDefinitionId", lastProcessDefinition.getId()); + processInstanceJSON.put("processDefinitionKey", lastProcessDefinition.getKey()); + processInstanceJSON.put("processDefinitionName", lastProcessDefinition.getName()); + processInstanceArray.add(processInstanceJSON); + } + } + + if (processInstanceArray.size() > 0) { + propertiesJSON.put("processDefinitons", processInstanceArray); + } + } + } + + activityJSON.put("activityId", activity.getId()); + activityJSON.put("properties", propertiesJSON); + if (multiInstance != null) + activityJSON.put("multiInstance", multiInstance); + if (collapsed) + activityJSON.put("collapsed", collapsed); + if (nestedActivityArray.size() > 0) + activityJSON.put("nestedActivities", nestedActivityArray); + if (isInterrupting != null) + activityJSON.put("isInterrupting", isInterrupting); + + activityJSON.put("x", activity.getX()); + activityJSON.put("y", activity.getY()); + activityJSON.put("width", activity.getWidth()); + activityJSON.put("height", activity.getHeight()); + + activityArray.add(activityJSON); + + // Nested activities (boundary events) + for (ActivityImpl nestedActivity : activity.getActivities()) { + getActivity(processInstanceId, nestedActivity, activityArray, sequenceFlowArray, + processInstance, highLightedFlows, subProcessInstanceMap); + } + } + + private Map getProcessDefinitionResponse(ProcessDefinitionEntity processDefinition) { + JSONObject pdrJSON = new JSONObject(); + pdrJSON.put("id", processDefinition.getId()); + pdrJSON.put("name", processDefinition.getName()); + pdrJSON.put("key", processDefinition.getKey()); + pdrJSON.put("version", processDefinition.getVersion()); + pdrJSON.put("deploymentId", processDefinition.getDeploymentId()); + pdrJSON.put("isGraphicNotationDefined", isGraphicNotationDefined(processDefinition)); + return pdrJSON; + } + + private boolean isGraphicNotationDefined(ProcessDefinitionEntity processDefinition) { + return ((ProcessDefinitionEntity) repositoryService + .getProcessDefinition(processDefinition.getId())) + .isGraphicalNotationDefined(); + } +} diff --git a/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessDefinitionDiagramLayoutResource.java b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessDefinitionDiagramLayoutResource.java index f72ed1f99..26b362781 100644 --- a/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessDefinitionDiagramLayoutResource.java +++ b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessDefinitionDiagramLayoutResource.java @@ -1,14 +1,22 @@ package org.hswebframework.web.workflow.web.diagram; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.web.bind.annotation.*; /** - * * @author zhouhao * @since 3.0.0-RC */ @RestController @RequestMapping("/workflow/service/") -public class ProcessDefinitionDiagramLayoutResource extends org.activiti.rest.diagram.services.ProcessDefinitionDiagramLayoutResource { +public class ProcessDefinitionDiagramLayoutResource + extends BaseProcessDefinitionDiagramLayoutResource { + + @GetMapping( + value = {"/process-definition/{processDefinitionId}/diagram-layout"}, + produces = {"application/json"} + ) + public Object getDiagram(@PathVariable String processDefinitionId) { + return this.getDiagramNode(null, processDefinitionId); + } } diff --git a/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessInstanceDiagramLayoutResource.java b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessInstanceDiagramLayoutResource.java index db8d32357..b25ae7e72 100644 --- a/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessInstanceDiagramLayoutResource.java +++ b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessInstanceDiagramLayoutResource.java @@ -1,6 +1,8 @@ package org.hswebframework.web.workflow.web.diagram; +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; /** @@ -9,6 +11,17 @@ import org.springframework.web.bind.annotation.RestController; */ @RestController @RequestMapping("/workflow/service/") -public class ProcessInstanceDiagramLayoutResource extends org.activiti.rest.diagram.services.ProcessInstanceDiagramLayoutResource { +public class ProcessInstanceDiagramLayoutResource + extends BaseProcessDefinitionDiagramLayoutResource +{ + + @RequestMapping( + value = {"/process-instance/{processInstanceId}/diagram-layout"}, + method = {RequestMethod.GET}, + produces = {"application/json"} + ) + public Object getDiagram(@PathVariable String processInstanceId) { + return this.getDiagramNode(processInstanceId, (String)null); + } } diff --git a/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessInstanceHighlightsResource.java b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessInstanceHighlightsResource.java index 7dbcc33b0..6859677de 100644 --- a/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessInstanceHighlightsResource.java +++ b/hsweb-system/hsweb-system-workflow/hsweb-system-workflow-local/src/main/java/org/hswebframework/web/workflow/web/diagram/ProcessInstanceHighlightsResource.java @@ -1,13 +1,246 @@ +/* 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.hswebframework.web.workflow.web.diagram; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +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.*; + +import com.alibaba.fastjson.JSONArray; -/** - * @author zhouhao - * @since 3.0.0-RC - */ @RestController -@RequestMapping("/workflow/service/") -public class ProcessInstanceHighlightsResource extends org.activiti.rest.diagram.services.ProcessInstanceHighlightsResource { +@RequestMapping("/workflow/service") +public class ProcessInstanceHighlightsResource { + + @Autowired + private RuntimeService runtimeService; + + @Autowired + private RepositoryService repositoryService; + + @Autowired + private HistoryService historyService; + + + @GetMapping(value = "/process-instance/{processInstanceId}/highlights", produces = "application/json") + public Object getHighlighted(@PathVariable String processInstanceId) { + + JSONObject responseJSON = new JSONObject(); + + responseJSON.put("processInstanceId", processInstanceId); + + JSONArray activitiesArray = new JSONArray(); + JSONArray flowsArray = new JSONArray(); + + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId()); + + responseJSON.put("processDefinitionId", processInstance.getProcessDefinitionId()); + + List highLightedActivities = runtimeService.getActiveActivityIds(processInstanceId); + List highLightedFlows = getHighLightedFlows(processDefinition, processInstanceId); + + activitiesArray.addAll(highLightedActivities); + + flowsArray.addAll(highLightedFlows); + + + responseJSON.put("activities", activitiesArray); + responseJSON.put("flows", flowsArray); + + return responseJSON; + } + + + /** + * getHighLightedFlows + * + * @param processDefinition + * @param processInstanceId + * @return + */ + private List getHighLightedFlows(ProcessDefinitionEntity processDefinition, String processInstanceId) { + + List highLightedFlows = new ArrayList(); + + List historicActivityInstances = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processInstanceId) + //order by startime asc is not correct. use default order is correct. + //.orderByHistoricActivityInstanceStartTime().asc()/*.orderByActivityId().asc()*/ + .list(); + + LinkedList hisActInstList = new LinkedList(); + 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 activityList, LinkedList hisActInstList, List highLightedFlows) { + + //check out startEvents in activityList + List startEventActList = new ArrayList(); + Map activityMap = new HashMap(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 allOutgoingTrans = new ArrayList(); + allOutgoingTrans.addAll(activity.getOutgoingTransitions()); + allOutgoingTrans.addAll(getBoundaryEventOutgoingTransitions(activity)); + List 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 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 getBoundaryEventOutgoingTransitions(ActivityImpl activity) { + List boundaryTrans = new ArrayList(); + 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 getHighlightedFlows(List pvmTransitionList, LinkedList hisActInstList, boolean isParallel) { + + List highLightedFlowIds = new ArrayList(); + + 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 hisActInstList, String actId) { + for (HistoricActivityInstance hisActInst : hisActInstList) { + if (hisActInst.getActivityId().equals(actId)) { + return hisActInst; + } + } + return null; + } }