
org.ow2.bonita.deployment.IterationDetection Maven / Gradle / Ivy
/**
* Copyright (C) 2007 Bull S. A. S.
* Bull, Rue Jean Jaures, B.P.68, 78340, Les Clayes-sous-Bois
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation
* version 2.1 of the License.
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301, USA.
**/
package org.ow2.bonita.deployment;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ow2.bonita.definition.XpdlProcess;
import org.ow2.bonita.definition.activity.AbstractActivity;
import org.ow2.bonita.definition.activity.AbstractActivity.IterationDescriptor;
import org.ow2.bonita.definition.activity.AbstractActivity.JoinType;
import org.ow2.bonita.definition.activity.AbstractActivity.NodeInIterationDescriptor;
import org.ow2.bonita.definition.activity.AbstractActivity.SplitType;
import org.ow2.bonita.pvm.internal.model.NodeImpl;
import org.ow2.bonita.pvm.internal.model.ProcessDefinitionImpl;
import org.ow2.bonita.pvm.internal.model.TransitionImpl;
import org.ow2.bonita.pvm.model.Node;
import org.ow2.bonita.pvm.model.Transition;
import org.ow2.bonita.util.Misc;
/**
* @author Guillaume Porcher
*
*/
public final class IterationDetection {
private static final Logger LOG = Logger.getLogger(IterationDetection.class.getName());
/**
* Helper object to store data about cycle structure
*
* @author Guillaume Porcher
*/
private static class CycleObject {
// List of entry nodes
protected Set entryNodes = new HashSet();
// List of exit nodes
protected Set exitNodes = new HashSet();
// List of nodes in the cycle
protected Set nodesInPath = new HashSet();
// List of transitions that are in the cycle
protected Set transitions = new HashSet();
@Override
public String toString() {
return "entryNodes: " + this.entryNodes + ", exitNodes: " + this.exitNodes + " path: " + this.nodesInPath + ", transitions: " + this.transitions;
}
}
/**
* Simple object that describes a transition.
*
* @author Guillaume Porcher
*/
private static class MyTransition {
// source node
protected String fromNode;
// destination node
protected String toNode;
@Override
public String toString() {
return this.fromNode + "->" + this.toNode;
}
}
private IterationDetection() { }
/**
* Creates a spanning tree in spanningTree corresponding to the input process
* and lists transitions that are not in the spanning tree.
* Use a Deep First Search Algorithm for that.
* Transitions that are not in the spanning tree can be:
* - transitions that creates a second path between two nodes (e.g. split/join)
* - transitions that creates loops
* @param inProcess input process
* @param spanningTree process where the corresponding spanning tree will be created
* @return list of transitions of input process that are not in the spanning tree.
*/
private static List createSpanningTree(final XpdlProcess inProcess, final ProcessDefinitionImpl spanningTree) {
final Node inNode = inProcess.getInitial();
final NodeImpl outNode = spanningTree.createNode(inNode.getName());
// Create a spanning tree in outProcess and list transitions that are not in the spanning tree.
// Use a Deep First Search Algorithm for that.
return IterationDetection.processNode(inNode, outNode, spanningTree);
}
/**
* Deep first search algorithm that creates spanning tree
* (see {@link IterationDetection#createSpanningTree(ProcessDefinitionImpl, ProcessDefinitionImpl)}
* @param inNode node of the input process currently processed
* @param outNode corresponding node in the spanning tree
* @param outProcess spanning tree
* @return list of unprocessed transitions during the DFS
*/
private static List processNode(final Node inNode, final NodeImpl outNode, final ProcessDefinitionImpl outProcess) {
final List nonProcessedTransitions = new ArrayList();
if (inNode.hasOutgoingTransitions()) {
for (final Transition t : inNode.getOutgoingTransitions()) {
final NodeImpl destNode = (NodeImpl) t.getDestination();
final String destName = destNode.getName();
if (!outProcess.hasNode(destName)) {
// create node
final NodeImpl newNode = outProcess.createNode(destName);
outNode.createOutgoingTransition(newNode, t.getName());
nonProcessedTransitions.addAll(IterationDetection.processNode(t.getDestination(), newNode, outProcess));
} else {
// node already in the spanning tree, the transition is not in the spanning tree.
nonProcessedTransitions.add(t);
}
}
}
return nonProcessedTransitions;
}
/**
* Checks if there is a path from source to destination
* @param sourceNode source
* @param destNode destination
* @return true if there is a path from source to destination
*/
private static boolean existsPath(final NodeImpl sourceNode, final NodeImpl destNode) {
if (sourceNode.equals(destNode)) {
return true;
}
if (sourceNode.hasOutgoingTransitions()) {
for (final Transition t : sourceNode.getOutgoingTransitions()) {
if (IterationDetection.existsPath((NodeImpl) t.getDestination(), destNode)) {
return true;
}
}
}
return false;
}
/**
* Find transitions in the given list of transitions that would form a cycle if added to the given process.
* If a transition can be added to the process without creating a cycle, then the transition is added.
* The process returned is a DAG (directed acyclic graph)
* @param process input process (this process will be modified by calling this method)
* @param transitions transitions to check
* @return list of transitions that cannot be added to the DAG (transitions that would create a cycle).
*/
private static List findCycleTransitions(final ProcessDefinitionImpl process, final List transitions) {
final List cycleTransitions = new ArrayList();
for (final Transition t : transitions) {
final String sourceNodeName = t.getSource().getName();
final String destNodeName = t.getDestination().getName();
// Check if when a transition is added to the spanning tree, it creates a cycle
// check if there is already a path from transition dest to transition source.
// adding the transition would then create a cycle.
final NodeImpl sourceNode = process.getNode(sourceNodeName);
final NodeImpl destNode = process.getNode(destNodeName);
if (IterationDetection.existsPath(destNode, sourceNode)) {
cycleTransitions.add(t);
} else {
// add transition to the DAG
sourceNode.createOutgoingTransition(destNode);
}
}
return cycleTransitions;
}
/**
* Fill the path and transitions fields of the CycleObject c.
* @param c cycle object to fill
* @param sourceNode source node of the path
* @param destNode destination of the path
* @return
*/
private static boolean fillCycleObject(final CycleObject c, final NodeImpl sourceNode, final NodeImpl destNode) {
if (sourceNode.equals(destNode)) {
c.nodesInPath.add(sourceNode.getName());
return true;
}
if (sourceNode.hasOutgoingTransitions()) {
boolean res = false;
for (final Transition t : sourceNode.getOutgoingTransitions()) {
if (IterationDetection.fillCycleObject(c, (NodeImpl) t.getDestination(), destNode)) {
c.nodesInPath.add(sourceNode.getName());
final MyTransition myT = new MyTransition();
myT.fromNode = t.getSource().getName();
myT.toNode = t.getDestination().getName();
c.transitions.add(myT);
res = true;
}
}
return res;
}
return false;
}
/**
* Find entry nodes in the cycle. Checks that entry nodes does not use Xor Join.
* @param c cycle object to fill
* @param inProcess input process (xpdl process)
*/
private static void fillCycleObjectEntryNodes(final CycleObject c, final XpdlProcess inProcess) {
boolean hasEntryPointXor = false;
for (final String nodeName : c.nodesInPath) {
final NodeImpl sourceNode = inProcess.getNode(nodeName);
if (sourceNode.hasIncomingTransitions()) {
// check for entryNode:
// n is an entry node if:
// - a transition from a node that is not in the cycle goes to n.
// - a transition which is not in the cycle goes to n.
for (final Transition t : sourceNode.getIncomingTransitions()) {
JoinType joinType;
boolean isEntryNode = true;
if (c.nodesInPath.contains(t.getSource().getName())) {
for (final MyTransition myTr : c.transitions) {
if (myTr.fromNode.equals(t.getSource().getName())
&& myTr.toNode.equals(t.getDestination().getName())) {
isEntryNode = false;
}
}
}
if (isEntryNode) {
c.entryNodes.add(sourceNode.getName());
joinType = ((AbstractActivity)sourceNode.getBehaviour()).getJoinType();
if (JoinType.XOR.equals(joinType)) {
hasEntryPointXor = true;
} else if (JoinType.AND.equals(joinType)) {
throw new DeploymentRuntimeException("Error in cycle detection : " +
"cycle " + c + " has a start node " + sourceNode.getName() + " with a AND join."
+ " This is not allowed.");
}
}
}
}
}
if (c.entryNodes.size() == 0) {
throw new DeploymentRuntimeException("Error in cycle detection : cycle " + c + " has no start node");
}
if (!hasEntryPointXor) {
throw new DeploymentRuntimeException("Error in cycle detection : cycle " + c + " has no start node with a XOR join."
+ " Process execution can never enter this cycle.");
}
}
/**
* Find exit nodes in the cycle. Checks if exit nodes only use Xor split type.
* @param c cycle object to fill
* @param inProcess input process (xpdl process)
*/
private static void checkExitNodes(final CycleObject c, final XpdlProcess inProcess) {
for (final String nodeName : c.nodesInPath) {
final NodeImpl sourceNode = inProcess.getNode(nodeName);
if (sourceNode.hasOutgoingTransitions()) {
//check for exit
for (final Transition t : sourceNode.getOutgoingTransitions()) {
if (!c.nodesInPath.contains(t.getDestination().getName())) {
c.exitNodes.add(sourceNode.getName());
// sourceNode is an exit node
final SplitType splitType = ((AbstractActivity)sourceNode.getBehaviour()).getSplitType();
// Only allow XOR split for exit nodes
if (!SplitType.XOR.equals(splitType)) {
// TODO: change this to an exception
// throw new BonitaRuntimeException(
// "Error in iteration : " + nodeName + " is an exit node for cycle "
// + c.path + "." + Misc.LINE_SEPARATOR
// + "Split type of this node is " + splitType + " but only XOR is allowed.");
IterationDetection.LOG.severe("Potential issue in iteration : " + nodeName + " is an exit node for cycle "
+ c.nodesInPath + "." + Misc.LINE_SEPARATOR
+ "Split type of this node is " + splitType + " but only XOR is supported." + Misc.LINE_SEPARATOR
+ "An exception will be thrown at runtime if more than one transition is enabled at the same time.");
}
}
}
}
}
}
/**
* Create structure to add to the XpdlProcess to handle cycles.
* For that, the cycle structure is checked, then entry nodes are resolved.
* @param inProcess XpdlProcess
* @param dag acyclic graph on which cycle structure will be found
* @param transitions list of transitions that creates cycles
* @return a map where the key is the name of a node in the input process,
* and the value a list of IterationDescriptor that needs to be added to the node definition.
*/
private static Map> findCycleInits(final XpdlProcess inProcess,
final ProcessDefinitionImpl dag, final List transitions) {
final Map> iterationDescriptors = new HashMap>();
for (final Transition t : transitions) {
final String sourceNodeName = t.getSource().getName();
final String destNodeName = t.getDestination().getName();
final NodeImpl sourceNode = dag.getNode(sourceNodeName);
final NodeImpl destNode = dag.getNode(destNodeName);
// Add current transition to the DAG to create a cycle
final TransitionImpl transition = (TransitionImpl) sourceNode.createOutgoingTransition(destNode);
// Create object that represents the cycle structure
final CycleObject c = new CycleObject();
// compute the cycle path and transitions
if (IterationDetection.fillCycleObject(c, destNode, sourceNode)) {
final MyTransition myT = new MyTransition();
myT.fromNode = sourceNodeName;
myT.toNode = destNodeName;
c.transitions.add(myT);
}
// find entry nodes
IterationDetection.fillCycleObjectEntryNodes(c, inProcess);
// check exit nodes
IterationDetection.checkExitNodes(c, inProcess);
if (IterationDetection.LOG.isLoggable(Level.FINE)) {
IterationDetection.LOG.fine("CycleObject :" + c);
}
// Remove added transition to have a DAG for other transitions.
sourceNode.removeOutgoingTransition(transition);
destNode.removeIncomingTransition(transition);
// Create IterationDescriptors for the XpdlProcess
final Set nodeDescriptors = new HashSet();
for (final String nodeName : c.nodesInPath) {
nodeDescriptors.add(
new NodeInIterationDescriptor(
inProcess.getNode(nodeName),
c.entryNodes.contains(nodeName),
c.exitNodes.contains(nodeName)
)
);
}
final IterationDescriptor itDescr = new IterationDescriptor(nodeDescriptors);
for (final String nodeName : c.nodesInPath) {
if (!iterationDescriptors.containsKey(nodeName)) {
iterationDescriptors.put(nodeName, new ArrayList());
}
iterationDescriptors.get(nodeName).add(itDescr);
}
}
return iterationDescriptors;
}
/**
* Find cycles in the xpdlProcess and add the required structures to handle iterations
* @param inProcess process to check and modify
* @return inProcess
*/
public static XpdlProcess findIterations(final XpdlProcess inProcess) {
final ProcessDefinitionImpl outProcess = new ProcessDefinitionImpl();
// Create a spanning tree in outProcess and list transitions that are not in the spanning tree.
// Use a Deep First Search Algorithm for that.
final List unprocessedTransitions = IterationDetection.createSpanningTree(inProcess, outProcess);
if (IterationDetection.LOG.isLoggable(Level.FINE)) {
IterationDetection.LOG.info("Unprocessed transitions: " + unprocessedTransitions);
}
// Transitions can be out of the spanning tree because:
// it's in a split join
// or it's a cycle
// The second pass determines if the transition creates a cycle and creates a DAG (Directed acyclic graph)
final List cycleTransitions = IterationDetection.findCycleTransitions(outProcess, unprocessedTransitions);
if (IterationDetection.LOG.isLoggable(Level.FINE)) {
IterationDetection.LOG.info("Cycle transitions: " + cycleTransitions);
}
// create Iteration descriptors
final Map> iterationDescriptors = IterationDetection.findCycleInits(inProcess, outProcess, cycleTransitions);
if (IterationDetection.LOG.isLoggable(Level.FINE)) {
IterationDetection.LOG.info("iteration descriptors: " + iterationDescriptors);
}
// update XpdlProcess
for (final Map.Entry> itDesc : iterationDescriptors.entrySet()) {
final AbstractActivity activity = (AbstractActivity) inProcess.getNode(itDesc.getKey()).getBehaviour();
activity.setIterationDescriptors(itDesc.getValue());
}
return inProcess;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy