提交 f1a9010a 编写于 作者: J Joram Barrez 提交者: Tijs Rademakers

Merge

上级 5ecd7fc8
......@@ -14,6 +14,7 @@ package org.activiti.engine.impl.agenda;
import java.util.LinkedList;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.pvm.delegate.ActivityExecution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -25,10 +26,12 @@ public class Agenda {
private static final Logger logger = LoggerFactory.getLogger(Agenda.class);
protected CommandContext commandContext;
protected LinkedList<Runnable> operations = new LinkedList<Runnable>();
public Agenda() {
public Agenda(CommandContext commandContext) {
this.commandContext = commandContext;
}
public boolean isEmpty() {
......@@ -68,5 +71,21 @@ public class Agenda {
public void planDestroyScopeOperation(ActivityExecution execution) {
planOperation(new DestroyScopeOperation(this, execution));
}
public void planExecuteInactiveBehaviorsOperation() {
planOperation(new ExecuteInactiveBehaviorsOperation(this));
}
public CommandContext getCommandContext() {
return commandContext;
}
public void setCommandContext(CommandContext commandContext) {
this.commandContext = commandContext;
}
public LinkedList<Runnable> getOperations() {
return operations;
}
}
......@@ -57,7 +57,7 @@ public class EndExecutionOperation extends AbstractOperation {
// Delete current execution
logger.debug("Ending execution {}", execution.getId());
deleteExecution(commandContext, executionEntity);
logger.debug("Parent execution found. Continuing process using execution {}", parentExecution.getId());
parentExecution.setCurrentFlowElement(executionEntity.getCurrentFlowElement());
agenda.planTakeOutgoingSequenceFlowsOperation(parentExecution, true);
......
package org.activiti.engine.impl.agenda;
import java.util.ArrayList;
import java.util.Collection;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.pvm.delegate.InactiveActivityBehavior;
import org.activiti.engine.impl.util.cache.ProcessDefinitionCacheUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Joram Barrez
*/
public class ExecuteInactiveBehaviorsOperation extends AbstractOperation {
private static final Logger logger = LoggerFactory.getLogger(ExecuteInactiveBehaviorsOperation.class);
protected Collection<ExecutionEntity> involvedExecutions;
public ExecuteInactiveBehaviorsOperation(Agenda agenda) {
super(agenda, null);
this.involvedExecutions = agenda.getCommandContext().getInvolvedExecutions();
}
@Override
public void run() {
/**
* Algortithm: for each execution that is involved in this command context,
* 1) Verify if its process definitions has any InactiveActivityBehavior instances.
* 2) If so, verify if there are any executions inactive in those
* 3) Execute the inactivated behavior
*/
CommandContext commandContext = Context.getCommandContext();
for (ExecutionEntity executionEntity : involvedExecutions) {
Process process = ProcessDefinitionCacheUtil.getCachedProcess(executionEntity.getProcessDefinitionId());
Collection<String> flowNodeIdsWithInactivatedBehavior = new ArrayList<String>();
for (FlowNode flowNode : process.findFlowElementsOfType(FlowNode.class)) {
if (flowNode.getBehavior() instanceof InactiveActivityBehavior) {
flowNodeIdsWithInactivatedBehavior.add(flowNode.getId());
}
}
// Only check if the process actually has inactivated functionality
// TODO: this information could be cached
if (flowNodeIdsWithInactivatedBehavior.size() > 0) {
Collection<ExecutionEntity> inactiveExecutions = commandContext.getExecutionEntityManager()
.getInactiveExecutionsForProcessInstance(executionEntity.getProcessInstanceId());
for (ExecutionEntity inactiveExecution : inactiveExecutions) {
if (!inactiveExecution.isActive()
&& flowNodeIdsWithInactivatedBehavior.contains(inactiveExecution.getActivityId())
&& !commandContext.getDbSqlSession().isPersistentObjectDeleted(inactiveExecution)) {
FlowNode flowNode = (FlowNode) process.getFlowElement(inactiveExecution.getActivityId(), true);
InactiveActivityBehavior inactiveActivityBehavior = ((InactiveActivityBehavior)flowNode.getBehavior());
logger.debug("Found InactiveActivityBehavior instance of class {} that can be executed on activity '{}'",
inactiveActivityBehavior.getClass(), flowNode.getId());
inactiveActivityBehavior.executeInactive(inactiveExecution);;
}
}
}
}
}
}
......@@ -39,11 +39,11 @@ public class TakeOutgoingSequenceFlowsOperation extends AbstractOperation {
execution.setCurrentActivityId(currentFlowElement.getId());
}
// If execution is a scope, the scope must first be destroyed before we can continue
// if (execution.isScope()) {
// coreEngine.destroyScope(commandContext, execution);
// // return;
// }
// If execution is a scope (and not the process instance), the scope must first be destroyed before we can continue
if (execution.getParentId() != null && execution.isScope()) {
agenda.planDestroyScopeOperation(execution);
// return;
}
// No scope, can continue
if (currentFlowElement instanceof FlowNode) {
......@@ -56,7 +56,8 @@ public class TakeOutgoingSequenceFlowsOperation extends AbstractOperation {
protected void leaveFlowNode(FlowNode flowNode) {
logger.debug("Leaving flow node {} by following it's {} outgoing sequenceflow", flowNode.getClass(), flowNode.getOutgoingFlows().size());
logger.debug("Leaving flow node {} with id '{}' by following it's {} outgoing sequenceflow",
flowNode.getClass(), flowNode.getId(), flowNode.getOutgoingFlows().size());
// Determine which sequence flows can be used for leaving
List<SequenceFlow> outgoingSequenceFlow = new ArrayList<SequenceFlow>();
......
......@@ -12,20 +12,16 @@
*/
package org.activiti.engine.impl.bpmn.behavior;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Collection;
import java.util.Iterator;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.delegate.Expression;
import org.activiti.engine.impl.Condition;
import org.activiti.engine.impl.bpmn.helper.SkipExpressionUtil;
import org.activiti.engine.impl.bpmn.parser.BpmnParse;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
import org.activiti.engine.impl.pvm.delegate.ActivityExecution;
import org.activiti.engine.impl.pvm.delegate.InactiveActivityBehavior;
import org.activiti.engine.impl.util.ExecutionGraphUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -37,156 +33,67 @@ import org.slf4j.LoggerFactory;
* @author Tom Van Buskirk
* @author Joram Barrez
*/
public class InclusiveGatewayActivityBehavior extends GatewayActivityBehavior {
private static final long serialVersionUID = 1L;
private static Logger log = LoggerFactory.getLogger(InclusiveGatewayActivityBehavior.class.getName());
public void execute(ActivityExecution execution) {
execution.inactivate();
lockConcurrentRoot(execution);
PvmActivity activity = execution.getActivity();
if (!activeConcurrentExecutionsExist(execution)) {
if (log.isDebugEnabled()) {
log.debug("inclusive gateway '{}' activates", activity.getId());
}
List<ActivityExecution> joinedExecutions = execution.findInactiveConcurrentExecutions(activity);
String defaultSequenceFlow = (String) execution.getActivity().getProperty("default");
List<PvmTransition> transitionsToTake = new ArrayList<PvmTransition>();
for (PvmTransition outgoingTransition : execution.getActivity().getOutgoingTransitions()) {
Expression skipExpression = outgoingTransition.getSkipExpression();
if (!SkipExpressionUtil.isSkipExpressionEnabled(execution, skipExpression)) {
if (defaultSequenceFlow == null || !outgoingTransition.getId().equals(defaultSequenceFlow)) {
Condition condition = (Condition) outgoingTransition.getProperty(BpmnParse.PROPERTYNAME_CONDITION);
if (condition == null || condition.evaluate(execution)) {
transitionsToTake.add(outgoingTransition);
}
}
}
else if (SkipExpressionUtil.shouldSkipFlowElement(execution, skipExpression)){
transitionsToTake.add(outgoingTransition);
}
}
if (!transitionsToTake.isEmpty()) {
execution.takeAll(transitionsToTake, joinedExecutions);
} else {
if (defaultSequenceFlow != null) {
PvmTransition defaultTransition = execution.getActivity().findOutgoingTransition(defaultSequenceFlow);
if (defaultTransition != null) {
execution.take(defaultTransition);
} else {
throw new ActivitiException("Default sequence flow '"
+ defaultSequenceFlow + "' could not be not found");
}
} else {
// No sequence flow could be found, not even a default one
throw new ActivitiException(
"No outgoing sequence flow of the inclusive gateway '"
+ execution.getActivity().getId()
+ "' could be selected for continuing the process");
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("Inclusive gateway '{}' does not activate", activity.getId());
}
}
}
List<? extends ActivityExecution> getLeaveExecutions(ActivityExecution parent) {
List<ActivityExecution> executionlist = new ArrayList<ActivityExecution>();
List<? extends ActivityExecution> subExecutions = parent.getExecutions();
if (subExecutions.isEmpty()) {
executionlist.add(parent);
} else {
for (ActivityExecution concurrentExecution : subExecutions) {
executionlist.addAll(getLeaveExecutions(concurrentExecution));
}
}
return executionlist;
}
public boolean activeConcurrentExecutionsExist(ActivityExecution execution) {
PvmActivity activity = execution.getActivity();
if (execution.isConcurrent()) {
for (ActivityExecution concurrentExecution : getLeaveExecutions(execution.getParent())) {
if (concurrentExecution.isActive() && concurrentExecution.getId().equals(execution.getId()) == false) {
// TODO: when is transitionBeingTaken cleared? Should we clear it?
boolean reachable = false;
PvmTransition pvmTransition = ((ExecutionEntity) concurrentExecution).getTransitionBeingTaken();
if (pvmTransition != null) {
reachable = isReachable(pvmTransition.getDestination(), activity, new HashSet<PvmActivity>());
} else {
reachable = isReachable(concurrentExecution.getActivity(), activity, new HashSet<PvmActivity>());
}
if (reachable) {
if (log.isDebugEnabled()) {
log.debug("an active concurrent execution found: '{}'", concurrentExecution.getActivity());
}
return true;
}
}
}
} else if (execution.isActive()) { // is this ever true?
if (log.isDebugEnabled()) {
log.debug("an active concurrent execution found: '{}'", execution.getActivity());
}
return true;
}
return false;
}
protected boolean isReachable(PvmActivity srcActivity,
PvmActivity targetActivity, Set<PvmActivity> visitedActivities) {
// if source has no outputs, it is the end of the process, and its parent process should be checked.
if (srcActivity.getOutgoingTransitions().isEmpty()) {
visitedActivities.add(srcActivity);
if (!(srcActivity.getParent() instanceof PvmActivity)) {
return false;
}
srcActivity = (PvmActivity) srcActivity.getParent();
}
if (srcActivity.equals(targetActivity)) {
return true;
}
// To avoid infinite looping, we must capture every node we visit
// and check before going further in the graph if we have already visitied
// the node.
visitedActivities.add(srcActivity);
List<PvmTransition> transitionList = srcActivity.getOutgoingTransitions();
if (transitionList != null && !transitionList.isEmpty()) {
for (PvmTransition pvmTransition : transitionList) {
PvmActivity destinationActivity = pvmTransition.getDestination();
if (destinationActivity != null && !visitedActivities.contains(destinationActivity)) {
boolean reachable = isReachable(destinationActivity, targetActivity, visitedActivities);
// If false, we should investigate other paths, and not yet return the
// result
if (reachable) {
return true;
}
}
}
}
return false;
}
public class InclusiveGatewayActivityBehavior extends GatewayActivityBehavior implements InactiveActivityBehavior {
private static final long serialVersionUID = 1L;
private static Logger logger = LoggerFactory.getLogger(InclusiveGatewayActivityBehavior.class.getName());
@Override
public void execute(ActivityExecution execution) {
// The join in the inclusive gateway works as follows:
// When an execution enters it, it is inactivated.
// All the inactivated executions stay in the inclusive gateway
// until ALL executions that CAN reach the inclusive gateway have reached it.
//
// This check is repeated on execution changes until the inactivated
// executions leave the gateway.
execution.inactivate();
executeInclusiveGatewayLogic((ExecutionEntity) execution);
}
@Override
public void executeInactive(ExecutionEntity executionEntity) {
executeInclusiveGatewayLogic(executionEntity);
}
protected void executeInclusiveGatewayLogic(ExecutionEntity execution) {
CommandContext commandContext = Context.getCommandContext();
ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
Collection<ExecutionEntity> allExecutions = executionEntityManager
.findChildExecutionsByProcessInstanceId(execution.getProcessInstanceId());
Iterator<ExecutionEntity> executionIterator = allExecutions.iterator();
boolean oneExecutionCanReachGateway = false;
while (!oneExecutionCanReachGateway && executionIterator.hasNext()) {
ExecutionEntity executionEntity = executionIterator.next();
if (!executionEntity.getActivityId().equals(execution.getCurrentActivityId())) {
boolean canReachGateway = ExecutionGraphUtil.isReachable(execution.getProcessDefinitionId(),
executionEntity.getActivityId(), execution.getCurrentActivityId());
if (canReachGateway) {
oneExecutionCanReachGateway = true;
}
}
}
// If no execution can reach the gateway, the gateway activates and executes fork behavior
if (!oneExecutionCanReachGateway) {
logger.debug("Inclusive gateway cannot be reached by any execution and is activated");
// Kill all executions here (except the incoming)
Collection<ExecutionEntity> executionsInGateway = executionEntityManager.getInactiveExecutionsInActivity(execution.getCurrentActivityId());
for (ExecutionEntity executionEntityInGateway : executionsInGateway) {
if (!executionEntityInGateway.getId().equals(execution.getId())) {
executionEntityManager.delete(executionEntityInGateway);
}
}
// Leave
// TODO: default sequence flow
commandContext.getAgenda().planTakeOutgoingSequenceFlowsOperation(execution, true);
}
}
}
......@@ -12,11 +12,9 @@
*/
package org.activiti.engine.impl.bpmn.parser.handler;
import org.activiti.bpmn.constants.BpmnXMLConstants;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.InclusiveGateway;
import org.activiti.engine.impl.bpmn.parser.BpmnParse;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
/**
......@@ -29,12 +27,7 @@ public class InclusiveGatewayParseHandler extends AbstractActivityBpmnParseHandl
}
protected void executeParse(BpmnParse bpmnParse, InclusiveGateway gateway) {
ActivityImpl activity = createActivityOnCurrentScope(bpmnParse, gateway, BpmnXMLConstants.ELEMENT_GATEWAY_INCLUSIVE);
activity.setAsync(gateway.isAsynchronous());
activity.setExclusive(!gateway.isNotExclusive());
activity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createInclusiveGatewayActivityBehavior(gateway));
gateway.setBehavior(bpmnParse.getActivityBehaviorFactory().createInclusiveGatewayActivityBehavior(gateway));
}
}
......@@ -12,6 +12,7 @@
*/
package org.activiti.engine.impl.cmd;
import java.util.Collection;
import java.util.List;
import org.activiti.engine.ActivitiException;
......@@ -57,7 +58,7 @@ public abstract class AbstractSetProcessInstanceStateCmd implements Command<Void
SuspensionStateUtil.setSuspensionState(executionEntity, getNewState());
// All child executions are suspended
List<ExecutionEntity> childExecutions = commandContext.getExecutionEntityManager().findChildExecutionsByProcessInstanceId(executionId);
Collection<ExecutionEntity> childExecutions = commandContext.getExecutionEntityManager().findChildExecutionsByProcessInstanceId(executionId);
for (ExecutionEntity childExecution : childExecutions) {
if (!childExecution.getId().equals(executionId)) {
SuspensionStateUtil.setSuspensionState(childExecution, getNewState());
......
......@@ -14,6 +14,7 @@
package org.activiti.engine.impl.cmd;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import org.activiti.engine.ActivitiException;
......@@ -113,7 +114,7 @@ public class SetProcessDefinitionVersionCmd implements Command<Void>, Serializab
commandContext.getHistoryManager().recordProcessDefinitionChange(processInstanceId, newProcessDefinition.getId());
// switch all sub-executions of the process instance to the new process definition version
List<ExecutionEntity> childExecutions = executionManager
Collection<ExecutionEntity> childExecutions = executionManager
.findChildExecutionsByProcessInstanceId(processInstanceId);
for (ExecutionEntity executionEntity : childExecutions) {
validateAndSwitchVersionOfExecution(commandContext, executionEntity, newProcessDefinition);
......
......@@ -13,6 +13,7 @@
package org.activiti.engine.impl.interceptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
......@@ -36,6 +37,7 @@ import org.activiti.engine.impl.persistence.entity.CommentEntityManager;
import org.activiti.engine.impl.persistence.entity.DeploymentEntityManager;
import org.activiti.engine.impl.persistence.entity.EventLogEntryEntityManager;
import org.activiti.engine.impl.persistence.entity.EventSubscriptionEntityManager;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
import org.activiti.engine.impl.persistence.entity.GroupIdentityManager;
import org.activiti.engine.impl.persistence.entity.HistoricActivityInstanceEntityManager;
......@@ -82,9 +84,11 @@ public class CommandContext {
protected List<CommandContextCloseListener> closeListeners;
protected Map<String, Object> attributes; // General-purpose storing of anything during the lifetime of a command context
protected Agenda agenda = new Agenda();
protected Agenda agenda = new Agenda(this);
protected Map<String, ExecutionEntity> involvedExecutions = new HashMap<String, ExecutionEntity>(1); // The executions involved with the command
protected Object result = null;
public void performOperation(AtomicOperation executionOperation, InterpretableExecution execution) {
nextOperations.add(executionOperation);
if (nextOperations.size()==1) {
......@@ -382,6 +386,22 @@ public class CommandContext {
return getSession(HistoryManager.class);
}
// Involved executions //////////////////////////////////////////////////////
public void addInvolvedExecution(ExecutionEntity executionEntity) {
if (executionEntity.getId() != null) {
involvedExecutions.put(executionEntity.getId(), executionEntity);
}
}
public boolean hasInvolvedExecutions() {
return involvedExecutions.size() > 0;
}
public Collection<ExecutionEntity> getInvolvedExecutions() {
return involvedExecutions.values();
}
// getters and setters //////////////////////////////////////////////////////
public TransactionContext getTransactionContext() {
......
......@@ -37,13 +37,12 @@ public class CommandInvoker extends AbstractCommandInterceptor {
// Run loop for agenda
executeOperations(commandContext);
// // At the end, call the execution tree change listeners.
// // TODO: optimization: only do this when the tree has actually changed
// // (ie check dbSqlSession).
// if (commandContext.hasInvolvedExecutions()) {
// commandContext.getAgenda().add(new ExecuteInactivatedBehavior(commandContext.getCoreEngine(), commandContext));
// executeOperations(commandContext);
// }
// At the end, call the execution tree change listeners.
// TODO: optimization: only do this when the tree has actually changed (ie check dbSqlSession).
if (commandContext.hasInvolvedExecutions()) {
commandContext.getAgenda().planExecuteInactiveBehaviorsOperation();
executeOperations(commandContext);
}
return (T) commandContext.getResult();
}
......
......@@ -276,9 +276,7 @@ public class ExecutionEntity extends VariableScopeImpl implements ActivityExecut
// initialize the new execution
createdExecution.setProcessDefinitionId(this.getProcessDefinitionId());
createdExecution.setProcessInstanceId(this.getProcessInstanceId() != null ? this.getProcessInstanceId() : this.getId());
// createdExecution.setProcessDefinition(getProcessDefinition());
// createdExecution.setProcessInstance(getProcessInstance());
// createdExecution.setActivity(getActivity());
createdExecution.setScope(false);
if (log.isDebugEnabled()) {
log.debug("Child execution {} created with parent {}", createdExecution, this.getId());
......
......@@ -107,8 +107,13 @@ public class ExecutionEntityManager extends AbstractEntityManager<ExecutionEntit
}
@SuppressWarnings("unchecked")
public List<ExecutionEntity> findChildExecutionsByProcessInstanceId(String processInstanceId) {
return getDbSqlSession().selectList("selectExecutionsByProcessInstanceId", processInstanceId);
public Collection<ExecutionEntity> findChildExecutionsByProcessInstanceId(final String processInstanceId) {
return getList("selectExecutionsByProcessInstanceId", processInstanceId, new CachedEntityMatcher<ExecutionEntity>() {
@Override
public boolean isRetained(ExecutionEntity executionEntity) {
return executionEntity.getProcessInstanceId() != null && executionEntity.getProcessInstanceId().equals(processInstanceId);
}
});
}
public ExecutionEntity findExecutionById(String executionId) {
......@@ -185,6 +190,18 @@ public class ExecutionEntityManager extends AbstractEntityManager<ExecutionEntit
});
}
public Collection<ExecutionEntity> getInactiveExecutionsForProcessInstance(final String processInstanceId) {
HashMap<String, String> params = new HashMap<String, String>(1);
params.put("processInstanceId", processInstanceId);
return getList("selectInactiveExecutionsForProcessInstance", params, new CachedEntityMatcher<ExecutionEntity>() {
public boolean isRetained(ExecutionEntity executionEntity) {
return executionEntity.getProcessInstanceId() != null
&& executionEntity.getProcessInstanceId().equals(processInstanceId)
&& !executionEntity.isActive();
}
});
}
@SuppressWarnings("unchecked")
public List<Execution> findExecutionsByNativeQuery(Map<String, Object> parameterMap, int firstResult, int maxResults) {
return getDbSqlSession().selectListWithRawParameter("selectExecutionByNativeQuery", parameterMap, firstResult, maxResults);
......
package org.activiti.engine.impl.pvm.delegate;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
/**
* If the behaviour of an element in a process implements this interface,
* it has a 'background job' functionality.
*
* The instance will be called at the end of executing the engine operations
* for each {@link ExecutionEntity} that currently is at the activity AND is inactive.
*
* @author Joram Barrez
*/
public interface InactiveActivityBehavior {
void executeInactive(ExecutionEntity executionEntity);
}
/* 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.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.activiti.bpmn.model.FlowElementsContainer;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.SubProcess;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.util.cache.ProcessDefinitionCacheUtil;
public class ExecutionGraphUtil {
/**
* Takes in a collection of executions belonging to the same process instance.
* Orders the executions in a list, first elements are the leaf, last element is the root elements.
*/
public static List<ExecutionEntity> orderFromRootToLeaf(Collection<ExecutionEntity> executions) {
List<ExecutionEntity> orderedList = new ArrayList<ExecutionEntity>(executions.size());
// Root elements
HashSet<String> previousIds = new HashSet<String>();
for (ExecutionEntity execution : executions) {
if (execution.getParentId() == null) {
orderedList.add(execution);
previousIds.add(execution.getId());
}
}
// Non-root elements
while (orderedList.size() < executions.size()) {
for (ExecutionEntity execution : executions) {
if (!previousIds.contains(execution.getId()) && previousIds.contains(execution.getParentId())) {
orderedList.add(execution);
previousIds.add(execution.getId());
}
}
}
return orderedList;
}
public static List<ExecutionEntity> orderFromLeafToRoot(Collection<ExecutionEntity> executions) {
List<ExecutionEntity> orderedList = orderFromRootToLeaf(executions);
Collections.reverse(orderedList);
return orderedList;
}
/**
* Verifies if the element with the given source identifier can reach the element with the target identifier
* through following sequence flow.
*/
public static boolean isReachable(String processDefinitionId, String sourceElementId, String targetElementId) {
// Fetch source and target elements
Process process = ProcessDefinitionCacheUtil.getCachedProcess(processDefinitionId);
FlowNode sourceElement = (FlowNode) process.getFlowElement(sourceElementId, true);
FlowNode targetElement = (FlowNode) process.getFlowElement(targetElementId, true);
if (sourceElement == null) {
throw new ActivitiException("Invalid sourceElementId '" + sourceElementId
+ "': no element found for this id n process definition '" + processDefinitionId + "'");
}
if (targetElement == null) {
throw new ActivitiException("Invalid targetElementId '" + targetElementId
+ "': no element found for this id n process definition '" + processDefinitionId + "'");
}
Set<String> visitedElements = new HashSet<String>();
return isReachable(process, sourceElement, targetElement, visitedElements);
}
public static boolean isReachable(Process process, FlowNode sourceElement, FlowNode targetElement, Set<String> visitedElements) {
// No outgoing seq flow: could be the end of eg . the process or an embedded subprocess
if (sourceElement.getOutgoingFlows().size() == 0) {
visitedElements.add(sourceElement.getId());
FlowElementsContainer parentElement = process.findParent(sourceElement);
if (parentElement != null && parentElement instanceof SubProcess) {
sourceElement = (SubProcess) parentElement;
} else {
return false;
}
}
if (sourceElement.getId().equals(targetElement.getId())) {
return true;
}
// To avoid infinite looping, we must capture every node we visit
// and check before going further in the graph if we have already visitedthe node.
visitedElements.add(sourceElement.getId());
List<SequenceFlow> sequenceFlows = sourceElement.getOutgoingFlows();
if (sequenceFlows != null && sequenceFlows.size() > 0) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
String targetRef = sequenceFlow.getTargetRef();
FlowNode sequenceFlowTarget = (FlowNode) process.getFlowElement(targetRef, true);
if (sequenceFlowTarget != null && !visitedElements.contains(sequenceFlowTarget.getId())) {
boolean reachable = isReachable(process, sequenceFlowTarget, targetElement, visitedElements);
if (reachable) {
return true;
}
}
}
}
return false;
}
}
......@@ -251,6 +251,13 @@
where ACT_ID_ = #{parameter.activityId}
</select>
<select id="selectInactiveExecutionsForProcessInstance" parameterType="org.activiti.engine.impl.db.ListQueryParameterObject" resultMap="executionResultMap">
select *
from ${prefix}ACT_RU_EXECUTION
where PROC_INST_ID_ = #{parameter.processInstanceId}
and IS_ACTIVE_ = 0
</select>
<select id="selectExecutionsByQueryCriteria" parameterType="org.activiti.engine.impl.ExecutionQueryImpl" resultMap="executionResultMap">
${limitBefore}
select distinct RES.* ${limitBetween}, P.KEY_ as ProcessDefinitionKey, P.ID_ as ProcessDefinitionId
......
......@@ -326,5 +326,89 @@ public class Activiti6Tests extends AbstractActvitiTest {
assertEquals(0, runtimeService.createExecutionQuery().count());
}
@Test
@org.activiti.engine.test.Deployment
public void testNonInterruptingMoreComplex2() {
// Use case 1: no timers fire
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("nonInterruptingWithInclusiveMerge");
assertNotNull(processInstance);
assertFalse(processInstance.isEnded());
List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).orderByTaskName().asc().list();
assertEquals(2, tasks.size());
assertEquals("A", tasks.get(0).getName());
assertEquals("B", tasks.get(1).getName());
assertEquals(2, managementService.createJobQuery().count());
// Completing A
taskService.complete(tasks.get(0).getId());
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).orderByTaskName().asc().list();
assertEquals(1, tasks.size());
assertEquals("B", tasks.get(0).getName());
assertEquals(1, managementService.createJobQuery().count());
// Completing B should end the process
taskService.complete(tasks.get(0).getId());
assertEquals(0, managementService.createJobQuery().count());
assertEquals(0, runtimeService.createExecutionQuery().count());
// Use case 2: The non interrupting timer on B fires
processInstance = runtimeService.startProcessInstanceByKey("nonInterruptingWithInclusiveMerge");
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).orderByTaskName().asc().list();
assertEquals(2, tasks.size());
assertEquals("A", tasks.get(0).getName());
assertEquals("B", tasks.get(1).getName());
assertEquals(2, managementService.createJobQuery().count());
// Completing B
taskService.complete(tasks.get(1).getId());
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).orderByTaskName().asc().list();
assertEquals(1, tasks.size());
assertEquals("A", tasks.get(0).getName());
assertEquals(1, managementService.createJobQuery().count());
// Firing the timer should activate E and F too
managementService.executeJob(managementService.createJobQuery().singleResult().getId());
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).orderByTaskName().asc().list();
assertEquals(3, tasks.size());
assertEquals("A", tasks.get(0).getName());
assertEquals("C", tasks.get(1).getName());
assertEquals("D", tasks.get(2).getName());
// Firing the timer on D
assertEquals(1, managementService.createJobQuery().count());
managementService.executeJob(managementService.createJobQuery().singleResult().getId());
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).orderByTaskName().asc().list();
assertEquals(4, tasks.size());
assertEquals("A", tasks.get(0).getName());
assertEquals("C", tasks.get(1).getName());
assertEquals("D", tasks.get(2).getName());
assertEquals("G", tasks.get(3).getName());
// Completing C, D, A and G in that order to give the engine a bit of excercise
taskService.complete(taskService.createTaskQuery().taskName("C").singleResult().getId());
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).orderByTaskName().asc().list();
assertEquals(3, tasks.size());
assertEquals("A", tasks.get(0).getName());
assertEquals("D", tasks.get(1).getName());
assertEquals("G", tasks.get(2).getName());
taskService.complete(taskService.createTaskQuery().taskName("D").singleResult().getId());
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).orderByTaskName().asc().list();
assertEquals(2, tasks.size());
assertEquals("A", tasks.get(0).getName());
assertEquals("G", tasks.get(1).getName());
taskService.complete(taskService.createTaskQuery().taskName("A").singleResult().getId());
tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).orderByTaskName().asc().list();
assertEquals(1, tasks.size());
assertEquals("G", tasks.get(0).getName());
taskService.complete(taskService.createTaskQuery().taskName("G").singleResult().getId());
assertEquals(0, runtimeService.createExecutionQuery().count());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef" xmlns:modeler="http://activiti.com/modeler" modeler:version="1.0en" modeler:exportDateTime="20140421160834832">
<process id="nonInterruptingWithInclusiveMerge" isExecutable="true">
<startEvent id="sid-4EE6BBAE-E5AC-4DEB-AE43-C8732945403F"></startEvent>
<sequenceFlow id="sid-7B273A13-5CDA-46FA-9024-85EA2487FDD3" sourceRef="sid-4EE6BBAE-E5AC-4DEB-AE43-C8732945403F" targetRef="fork1"></sequenceFlow>
<parallelGateway id="fork1"></parallelGateway>
<userTask id="taskA" name="A" activiti:exclusive="false" activiti:assignee="kermit"></userTask>
<sequenceFlow id="sid-4F0D9C1E-46CD-4B47-BFA1-CC67B2A288CF" sourceRef="fork1" targetRef="taskA"></sequenceFlow>
<userTask id="taskB" name="B" activiti:exclusive="false" activiti:assignee="kermit"></userTask>
<sequenceFlow id="sid-DE3ED8E8-4696-45C1-AD10-245FCCEF676E" sourceRef="fork1" targetRef="taskB"></sequenceFlow>
<boundaryEvent id="sid-A1F1D200-DCC6-44AB-964D-162F594A1724" attachedToRef="taskA" cancelActivity="false">
<timerEventDefinition>
<timeDuration>PT5M</timeDuration>
</timerEventDefinition>
</boundaryEvent>
<sequenceFlow id="sid-99FEBF72-9C07-427C-9D83-46758A6A5ECC" sourceRef="sid-A1F1D200-DCC6-44AB-964D-162F594A1724" targetRef="fork2"></sequenceFlow>
<parallelGateway id="fork2"></parallelGateway>
<userTask id="taskD" name="D" activiti:exclusive="false" activiti:assignee="kermit"></userTask>
<sequenceFlow id="sid-3B8C92A0-5605-4218-8C3D-991FD817867F" sourceRef="fork2" targetRef="taskD"></sequenceFlow>
<userTask id="taskC" name="C" activiti:exclusive="false" activiti:assignee="kermit"></userTask>
<sequenceFlow id="sid-5C720FB7-AAE2-4129-89C2-D65B9E9D467C" sourceRef="fork2" targetRef="taskC"></sequenceFlow>
<boundaryEvent id="sid-DC4DD5EF-F7F2-4FB3-A26F-75AC52593F0C" attachedToRef="taskB" cancelActivity="true">
<timerEventDefinition>
<timeDuration>PT5M</timeDuration>
</timerEventDefinition>
</boundaryEvent>
<sequenceFlow id="sid-1C30450C-2EF9-49FA-AE6F-F4C5605F5482" sourceRef="sid-DC4DD5EF-F7F2-4FB3-A26F-75AC52593F0C" targetRef="fork3"></sequenceFlow>
<parallelGateway id="fork3"></parallelGateway>
<userTask id="taskE" name="E" activiti:exclusive="false" activiti:assignee="kermit"></userTask>
<sequenceFlow id="sid-684C089F-C914-4658-99E9-EA0C78368AE4" sourceRef="fork3" targetRef="taskE"></sequenceFlow>
<userTask id="taskF" name="F" activiti:exclusive="false" activiti:assignee="kermit"></userTask>
<sequenceFlow id="sid-D95AFE80-F75A-4690-B439-FEE684323242" sourceRef="fork3" targetRef="taskF"></sequenceFlow>
<boundaryEvent id="sid-A8E37A3C-0556-4C3D-849D-C0B2670E9B33" attachedToRef="taskD" cancelActivity="false">
<timerEventDefinition>
<timeDuration>PT5M</timeDuration>
</timerEventDefinition>
</boundaryEvent>
<userTask id="taskG" name="G" activiti:exclusive="false" activiti:assignee="kermit"></userTask>
<sequenceFlow id="sid-B3302BE0-25E6-4B8A-9A98-394763BFFD57" sourceRef="sid-A8E37A3C-0556-4C3D-849D-C0B2670E9B33" targetRef="taskG"></sequenceFlow>
<sequenceFlow id="sid-93BBCB2F-975D-4850-A03C-F4697F754B57" sourceRef="taskF" targetRef="join3"></sequenceFlow>
<sequenceFlow id="sid-77D89B22-F86D-4B4A-979A-5DF67E87EB27" sourceRef="taskE" targetRef="join3"></sequenceFlow>
<exclusiveGateway id="sid-3C9987C3-A192-47B4-A151-1A2BF6731DB1"></exclusiveGateway>
<sequenceFlow id="sid-654D7D7F-813D-4706-8BFE-91C995E62E89" sourceRef="taskB" targetRef="sid-3C9987C3-A192-47B4-A151-1A2BF6731DB1"></sequenceFlow>
<sequenceFlow id="sid-85955B9A-B371-4726-86C1-51BA8A3A52A9" sourceRef="join3" targetRef="sid-3C9987C3-A192-47B4-A151-1A2BF6731DB1"></sequenceFlow>
<sequenceFlow id="sid-481279FB-8AC2-41DB-9ECA-FD3DB21F7FFB" sourceRef="taskD" targetRef="join1"></sequenceFlow>
<sequenceFlow id="sid-2D6D3AF1-841B-43C8-9CD4-811C6D052A49" sourceRef="taskA" targetRef="join1"></sequenceFlow>
<sequenceFlow id="sid-907B6B46-1653-4C12-9424-ADEC628CDEB4" sourceRef="sid-3C9987C3-A192-47B4-A151-1A2BF6731DB1" targetRef="join2"></sequenceFlow>
<parallelGateway id="join2"></parallelGateway>
<sequenceFlow id="sid-DF04F892-5C6D-42F3-A334-23B4EE55E2E0" sourceRef="join1" targetRef="join2"></sequenceFlow>
<endEvent id="sid-540A205A-9797-4241-976D-FC1C3F1054A7"></endEvent>
<sequenceFlow id="sid-3CA8B470-1EFA-4D08-AD92-C3EBB9207AB5" sourceRef="join2" targetRef="sid-540A205A-9797-4241-976D-FC1C3F1054A7"></sequenceFlow>
<sequenceFlow id="sid-BE07659D-C512-497D-B2E9-06DD7CD43596" sourceRef="taskC" targetRef="join1"></sequenceFlow>
<endEvent id="sid-B956592B-3523-4AEE-8859-7E15190DF6A3"></endEvent>
<sequenceFlow id="sid-9B38FA93-D212-4A0E-9D16-8BD97C66C0B7" sourceRef="taskG" targetRef="sid-B956592B-3523-4AEE-8859-7E15190DF6A3"></sequenceFlow>
<inclusiveGateway id="join1"></inclusiveGateway>
<inclusiveGateway id="join3"></inclusiveGateway>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_process">
<bpmndi:BPMNPlane bpmnElement="process" id="BPMNPlane_process">
<bpmndi:BPMNShape bpmnElement="sid-4EE6BBAE-E5AC-4DEB-AE43-C8732945403F" id="BPMNShape_sid-4EE6BBAE-E5AC-4DEB-AE43-C8732945403F">
<omgdc:Bounds height="30.0" width="30.0" x="76.875" y="396.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="fork1" id="BPMNShape_fork1">
<omgdc:Bounds height="40.0" width="40.0" x="152.3125" y="391.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="taskA" id="BPMNShape_taskA">
<omgdc:Bounds height="80.0" width="100.0" x="236.19653575831592" y="300.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="taskB" id="BPMNShape_taskB">
<omgdc:Bounds height="80.0" width="100.0" x="237.3125" y="480.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-A1F1D200-DCC6-44AB-964D-162F594A1724" id="BPMNShape_sid-A1F1D200-DCC6-44AB-964D-162F594A1724">
<omgdc:Bounds height="31.0" width="31.0" x="270.1385536374739" y="283.6875934469829"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="fork2" id="BPMNShape_fork2">
<omgdc:Bounds height="40.0" width="40.0" x="267.13177681873697" y="150.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="taskD" id="BPMNShape_taskD">
<omgdc:Bounds height="80.0" width="100.0" x="390.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="taskC" id="BPMNShape_taskC">
<omgdc:Bounds height="80.0" width="100.0" x="390.0" y="15.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-DC4DD5EF-F7F2-4FB3-A26F-75AC52593F0C" id="BPMNShape_sid-DC4DD5EF-F7F2-4FB3-A26F-75AC52593F0C">
<omgdc:Bounds height="31.0" width="31.0" x="264.1921956739772" y="545.4831493164252"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="fork3" id="BPMNShape_fork3">
<omgdc:Bounds height="40.0" width="40.0" x="260.0382935109658" y="615.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="taskE" id="BPMNShape_taskE">
<omgdc:Bounds height="80.0" width="100.0" x="345.0382935109658" y="595.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="taskF" id="BPMNShape_taskF">
<omgdc:Bounds height="80.0" width="100.0" x="345.0" y="720.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-A8E37A3C-0556-4C3D-849D-C0B2670E9B33" id="BPMNShape_sid-A8E37A3C-0556-4C3D-849D-C0B2670E9B33">
<omgdc:Bounds height="31.0" width="31.0" x="430.2235671828906" y="195.08529456958772"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="taskG" id="BPMNShape_taskG">
<omgdc:Bounds height="80.00000000000003" width="100.0" x="525.0" y="239.99999999999997"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-3C9987C3-A192-47B4-A151-1A2BF6731DB1" id="BPMNShape_sid-3C9987C3-A192-47B4-A151-1A2BF6731DB1">
<omgdc:Bounds height="40.0" width="40.0" x="630.0" y="501.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="join2" id="BPMNShape_join2">
<omgdc:Bounds height="40.0" width="40.0" x="795.0" y="501.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-540A205A-9797-4241-976D-FC1C3F1054A7" id="BPMNShape_sid-540A205A-9797-4241-976D-FC1C3F1054A7">
<omgdc:Bounds height="28.0" width="28.0" x="880.0" y="507.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-B956592B-3523-4AEE-8859-7E15190DF6A3" id="BPMNShape_sid-B956592B-3523-4AEE-8859-7E15190DF6A3">
<omgdc:Bounds height="28.0" width="28.0" x="670.0" y="266.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="join1" id="BPMNShape_join1">
<omgdc:Bounds height="40.0" width="40.0" x="720.0" y="150.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="join3" id="BPMNShape_join3">
<omgdc:Bounds height="40.0" width="40.0" x="630.0" y="615.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-481279FB-8AC2-41DB-9ECA-FD3DB21F7FFB" id="BPMNEdge_sid-481279FB-8AC2-41DB-9ECA-FD3DB21F7FFB">
<omgdi:waypoint x="490.0" y="170.0"></omgdi:waypoint>
<omgdi:waypoint x="720.0" y="170.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-2D6D3AF1-841B-43C8-9CD4-811C6D052A49" id="BPMNEdge_sid-2D6D3AF1-841B-43C8-9CD4-811C6D052A49">
<omgdi:waypoint x="336.1965357583159" y="340.0"></omgdi:waypoint>
<omgdi:waypoint x="740.0" y="340.0"></omgdi:waypoint>
<omgdi:waypoint x="740.0" y="190.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-5C720FB7-AAE2-4129-89C2-D65B9E9D467C" id="BPMNEdge_sid-5C720FB7-AAE2-4129-89C2-D65B9E9D467C">
<omgdi:waypoint x="287.63177681873697" y="150.5"></omgdi:waypoint>
<omgdi:waypoint x="287.63177681873697" y="55.0"></omgdi:waypoint>
<omgdi:waypoint x="390.0" y="55.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-907B6B46-1653-4C12-9424-ADEC628CDEB4" id="BPMNEdge_sid-907B6B46-1653-4C12-9424-ADEC628CDEB4">
<omgdi:waypoint x="669.5" y="521.5"></omgdi:waypoint>
<omgdi:waypoint x="795.5" y="521.5"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-7B273A13-5CDA-46FA-9024-85EA2487FDD3" id="BPMNEdge_sid-7B273A13-5CDA-46FA-9024-85EA2487FDD3">
<omgdi:waypoint x="106.87471700209974" y="411.09214041957864"></omgdi:waypoint>
<omgdi:waypoint x="152.68605076309404" y="411.37355076309404"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-1C30450C-2EF9-49FA-AE6F-F4C5605F5482" id="BPMNEdge_sid-1C30450C-2EF9-49FA-AE6F-F4C5605F5482">
<omgdi:waypoint x="280.34503932923866" y="577.4824192628023"></omgdi:waypoint>
<omgdi:waypoint x="280.7098665920129" y="615.6715730810471"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-99FEBF72-9C07-427C-9D83-46758A6A5ECC" id="BPMNEdge_sid-99FEBF72-9C07-427C-9D83-46758A6A5ECC">
<omgdi:waypoint x="286.37227844268637" y="283.6893006407042"></omgdi:waypoint>
<omgdi:waypoint x="287.75005168273145" y="189.3817251360055"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-77D89B22-F86D-4B4A-979A-5DF67E87EB27" id="BPMNEdge_sid-77D89B22-F86D-4B4A-979A-5DF67E87EB27">
<omgdi:waypoint x="445.0382935109658" y="635.0978620253642"></omgdi:waypoint>
<omgdi:waypoint x="630.4607784237967" y="635.4607784237967"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-B3302BE0-25E6-4B8A-9A98-394763BFFD57" id="BPMNEdge_sid-B3302BE0-25E6-4B8A-9A98-394763BFFD57">
<omgdi:waypoint x="446.2235671828906" y="227.08529456958772"></omgdi:waypoint>
<omgdi:waypoint x="446.2235671828906" y="280.0"></omgdi:waypoint>
<omgdi:waypoint x="525.0" y="280.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-654D7D7F-813D-4706-8BFE-91C995E62E89" id="BPMNEdge_sid-654D7D7F-813D-4706-8BFE-91C995E62E89">
<omgdi:waypoint x="337.3125" y="520.2065049044915"></omgdi:waypoint>
<omgdi:waypoint x="630.417055469155" y="521.417055469155"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-3B8C92A0-5605-4218-8C3D-991FD817867F" id="BPMNEdge_sid-3B8C92A0-5605-4218-8C3D-991FD817867F">
<omgdi:waypoint x="306.6943310503003" y="170.43744576843662"></omgdi:waypoint>
<omgdi:waypoint x="390.0" y="170.16407620616707"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-BE07659D-C512-497D-B2E9-06DD7CD43596" id="BPMNEdge_sid-BE07659D-C512-497D-B2E9-06DD7CD43596">
<omgdi:waypoint x="490.0" y="55.0"></omgdi:waypoint>
<omgdi:waypoint x="740.5" y="55.0"></omgdi:waypoint>
<omgdi:waypoint x="740.5" y="150.5"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-4F0D9C1E-46CD-4B47-BFA1-CC67B2A288CF" id="BPMNEdge_sid-4F0D9C1E-46CD-4B47-BFA1-CC67B2A288CF">
<omgdi:waypoint x="172.8125" y="391.5"></omgdi:waypoint>
<omgdi:waypoint x="172.8125" y="340.0"></omgdi:waypoint>
<omgdi:waypoint x="236.19653575831592" y="340.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-684C089F-C914-4658-99E9-EA0C78368AE4" id="BPMNEdge_sid-684C089F-C914-4658-99E9-EA0C78368AE4">
<omgdi:waypoint x="299.6216268442991" y="635.4166666666666"></omgdi:waypoint>
<omgdi:waypoint x="345.0382935109658" y="635.2183406113537"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-9B38FA93-D212-4A0E-9D16-8BD97C66C0B7" id="BPMNEdge_sid-9B38FA93-D212-4A0E-9D16-8BD97C66C0B7">
<omgdi:waypoint x="625.0" y="280.0"></omgdi:waypoint>
<omgdi:waypoint x="670.0" y="280.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-3CA8B470-1EFA-4D08-AD92-C3EBB9207AB5" id="BPMNEdge_sid-3CA8B470-1EFA-4D08-AD92-C3EBB9207AB5">
<omgdi:waypoint x="834.6217948717949" y="521.3782051282051"></omgdi:waypoint>
<omgdi:waypoint x="880.0002839785394" y="521.0891701657418"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-85955B9A-B371-4726-86C1-51BA8A3A52A9" id="BPMNEdge_sid-85955B9A-B371-4726-86C1-51BA8A3A52A9">
<omgdi:waypoint x="650.4122807017544" y="615.4122807017544"></omgdi:waypoint>
<omgdi:waypoint x="650.0869565217391" y="540.9130434782609"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-93BBCB2F-975D-4850-A03C-F4697F754B57" id="BPMNEdge_sid-93BBCB2F-975D-4850-A03C-F4697F754B57">
<omgdi:waypoint x="445.0" y="760.0"></omgdi:waypoint>
<omgdi:waypoint x="650.5" y="760.0"></omgdi:waypoint>
<omgdi:waypoint x="650.5" y="654.5"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-DF04F892-5C6D-42F3-A334-23B4EE55E2E0" id="BPMNEdge_sid-DF04F892-5C6D-42F3-A334-23B4EE55E2E0">
<omgdi:waypoint x="759.5" y="170.5"></omgdi:waypoint>
<omgdi:waypoint x="815.0" y="170.5"></omgdi:waypoint>
<omgdi:waypoint x="815.0" y="501.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-DE3ED8E8-4696-45C1-AD10-245FCCEF676E" id="BPMNEdge_sid-DE3ED8E8-4696-45C1-AD10-245FCCEF676E">
<omgdi:waypoint x="172.8125" y="430.5"></omgdi:waypoint>
<omgdi:waypoint x="172.8125" y="520.0"></omgdi:waypoint>
<omgdi:waypoint x="237.3125" y="520.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-D95AFE80-F75A-4690-B439-FEE684323242" id="BPMNEdge_sid-D95AFE80-F75A-4690-B439-FEE684323242">
<omgdi:waypoint x="280.5382935109658" y="654.5"></omgdi:waypoint>
<omgdi:waypoint x="280.5382935109658" y="760.0"></omgdi:waypoint>
<omgdi:waypoint x="345.0" y="760.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册