/* 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.bpmn.behavior; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import org.activiti.engine.ActivitiException; import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.pvm.delegate.ActivityBehavior; import org.activiti.engine.impl.pvm.delegate.ActivityExecution; import org.activiti.engine.impl.pvm.process.ActivityImpl; /** * @author Joram Barrez */ public class ParallelMultiInstanceBehavior extends MultiInstanceActivityBehavior { public ParallelMultiInstanceBehavior(ActivityImpl activity, AbstractBpmnActivityBehavior originalActivityBehavior) { super(activity, originalActivityBehavior); } /** * Handles the parallel case of spawning the instances. * Will create child executions accordingly for every instance needed. */ protected void createInstances(ActivityExecution execution) throws Exception { int nrOfInstances = resolveNrOfInstances(execution); if (nrOfInstances <= 0) { throw new ActivitiException("Invalid number of instances: must be positive integer value" + ", but was " + nrOfInstances); } setLoopVariable(execution, NUMBER_OF_INSTANCES, nrOfInstances); setLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES, 0); setLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES, nrOfInstances); List<ActivityExecution> concurrentExecutions = new ArrayList<ActivityExecution>(); for (int loopCounter=0; loopCounter<nrOfInstances; loopCounter++) { ActivityExecution concurrentExecution = execution.createExecution(); concurrentExecution.setActive(true); concurrentExecution.setConcurrent(true); concurrentExecution.setScope(false); // In case of an embedded subprocess, and extra child execution is required // Otherwise, all child executions would end up under the same parent, // without any differentation to which embedded subprocess they belong if (isExtraScopeNeeded()) { ActivityExecution extraScopedExecution = concurrentExecution.createExecution(); extraScopedExecution.setActive(true); extraScopedExecution.setConcurrent(false); extraScopedExecution.setScope(true); concurrentExecution = extraScopedExecution; } concurrentExecutions.add(concurrentExecution); logLoopDetails(concurrentExecution, "initialized", loopCounter, 0, nrOfInstances, nrOfInstances); } // Before the activities are executed, all executions MUST be created up front // Do not try to merge this loop with the previous one, as it will lead to bugs, // due to possible child execution pruning. for (int loopCounter=0; loopCounter<nrOfInstances; loopCounter++) { ActivityExecution concurrentExecution = concurrentExecutions.get(loopCounter); // executions can be inactive, if instances are all automatics (no-waitstate) // and completionCondition has been met in the meantime if (concurrentExecution.isActive() && !concurrentExecution.isEnded() && concurrentExecution.getParent().isActive() && !concurrentExecution.getParent().isEnded()) { setLoopVariable(concurrentExecution, LOOP_COUNTER, loopCounter); executeOriginalBehavior(concurrentExecution, loopCounter); } } } /** * Called when the wrapped {@link ActivityBehavior} calls the * {@link AbstractBpmnActivityBehavior#leave(ActivityExecution)} method. * Handles the completion of one of the parallel instances */ public void leave(ActivityExecution execution) { callActivityEndListeners(execution); int loopCounter = getLoopVariable(execution, LOOP_COUNTER); int nrOfInstances = getLoopVariable(execution, NUMBER_OF_INSTANCES); int nrOfCompletedInstances = getLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES) + 1; int nrOfActiveInstances = getLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES) - 1; if (isExtraScopeNeeded()) { // In case an extra scope was created, it must be destroyed first before going further ExecutionEntity extraScope = (ExecutionEntity) execution; execution = execution.getParent(); extraScope.remove(); } setLoopVariable(execution.getParent(), NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances); setLoopVariable(execution.getParent(), NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances); logLoopDetails(execution, "instance completed", loopCounter, nrOfCompletedInstances, nrOfActiveInstances, nrOfInstances); ExecutionEntity executionEntity = (ExecutionEntity) execution; executionEntity.inactivate(); executionEntity.getParent().forceUpdate(); List<ActivityExecution> joinedExecutions = executionEntity.findInactiveConcurrentExecutions(execution.getActivity()); if (joinedExecutions.size() == nrOfInstances || completionConditionSatisfied(execution)) { // Removing all active child executions (ie because completionCondition is true) List<ExecutionEntity> executionsToRemove = new ArrayList<ExecutionEntity>(); for (ActivityExecution childExecution : executionEntity.getParent().getExecutions()) { if (childExecution.isActive()) { executionsToRemove.add((ExecutionEntity) childExecution); } } for (ExecutionEntity executionToRemove : executionsToRemove) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Execution " + executionToRemove + " still active, " + "but multi-instance is completed. Removing this execution."); } executionToRemove.inactivate(); executionToRemove.deleteCascade("multi-instance completed"); } executionEntity.takeAll(executionEntity.getActivity().getOutgoingTransitions(), joinedExecutions); } } }