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:
Comments