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

Merge

上级 5ecd7fc8
...@@ -14,6 +14,7 @@ package org.activiti.engine.impl.agenda; ...@@ -14,6 +14,7 @@ package org.activiti.engine.impl.agenda;
import java.util.LinkedList; import java.util.LinkedList;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.pvm.delegate.ActivityExecution; import org.activiti.engine.impl.pvm.delegate.ActivityExecution;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -25,10 +26,12 @@ public class Agenda { ...@@ -25,10 +26,12 @@ public class Agenda {
private static final Logger logger = LoggerFactory.getLogger(Agenda.class); private static final Logger logger = LoggerFactory.getLogger(Agenda.class);
protected CommandContext commandContext;
protected LinkedList<Runnable> operations = new LinkedList<Runnable>(); protected LinkedList<Runnable> operations = new LinkedList<Runnable>();
public Agenda() { public Agenda(CommandContext commandContext) {
this.commandContext = commandContext;
} }
public boolean isEmpty() { public boolean isEmpty() {
...@@ -68,5 +71,21 @@ public class Agenda { ...@@ -68,5 +71,21 @@ public class Agenda {
public void planDestroyScopeOperation(ActivityExecution execution) { public void planDestroyScopeOperation(ActivityExecution execution) {
planOperation(new DestroyScopeOperation(this, 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 { ...@@ -57,7 +57,7 @@ public class EndExecutionOperation extends AbstractOperation {
// Delete current execution // Delete current execution
logger.debug("Ending execution {}", execution.getId()); logger.debug("Ending execution {}", execution.getId());
deleteExecution(commandContext, executionEntity); deleteExecution(commandContext, executionEntity);
logger.debug("Parent execution found. Continuing process using execution {}", parentExecution.getId()); logger.debug("Parent execution found. Continuing process using execution {}", parentExecution.getId());
parentExecution.setCurrentFlowElement(executionEntity.getCurrentFlowElement()); parentExecution.setCurrentFlowElement(executionEntity.getCurrentFlowElement());
agenda.planTakeOutgoingSequenceFlowsOperation(parentExecution, true); 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 { ...@@ -39,11 +39,11 @@ public class TakeOutgoingSequenceFlowsOperation extends AbstractOperation {
execution.setCurrentActivityId(currentFlowElement.getId()); execution.setCurrentActivityId(currentFlowElement.getId());
} }
// If execution is a scope, the scope must first be destroyed before we can continue // If execution is a scope (and not the process instance), the scope must first be destroyed before we can continue
// if (execution.isScope()) { if (execution.getParentId() != null && execution.isScope()) {
// coreEngine.destroyScope(commandContext, execution); agenda.planDestroyScopeOperation(execution);
// // return; // return;
// } }
// No scope, can continue // No scope, can continue
if (currentFlowElement instanceof FlowNode) { if (currentFlowElement instanceof FlowNode) {
...@@ -56,7 +56,8 @@ public class TakeOutgoingSequenceFlowsOperation extends AbstractOperation { ...@@ -56,7 +56,8 @@ public class TakeOutgoingSequenceFlowsOperation extends AbstractOperation {
protected void leaveFlowNode(FlowNode flowNode) { 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 // Determine which sequence flows can be used for leaving
List<SequenceFlow> outgoingSequenceFlow = new ArrayList<SequenceFlow>(); List<SequenceFlow> outgoingSequenceFlow = new ArrayList<SequenceFlow>();
......
...@@ -12,20 +12,16 @@ ...@@ -12,20 +12,16 @@
*/ */
package org.activiti.engine.impl.bpmn.behavior; package org.activiti.engine.impl.bpmn.behavior;
import java.util.ArrayList; import java.util.Collection;
import java.util.HashSet; import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.activiti.engine.ActivitiException; import org.activiti.engine.impl.context.Context;
import org.activiti.engine.delegate.Expression; import org.activiti.engine.impl.interceptor.CommandContext;
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.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.pvm.PvmActivity; import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.delegate.ActivityExecution; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -37,156 +33,67 @@ import org.slf4j.LoggerFactory; ...@@ -37,156 +33,67 @@ import org.slf4j.LoggerFactory;
* @author Tom Van Buskirk * @author Tom Van Buskirk
* @author Joram Barrez * @author Joram Barrez
*/ */
public class InclusiveGatewayActivityBehavior extends GatewayActivityBehavior { public class InclusiveGatewayActivityBehavior extends GatewayActivityBehavior implements InactiveActivityBehavior {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static Logger log = LoggerFactory.getLogger(InclusiveGatewayActivityBehavior.class.getName()); private static Logger logger = LoggerFactory.getLogger(InclusiveGatewayActivityBehavior.class.getName());
public void execute(ActivityExecution execution) { @Override
public void execute(ActivityExecution execution) {
execution.inactivate(); // The join in the inclusive gateway works as follows:
lockConcurrentRoot(execution); // When an execution enters it, it is inactivated.
// All the inactivated executions stay in the inclusive gateway
PvmActivity activity = execution.getActivity(); // until ALL executions that CAN reach the inclusive gateway have reached it.
if (!activeConcurrentExecutionsExist(execution)) { //
// This check is repeated on execution changes until the inactivated
if (log.isDebugEnabled()) { // executions leave the gateway.
log.debug("inclusive gateway '{}' activates", activity.getId());
} execution.inactivate();
executeInclusiveGatewayLogic((ExecutionEntity) execution);
List<ActivityExecution> joinedExecutions = execution.findInactiveConcurrentExecutions(activity); }
String defaultSequenceFlow = (String) execution.getActivity().getProperty("default");
List<PvmTransition> transitionsToTake = new ArrayList<PvmTransition>(); @Override
public void executeInactive(ExecutionEntity executionEntity) {
for (PvmTransition outgoingTransition : execution.getActivity().getOutgoingTransitions()) { executeInclusiveGatewayLogic(executionEntity);
}
Expression skipExpression = outgoingTransition.getSkipExpression();
if (!SkipExpressionUtil.isSkipExpressionEnabled(execution, skipExpression)) { protected void executeInclusiveGatewayLogic(ExecutionEntity execution) {
if (defaultSequenceFlow == null || !outgoingTransition.getId().equals(defaultSequenceFlow)) { CommandContext commandContext = Context.getCommandContext();
Condition condition = (Condition) outgoingTransition.getProperty(BpmnParse.PROPERTYNAME_CONDITION); ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
if (condition == null || condition.evaluate(execution)) { Collection<ExecutionEntity> allExecutions = executionEntityManager
transitionsToTake.add(outgoingTransition); .findChildExecutionsByProcessInstanceId(execution.getProcessInstanceId());
} Iterator<ExecutionEntity> executionIterator = allExecutions.iterator();
} boolean oneExecutionCanReachGateway = false;
} while (!oneExecutionCanReachGateway && executionIterator.hasNext()) {
else if (SkipExpressionUtil.shouldSkipFlowElement(execution, skipExpression)){ ExecutionEntity executionEntity = executionIterator.next();
transitionsToTake.add(outgoingTransition); if (!executionEntity.getActivityId().equals(execution.getCurrentActivityId())) {
} boolean canReachGateway = ExecutionGraphUtil.isReachable(execution.getProcessDefinitionId(),
} executionEntity.getActivityId(), execution.getCurrentActivityId());
if (canReachGateway) {
if (!transitionsToTake.isEmpty()) { oneExecutionCanReachGateway = true;
execution.takeAll(transitionsToTake, joinedExecutions); }
}
} else { }
if (defaultSequenceFlow != null) { // If no execution can reach the gateway, the gateway activates and executes fork behavior
PvmTransition defaultTransition = execution.getActivity().findOutgoingTransition(defaultSequenceFlow); if (!oneExecutionCanReachGateway) {
if (defaultTransition != null) {
execution.take(defaultTransition); logger.debug("Inclusive gateway cannot be reached by any execution and is activated");
} else {
throw new ActivitiException("Default sequence flow '" // Kill all executions here (except the incoming)
+ defaultSequenceFlow + "' could not be not found"); Collection<ExecutionEntity> executionsInGateway = executionEntityManager.getInactiveExecutionsInActivity(execution.getCurrentActivityId());
} for (ExecutionEntity executionEntityInGateway : executionsInGateway) {
} else { if (!executionEntityInGateway.getId().equals(execution.getId())) {
// No sequence flow could be found, not even a default one executionEntityManager.delete(executionEntityInGateway);
throw new ActivitiException( }
"No outgoing sequence flow of the inclusive gateway '" }
+ execution.getActivity().getId()
+ "' could be selected for continuing the process"); // Leave
}
} // TODO: default sequence flow
} else { commandContext.getAgenda().planTakeOutgoingSequenceFlowsOperation(execution, true);
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;
}
} }
...@@ -12,11 +12,9 @@ ...@@ -12,11 +12,9 @@
*/ */
package org.activiti.engine.impl.bpmn.parser.handler; 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.BaseElement;
import org.activiti.bpmn.model.InclusiveGateway; import org.activiti.bpmn.model.InclusiveGateway;
import org.activiti.engine.impl.bpmn.parser.BpmnParse; 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 ...@@ -29,12 +27,7 @@ public class InclusiveGatewayParseHandler extends AbstractActivityBpmnParseHandl
} }
protected void executeParse(BpmnParse bpmnParse, InclusiveGateway gateway) { protected void executeParse(BpmnParse bpmnParse, InclusiveGateway gateway) {
ActivityImpl activity = createActivityOnCurrentScope(bpmnParse, gateway, BpmnXMLConstants.ELEMENT_GATEWAY_INCLUSIVE); gateway.setBehavior(bpmnParse.getActivityBehaviorFactory().createInclusiveGatewayActivityBehavior(gateway));
activity.setAsync(gateway.isAsynchronous());
activity.setExclusive(!gateway.isNotExclusive());
activity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createInclusiveGatewayActivityBehavior(gateway));
} }
} }
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
*/ */
package org.activiti.engine.impl.cmd; package org.activiti.engine.impl.cmd;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.activiti.engine.ActivitiException; import org.activiti.engine.ActivitiException;
...@@ -57,7 +58,7 @@ public abstract class AbstractSetProcessInstanceStateCmd implements Command<Void ...@@ -57,7 +58,7 @@ public abstract class AbstractSetProcessInstanceStateCmd implements Command<Void
SuspensionStateUtil.setSuspensionState(executionEntity, getNewState()); SuspensionStateUtil.setSuspensionState(executionEntity, getNewState());
// All child executions are suspended // All child executions are suspended
List<ExecutionEntity> childExecutions = commandContext.getExecutionEntityManager().findChildExecutionsByProcessInstanceId(executionId); Collection<ExecutionEntity> childExecutions = commandContext.getExecutionEntityManager().findChildExecutionsByProcessInstanceId(executionId);
for (ExecutionEntity childExecution : childExecutions) { for (ExecutionEntity childExecution : childExecutions) {
if (!childExecution.getId().equals(executionId)) { if (!childExecution.getId().equals(executionId)) {
SuspensionStateUtil.setSuspensionState(childExecution, getNewState()); SuspensionStateUtil.setSuspensionState(childExecution, getNewState());
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
package org.activiti.engine.impl.cmd; package org.activiti.engine.impl.cmd;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.activiti.engine.ActivitiException; import org.activiti.engine.ActivitiException;
...@@ -113,7 +114,7 @@ public class SetProcessDefinitionVersionCmd implements Command<Void>, Serializab ...@@ -113,7 +114,7 @@ public class SetProcessDefinitionVersionCmd implements Command<Void>, Serializab
commandContext.getHistoryManager().recordProcessDefinitionChange(processInstanceId, newProcessDefinition.getId()); commandContext.getHistoryManager().recordProcessDefinitionChange(processInstanceId, newProcessDefinition.getId());
// switch all sub-executions of the process instance to the new process definition version // switch all sub-executions of the process instance to the new process definition version
List<ExecutionEntity> childExecutions = executionManager Collection<ExecutionEntity> childExecutions = executionManager
.findChildExecutionsByProcessInstanceId(processInstanceId); .findChildExecutionsByProcessInstanceId(processInstanceId);
for (ExecutionEntity executionEntity : childExecutions) { for (ExecutionEntity executionEntity : childExecutions) {
validateAndSwitchVersionOfExecution(commandContext, executionEntity, newProcessDefinition); validateAndSwitchVersionOfExecution(commandContext, executionEntity, newProcessDefinition);
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
package org.activiti.engine.impl.interceptor; package org.activiti.engine.impl.interceptor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
...@@ -36,6 +37,7 @@ import org.activiti.engine.impl.persistence.entity.CommentEntityManager; ...@@ -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.DeploymentEntityManager;
import org.activiti.engine.impl.persistence.entity.EventLogEntryEntityManager; import org.activiti.engine.impl.persistence.entity.EventLogEntryEntityManager;
import org.activiti.engine.impl.persistence.entity.EventSubscriptionEntityManager; 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.ExecutionEntityManager;
import org.activiti.engine.impl.persistence.entity.GroupIdentityManager; import org.activiti.engine.impl.persistence.entity.GroupIdentityManager;
import org.activiti.engine.impl.persistence.entity.HistoricActivityInstanceEntityManager; import org.activiti.engine.impl.persistence.entity.HistoricActivityInstanceEntityManager;
...@@ -82,9 +84,11 @@ public class CommandContext { ...@@ -82,9 +84,11 @@ public class CommandContext {
protected List<CommandContextCloseListener> closeListeners; protected List<CommandContextCloseListener> closeListeners;
protected Map<String, Object> attributes; // General-purpose storing of anything during the lifetime of a command context 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; protected Object result = null;
public void performOperation(AtomicOperation executionOperation, InterpretableExecution execution) { public void performOperation(AtomicOperation executionOperation, InterpretableExecution execution) {
nextOperations.add(executionOperation); nextOperations.add(executionOperation);
if (nextOperations.size()==1) { if (nextOperations.size()==1) {
...@@ -382,6 +386,22 @@ public class CommandContext { ...@@ -382,6 +386,22 @@ public class CommandContext {
return getSession(HistoryManager.class); 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 ////////////////////////////////////////////////////// // getters and setters //////////////////////////////////////////////////////
public TransactionContext getTransactionContext() { public TransactionContext getTransactionContext() {
......
...@@ -37,13 +37,12 @@ public class CommandInvoker extends AbstractCommandInterceptor { ...@@ -37,13 +37,12 @@ public class CommandInvoker extends AbstractCommandInterceptor {
// Run loop for agenda // Run loop for agenda
executeOperations(commandContext); executeOperations(commandContext);
// // At the end, call the execution tree change listeners. // At the end, call the execution tree change listeners.
// // TODO: optimization: only do this when the tree has actually changed // TODO: optimization: only do this when the tree has actually changed (ie check dbSqlSession).
// // (ie check dbSqlSession). if (commandContext.hasInvolvedExecutions()) {
// if (commandContext.hasInvolvedExecutions()) { commandContext.getAgenda().planExecuteInactiveBehaviorsOperation();
// commandContext.getAgenda().add(new ExecuteInactivatedBehavior(commandContext.getCoreEngine(), commandContext)); executeOperations(commandContext);
// executeOperations(commandContext); }
// }
return (T) commandContext.getResult(); return (T) commandContext.getResult();
} }
......
...@@ -276,9 +276,7 @@ public class ExecutionEntity extends VariableScopeImpl implements ActivityExecut ...@@ -276,9 +276,7 @@ public class ExecutionEntity extends VariableScopeImpl implements ActivityExecut
// initialize the new execution // initialize the new execution
createdExecution.setProcessDefinitionId(this.getProcessDefinitionId()); createdExecution.setProcessDefinitionId(this.getProcessDefinitionId());
createdExecution.setProcessInstanceId(this.getProcessInstanceId() != null ? this.getProcessInstanceId() : this.getId()); createdExecution.setProcessInstanceId(this.getProcessInstanceId() != null ? this.getProcessInstanceId() : this.getId());
// createdExecution.setProcessDefinition(getProcessDefinition()); createdExecution.setScope(false);
// createdExecution.setProcessInstance(getProcessInstance());
// createdExecution.setActivity(getActivity());
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Child execution {} created with parent {}", createdExecution, this.getId()); log.debug("Child execution {} created with parent {}", createdExecution, this.getId());
......
...@@ -107,8 +107,13 @@ public class ExecutionEntityManager extends AbstractEntityManager<ExecutionEntit ...@@ -107,8 +107,13 @@ public class ExecutionEntityManager extends AbstractEntityManager<ExecutionEntit
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public List<ExecutionEntity> findChildExecutionsByProcessInstanceId(String processInstanceId) { public Collection<ExecutionEntity> findChildExecutionsByProcessInstanceId(final String processInstanceId) {
return getDbSqlSession().selectList("selectExecutionsByProcessInstanceId", 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) { public ExecutionEntity findExecutionById(String executionId) {
...@@ -185,6 +190,18 @@ public class ExecutionEntityManager extends AbstractEntityManager<ExecutionEntit ...@@ -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") @SuppressWarnings("unchecked")
public List<Execution> findExecutionsByNativeQuery(Map<String, Object> parameterMap, int firstResult, int maxResults) { public List<Execution> findExecutionsByNativeQuery(Map<String, Object> parameterMap, int firstResult, int maxResults) {
return getDbSqlSession().selectListWithRawParameter("selectExecutionByNativeQuery", parameterMap, firstResult, 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 @@ ...@@ -251,6 +251,13 @@
where ACT_ID_ = #{parameter.activityId} where ACT_ID_ = #{parameter.activityId}
</select> </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"> <select id="selectExecutionsByQueryCriteria" parameterType="org.activiti.engine.impl.ExecutionQueryImpl" resultMap="executionResultMap">
${limitBefore} ${limitBefore}
select distinct RES.* ${limitBetween}, P.KEY_ as ProcessDefinitionKey, P.ID_ as ProcessDefinitionId select distinct RES.* ${limitBetween}, P.KEY_ as ProcessDefinitionKey, P.ID_ as ProcessDefinitionId
......
...@@ -326,5 +326,89 @@ public class Activiti6Tests extends AbstractActvitiTest { ...@@ -326,5 +326,89 @@ public class Activiti6Tests extends AbstractActvitiTest {
assertEquals(0, runtimeService.createExecutionQuery().count()); 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.
先完成此消息的编辑!
想要评论请 注册