/* 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.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.delegate.BpmnError;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.Expression;
import org.activiti.engine.impl.bpmn.helper.ErrorPropagation;
import org.activiti.engine.impl.bpmn.helper.ScopeUtil;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.delegate.ExecutionListenerInvocation;
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.delegate.CompositeActivityBehavior;
import org.activiti.engine.impl.pvm.delegate.SubProcessActivityBehavior;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.runtime.AtomicOperation;
/**
* Implementation of the multi-instance functionality as described in the BPMN 2.0 spec.
*
* Multi instance functionality is implemented as an {@link ActivityBehavior} that
* wraps the original {@link ActivityBehavior} of the activity.
*
* Only subclasses of {@link AbstractBpmnActivityBehavior} can have multi-instance
* behavior. As such, special logic is contained in the {@link AbstractBpmnActivityBehavior}
* to delegate to the {@link MultiInstanceActivityBehavior} if needed.
*
* @author Joram Barrez
* @author Falko Menge
*/
public abstract class MultiInstanceActivityBehavior extends FlowNodeActivityBehavior
implements CompositeActivityBehavior, SubProcessActivityBehavior {
protected static final Logger LOGGER = Logger.getLogger(MultiInstanceActivityBehavior.class.getName());
// Variable names for outer instance(as described in spec)
protected final String NUMBER_OF_INSTANCES = "nrOfInstances";
protected final String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";
protected final String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";
// Variable names for inner instances (as described in the spec)
protected final String LOOP_COUNTER = "loopCounter";
// Instance members
protected ActivityImpl activity;
protected AbstractBpmnActivityBehavior innerActivityBehavior;
protected Expression loopCardinalityExpression;
protected Expression completionConditionExpression;
protected Expression collectionExpression;
protected String collectionVariable;
protected String collectionElementVariable;
/**
* @param innerActivityBehavior The original {@link ActivityBehavior} of the activity
* that will be wrapped inside this behavior.
* @param isSequential Indicates whether the multi instance behavior
* must be sequential or parallel
*/
public MultiInstanceActivityBehavior(ActivityImpl activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
this.activity = activity;
setInnerActivityBehavior(innerActivityBehavior);
}
public void execute(ActivityExecution execution) throws Exception {
if (getLoopVariable(execution, LOOP_COUNTER) == null) {
try {
createInstances(execution);
} catch (BpmnError error) {
ErrorPropagation.propagateError(error, execution);
}
} else {
innerActivityBehavior.execute(execution);
}
}
protected abstract void createInstances(ActivityExecution execution) throws Exception;
// Intercepts signals, and delegates it to the wrapped {@link ActivityBehavior}.
public void signal(ActivityExecution execution, String signalName, Object signalData) throws Exception {
innerActivityBehavior.signal(execution, signalName, signalData);
}
// required for supporting embedded subprocesses
public void lastExecutionEnded(ActivityExecution execution) {
ScopeUtil.createEventScopeExecution((ExecutionEntity) execution);
leave(execution);
}
// required for supporting external subprocesses
public void completing(DelegateExecution execution, DelegateExecution subProcessInstance) throws Exception {
}
// required for supporting external subprocesses
public void completed(ActivityExecution execution) throws Exception {
leave(execution);
}
// Helpers //////////////////////////////////////////////////////////////////////
@SuppressWarnings("rawtypes")
protected int resolveNrOfInstances(ActivityExecution execution) {
int nrOfInstances = -1;
if (loopCardinalityExpression != null) {
nrOfInstances = resolveLoopCardinality(execution);
} else if (collectionExpression != null) {
Object obj = collectionExpression.getValue(execution);
if (!(obj instanceof Collection)) {
throw new ActivitiException(collectionExpression.getExpressionText()+"' didn't resolve to a Collection");
}
nrOfInstances = ((Collection) obj).size();
} else if (collectionVariable != null) {
Object obj = execution.getVariable(collectionVariable);
if (!(obj instanceof Collection)) {
throw new ActivitiException("Variable " + collectionVariable+"' is not a Collection");
}
nrOfInstances = ((Collection) obj).size();
} else {
throw new ActivitiException("Couldn't resolve collection expression nor variable reference");
}
return nrOfInstances;
}
@SuppressWarnings("rawtypes")
protected void executeOriginalBehavior(ActivityExecution execution, int loopCounter) throws Exception {
if (usesCollection() && collectionElementVariable != null) {
Collection collection = null;
if (collectionExpression != null) {
collection = (Collection) collectionExpression.getValue(execution);
} else if (collectionVariable != null) {
collection = (Collection) execution.getVariable(collectionVariable);
}
Object value = null;
int index = 0;
Iterator it = collection.iterator();
while (index <= loopCounter) {
value = it.next();
index++;
}
setLoopVariable(execution, collectionElementVariable, value);
}
// If loopcounter == 1, then historic activity instance already created, no need to
// pass through executeActivity again since it will create a new historic activity
if (loopCounter == 0) {
innerActivityBehavior.execute(execution);
} else {
execution.executeActivity(activity);
}
}
protected boolean usesCollection() {
return collectionExpression != null
|| collectionVariable != null;
}
protected boolean isExtraScopeNeeded() {
// special care is needed when the behavior is an embedded subprocess (not very clean, but it works)
return innerActivityBehavior instanceof org.activiti.engine.impl.bpmn.behavior.SubProcessActivityBehavior;
}
protected int resolveLoopCardinality(ActivityExecution execution) {
// Using Number since expr can evaluate to eg. Long (which is also the default for Juel)
Object value = loopCardinalityExpression.getValue(execution);
if (value instanceof Number) {
return ((Number) value).intValue();
} else if (value instanceof String) {
return Integer.valueOf((String) value);
} else {
throw new ActivitiException("Could not resolve loopCardinality expression '"
+loopCardinalityExpression.getExpressionText()+"': not a number nor number String");
}
}
protected boolean completionConditionSatisfied(ActivityExecution execution) {
if (completionConditionExpression != null) {
Object value = completionConditionExpression.getValue(execution);
if (! (value instanceof Boolean)) {
throw new ActivitiException("completionCondition '"
+ completionConditionExpression.getExpressionText()
+ "' does not evaluate to a boolean value");
}
Boolean booleanValue = (Boolean) value;
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Completion condition of multi-instance satisfied: " + booleanValue);
}
return booleanValue;
}
return false;
}
protected void setLoopVariable(ActivityExecution execution, String variableName, Object value) {
execution.setVariableLocal(variableName, value);
}
protected Integer getLoopVariable(ActivityExecution execution, String variableName) {
Object value = execution.getVariableLocal(variableName);
ActivityExecution parent = execution.getParent();
while (value == null && parent != null) {
value = parent.getVariableLocal(variableName);
parent = parent.getParent();
}
return (Integer) value;
}
/**
* Since no transitions are followed when leaving the inner activity,
* it is needed to call the end listeners yourself.
*/
protected void callActivityEndListeners(ActivityExecution execution) {
// TODO: This is currently done without a proper {@link AtomicOperation} causing problems, see http://jira.codehaus.org/browse/ACT-1339
List<ExecutionListener> listeners = activity.getExecutionListeners(org.activiti.engine.impl.pvm.PvmEvent.EVENTNAME_END);
for (ExecutionListener executionListener : listeners) {
try {
Context.getProcessEngineConfiguration()
.getDelegateInterceptor()
.handleInvocation(new ExecutionListenerInvocation(executionListener, execution));
} catch (Exception e) {
throw new ActivitiException("Couldn't execute end listener", e);
}
}
}
protected void logLoopDetails(ActivityExecution execution, String custom, int loopCounter,
int nrOfCompletedInstances, int nrOfActiveInstances, int nrOfInstances) {
if (LOGGER.isLoggable(Level.FINE)) {
StringBuilder strb = new StringBuilder();
strb.append("Multi-instance '" + execution.getActivity() + "' " + custom + ". ");
strb.append("Details: loopCounter=" + loopCounter + ", ");
strb.append("nrOrCompletedInstances=" + nrOfCompletedInstances + ", ");
strb.append("nrOfActiveInstances=" + nrOfActiveInstances+ ", ");
strb.append("nrOfInstances=" + nrOfInstances);
LOGGER.fine(strb.toString());
}
}
// Getters and Setters ///////////////////////////////////////////////////////////
public Expression getLoopCardinalityExpression() {
return loopCardinalityExpression;
}
public void setLoopCardinalityExpression(Expression loopCardinalityExpression) {
this.loopCardinalityExpression = loopCardinalityExpression;
}
public Expression getCompletionConditionExpression() {
return completionConditionExpression;
}
public void setCompletionConditionExpression(Expression completionConditionExpression) {
this.completionConditionExpression = completionConditionExpression;
}
public Expression getCollectionExpression() {
return collectionExpression;
}
public void setCollectionExpression(Expression collectionExpression) {
this.collectionExpression = collectionExpression;
}
public String getCollectionVariable() {
return collectionVariable;
}
public void setCollectionVariable(String collectionVariable) {
this.collectionVariable = collectionVariable;
}
public String getCollectionElementVariable() {
return collectionElementVariable;
}
public void setCollectionElementVariable(String collectionElementVariable) {
this.collectionElementVariable = collectionElementVariable;
}
public void setInnerActivityBehavior(AbstractBpmnActivityBehavior innerActivityBehavior) {
this.innerActivityBehavior = innerActivityBehavior;
this.innerActivityBehavior.setMultiInstanceActivityBehavior(this);
}
}