The cursor and the loop expressions

These two expressions are extended versions of the 'sequence' expression, they allow for complex control of the flow.

cursor

The 'cursor' expression was initially implemented as subprocess stored in etc/engine/library.xml. It's now available as a java-implemented expression, making it faster.

This expression behaves like a 'sequence', but as soon as the field '__cursor_command__' takes the value 'back', it will go backward. If this field takes the value 'break', the cursor will exit.

The '__cursor_command__' field understands five messages (value) : 'back', 'break', 'cancel', 'rewind' and 'continue'. 'cancel' and 'break' are aliases, as are 'continue' and 'rewind'. This may sound a bit tortured at first, maybe it is, this aliasing comes from the merge that occured between the expressions <cursor> and <loop>. One should use whatever fits his mindset and / or its processes domain.

In this example, if none of the participants sets the field '__cursor_command__' to any special value, the flow will go a, b, c, d. Now imagine that the participant 'b' sets the field to 'back' the first time it receives the workitem, it will go a, b, a, b, c, d.


    <sequence>

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

        <participant ref="d" />

    </sequence>

If the participant b and c set the field to back the first time, the flow will go a, b, a, b, c, b, c, d. If b sets the field to 'break', the flow will go a, b, d.

There is yet another command that the cursor expression understands : 'rewind'. It puts the flow back at the first child of the cursor. Thus, in our example, if the participant 'c' sets the value of the field '__cursor_command__' to 'rewind', the flow would go a, b, c, a, b, c, d.

The 'cursor' expression is at the heart of the pattern 10 (the section called “Pattern 10 (Arbitrary Cycles)”).

This expression takes an optional attribute 'disallow'. The value of this attribute is a comma-separated list of cursor commands to be neutralized. For example :


        <cursor disallow="break, rewind">
            <participant ref="a" />
            <participant ref="b" />
            <participant ref="c" />
        </cursor>

where the only available commands are "" (forward) and "back", the participants are prevented from ordering a "break" and a "rewind" (these commands are disallowed). When 'break' and 'rewind' are ordered, they are turned into '' (which makes the cursor simply step one child).

As said, 'cursor' (and its daughter expression 'loop') do obey to the field '__cursor_command__' and the values it might take. Here are five small expressions (small because they are leaf expressions, taking no children) that simply store their name as the value of the '__cursor_command__' field, thus driving the 'cursor' or the 'loop' in which they are located.

The 'cursor' expression understands a 'command_field' attribute. It's useful for specifying another field than '__cursor_command__' as the holder of the command. The current (1.7.1) drawback, is that 'break', 'rewind' and other such expressions are all working with '__cursor_command__'. The attribute 'command_field' may still be helpful in some cases.

break

Simply make the flow break out of the 'cursor' or the 'loop'.

cancel

'cancel' is an alias for 'break'. It has thus the same effect.

rewind

Makes the flow of control start again at the beginning of the cursor or the loop.

continue

This is an alias for the 'continue' expressions (as cursors and loops do understand both the 'continue' and the 'rewind' commands).

back

Makes the flow go back one step.

If the command starts with 'back' and is followed by an integer value, the flow will get back by this number of steps. For instance, setting the '__cursor_command__' field to 'back 3' will make the flow jump back 3 steps. If that involves having to jump at a negative index, the cursor implementation takes care of starting at step 0 (the first step).

skip

Makes the flow go forth one step.

If the command starts with 'skip' and is followed by an integer value, the flow will get back by this number of steps. For instance, setting the '__cursor_command__' field to 'skip 3' will make the flow jump back 3 steps. If the skip orders to jump 'out of the cursor', the engine will simply jump only til the last element of the 'cursor' expression.

loop

The <loop> expression allows for a process to iterate over a segment of flow while or until a condition verifies.


    <sequence>
        <set variable="idx0" value="0" />
        <loop>
            <sequence>
                <participant ref="${field:target}" />
                <inc variable="idx0" />
            </sequence>
            <until>
                <or>
                    <equals value="${idx0}" other-value="5" />
                    <equals variable-value="//stop_loops" other-value="true" />
                </or>
            </until>
        </loop>
    </sequence>

In this example, the loop will run 5 times, sequentially dispatching workitems to the participant whose name may be found in the field named 'target'. If the global variable 'stop_loops' is set to 'true', the loop may end sooner.

