[jbpm-commits] JBoss JBPM SVN: r6084 - in jbpm4/trunk/modules: bpmn/src/main/java/org/jbpm/bpmn/model and 5 other directories.

do-not-reply at jboss.org do-not-reply at jboss.org
Fri Jan 15 19:21:04 EST 2010


Author: jbarrez
Date: 2010-01-15 19:21:04 -0500 (Fri, 15 Jan 2010)
New Revision: 6084

Added:
   jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/gateway/InclusiveGatewayTest.java
Modified:
   jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/BpmnActivity.java
   jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/InclusiveGatewayActivity.java
   jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/ParallelGatewayActivity.java
   jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/model/BpmnProcessDefinition.java
   jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/parser/BpmnParser.java
   jbpm4/trunk/modules/test-base/src/main/java/org/jbpm/test/assertion/CollectionAssertions.java
   jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/ParallelGatewayTest.java
   jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/gateway/ParallelGatewayMergeTest.java
   jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/test/activity/forkjoin/MultipleForksTest.java
Log:
JBPM2738: working on the inclusive gateway. Covered basic split/merge behaviour with new logic and test cases.

Modified: jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/BpmnActivity.java
===================================================================
--- jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/BpmnActivity.java	2010-01-15 14:04:04 UTC (rev 6083)
+++ jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/BpmnActivity.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -107,14 +107,21 @@
     }
   }
 
