Other expressions

Miscellaneous expressions, some of them are tools (like 'log'), some others are rather advanced (like 'eval' for example).

save and restore

The 'save' expression is used for saving a workitem into a variable, the 'restore' expression can then later restore it from the variable and make it the workitem 'in use' by the current flow.

Here is an example of a flow using 'when', 'save' and 'restore' to achieve interesting effects :


<?xml version="1.0" encoding="UTF-8"?> 

<process-definition name="example" revision="xx.yy.zz" >

    <description>
save and restore example
    </description>

    <concurrence>

        <when>
            <defined variable-value="/saved-workitem" />
            <sequence>
                <!--
                    makes the workitem used by this 'thread' of execution
                    the workitem saved in the "/saved-workitem" variable.
                -->
                <restore from-variable="/saved-workitem" />
                <!-- 
                     other possibilities :

                <restore 
                    from-variable="/saved-workitem" 
                    merge-lead="restored"
                />

                     or

                <restore 
                    from-variable="/saved-workitem" 
                    merge-lead="current"
                />

                     if 'merge' is not present, then the restored workitem
                     simply replaces the current workitem.
                -->
                <participant ref="role-bravo" />
            </sequence>
        </when>

        <sequence>
            <participant ref="role-alpha" />
            <!--
                saves the workitem in the "/saved-workitem" variable.
                The initial forward slash indicates that the variable must
                be set at the root expression level. In this example, the
                root expression is the 'concurrence' expression.
            -->
            <save to-variable="/saved-workitem" />
        </sequence>

    </concurrence>

</process-definition>

This process directly goes to 'role-alpha', when somehow this participant finished its work with the workitem, it is saved in the variable 'saved-workitem'. As there is a slash in front of 'saved-workitem', it means that this is a global variable (accessible from anywhere in the process definition).

Three type of merge are possible for the 'restore' operation. The first one is implicit when there is no 'merge-lead' attribute specified : the restored workitem replaces (hides) the workitem used until the restore. When there is a 'merge-lead' specified, the current and the restored workitem are merged. In case of 'merge-lead' set to "restored", if a field appears both in the restored and in the current workitem, the value found in the restored workitem will take precedence. You can give the priority of the merge to the current workitem by setting 'merge-lead' to "current".

The 'save' and 'restore' expression are very useful. They are mainly used with global variables (variable whose names is preceded by a slash).

The 'restore' expression has another attribute : 'to-field'. This attribute invalidates the effect of the 'merge-lead' attribute.


    <restore from-variable="/saved-workitem" to-field="restored" />

This process definition snippet will take the workitem saved in the global variable named 'saved-workitem' and insert its attribute map has the content of the field named 'restored' of the current workitem. This technique allows for an easy merge system.

Saving to files and restoring from them

This feature is part of OpenWFE since the [pre-]release 1.7.2pre3.

The <save> expression has a 'to-file' attribute and the <restore> has the counterpart : 'from-file'. That makes such definitions possible :


    <sequence>
        <participant ref="role-alpha" />
        <save to-file="workitems/wi_1.xml" />

        <participant ref="role-bravo" />
        <save to-file="workitems/wi_2.xml" />

        <participant ref="role-gamma" />
        <save to-file="workitems/wi_final.xml" />
    </sequence>

In that [trivial] definition, the workitem is backed up after each participant (a candidate for a subprocess definition integrate that 'archival' step). <save to-file="..." /> is very similar to the FileArchiving participant found in the section called “Implementing custom participants”.

A restore can be performed from another segment of the same flow or from another process like this :


        <restore from-file="workitems/wi_final.xml" remove-file="yes" />

The 'remove-file' attribute set to 'yes' (or 'true') indicates that after a [successfule] restore, the file should be deleted. The 'merge-lead' and the 'to-field' attributes still apply.

eval

Since OpenWFE 1.6.1, there is an 'eval' expression. Its purpose is to give an even greater flexibility to process definitions.

Let's imagine a scenario where sometimes a segment of workflow has to be executed in sequence and sometimes within a concurrence :


        <if>
            <equals variable-value="order" other-value="concurrence" />

            <!-- then -->
            <concurrence>
                <participant ref="cio" />
                <participant ref="cto" />
            </concurrence>

            <!-- else -->
            <sequence>
                <participant ref="cio" />
                <participant ref="cto" />
            </sequence>
        </if>

Seems a bit long, the repetition of participants is rather ugly too. With 'eval', this could become :


    <sequence>
        <sub0 order="concurrence" p1="cio" p2="cto" />
    </sequence>

    <process-definition name="sub0">
        <eval>
            <![CDATA[
            <${order}>
                <participant ref="${p1}" />
                <participant ref="${p2}" />
            </${order}>
            ]]>
        </eval>
    </process-definition>

