MultiChoiceForkAH

This Multi Choice fork will create an appropriate thread of execution (token) for each dynamically specified item.  For instance, in the case of a customer order containing one phone service product, one internet service product, and two web hosting products:  one token will be created and sent down the 'phone' transition, one token will be created and sent down the 'internet' transition, and two separate tokens can be each created and sent separately down the 'web' transition.

 

Optionally, this fork can be configured to simply create a token for each combination of 'item' and 'transition'.

 

A Collection of Objects driving the choices must be stored in a process instance variable, and each object must share a common accessor method for determining its 'type' (or just use a String).  Just about everything is configurable--see the comments in the sample process definition and attached code.

 

<?xml version="1.0" encoding="UTF-8"?>
<process-definition name='ForEachFork Test'> 
    <start-state name='START' >
        <event type='node-leave' >
            <script>
                <expression>List list = new ArrayList(); list.add("item1"); list.add("item2"); list.add("item3");</expression>
                <variable name='myList' access='write' mapped-name='list' ></variable>
             </script>
        </event>
        <transition name='done' to='_TEST_FORK'></transition>
    </start-state>

    <!-- Attempts to provide a robust implementation of workflow pattern 6, multi-choice. -->
    <state name='_TEST_FORK' >
        <event type='node-enter' >
            <action class='org.bminer.jbpm.pd.ah.ForEachForkAH' config-type='bean'>
                <!-- process instance variable containing a Collection of items for which we will
                     create sub tokens. -->
                <inputCollectionVar>myList</inputCollectionVar>
                <!-- If the collection is not of Strings, this property may be specified and  
                     should be available via a 'get', 'is', or 'has' accessor method on each item in 
                     the collection.  Alternatively, an EL expression may be supplied where
                     'tempForEachForkAHInputItem' is the Input Item being evaluated against. 
                     The returned value must be a String or String.valueOf() compatible value. 
                     The value will be used to identify the item and to match
                     against available transition names when matchItemWithTrans = 'true'.
                     If not provided for non-String inputCollections, toString() will be
                     called on those items to provide a String value. -->
                <inputItemProperty></inputItemProperty>
                <!-- The variable name in the branch token which will hold the Input Item from the
                     inputCollectionVar that caused the creation of the branch token.  If no name is
                     given, the Input Item will not be stored separately under its associated
                     branch token. -->
                <inputItemMappedName>inputItem</inputItemMappedName>
                <!-- 'true' will create a token for each inputCollection item that corresponds to a
                     non-reserved transition name, and then signal that token down the matching
                     transition. ('reserved' transition names include those configured under
                     'noMatchTrans' and 'noListTrans' as well as any transition beginning with 
                     '_sys_'.)  'false' will create and signal a token for every combination of 
                     inputCollection item and transition. Required. -->
                <matchItemWithTrans>true</matchItemWithTrans>
                <!-- 'true' reduces the inputCollection to a Set prior to matching the collection
                     to the configured transitions. Default is 'false' -->
                <matchUnique>true</matchUnique>
                <!-- When using matchItemWithTrans = 'true', this is the default transition to take for 
               any inputCollection item that has no matching transition name, 
                     otherwise no token will be generated for that item -->
                <noMatchTrans>noMatch</noMatchTrans>
                <!-- 'true' if the specified 'noMatchTrans' should be taken only once regardless of 
                     how many un-matched items exist, or 'false' if the 'noMatchTrans' should be 
                     taken for each item that has no matching transition. Default is 'true'. -->
                <noMatchDoTransOnce>true</noMatchDoTransOnce>
                <!-- If any item fails to have a matching transition, set this to 'true' if other matched 
                     transitions should be taken, 'false' if only 'notMatchTrans' transitions should be taken. 
                     In other words, if there is any failure to match, nothing but the failure(s) will be 
                     processed if this is set to 'false'.  Default is 'false'. -->
                <noMatchDoMatched>false</noMatchDoMatched>
                <!-- transition to take if there is nothing in the inputCollection, otherwise
                     nothing will be done and we'll halt execution in this node.
                     Since we don't fork in this scenario, any subsequent nodes should
                     lead the root token past the join that corresponds to this fork -->
                <noListTrans>noList</noListTrans>
            </action>
        </event>
        <transition name='item1' to='ITEM_1' ></transition>
     <transition name='item2' to='ITEM_2' ></transition>
     <transition name='item3' to='ITEM_3' ></transition>
        <transition name='noMatch' to='UNSUPPORTED_ITEM' ></transition>
        <transition name='noList' to='NO_ITEMS' ></transition>
        <transition name='_sys_redoNode' to='_TEST_FORK' ></transition>
    </state>

    <state name='ITEM_1' >
     <event type='node-enter' >
         <script>System.out.println("Processing item 1!");</script>
     </event>
        <transition name='done' to='JOIN' ></transition>
    </state>

    <state name='ITEM_2' >
     <event type='node-enter' >
          <script>System.out.println("Processing item 2!");</script>
     </event>
        <transition name='done' to='JOIN' ></transition>
    </state>

    <state name='ITEM_3' >
     <event type='node-enter' >
          <script>System.out.println("Processing item 3!");</script>
     </event>
        <transition name='done' to='JOIN' ></transition>
    </state>
    
    <state name='UNSUPPORTED_ITEM' >
     <event type='node-enter' >
          <script>System.out.println("Received an unsupported item!");</script>
     </event>
        <transition name='done' to='JOIN' ></transition>
    </state>
    
    <state name='NO_ITEMS' >
     <event type='node-enter' >
          <script>System.out.println("There was no collection, so I didn't fork!");</script>
     </event>
        <transition name='nothingToDo' to='END' ></transition>
    </state>

    <join name='JOIN'>
     <transition name='done' to='END' ></transition>
    </join>

    <end-state name='END' ></end-state>
    
</process-definition>

 

Hope this is useful.

 

(updated to provide a more recent example that fixes two mentioned bugs and includes the "inputMappedName" configuration parameter.)

 

 

 

Referenced by: