/**
* Copyright 2010 Red Hat, Inc. and/or its affiliates.
*
* 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.jbpm.workflow.instance.node;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.drools.core.util.MVELSafeHelper;
import org.jbpm.process.core.ContextContainer;
import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.process.instance.ContextInstance;
import org.jbpm.process.instance.context.variable.VariableScopeInstance;
import org.jbpm.workflow.core.node.ForEachNode;
import org.jbpm.workflow.core.node.ForEachNode.ForEachJoinNode;
import org.jbpm.workflow.core.node.ForEachNode.ForEachSplitNode;
import org.jbpm.workflow.instance.NodeInstance;
import org.jbpm.workflow.instance.NodeInstanceContainer;
import org.jbpm.workflow.instance.impl.NodeInstanceImpl;
import org.jbpm.workflow.instance.impl.NodeInstanceResolverFactory;
import org.kie.api.definition.process.Connection;
import org.kie.api.definition.process.Node;
import org.mvel2.integration.VariableResolver;
import org.mvel2.integration.impl.SimpleValueResolver;
/**
* Runtime counterpart of a for each node.
*
*/
public class ForEachNodeInstance extends CompositeContextNodeInstance {
private static final long serialVersionUID = 510l;
private static final String TEMP_OUTPUT_VAR = "foreach_output";
public ForEachNode getForEachNode() {
return (ForEachNode) getNode();
}
public NodeInstance getNodeInstance(final Node node) {
// TODO do this cleaner for split / join of for each?
if (node instanceof ForEachSplitNode) {
ForEachSplitNodeInstance nodeInstance = new ForEachSplitNodeInstance();
nodeInstance.setNodeId(node.getId());
nodeInstance.setNodeInstanceContainer(this);
nodeInstance.setProcessInstance(getProcessInstance());
String uniqueID = (String) node.getMetaData().get("UniqueId");
assert uniqueID != null : node.getName() + " does not have a unique id.";
if (uniqueID == null) {
uniqueID = node.getId()+"";
}
int level = this.getLevelForNode(uniqueID);
nodeInstance.setLevel(level);
return nodeInstance;
} else if (node instanceof ForEachJoinNode) {
ForEachJoinNodeInstance nodeInstance = (ForEachJoinNodeInstance)
getFirstNodeInstance(node.getId());
if (nodeInstance == null) {
nodeInstance = new ForEachJoinNodeInstance();
nodeInstance.setNodeId(node.getId());
nodeInstance.setNodeInstanceContainer(this);
nodeInstance.setProcessInstance(getProcessInstance());
String uniqueID = (String) node.getMetaData().get("UniqueId");
assert uniqueID != null : node.getName() + " does not have a unique id.";
if (uniqueID == null) {
uniqueID = node.getId()+"";
}
int level = this.getLevelForNode(uniqueID);
nodeInstance.setLevel(level);
}
return nodeInstance;
}
return super.getNodeInstance(node);
}
@Override
public ContextContainer getContextContainer() {
return (ContextContainer) getForEachNode().getCompositeNode();
}
private Collection<?> evaluateCollectionExpression(String collectionExpression) {
// TODO: should evaluate this expression using MVEL
Object collection = null;
VariableScopeInstance variableScopeInstance = (VariableScopeInstance)
resolveContextInstance(VariableScope.VARIABLE_SCOPE, collectionExpression);
if (variableScopeInstance != null) {
collection = variableScopeInstance.getVariable(collectionExpression);
} else {
try {
collection = MVELSafeHelper.getEvaluator().eval(collectionExpression, new NodeInstanceResolverFactory(this));
} catch (Throwable t) {
throw new IllegalArgumentException(
"Could not find collection " + collectionExpression);
}
}
if (collection == null) {
return Collections.EMPTY_LIST;
}
if (collection instanceof Collection<?>) {
return (Collection<?>) collection;
}
if (collection.getClass().isArray() ) {
List<Object> list = new ArrayList<Object>();
for (Object o: (Object[]) collection) {
list.add(o);
}
return list;
}
throw new IllegalArgumentException(
"Unexpected collection type: " + collection.getClass());
}
public class ForEachSplitNodeInstance extends NodeInstanceImpl {
private static final long serialVersionUID = 510l;
public ForEachSplitNode getForEachSplitNode() {
return (ForEachSplitNode) getNode();
}
public void internalTrigger(org.kie.api.runtime.process.NodeInstance fromm, String type) {
String collectionExpression = getForEachNode().getCollectionExpression();
Collection<?> collection = evaluateCollectionExpression(collectionExpression);
((NodeInstanceContainer) getNodeInstanceContainer()).removeNodeInstance(this);
if (collection.isEmpty()) {
ForEachNodeInstance.this.triggerCompleted(org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE, true);
} else {
List<NodeInstance> nodeInstances = new ArrayList<NodeInstance>();
for (Object o: collection) {
String variableName = getForEachNode().getVariableName();
NodeInstance nodeInstance = (NodeInstance)
((NodeInstanceContainer) getNodeInstanceContainer()).getNodeInstance(getForEachSplitNode().getTo().getTo());
VariableScopeInstance variableScopeInstance = (VariableScopeInstance)
nodeInstance.resolveContextInstance(VariableScope.VARIABLE_SCOPE, variableName);
variableScopeInstance.setVariable(variableName, o);
nodeInstances.add(nodeInstance);
}
for (NodeInstance nodeInstance: nodeInstances) {
logger.debug( "Triggering [{}] in multi-instance loop.", ((NodeInstanceImpl) nodeInstance).getNodeId() );
((org.jbpm.workflow.instance.NodeInstance) nodeInstance).trigger(this, getForEachSplitNode().getTo().getToType());
}
if (!getForEachNode().isWaitForCompletion()) {
ForEachNodeInstance.this.triggerCompleted(org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE, false);
}
}
}
}
public class ForEachJoinNodeInstance extends NodeInstanceImpl {
private static final long serialVersionUID = 510l;
public ForEachJoinNode getForEachJoinNode() {
return (ForEachJoinNode) getNode();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void internalTrigger(org.kie.api.runtime.process.NodeInstance from, String type) {
Map<String, Object> tempVariables = new HashMap<String, Object>();
VariableScopeInstance subprocessVariableScopeInstance = null;
if (getForEachNode().getOutputVariableName() != null) {
subprocessVariableScopeInstance = (VariableScopeInstance) getContextInstance(VariableScope.VARIABLE_SCOPE);
Collection<Object> outputCollection = (Collection<Object>) subprocessVariableScopeInstance.getVariable(TEMP_OUTPUT_VAR);
if (outputCollection == null) {
outputCollection = new ArrayList<Object>();
}
VariableScopeInstance variableScopeInstance = (VariableScopeInstance)
((NodeInstanceImpl)from).resolveContextInstance(VariableScope.VARIABLE_SCOPE, getForEachNode().getOutputVariableName());
Object outputVariable = null;
if (variableScopeInstance != null) {
outputVariable = variableScopeInstance.getVariable(getForEachNode().getOutputVariableName());
}
outputCollection.add(outputVariable);
subprocessVariableScopeInstance.setVariable(TEMP_OUTPUT_VAR, outputCollection);
// add temp collection under actual mi output name for completion condition evaluation
tempVariables.put(getForEachNode().getOutputVariableName(), outputVariable);
String outputCollectionName = getForEachNode().getOutputCollectionExpression();
if (outputCollection != null) {
tempVariables.put(outputCollectionName, outputCollection);
}
}
boolean isCompletionConditionMet = evaluateCompletionCondition(getForEachNode().getCompletionConditionExpression(), tempVariables);
if (getNodeInstanceContainer().getNodeInstances().size() == 1 || isCompletionConditionMet) {
String outputCollection = getForEachNode().getOutputCollectionExpression();
if (outputCollection != null) {
VariableScopeInstance variableScopeInstance = (VariableScopeInstance) resolveContextInstance(VariableScope.VARIABLE_SCOPE, outputCollection);
Collection<?> outputVariable = (Collection<?>) variableScopeInstance.getVariable(outputCollection);
if (outputVariable != null) {
outputVariable.addAll((Collection) subprocessVariableScopeInstance.getVariable(TEMP_OUTPUT_VAR));
} else {
outputVariable = (Collection<Object>) subprocessVariableScopeInstance.getVariable(TEMP_OUTPUT_VAR);
}
variableScopeInstance.setVariable(outputCollection, outputVariable);
}
((NodeInstanceContainer) getNodeInstanceContainer()).removeNodeInstance(this);
if (getForEachNode().isWaitForCompletion()) {
if (!"true".equals(System.getProperty("jbpm.enable.multi.con"))) {
triggerConnection(getForEachJoinNode().getTo());
} else {
List<Connection> connections = getForEachJoinNode().getOutgoingConnections(org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE);
for (Connection connection : connections) {
triggerConnection(connection);
}
}
}
}
}
private boolean evaluateCompletionCondition(String expression, Map<String, Object> tempVariables) {
if (expression == null || expression.isEmpty()) {
return false;
}
try {
Object result = MVELSafeHelper.getEvaluator().eval(expression, new ForEachNodeInstanceResolverFactory(this, tempVariables));
if ( !(result instanceof Boolean) ) {
throw new RuntimeException( "Completion condition expression must return boolean values: " + result
+ " for expression " + expression);
}
return ((Boolean) result).booleanValue();
} catch (Throwable t) {
t.printStackTrace();
throw new IllegalArgumentException("Could not evaluate completion condition " + expression);
}
}
}
@Override
public ContextInstance getContextInstance(String contextId) {
ContextInstance contextInstance = super.getContextInstance(contextId);
if (contextInstance == null) {
contextInstance = resolveContextInstance(contextId, TEMP_OUTPUT_VAR);
setContextInstance(contextId, contextInstance);
}
return contextInstance;
}
@Override
public int getLevelForNode(String uniqueID) {
// always 1 for for each
return 1;
}
private class ForEachNodeInstanceResolverFactory extends NodeInstanceResolverFactory {
private static final long serialVersionUID = -8856846610671009685L;
private Map<String, Object> tempVariables;
public ForEachNodeInstanceResolverFactory(NodeInstance nodeInstance, Map<String, Object> tempVariables) {
super(nodeInstance);
this.tempVariables = tempVariables;
}
@Override
public boolean isResolveable(String name) {
boolean result = tempVariables.containsKey(name);
if (result) {
return result;
}
return super.isResolveable(name);
}
@Override
public VariableResolver getVariableResolver(String name) {
if (tempVariables.containsKey(name)) {
return new SimpleValueResolver(tempVariables.get(name));
}
return super.getVariableResolver(name);
}
}
}