'Quoting' the inner XML snip with CDATA is essential, else the engine might try to immediately evaluate the snip.

The 'eval' expression may also alternatively fetch its XML input from a field in the current workitem :


    <sequence>
        <participant ref="special-agent" />
        <eval field="snip_to_execute" />
    </sequence>

In this sequence, the preceding 'special-agent' could fill the field 'snip_to_execute' with XML it generated on the fly. Of course, calling a subprocess via a URL pointing to a jsp/cgi script generating flow definitions on the fly and tailor-made would be equivalent.

eval : escaping the dollar notation

It's sometimes necessary to prevent the substitution of dollar notations. For example, in this example,


    <sequence>
        <set variable="p1" value="approver" />
        <eval>
            <![CDATA[
            <sequence>
                <set variable="p1" value="reviewer" />
                <participant ref="${p1}" />
            </sequence>
            ]]>
        </eval>
    </sequence>

the participant that will receive a workitem will be "approver", because the engine will have evaluated :


            <sequence>
                <set variable="p1" value="reviewer" />
                <participant ref="approver" />
            </sequence>

where the variable 'p1' is not consulted.

In order to prevent the immediate evaluation of the dollar notation, you have to escape it by prefixing it with a \ (backslash) :


    <sequence>
        <set variable="p1" value="approver" />
        <eval>
            <![CDATA[
            <sequence>
                <set variable="p1" value="reviewer" />
                <participant ref="\${p1}" />
            </sequence>
            ]]>
        </eval>
    </sequence>

What will be evaluated looks thus like :


            <sequence>
                <set variable="p1" value="reviewer" />
                <participant ref="${p1}" />
            </sequence>

The backslash vanished, the dollar notation is ready for evaluation at the right moment.

log

As OpenWFE features a process definition language, this langage provides you logging capabilities (very useful when debugging a newly designed process).


    <log message="Kilroy was here" />

This message "Kilroy was here" will appear in the history log (by default logs/history.log).


    <log 
        level="debug" 
        engine-log="yes" 
        message="set field 'request_status_${i}' to '${f:request_status_${i}}'" 
    />