The conditional <while> or <until> may be placed before or after the iteration body. The <loop> only accepts two children : a conditional and a body. One of them may be omitted, but a loop without conditional will iterate forever (well, until the engine gets stopped or the host on which it runs shuts down).

The loop expression is in fact a 'looping cursor expression', thus it understantds the same commands put in the same '__cursor_command__' field. The expressions 'back', 'continue' / 'rewind' and 'break' are, likewise, usable from within a 'loop' expression.

It's sometimes very advantageous to use a loop instead of a recursive subprocess call (especially in case of 'tail recursions'). The following :


    <sequence>
        ...
        <sub0/>
        ...
    </sequence>

    <process-definition name="sub0">
        <sequence>
            <participant field-ref="target" />
            <if>
                <equals field-value="agreed" other-value="false" />
                <sub0 />
            </if>
        </sequence>
    </process-definition>

had better be written like that :


    <sequence>
        ...
        <loop>
            <participant field-ref="target" />
            <while>
                <equals field-value="agreed" other-value="false" />
            </while>
        </loop>
        ...
    </sequence>

It's much more lightweight for the engine. Using a recursion consumes resources linearly. With a loop, each iteration consumes the same resources as the preceding one. Let's stay that an iteration costs x resources (usually memory), after ten recursions, 10 * x ressources will be occupied. With a loop, iteration over iteration, the cost remains at 1 * x.

In many cases, writing a recursion is way more elegant than a loop. A future release of OpenWFE will include optimization code to turn tail-recursive code into a loop, transparently for the designer. Of course, the opportunity to directly use a loop (or an iterator) will still remain.

Some people may also find it easier to model their thoughts with a loop rather than a recursive process.

Since OpenWFE 1.6.2


    <loop>
        <sequence>
            <participant ref="alfred" />
            <participant ref="bruno" />
        </sequence>
        <while>
            <equals field-value="bruno_agreed" other-value="false" />
        </while>
    </loop>

is equivalent to


    <loop>
        <participant ref="alfred" />
        <participant ref="bruno" />
        <while>
            <equals field-value="bruno_agreed" other-value="false" />
        </while>
    </loop>

Lots of people were writing it the 2nd way.

until

As soon as the condition wrapped within the <until> expression is realized, the loop will end (of course upon evaluation of the condition, after or before the loop body evaluation).

while

The loop is meant to run as long as the condition within the <while> evaluates to 'true'.

break, cancel, continue, rewind, back

The 'cursor' and the 'loop' expressions check the value of the field '__cursor_command__'. But sometimes it's desirable to explicitely state in the process definition what to do. As explained in this section, 'cursor' and 'loop' do understand the commands 'break', 'cancel', 'continue', 'rewind' and 'back'. Each of them corresponds to an expression with the same name than can be put within the cursor or the loop.


    <loop>
        <participant ref="alfred" />
        <while>
            <equals field-value="alfred_agreed" other-value="false" />
        </while>
        <participant ref="bruno" />
    </loop>

is thus equivalent to :


    <loop>
        <participant ref="alfred" />
        <if test="${f:alfred_agreed}"><break/></if>
        <participant ref="bruno" />
    </loop>

In fact all those small expressions are implemented by a unique java class openwfe.org.engine.expression.CursorCommandExpression, as configured within the file etc/engine/expression-map.xml :


    <expression
	name="break"
	class="openwfe.org.engine.expressions.CursorCommandExpression"
    />
    <expression
	name="cancel"
	class="openwfe.org.engine.expressions.CursorCommandExpression"
    />
    <expression
	name="continue"
	class="openwfe.org.engine.expressions.CursorCommandExpression"
    />
    <expression
	name="rewind"
	class="openwfe.org.engine.expressions.CursorCommandExpression"
    />
    <expression
	name="back"
	class="openwfe.org.engine.expressions.CursorCommandExpression"
    />

This expression is simply equivalent to ('break' for example) :


    <set field="__cursor_command__" value="break" />

but it's much shorter of course.

The 'skip' and the 'back' expressions do understand the 'step' attribute.


    <cursor>
        <participant ref="p0" />
        <participant ref="p1" />

        <participant ref="p2" />
        <participant ref="p3" />

        <if test="${back_to_p1}">
            <back step="2" />
        </if>

        <participant ref="p4" />
    <cursor>

If the the field 'back_to_p1' is set to 'true' just after the participant 'p3', the flow will continue back at participant 'p1'.