9 Replies Latest reply on Jun 30, 2010 6:28 AM by toni wegner

    nested forks

    Sebastian Castellanos Newbie

      Hi,
      I have a situation where you have two nested fork, at the first fork left a task unfinished and launched the second fork with two more tasks, but when it launches the second fork, I repeat one of the tasks of the second Fork.
      it means if the second Fork is associated with(Task 1, Task 2), when executed, throws (Task 1, Task 2, Task 1).
      The second fork should not only launch their two tasks?, By repeating one of them?. this is normal operation with two nested Fork?.

      Greetings

      Sebastian.

        • 1. Re: nested forks
          Santanu Saraswati Novice

          Sebastian,
          So far I understand, you have some flow like this:

          <process name="ForumFlow2" xmlns="http://jbpm.org/4.0/jpdl">
           <start g="146,26,48,48" name="start1">
           <transition g="-43,-18" name="to fork1" to="fork1"/>
           </start>
           <fork g="148,100,48,48" name="fork1">
           <transition g="-44,-18" name="to task1" to="task1"/>
           <transition g="-44,-18" name="to task2" to="task3"/>
           </fork>
           <task g="205,191,92,52" name="task3">
           <transition g="-41,-18" name="to join1" to="join1"/>
           </task>
           <task g="59,196,92,52" name="task1">
           <transition g="-41,-18" name="to join1" to="join1"/>
           </task>
           <join g="152,287,48,48" name="join1" multiplicity="1">
           <transition g="-43,-18" name="to fork2" to="fork2"/>
           </join>
           <fork g="298,285,48,48" name="fork2">
           <transition g="-44,-18" name="to task3" to="task2"/>
           <transition g="-44,-18" name="to task1" to="task1"/>
           </fork>
           <task g="399,189,92,52" name="task2">
           <transition g="-41,-18" name="to join2" to="join2"/>
           </task>
           <task g="408,339,92,52" name="task1">
           <transition g="-41,-18" name="to join2" to="join2"/>
           </task>
           <join g="555,267,48,48" name="join2">
           <transition g="-42,-18" name="to end1" to="end1"/>
           </join>
           <end g="559,388,48,48" name="end1"/>
          </process>
          


          In the first fork-join, you complete only task2 and leave task1 open and move on to the second fork-join. There you have two more tasks task1 and task3. Now when you search for active tasks you get task1, task1 and task3. But you expect only task1 and task3 to come and the earlier task1 should not come.

          To achieve this you need to cancel task1 while taking a transition from first join node. Otherwise incomplete task task1 will always appear in the active task lists.

          I would definitely wish to have a join node which based on some configuration cancels all the pending tasks automatically. But so far nothing such is available.

          I hope I got you problem correct. If not then that will establish Tom's point in the sticky - Give the jpdl and test case to get a solution quickly :)


          • 2. Re: nested forks
            Sebastian Castellanos Newbie

            I understand your idea, but i think that is not my situation.
            I am attaching an example.

            
            <process name="a" xmlns="http://jbpm.org/4.0/jpdl">
            
             <start g="5,129,48,48" name="start1">
             <transition to="fork1"/>
             </start>
             <end g="957,121,48,48" name="end1"/>
            
             <task g="321,197,92,52" name="task2" swimlane="B">
             <transition g="444,222:" to="join2"/>
             </task>
             <task g="75,384,92,52" name="task3" swimlane="A">
             <transition g="684,410:" to="join1"/>
             </task>
             <task g="745,120,92,52" name="task8">
             <transition to="end1"/>
             </task>
             <task g="513,120,92,52" name="task9" swimlane="B">
             <transition to="join1"/>
             </task>
             <task g="324,46,92,52" name="task1" swimlane="C">
             <transition g="440,70:" to="join2"/>
             </task>
             <fork g="266,125,48,48" name="fork2">
             <transition g="289,73:" to="task1"/>
             <transition g="292,226:" to="task2"/>
             </fork>
             <join g="417,123,48,48" name="join2">
             <transition to="task9"/>
             </join>
             <join g="652,119,48,48" name="join1">
             <transition to="task8"/>
             </join>
             <fork g="93,126,48,48" name="fork1">
             <transition to="task3"/>
             <transition to="fork2"/>
             </fork>
            </process>
            
            


            In this case, it`s repeating Task1 in the second Fork (fork2).

            Thanks for responding so fast.

            • 3. Re: nested forks
              Sebastian Castellanos Newbie


              I attach a test case for the situation described above.

              Greetings and thanks for the support so far.

              Sebatian

              package org.jbpm.examples.task.swimlane;
              
              import java.util.List;
              
              import org.jbpm.api.task.Task;
              import org.jbpm.test.JbpmTestCase;
              
              /**
               * @author Tom Baeyens
               */
              public class TaskSwimlaneTest extends JbpmTestCase {
              
               String deploymentId;
              
               protected void setUp() throws Exception {
               super.setUp();
              
               deploymentId = repositoryService.createDeployment()
               .addResourceFromClasspath("org/jbpm/examples/task/swimlane/process.jpdl.xml")
               .deploy();
              
               }
              
               protected void tearDown() throws Exception {
               // delete process deployment
               repositoryService.deleteDeploymentCascade(deploymentId);
              
               super.tearDown();
               }
              
               public void testTaskSwimlane() {
               executionService.startProcessInstanceByKey("TaskSwimlane");
              
               List<Task> taskList = taskService.createTaskQuery().list();
               assertEquals(3, taskService.createTaskQuery().list().size());
               assertEquals("Expected only 3 task in task list", 3, taskList.size());
               }
              }
              
              


              • 4. Re: nested forks
                Santanu Saraswati Novice

                Hi Sebastian,
                You found a bug there. It took sometime to understand why is it behaving that way. I think the problem is there in ForkActivity class. Actually the following things happen in sequence transition from one Fork node to any other node.

                1. Create child execution for each transition
                2. Mark state of each child execution as STATE_ACTIVE_CONCURRENT
                3. take the transition
                next few steps are regular transition steps:
                4. Perform transition end activity (for the source node)
                5. Perform transition take activity
                6. Execute transition start event listener on destination
                7. Perform transition start activity (for destination)
                8. Execute the activity behaviour of the destination

                Now for your case, at the last step it will execute ForkActivity again. Here is the fork activity execute method. Please notice the line in bold and the if block surrounding:

                 public void execute(ExecutionImpl execution) {
                 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();
                 if ( (condition==null)
                 || (condition.evaluate(execution))
                 ) {
                 forkingTransitions.add(transition);
                 }
                 }
                
                 // if no outgoing transitions should be forked,
                 if (forkingTransitions.size()==0) {
                 // end this execution
                 execution.end();
                
                 // if there is exactly 1 transition to be taken, just use the incoming execution
                 } else if (forkingTransitions.size()==1) {
                 execution.take(forkingTransitions.get(0));
                
                 // if there are more transitions
                 } else {
                 ExecutionImpl concurrentRoot = null;
                 if (Execution.STATE_ACTIVE_ROOT.equals(execution.getState())) {
                 concurrentRoot = execution;
                 execution.setState(Execution.STATE_INACTIVE_CONCURRENT_ROOT);
                 execution.setActivity(null);
                 } else if (Exec ution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) {
                 concurrentRoot = execution.getParent();
                 }
                
                 for (Transition transition: forkingTransitions) {
                 // launch a concurrent path of execution
                 String childExecutionName = transition.getName();
                 ExecutionImpl concurrentExecution = concurrentRoot.createExecution(childExecutionName);
                 concurrentExecution.setActivity(activity);
                 concurrentExecution.setState(Execution.STATE_ACTIVE_CONCURRENT);
                 concurrentExecution.take(transition);
                
                 if (concurrentRoot.isEnded()) {
                 break;
                 }
                 }
                 }
                 }
                


                For a fork, the state of any child execution is Execution.STATE_ACTIVE_CONCURRENT. So for our nested fork state will be STATE_ACTIVE_CONCURRENT and it will satisfy the else if condition:

                 if (Execution.STATE_ACTIVE_ROOT.equals(execution.getState())) {
                 concurrentRoot = execution;
                 execution.setState(Execution.STATE_INACTIVE_CONCURRENT_ROOT);
                 execution.setActivity(null);
                 } else if (Exec ution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) {
                 concurrentRoot = execution.getParent();
                 }
                


                So the root of the children of the nested fork becomes the first level fork! Now the codes goes into the next loop:

                 for (Transition transition: forkingTransitions) {
                 // launch a concurrent path of execution
                 String childExecutionName = transition.getName();
                 ExecutionImpl concurrentExecution = concurrentRoot.createExecution(childExecutionName);
                 concurrentExecution.setActivity(activity);
                 concurrentExecution.setState(Execution.STATE_ACTIVE_CONCURRENT);
                 concurrentExecution.take(transition);
                
                 if (concurrentRoot.isEnded()) {
                 break;
                 }
                 }
                


                Here
                ExecutionImpl concurrentExecution = concurrentRoot.createExecution(childExecutionName);
                line creates a child execution of actually the grand parent execution. In the same createExecution method of ExecutionImpl it also sets the propagation to explicit to avoid automatic transition.

                public ExecutionImpl createExecution(String name) {
                 // when an activity calls createExecution, propagation is explicit.
                 // this means that the default propagation (proceed()) will not be called
                 propagation = Propagation.EXPLICIT;
                 ...
                 ...
                }
                


                Now the stuation where we land up is - the grand parent execution of the first fork (fork1) is set to propagation EXPLICIT, but it was already EXPLICIT because the grand parent has its own children and while taking transition to the children it became EXPLICIT. The execution of second lavel fork (fork2) has never been used to create a child execution. So its propagation status remains UNSPECIFIED.

                All executions with unspecified propagation status proceeds automatically. So does our fork2. So fork2 takes its default transition again. Which for your case is to Task1. So it goes ahead and executes TaskActivity for Task1 again. Thus we get two Task1 created at the end.

                Now if we change the line in bold there to this:
                 if (Execution.STATE_ACTIVE_ROOT.equals(execution.getState())) {
                 concurrentRoot = execution;
                 execution.setState(Execution.STATE_INACTIVE_CONCURRENT_ROOT);
                 execution.setActivity(null);
                 } else if (Exec ution.STATE_ACTIVE_CONCURRENT.equals(execution.getState())) {
                 //concurrentRoot = execution.getParent();
                 concurrentRoot = execution;
                 }
                


                Basically we make the current root always the current execution and create children of that. This will fix your problem.

                Now I have no idea why for nested forks the parent of the inner fork is used to create children of the inner fork is not very clear to me. I would expect there must be some usecase which require this.

                So the only thing we can do now is to report a bug. You also have the nice flow and testcase ready.

                Well, the post became quite big and complex. Hope this is not very confusing!

                • 5. Re: nested forks
                  Sebastian Castellanos Newbie


                  Thank you very much for your help, I have already reported the bug.
                  You think of any alternative for the management of the Fork? because I need to shoot 2 tasks,and one of the "optional", which is not controlled at the end. but without a Fork not how.
                  here ther is an example but not working

                  I would like the task 2 shot in Task 3 and Task 4, but I can not achieve.

                  <process name="c" xmlns="http://jbpm.org/4.0/jpdl">
                   <start g="15,176,48,48" name="start1">
                   <transition to="task1"/>
                   </start>
                   <end g="655,182,48,48" name="end1"/>
                   <task g="241,173,92,52" name="task2">
                   <transition to="task4"/>
                   <transition to="task3"/>
                   </task>
                   <task g="357,61,92,52" name="task3">
                   <transition to="task5"/>
                   </task>
                   <task g="362,327,92,52" name="task4">
                   <transition to="task5"/>
                   </task>
                   <task g="113,174,92,52" name="task1">
                   <transition to="task2"/>
                   </task>
                   <task name="task5" g="489,181,92,52">
                   <transition to="end1"/>
                   </task>
                  
                  


                  • 7. Re: nested forks
                    Santanu Saraswati Novice

                    We can use our understanding about the bug to have a hack for the time being.

                    We see task1 twice because the transition to task1 is the default transition of the fork node. To stop task1 from appearing twice we need to make sure that it is not the default transition. Instead we can add a dummy node for default transition to make sure that the rest of the forked nodes behave properly. We can try a flow like this:

                    <?xml version="1.0" encoding="UTF-8"?>
                    
                    <process name="ForumFlow2" xmlns="http://jbpm.org/4.0/jpdl">
                    
                     <start g="5,129,48,48" name="start1">
                     <transition to="fork1"/>
                     </start>
                     <end g="957,121,48,48" name="end1"/>
                    
                     <task g="300,199,92,52" name="task2">
                     <transition g="444,222:" to="join2"/>
                     </task>
                     <task g="334,329,92,52" name="task3">
                     <transition g="679,358:" to="join1"/>
                     </task>
                     <task g="745,120,92,52" name="task8">
                     <transition to="end1"/>
                     </task>
                     <task g="513,120,92,52" name="task9">
                     <transition to="join1"/>
                     </task>
                     <task g="294,123,92,52" name="task1">
                     <transition to="join2"/>
                     </task>
                     <fork g="211,130,48,48" name="fork2">
                     <transition name="to java1" to="java1" g="235,68:-45,-18"/>
                     <transition to="task1"/>
                     <transition g="235,228:" to="task2"/>
                     </fork>
                     <join g="417,123,48,48" name="join2">
                     <transition to="task9"/>
                     </join>
                     <join g="652,119,48,48" name="join1">
                     <transition to="task8"/>
                     </join>
                     <fork g="93,126,48,48" name="fork1">
                     <transition g="114,355:" to="task3"/>
                     <transition to="fork2"/>
                     </fork>
                     <java name="java1"
                     class="org.jbpm.test.DoNothingActivity"
                     method="doNothing"
                     g="301,41,92,52">
                     </java>
                    </process>
                    


                    And a dummy java activity class

                    package org.jbpm.test;
                    
                    /**
                     * @author Santanu
                     *
                     */
                    public class DoNothingActivity{
                    
                     public void doNothing() {
                     System.out.println("Doing nothing");
                     }
                    
                    }
                    


                    This should give you correct list of tasks after the nested fork.


                    • 8. Re: nested forks
                      Sebastian Castellanos Newbie

                      Thank you very much for the contribution, both solutions worked.
                      in the end I did was change the line that You mentioned earlier,
                      concurrentRoot = execution.getParent (); by concurrentRoot = execution;
                      and it worked perfectly

                      Thank you very much ;)
                      Greetings

                      • 9. Re: nested forks
                        toni wegner Newbie

                        .... this problem is fixed in version 4.4. I tested it.

                         

                        kind regards