All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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