In this more advanced example, the message not only goes to logs/history.log but also to logs/engine.log. The message is is flagged as 'debug'. This advanced example also shows the usage of nested variable / field resolution. ${i} will be replaced by the value of the variable named 'i' and (let's assumed 'i' has the value 7) ${f:request_status_${i}} is thus resolved to ${f:request_status_7} which will be resolved to the value of the the field named 'request_status_7' of the current workitem.

do, undo and redo

The 'undo' has always been a very demanded feature for OpenWFE. Its implementation brings transactional mechanisms to the workflow engine.

The 'do' expression marks the root of a workflow tree that can get undone or redone. 'undo' is obvious, the tree marked by the enclosing 'do' gets cancelled and the flow continues after the 'do' closure.


 <sequence>
 
     <participant ref="a" />
 
     <do>
 	<sequence>
 
 	    <participant ref="b" />
 	    <participant ref="c" />
 
 	    <if>
 		<equals field-value="f_ok" other-value="false" />
 		<redo />
 	    </if>
 
 	    <participant ref="d" />
 
 	    <if>
 		<equals field-value="f_cancel" other-value="true" />
 		<undo />
 	    </if>
 
 	</sequence>
     </do>
 
     <participant ref="e" />
 
 </sequence>

In this example, the 'undo' (in the second 'if') would cancel the 'do' and the flow would resume to participant 'e'.

The 'redo' operation performs an 'undo', but then, instead of resuming after the 'do', it replay the 'do' like it was when it got applied.

Talking about transactions, the 'do' might be considered like a 'BEGIN', the end of the 'do' like a 'COMMIT'. The 'undo' is a 'ROLLBACK'.

The do/undo/redo feature is very powerful. Use it with care : the classical worklist shipped with OpenWFE knows how to cancel workitems, but if you implement your own agent, implement it so that it can handle 'cancel items' and maybe stop its work.

This feature is also meant for error management : upon an agent error, the work of other agent (automatic participants) / participants may get cancelled. That's the transactional idea behind this.

Named 'do'

When you don't use any 'name' attribute for the 'do' expression, the 'undo' and the 'redo' expressions will trigger the 'do' immediately above them.

With named 'do', you can determine precisely which 'undo'/'redo' triggers which 'do'.


    <sequence>

        <do name="do_0">
            <sequence>
                <participant ref="a" />

                <do name="do_1">
                    <sequence>
                        <participant ref="b" />

                        <if test="${c:len(${f:ok})} > -1">
                            <undo ref="do_0" />
                        </if>
                    </sequence>
                </do>

                <participant ref="c" />
            </sequence>
        </do>

        <participant ref="d" />

    </sequence>

interleaved

This is a direct implementation of the workflow pattern #17 (http://www.workflowpatterns.com).


        <interleaved>
            <participant ref="a" />
            <participant ref="b" />
            <participant ref="c" />
        </interleaved>

In this example, the 'interleaved' expression has three children. The expression will take care of launching them one at a time, in a random order.

In the current implementation, the workitem is shared among all the children. So for instance, if participant 'b' added a field 'f1', this will be visible to participant getting called afterwards. Future implementations will probably use the facilities of the 'concurrence' expression to 'synchronize' the payload of the workitem (the section called “concurrence”).

cancel-process

Introduced in OpenWFE 1.7.0. Allows for explicit termination of a process, directly from the definition, without passing through the control interface.

For example :


    <concurrence>
        <participant ref="uno" />
        <participant ref="dos" />
        <sequence>
            <participant ref="tres" />
            <cancel-process />
        </sequence>
    </concurrence>

As soon as the participant 'tres' finished playing with the workitem the engine sent to him (it), the whole process will be cancelled (terminated), whatever may 'uno' or 'dos' have done.

This expression is used to implement the section called “Pattern 20 (Cancel Case)”.

forget

Introduced in OpenWFE 1.7.1. The 'forget' expression is used to indicate the engine that the expression it wraps should be forgotten by the rest of the process.


    <sequence>
        <participant ref="user-one" />
        <forget>
            <sequence>
                <participant ref="user-two" />
                <participant ref="user-three" />
            </sequence>
        </forget>
        <participant ref="user-four" />
    </sequence>

In this example, the flow will reach 'user-one' then fork a branch to 'user-two' and 'user-three' while immediately resuming to 'user-four'. Once 'user-three' will have replied, the 'forgotten' branch will be over.

The 'forget' expression is at its best when used within a 'concurrence'.


<process-definition name="flow_segment" revision="0.1">
    <sequence>

        <!-- ... first part of the flow  -->

        <concurrence>
            <participant ref="lazy-one" />
            <forget>
                <sequence>
                    <sleep for="5d" />
                    <participant ref="send-email" />
                    <set variable="/email-sent" value="true" />
                </sequence>
            </forget>
        </concurrence>

        <!-- the rest of the flow ... -->

    </sequence>

</process-definition>

In this example, the 'forgotten sequence' will remain 'alive' after the participant 'lazy-one' replied, thus ending the concurrence. One could say that the flow forked a branch (a dead-end though).

lose

Introduced in OpenWFE 1.7.1. The 'lose' expression is somehow complementary to the 'forget' expression. Where the 'forget' expression triggers its child in a separate execution thread and immediately replies to its flow, the 'lose' expression applies its child (in the same thread) and never replies.

'lose' only makes sense within a concurrence.


<process-definition name="flow_segment" revision="0.1">
    <sequence>

        <!-- ... first part of the flow  -->

        <concurrence count="1">
            <participant ref="lazy-one" />
            <lose>
                <sequence>
                    <sleep for="5d" />
                    <participant ref="send-email" />
                    <set variable="/email-sent" value="true" />
                </sequence>
            </lose>
        </concurrence>

        <if test="${email-sent} != true">
            <participant ref="send-email" />
        </if>

        <!-- the rest of the flow ... -->

    </sequence>

</process-definition>

The 'lose' process tree will never reply. As the concurrence wait for only 1 (count="1") answer from its children, as soon as the participant 'lazy-one' will have replied, the 'lose' tree will get cancelled. A 'forget' tree cannot get cancelled as it already replied and its child branch continues independently.

rotate

'rotate' could have been called '180degrees', it turns an horizontal set of data into a vertical one.


    <sequence>
        <set field="horizontal" value="a, b, c" />
        <rotate field="horizontal" />
    </sequence>

Will replace the string value 'a, b, c' of the field 'horizontal' with a ListAttribute containing the values 'a', ' b', ' c'. To trim the values before they are put into the ListAttribute, one can write :


    <sequence>
        <set field="horizontal" value="a, b, c" />
        <rotate field="horizontal" trim="yes" />
    </sequence>

To put the values (the ListAttribute instance) into a field named 'vertical' :


    <sequence>
        <set field="horizontal" value="a, b, c" />
        <rotate field="horizontal" to-field="vertical"/>
    </sequence>

The 'rotate' expression when applied to a list attribute will 'flatten' it, turning it into something horizontal.


    <sequence>
        <set field="vertical">
            <a>
            <list>
                <string>a</string>
                <string>b</string>
                <string>c</string>
            </list>
            </a>
        </set>
        <rotate field="vertical" to-field="horizontal" />
    </sequence>

Will store the string value "a,b,c" in the field named 'horizontal'.

'rotate' only works with fields, no 'variable' or 'to-variable' attributes are available.