+  /**
+   * Returns the list of outgoing sequence flow for this activity.
+   * If the boolean 'checkConditions' is true, conditions on the sequence flow will be evaluated.
+   * 
+   * Note that for activities that have a default sequence flow (eg Exclusive gateway),
+   * the default sequence flow will NOT be included in the returned list.
+   */
   protected List<Transition> findOutgoingSequenceFlow(ExecutionImpl execution, boolean checkConditions) {
     Activity activity = execution.getActivity();
     // evaluate the conditions and find the transitions that should be forked
     List<Transition> forkingTransitions = new ArrayList<Transition>();
     List<Transition> outgoingTransitions = activity.getOutgoingTransitions();
+    
     for (Transition transition : outgoingTransitions) {
       Condition condition = transition.getCondition();
-      // also ignore the default transition of the exclusive gateway
       if ( ( (condition == null) 
              || (!checkConditions) 
              || (condition.evaluate(execution))

Modified: jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/InclusiveGatewayActivity.java
===================================================================
--- jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/InclusiveGatewayActivity.java	2010-01-15 14:04:04 UTC (rev 6083)
+++ jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/InclusiveGatewayActivity.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -21,9 +21,21 @@
  */
 package org.jbpm.bpmn.flownodes;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.hibernate.LockMode;
+import org.hibernate.Session;
+import org.jbpm.api.Execution;
+import org.jbpm.api.JbpmException;
 import org.jbpm.api.activity.ActivityExecution;
+import org.jbpm.bpmn.model.BpmnProcessDefinition;
 import org.jbpm.internal.log.Log;
+import org.jbpm.pvm.internal.env.EnvironmentImpl;
+import org.jbpm.pvm.internal.model.Activity;
 import org.jbpm.pvm.internal.model.ExecutionImpl;
+import org.jbpm.pvm.internal.model.Transition;
 
 /**
  * @author Joram Barrez
@@ -33,6 +45,8 @@
   private static final long serialVersionUID = 1L;
   
   private static final Log LOG = Log.getLog(InclusiveGatewayActivity.class.getName());
+  
+  protected LockMode lockMode = LockMode.UPGRADE;
 
   public void execute(ActivityExecution execution) throws Exception {
     execute((ExecutionImpl) execution);
@@ -40,14 +54,147 @@
   
   public void execute(ExecutionImpl execution) {
     int nrOfIncoming = execution.getActivity().getIncomingTransitions().size();
+    
     if (nrOfIncoming == 1) { // no merge behaviour needed, save some time and do a fork immediately
       if (LOG.isDebugEnabled()) {
         LOG.debug("Only one incoming sequence flow found. Executing fork logic only.");
       }
-      proceed(execution, findOutgoingSequenceFlow(execution, CONDITIONS_CHECKED));
-    } 
+      fork(execution);
+    } else {
+   
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Multiple incoming sequence flow found. Executing join logic.");
+      }
+      boolean allExecutionsArrived = handleIncomingExecution(execution);
+      
+      if (allExecutionsArrived) {
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("All executions have reached the inclusive join. Executing fork logic.");
+        }
+        ExecutionImpl outgoingExecution = join(execution);
+        fork(outgoingExecution);
+      }
+      
+    }
     
-    // TODO finish
+   
   }
+  
+  public void fork(ExecutionImpl execution) {
+    List<Transition> outgointSeqFlow = findOutgoingSequenceFlow(execution, CONDITIONS_CHECKED);
+    
+    if (outgointSeqFlow.isEmpty()) {
+      Transition defaultSeqFlow = execution.getActivity().getDefaultOutgoingTransition();
+      if (defaultSeqFlow != null) {
+        outgointSeqFlow.add(defaultSeqFlow);
+      } else {
+        throw new JbpmException("No sequenceFlow condition evaluated to true for " + 
+                execution.getActivity() + " and no default sequenceFlow was speficied");
+      }
+    }
+    
+    proceed(execution, outgointSeqFlow);
+  }
+  
+  /**
+   * Joins the incoming executions.
+   * Returns true if all executions have reached the gateway.
+   */
+  protected boolean handleIncomingExecution(ExecutionImpl execution) {
+    if (Execution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) {
+     
+      // force version increment in the parent execution
+      Session session = EnvironmentImpl.getFromCurrent(Session.class);
+      session.lock(execution.getParent(), lockMode);
 
+      execution.setState(Execution.STATE_INACTIVE_JOIN);
+      execution.waitForSignal();
+
+      return isComplete(execution);
+      
+    } else {
+      throw new JbpmException("invalid execution state: " + execution.getState());
+    }
+  }
+  
+  /**
+   * Joins all the incoming executions currently waiting at the gateway.
+   * 
+   * @return An execution that can be used to leave the gateway (one outgoing sequence flow)
+   * or to create child executions on (fork behaviour when multiple outgoing sequence flow).
+   */
+  protected ExecutionImpl join(ExecutionImpl execution) {
+    Activity activity = execution.getActivity();
+    ExecutionImpl concurrentRoot = execution.getParent();
+    List<ExecutionImpl> joinedExecutions = getJoinedExecutions(concurrentRoot, activity);
+    
+    endJoinedExecutions(joinedExecutions);
+      
+    ExecutionImpl outgoingExecution = null;
+    if (concurrentRoot.getExecutions().size() == 0) {
+      outgoingExecution = concurrentRoot;
+      outgoingExecution.setState(Execution.STATE_ACTIVE_ROOT);
+    } else {
+      outgoingExecution = concurrentRoot.createExecution();
+      outgoingExecution.setState(Execution.STATE_ACTIVE_CONCURRENT);
+    }
+      
+    outgoingExecution.setActivity(activity);
+    return outgoingExecution;
+  }
+  
+  /**
+   * Section 14.3.2 of the BPMN 2.0 specification.
+   * 
+   * The Inclusive Gateway is activated if 
+   *   - At least one incoming sequence flow has at least one Token and 
+   *   - for each empty incoming sequence flow, there is no Token in the graph anywhere 
+   *     upstream of this sequence flow, i.e., there is no directed path (formed by Sequence Flow) 
+   *     from a Token to this sequence flow unless 
+   *       - the path visits the inclusive gateway or 
+   *       - the path visits a node that has a directed path to a non-empty incoming sequence 
+   *         flow of the inclusive gateway. 
+   */
+  protected boolean isComplete(ExecutionImpl incomingExecution) {
+    String currentActivityId = incomingExecution.getActivityName(); // id is stored in the name attribute
+    Collection<ExecutionImpl> allExecutions = incomingExecution.getProcessInstance().getExecutions();
+    BpmnProcessDefinition processDefinition = (BpmnProcessDefinition) incomingExecution.getProcessDefinition();
+  
+    for (ExecutionImpl execution : allExecutions) {
+      String activityId = execution.getActivityName(); // id is stored in the name attribute
+      if (activityId != null && !currentActivityId.equals(activityId)) {
+        if (processDefinition.isReachable(activityId, currentActivityId)) {
+          return false;
+        }
+      }
+    }
+    
+    return true;
+  }
+  
+  protected List<ExecutionImpl> getJoinedExecutions(ExecutionImpl concurrentRoot, Activity activity) {
+    List<ExecutionImpl> joinedExecutions = new ArrayList<ExecutionImpl>();
+    List<ExecutionImpl> concurrentExecutions = (List<ExecutionImpl>)concurrentRoot.getExecutions();
+    for (ExecutionImpl concurrentExecution: (List<ExecutionImpl>)concurrentExecutions) {
+      if ( (Execution.STATE_INACTIVE_JOIN.equals(concurrentExecution.getState()))
+           && (concurrentExecution.getActivity()==activity)
+         ) {
+        joinedExecutions.add(concurrentExecution);
+      }
+    }
+    
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Found " + joinedExecutions.size() + " executions currently waiting at the gateway");
+    }
+    
+    return joinedExecutions;
+  }
+
+  protected void endJoinedExecutions(List<ExecutionImpl> joinedExecutions) {
+    for (ExecutionImpl joinedExecution: joinedExecutions) {
+      joinedExecution.end();
+    }
+  }
+  
+
 }

Modified: jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/ParallelGatewayActivity.java
===================================================================
--- jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/ParallelGatewayActivity.java	2010-01-15 14:04:04 UTC (rev 6083)
+++ jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/flownodes/ParallelGatewayActivity.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -44,7 +44,7 @@
   
   private static final long serialVersionUID = 1L;
   
-  LockMode lockMode = LockMode.UPGRADE;
+  protected LockMode lockMode = LockMode.UPGRADE;
 
   public void execute(ActivityExecution execution) {
     execute((ExecutionImpl) execution);
@@ -54,31 +54,38 @@
     int nrOfIncoming = execution.getActivity().getIncomingTransitions().size();
     
     if (nrOfIncoming == 1) { // no join behaviour needed, save some time and do a fork immediately
+      
       if (LOG.isDebugEnabled()) {
         LOG.debug("Only one incoming sequence flow found. Executing fork logic.");
       }
       fork(execution);
-    } else { // Join behaviour needed
       
+    } else {
+      
       if (LOG.isDebugEnabled()) {
         LOG.debug("Multiple incoming sequence flow found. Executing join logic.");
       }
-      join(execution);
+      boolean allExecutionsArrived = handleIncomingExecution(execution);
       
       // After executing the join functionality, it could be that all executions have arrived 
-      // at the gateway. In that case, the gateway can be left using the fork functionality.
-      proceedIfPossible(execution);
+      // at the gateway. In that case, the gateway can be left.
+      if (allExecutionsArrived) {
+        ExecutionImpl outgoingExecution = join(execution);
+        fork(outgoingExecution);
+      }
       
     }
-
-   
   }
   
   protected void fork(ExecutionImpl execution) {
     proceed(execution, findOutgoingSequenceFlow(execution, CONDITIONS_IGNORED));
   }
   
-  protected void join(ExecutionImpl execution) {
+  /**
+   * Joins the incoming executions.
+   * Returns true if all executions have reached the gateway.
+   */
+  protected boolean handleIncomingExecution(ExecutionImpl execution) {
     if (Execution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) {
      
       // force version increment in the parent execution
@@ -87,36 +94,46 @@
 
       execution.setState(Execution.STATE_INACTIVE_JOIN);
       execution.waitForSignal();
+
+      return isComplete(execution);
       
     } else {
       throw new JbpmException("invalid execution state: " + execution.getState());
     }
   }
   
-  protected void proceedIfPossible(ExecutionImpl execution) {
+  /**
+   * Joins all the incoming executions currently waiting at the gateway.
+   * 
+   * @return An execution that can be used to leave the gateway (one outgoing sequence flow)
+   * or to create child executions on (fork behaviour when multiple outgoing sequence flow).
+   */
+  protected ExecutionImpl join(ExecutionImpl execution) {
     Activity activity = execution.getActivity();
     ExecutionImpl concurrentRoot = execution.getParent();
     List<ExecutionImpl> joinedExecutions = getJoinedExecutions(concurrentRoot, activity);
     
-    if (isComplete(joinedExecutions, activity)) {
+    endJoinedExecutions(joinedExecutions);
       
-      endJoinedExecutions(joinedExecutions);
+    ExecutionImpl outgoingExecution = null;
+    if (concurrentRoot.getExecutions().size() == 0) {
+      outgoingExecution = concurrentRoot;
+      outgoingExecution.setState(Execution.STATE_ACTIVE_ROOT);
+    } else {
+      outgoingExecution = concurrentRoot.createExecution();
+      outgoingExecution.setState(Execution.STATE_ACTIVE_CONCURRENT);
+    }
       
-      ExecutionImpl outgoingExecution = null;
-      if (concurrentRoot.getExecutions().size() == 0) {
-        outgoingExecution = concurrentRoot;
-        outgoingExecution.setState(Execution.STATE_ACTIVE_ROOT);
-      } else {
-        outgoingExecution = concurrentRoot.createExecution();
-        outgoingExecution.setState(Execution.STATE_ACTIVE_CONCURRENT);
-      }
-      
-      outgoingExecution.setActivity(activity);
-      fork(outgoingExecution);
-    }
+    outgoingExecution.setActivity(activity);
+    return outgoingExecution;
   }
   
-  protected boolean isComplete(List<ExecutionImpl> joinedExecutions, Activity activity) {
+  protected boolean isComplete(ExecutionImpl execution) {
+    
+    Activity activity = execution.getActivity();
+    ExecutionImpl concurrentRoot = execution.getParent();
+    List<ExecutionImpl> joinedExecutions = getJoinedExecutions(concurrentRoot, activity);
+    
     boolean result = joinedExecutions.size() == activity.getIncomingTransitions().size();
     if (LOG.isDebugEnabled()) {
       LOG.debug("All incoming executions have arrived at the gateway: " + result);

Modified: jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/model/BpmnProcessDefinition.java
===================================================================
--- jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/model/BpmnProcessDefinition.java	2010-01-15 14:04:04 UTC (rev 6083)
+++ jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/model/BpmnProcessDefinition.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -23,12 +23,15 @@
 
 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 org.jbpm.bpmn.common.Resource;
 import org.jbpm.pvm.internal.model.ExecutionImpl;
 import org.jbpm.pvm.internal.model.ProcessDefinitionImpl;
+import org.jbpm.pvm.internal.model.Transition;
 import org.jbpm.pvm.internal.model.VariableDefinitionImpl;
 import org.jbpm.pvm.internal.task.TaskDefinitionImpl;
 import org.w3c.dom.Element;
@@ -37,13 +40,15 @@
 
   private static final long serialVersionUID = 1L;
 
-  Map<String, TaskDefinitionImpl> taskDefinitions = new HashMap<String, TaskDefinitionImpl>();
-  List<VariableDefinitionImpl> processVariableDefinitions = new ArrayList<VariableDefinitionImpl>();
-  Map<String, Element> messages = new HashMap<String, Element>();
-  Map<String, Element> itemDefinitions = new HashMap<String, Element>();
-  Map<String, Element> interfaces = new HashMap<String, Element>();
-  Map<String, Element> operations = new HashMap<String, Element>();
-  Map<String, Resource> resources = new HashMap<String, Resource>();
+  protected Map<String, TaskDefinitionImpl> taskDefinitions = new HashMap<String, TaskDefinitionImpl>();
+  protected List<VariableDefinitionImpl> processVariableDefinitions = new ArrayList<VariableDefinitionImpl>();
+  protected Map<String, Element> messages = new HashMap<String, Element>();
+  protected Map<String, Element> itemDefinitions = new HashMap<String, Element>();
+  protected Map<String, Element> interfaces = new HashMap<String, Element>();
+  protected Map<String, Element> operations = new HashMap<String, Element>();
+  protected Map<String, Resource> resources = new HashMap<String, Resource>();
+  protected Map<String, Transition> sequenceFlow = new HashMap<String, Transition>();
+  protected Map<String, Set<String>> sourceToTargetMapping = new HashMap<String, Set<String>>(); // convience mapping: sourceRefId to all targetRefs
 
   protected ExecutionImpl newProcessInstance() {
     return new ExecutionImpl();
@@ -112,4 +117,38 @@
     this.itemDefinitions = itemDefinitions;
   }
 
+  public void addSequenceFlow(String transitionId, Transition transition) {
+    this.sequenceFlow.put(transitionId, transition);
+    
+    String source = transition.getSource().getName();
+    if (sourceToTargetMapping.get(source) == null) {
+      sourceToTargetMapping.put(source, new HashSet<String>());
+    }
+    sourceToTargetMapping.get(source).add(transition.getDestination().getName());
+  }
+  
+  public Map<String, Set<String>> getSourceToTargetMapping() {
+    return sourceToTargetMapping;
+  }
+  
+  public boolean isReachable(String srcActivityId , String dstActivityId) {
+    return isReachable(srcActivityId, dstActivityId, new HashSet<String>());
+  }
+  
+  protected boolean isReachable(String srcActivityId , String dstActivityId, Set<String> alreadyVisited) {
+    if (srcActivityId.equals(dstActivityId)) {
+      return true;
+    } else {
+      alreadyVisited.add(srcActivityId);
+      for (String destinationId : sourceToTargetMapping.get(srcActivityId)) {
+        if (!alreadyVisited.contains(destinationId)) {
+          if (isReachable(destinationId, dstActivityId, alreadyVisited)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
 }

Modified: jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/parser/BpmnParser.java
===================================================================
--- jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/parser/BpmnParser.java	2010-01-15 14:04:04 UTC (rev 6083)
+++ jbpm4/trunk/modules/bpmn/src/main/java/org/jbpm/bpmn/parser/BpmnParser.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -32,11 +32,8 @@
 import org.jbpm.api.activity.ActivityBehaviour;
 import org.jbpm.bpmn.common.Resource;
 import org.jbpm.bpmn.common.ResourceParameter;
-import org.jbpm.bpmn.flownodes.AbstractGatewayActivity;
 import org.jbpm.bpmn.flownodes.BpmnBinding;
 import org.jbpm.bpmn.flownodes.DatabasedGatewayActivity;
-import org.jbpm.bpmn.flownodes.ExclusiveGatewayActivity;
-import org.jbpm.bpmn.flownodes.InclusiveGatewayActivity;
 import org.jbpm.bpmn.model.BpmnProcessDefinition;
 import org.jbpm.internal.log.Log;
 import org.jbpm.pvm.internal.model.ActivityImpl;
@@ -118,7 +115,6 @@
         processDefinition.setDescription(description);
       }
 
-      // TODO: should be done in a different way? On a different level?
       parseResources((Element)processElement.getParentNode(), parse, processDefinition);
       
       parseInterfaces((Element)processElement.getParentNode(), parse, processDefinition);
@@ -135,7 +131,7 @@
       parseActivities(processElement, parse, processDefinition);
 
       // bind activities to their destinations
-      parseSequenceFlows(processElement, parse, processDefinition);
+      parseSequenceFlow(processElement, parse, processDefinition);
 
     } finally {
       parse.contextStackPop();
@@ -233,7 +229,7 @@
     }
   }
 
-  public void parseSequenceFlows(Element element, Parse parse, CompositeElementImpl compositeElement) {
+  public void parseSequenceFlow(Element element, Parse parse, BpmnProcessDefinition processDefinition) {
     List<Element> transitionElements = XmlUtil.elements(element, "sequenceFlow");
     for (Element transitionElement : transitionElements) {
       String transitionName = XmlUtil.attribute(transitionElement, "name", false, parse);
@@ -246,19 +242,26 @@
       }
       Element conditionElement = XmlUtil.element(transitionElement, "conditionExpression");
 
-      TransitionImpl transition = compositeElement.findActivity(sourceRef).createOutgoingTransition();
+      TransitionImpl transition = processDefinition.findActivity(sourceRef).createOutgoingTransition();
 
       try {
         // If something went wrong parsing the activity, there is no behaviour and an exception is thrown in .getBehaviour()
-        ActivityBehaviour a = compositeElement.findActivity(sourceRef).getActivityBehaviour();
-        if (a instanceof DatabasedGatewayActivity) {
-          if (transitionId.equals(((DatabasedGatewayActivity) a).getDefault())) {
-            compositeElement.findActivity(sourceRef).setDefaultOutgoingTransition(transition);
+        ActivityBehaviour behaviour = processDefinition.findActivity(sourceRef).getActivityBehaviour();
+        if (behaviour instanceof DatabasedGatewayActivity) {
+          DatabasedGatewayActivity databasedGatewayActivity = (DatabasedGatewayActivity) behaviour;
+          String defaultSeqFlow = databasedGatewayActivity.getDefault();
+          if (defaultSeqFlow != null) {
+            if (transitionId.equals(defaultSeqFlow)) {
+              processDefinition.findActivity(sourceRef).setDefaultOutgoingTransition(transition);
+            }
+          } else {
+            processDefinition.findActivity(sourceRef).setDefaultOutgoingTransition(null);
           }
         } else {
           // Other flownodes do not have default sequenceFlows, so set it to null
-          compositeElement.findActivity(sourceRef).setDefaultOutgoingTransition(null);
+          processDefinition.findActivity(sourceRef).setDefaultOutgoingTransition(null);
         }
+
       } catch (JbpmException je) {
         // catch it and only re-throw if not this specific exception.
         if (!je.getMessage().contains("no behaviour on")) {
@@ -280,15 +283,22 @@
           transition.setCondition(expressionCondition);
 
         } else {
-          parse.addProblem("Type of the conditionExpression on sequenceFlow with id=" + transitionId + " is of onsupported type 'bpmn:tExpression'",
-                  transitionElement);
+          parse.addProblem("Type of the conditionExpression on sequenceFlow with id=" + 
+                  transitionId + " is of onsupported type 'bpmn:tExpression'", transitionElement);
         }
       }
 
-      compositeElement.findActivity(targetRef).addIncomingTransition(transition);
+      ActivityImpl activity = processDefinition.findActivity(targetRef);
+      if (activity != null) {
+        activity.addIncomingTransition(transition);
+      } else {
+        parse.addProblem("TargetRef '" + targetRef + "' cannot be found");
+      }
 
       transition.setName(transitionId);
       transition.setDescription(transitionName);
+      
+      processDefinition.addSequenceFlow(transitionId, transition);
     }
   }
   public void parseDefinition(Element documentElement, Parse parse) {

Modified: jbpm4/trunk/modules/test-base/src/main/java/org/jbpm/test/assertion/CollectionAssertions.java
===================================================================
--- jbpm4/trunk/modules/test-base/src/main/java/org/jbpm/test/assertion/CollectionAssertions.java	2010-01-15 14:04:04 UTC (rev 6083)
+++ jbpm4/trunk/modules/test-base/src/main/java/org/jbpm/test/assertion/CollectionAssertions.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -24,6 +24,7 @@
  */
 package org.jbpm.test.assertion;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
@@ -40,7 +41,7 @@
    * Compares the elements of the two given collections.
    * The order of elements is not checked.
    */
-  public static <T>  void assertElementsEqual(Collection<T> collection1, Collection<T> collection2) {
+  public static <T>  void assertContainsSameElements(Collection<T> collection1, Collection<T> collection2) {
     
     Assert.assertTrue("One of the given collections is null, while the other collection is not null",
                     (collection1 == null && collection2 == null) 
@@ -62,6 +63,14 @@
     
   }
   
+  /**
+   * Compares the elements of the two given collections.
+   * The order of elements is not checked.
+   */
+  public static <T>  void assertContainsSameElements(Collection<T> collection1, T ... elements) {
+    assertContainsSameElements(collection1, Arrays.asList(elements));
+  }
+  
   @SuppressWarnings("unchecked")
   private static String debugCollections(Collection ... collections) {
     StringBuilder strb = new StringBuilder();

Modified: jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/ParallelGatewayTest.java
===================================================================
--- jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/ParallelGatewayTest.java	2010-01-15 14:04:04 UTC (rev 6083)
+++ jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/ParallelGatewayTest.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -68,7 +68,7 @@
         List<Task> allTasks = taskQuery.list();
 
         assertEquals(2, allTasks.size());
-        CollectionAssertions.assertElementsEqual(Arrays.asList("UserTaskLeg1", "UserTaskLeg2"), 
+        CollectionAssertions.assertContainsSameElements(Arrays.asList("UserTaskLeg1", "UserTaskLeg2"), 
                 Arrays.asList(allTasks.get(0).getActivityName(), allTasks.get(1).getActivityName()));
         
         // specifying a transition is unnecessary, BPMN has outgoing AND semantic!

Added: jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/gateway/InclusiveGatewayTest.java
===================================================================
--- jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/gateway/InclusiveGatewayTest.java	                        (rev 0)
+++ jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/gateway/InclusiveGatewayTest.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -0,0 +1,159 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This 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; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jbpm.bpmn.test.gateway;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jbpm.api.ProcessInstance;
+import org.jbpm.test.JbpmTestCase;
+import org.jbpm.test.assertion.CollectionAssertions;
+
+/**
+ * @author Joram Barrez
+ */
+public class InclusiveGatewayTest extends JbpmTestCase {
+  
+  private static final String SIMPLE_SPLIT =
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='simpleInclusiveSplit' name='inclusiveSplit' >" +
+    "    <startEvent id='theStart' />" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='inclusiveGateway' />" +
+    "    <inclusiveGateway id='inclusiveGateway' />" +
+    "    <sequenceFlow id='flow2' sourceRef='inclusiveGateway' targetRef='wait1' >" +
+    "      <conditionExpression xsi:type='tFormalExpression'>${var &gt; 5}</conditionExpression>" +
+    "    </sequenceFlow>" +
+    "    <sequenceFlow id='flow3' sourceRef='inclusiveGateway' targetRef='wait2' />" + // wait2 will always be reached since it has no condition
+    "    <sequenceFlow id='flow4' sourceRef='inclusiveGateway' targetRef='wait3' >" +
+    "      <conditionExpression xsi:type='tFormalExpression'>${var &gt; 10}</conditionExpression>" +
+    "    </sequenceFlow>" +
+    "    <receiveTask id='wait1' />" +
+    "    <sequenceFlow id='flow5' sourceRef='wait1' targetRef='theEnd' />" +
+    "    <receiveTask id='wait2' />" +
+    "    <sequenceFlow id='flow6' sourceRef='wait2' targetRef='theEnd' />" +
+    "    <receiveTask id='wait3' />" +
+    "    <sequenceFlow id='flow7' sourceRef='wait3' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' >" +
+    "      <terminateEventDefinition/>" +
+    "    </endEvent>" +
+    "  </process>" +
+    "</definitions>";
+  
+  /* 
+   * Copy of the SIMPLE_SPLIT_PROCESS, where the 'always' outgoing sequence flow is replaced
+   * by a default sequence flow.
+   */
+  private static final String SIMPLE_SPLIT_WITH_DEFAULT =
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='simpleInclusiveSplitWithDefault' name='inclusiveSplitWithDefault' >" +
+    "    <startEvent id='theStart' />" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='inclusiveGateway' />" +
+    "    <inclusiveGateway id='inclusiveGateway' default='flow3' />" +
+    "    <sequenceFlow id='flow2' sourceRef='inclusiveGateway' targetRef='wait1' >" +
+    "      <conditionExpression xsi:type='tFormalExpression'>${var &gt; 5}</conditionExpression>" +
+    "    </sequenceFlow>" +
+    "    <sequenceFlow id='flow3' sourceRef='inclusiveGateway' targetRef='wait2' />" +
+    "    <sequenceFlow id='flow4' sourceRef='inclusiveGateway' targetRef='wait3' >" +
+    "      <conditionExpression xsi:type='tFormalExpression'>${var &gt; 10}</conditionExpression>" +
+    "    </sequenceFlow>" +
+    "    <receiveTask id='wait1' />" +
+    "    <sequenceFlow id='flow5' sourceRef='wait1' targetRef='theEnd' />" +
+    "    <receiveTask id='wait2' />" +
+    "    <sequenceFlow id='flow6' sourceRef='wait2' targetRef='theEnd' />" +
+    "    <receiveTask id='wait3' />" +
+    "    <sequenceFlow id='flow7' sourceRef='wait3' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' >" +
+    "      <terminateEventDefinition/>" +
+    "    </endEvent>" +
+    "  </process>" +
+    "</definitions>";
+  
+  private static final String SIMPLE_SPLIT_AND_MERGE =
+    "<definitions xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
+    "  <process id='simpleSplitAndMerge' name='inclusiveSplitAndMerge' >" +
+    "    <startEvent id='theStart' />" +
+    "    <sequenceFlow id='flow1' sourceRef='theStart' targetRef='inclusiveSplit' />" +
+    "    <inclusiveGateway id='inclusiveSplit' default='flow3' />" +
+    "    <sequenceFlow id='flow2' sourceRef='inclusiveSplit' targetRef='wait1' >" +
+    "      <conditionExpression xsi:type='tFormalExpression'>${var &gt; 5}</conditionExpression>" +
+    "    </sequenceFlow>" +
+    "    <sequenceFlow id='flow3' sourceRef='inclusiveSplit' targetRef='wait2' />" + 
+    "    <sequenceFlow id='flow4' sourceRef='inclusiveSplit' targetRef='wait3' >" +
+    "      <conditionExpression xsi:type='tFormalExpression'>${var &gt; 10}</conditionExpression>" +
+    "    </sequenceFlow>" +
+    "    <receiveTask id='wait1' />" +
+    "    <sequenceFlow id='flow5' sourceRef='wait1' targetRef='inclusiveJoin' />" +
+    "    <receiveTask id='wait2' />" +
+    "    <sequenceFlow id='flow6' sourceRef='wait2' targetRef='inclusiveJoin' />" +
+    "    <receiveTask id='wait3' />" +
+    "    <sequenceFlow id='flow7' sourceRef='wait3' targetRef='inclusiveJoin' />" +
+    "    <inclusiveGateway id='inclusiveJoin' />" +
+    "    <sequenceFlow id='flow8' sourceRef='inclusiveJoin' targetRef='theEnd' />" +
+    "    <endEvent id='theEnd' />" +
+    "  </process>" +
+    "</definitions>";
+  
+  public void testSimpleSplit() {
+    deployBpmn2XmlString(SIMPLE_SPLIT);
+    
+    // A var value < 5 will trigger all outgoing sequence flow
+    startAndVerifySimpleSplitProcess("simpleInclusiveSplit", 15, "wait1", "wait2", "wait3");
+    
+    // A var value 0 < x < 10 will trigger only two sequence flow
+    startAndVerifySimpleSplitProcess("simpleInclusiveSplit", 7, "wait1", "wait2");
+    
+    // A var value < 5 trigger only one sequence flow (the one without a condition)
+    startAndVerifySimpleSplitProcess("simpleInclusiveSplit", 3, "wait2");
+  }
+  
+  public void testSimpleSplitWithDefault() {
+    deployBpmn2XmlString(SIMPLE_SPLIT_WITH_DEFAULT);
+    
+    // A var value < 5 will trigger all outgoing sequence flow, but not the default one
+    startAndVerifySimpleSplitProcess("simpleInclusiveSplitWithDefault", 15, "wait1", "wait3");
+    
+    // A var value 0 < x < 10 will trigger only one sequence flow
+    startAndVerifySimpleSplitProcess("simpleInclusiveSplitWithDefault", 7, "wait1");
+    
+    // A var value < 5 trigger only one sequence flow (the default one)
+    startAndVerifySimpleSplitProcess("simpleInclusiveSplitWithDefault", 3, "wait2");
+  }
+  
+  public void testSimpleSplitAndMerge() {
+    deployBpmn2XmlString(SIMPLE_SPLIT_AND_MERGE);
+    
+    ProcessInstance pi = startAndVerifySimpleSplitProcess("simpleSplitAndMerge", 17, "wait1", "wait3");
+    executionService.signalExecutionById(pi.findActiveExecutionIn("wait1").getId());
+    assertProcessInstanceActive(pi);
+    executionService.signalExecutionById(pi.findActiveExecutionIn("wait3").getId());
+    assertProcessInstanceEnded(pi);
+  }
+  
+  private ProcessInstance startAndVerifySimpleSplitProcess(String processKey, Integer varValue, String ... expectedActivities) {
+    Map<String, Object> vars = new HashMap<String, Object>();
+    vars.put("var", varValue);
+    ProcessInstance pi = executionService.startProcessInstanceByKey(processKey, vars);
+    CollectionAssertions.assertContainsSameElements(pi.findActiveActivityNames(), expectedActivities);
+    return pi;
+  }
+  
+}

Modified: jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/gateway/ParallelGatewayMergeTest.java
===================================================================
--- jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/gateway/ParallelGatewayMergeTest.java	2010-01-15 14:04:04 UTC (rev 6083)
+++ jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/bpmn/test/gateway/ParallelGatewayMergeTest.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -113,14 +113,14 @@
     deployBpmn2XmlString(TEST_SIMPLE_MERGE_PROCESS);
     ProcessInstance pi = executionService.startProcessInstanceByKey("simpleMerge");
     pi.findActiveActivityNames();
-    CollectionAssertions.assertElementsEqual(pi.findActiveActivityNames(), Arrays.asList("wait1", "wait2"));
+    CollectionAssertions.assertContainsSameElements(pi.findActiveActivityNames(), Arrays.asList("wait1", "wait2"));
   }
   
   public void testNestedParallelMerge() {
     deployBpmn2XmlString(TEST_NESTED_MERGE_PROCESS);
     
     ProcessInstance pi = executionService.startProcessInstanceByKey("nestedMerge");
-    CollectionAssertions.assertElementsEqual(pi.findActiveActivityNames(), Arrays.asList("wait1", "wait2"));
+    CollectionAssertions.assertContainsSameElements(pi.findActiveActivityNames(), Arrays.asList("wait1", "wait2"));
     
     executionService.signalExecutionById(pi.findActiveExecutionIn("wait1").getId());
     executionService.signalExecutionById(pi.findActiveExecutionIn("wait2").getId());
@@ -131,7 +131,7 @@
     deployBpmn2XmlString(TEST_NESTED_MERGE_PROCESS_2);
     
     ProcessInstance pi = executionService.startProcessInstanceByKey("nestedMerge2");
-    CollectionAssertions.assertElementsEqual(pi.findActiveActivityNames(), Arrays.asList("wait"));
+    CollectionAssertions.assertContainsSameElements(pi.findActiveActivityNames(), Arrays.asList("wait"));
     executionService.signalExecutionById(pi.findActiveExecutionIn("wait").getId());
     assertProcessInstanceEnded(pi);
     

Modified: jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/test/activity/forkjoin/MultipleForksTest.java
===================================================================
--- jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/test/activity/forkjoin/MultipleForksTest.java	2010-01-15 14:04:04 UTC (rev 6083)
+++ jbpm4/trunk/modules/test-db/src/test/java/org/jbpm/test/activity/forkjoin/MultipleForksTest.java	2010-01-16 00:21:04 UTC (rev 6084)
@@ -162,7 +162,7 @@
       "</process>");
     
     ProcessInstance pi = executionService.startProcessInstanceByKey("nestedForks");
-    CollectionAssertions.assertElementsEqual(pi.findActiveActivityNames(), Arrays.asList("wait"));
+    CollectionAssertions.assertContainsSameElements(pi.findActiveActivityNames(), Arrays.asList("wait"));
     executionService.signalExecutionById(pi.findActiveExecutionIn("wait").getId());
     assertProcessInstanceEnded(pi);
   }



More information about the jbpm-commits mailing list