
org.ow2.bonita.deployment.IterationDetection Maven / Gradle / Ivy
The newest version!
/**
* 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.InternalProcess;
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.PVMProcessDefinitionImpl;
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.ExceptionManager;
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 InternalProcess inProcess, final PVMProcessDefinitionImpl 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(PVMProcessDefinitionImpl, PVMProcessDefinitionImpl)}
* @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 PVMProcessDefinitionImpl 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 PVMProcessDefinitionImpl 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 InternalProcess 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)) {
String message = ExceptionManager.getInstance().getFullMessage(
"bd_ID_1", c, sourceNode.getName());
throw new DeploymentRuntimeException(message);
}
}
}
}
}
if (c.entryNodes.size() == 0) {
String message = ExceptionManager.getInstance().getFullMessage("bd_ID_2", c);
throw new DeploymentRuntimeException(message);
}
if (!hasEntryPointXor) {
String message = ExceptionManager.getInstance().getFullMessage("bd_ID_3", c);
throw new DeploymentRuntimeException(message);
}
}
/**
* 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 InternalProcess 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 InternalProcess to handle cycles.
* For that, the cycle structure is checked, then entry nodes are resolved.
* @param inProcess InternalProcess
* @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 InternalProcess inProcess,
final PVMProcessDefinitionImpl 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 process
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 InternalProcess and add the required structures to handle iterations
* @param inProcess process to check and modify
* @return inProcess
*/
public static InternalProcess findIterations(final InternalProcess inProcess) {
final PVMProcessDefinitionImpl outProcess = new PVMProcessDefinitionImpl();
// 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 process
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