Functions

Functions produce string output to insert in a variable or in a field or to check against.


<?xml version="1.0" encoding="UTF-8"?> 
<process-definition 
    name="func_demo" 
    revision="0.2"
>

    <sequence>
        <set field="date" function-value="date()" />
        <participant ref="role-alpha" />
    </sequence>

</process-definition>

Available basic functions are described (roughly) in the javadoc.

Functions may also be called outside of 'function-value' attributes by using the '${c:x()}' or '${call:y()}' notation.


    <set field="date" value="${call:date()}" />

Nested dollar notations are possible :


    <set variable="i" value="3" />
    <set field="sum" value="${i} + ${i} = ${c:sum('${i}', '${i}')}" />

will yield the value "3 + 3 = 6" in the field named "sum".

These are possible ways to hand arguments to functions :


    <set field="sum" value="${c:sum(${i} ${j})}" />
    <set field="sum" value="${c:sum(${i}, ${j})}" />
    <set field="sum" value="${c:sum('${i}' '${j}')}" />
    <set field="sum" value="${c:sum('${i}', '${j}')}" />

When in doubt, always place quotes around all arguments. Quotes may be escaped :


    <set field="sum" value="${c:sort('red' 'king\'s blue' 'navy blue')}" />

How functions are made available to the engine

Functions, or rather java classes holding functions, are registered in a function map, which is a regular OpenWFE service. Here is the service instantiation, as found in etc/engine/engine-configuration.xml :


    <!--
        In order to evaluate the "function-value" attribute of the set
        expression, the engine needs a functionMap.
    -->
    <service
        name="functionMap"
        class="openwfe.org.engine.impl.functions.XmlFunctionMap"
    >
        <param>
            <param-name>functionMapFile</param-name>
            <param-value>etc/engine/function-map.xml</param-value>
        </param>
    </service>

This function map service will then parse etc/engine/function-map.xml which contains a list of classes providing function implementations :


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

<function-map>

    <!--
    <include url="file:etc/engine/function-map2.xml" />
    -->

    <class>openwfe.org.engine.impl.functions.BasicFunctions</class>

    <!--
    <class>com.yourcompany.workflow.impl.functions.YourFunctions</class>
    -->

</function-map>

Then implementation of a function looks like :


    /**
     * This function will return the current time as an ISO full date.
     */
    public static String now 
        (FlowExpression fe, InFlowWorkItem wi, String[] args)
    {
        return Time.toIsoDate();
    }

    /**
     * This function will return the current date as an ISO date.
     */
    public static String today 
        (FlowExpression fe, InFlowWorkItem wi, String[] args)
    {
        String sNow = Time.toIsoDate();
        int i = sNow.indexOf(" ");
        return sNow.substring(0, i);
    }

The method signature is really fixed, the function map uses it to recognize the static method as a 'function'.

Functions return Strings, they take as input the FlowExpression, the WorkItem and an array of Strings (args).

A potpourri of Functions

In this hypothetical process definition, many functions are displayed :


<process-definition name="function-potpourri" revision="0.01">
    <sequence>

        <!-- Nota bene : function names are case sensitive -->

        <!--
            stores in the field f the current time and date or simply
            the curent date (today())
        -->
        <set field="f" value="${call:now()}" />
        <set field="f" value="${call:today()}" />

        <!--
            parsing dates
        -->
        <set field="f" value="${c:toIsoDate('2004-12-30')}" />
        <set field="f" value="${c:toIsoDate('28.03.2006', 'dd.mm.yyyy')}" />

        <sleep until="${c:toIsoDate(${f:activation-date})}" />
            <!-- will sleep until the date specified in field 'activation-date' -->
        <set field="f" value="${c:date('28.03.2006')}" />
            <!-- 'date' is a synonym for 'toIsoDate' -->

        <!--
            stores in the field f the count of attributes found in the
            current workitem
            Fieldcount() is a synonymous function.
        -->
        <set field="f" value="${call:attributeCount()}" />
        <set field="f" value="${call:fieldCount()}" />

        <!-- 
            will store '4.0' as a string (attribute) in the field 'f'
        -->
        <set field="f" value="${call:sum('1' '3')}" />
        <set field="f" value="${call:add('1' '3')}" />

        <!-- 
            will store '8.0' as a string (attribute) in the field 'f'
        -->
        <set field="f" value="${call:mul('2' '2' '2')}" />

        <!--
            extracts the head element of a list (thus 'alpha')
        -->
        <set field="f" value="${call:car('alpha, bravo, charly')}" />

        <!--
            removes the head element of a list (thus 'bravo, charly')
        -->
        <set field="f" value="${call:cdr('alpha, bravo, charly')}" />

        <!--
            stores 3 in the variable 'v' 
            (the length of the input list to llen())
        -->
        <set variable="v" value="${c:llen('alpha, bravo, charly')}" />

        <!--
            builds the new list 'a, b, c, d'
        -->
        <set field="f" value="${call:cons('a', 'b, c, d')} />

        <!--
            returns 'bravo'
        -->
        <set field="f" value="${call:elt('1', 'alpha, bravo, charly')}" />

        <!--
            will return the same list but with a random order
        -->
        <set field="f" value="${call:shuffle('alpha, bravo, charly')}" />

        <!--
            will return the same list but in the reverse order
        -->
        <set field="f" value="${call:reverse('alpha, bravo, charly')}" />

        <!--
            will return the list, but sorted alphabetically
        -->
        <set field="f" value="${call:reverse('delta, alpha, bravo, charly')}" />

        <!--
            adding or substracting time
        -->
	<set field="tomorrow" value="${call:timeAdd('${f:now}', '1d')}" />
	<set field="yesterday" value="${call:tadd('${f:now}', '-1d2h33m10s')}" />

        <!--
            various random function calls
        -->
        <set field="a_random_int" value="${c:rint()}" />
        <set field="a_random_long" value="${c:rlong()}" />
        <set field="a_random_double" value="${c:rdouble()}" />

        <!--
            various boolean function calls
        -->
        <set field="f" value="${c:or(true, false)}" />
        <set field="f" value="${c:and(true, false)}" />
        <set field="f" value="${c:not(false)}" />
        <set field="f" value="${c:not('true')}" />
        <set field="f" value="${c:or(${approval}, ${f:manager_approval})}" />
        <set field="f" value="${c:xor(${approval}, ${f:manager_approval})}" />

        <!--
            inserting the current workflow instance id into the var 'v'
        -->
        <set var="v" value="${c:wfid()}" />

        <!--
            setting the current flow expression id as the value of the var 'v'
        -->
        <set var="v" value="${c:fei()}" />

        <!--
            this will set the string value 'set' within the variable 'v'
            (that's the current expression name ;-) )
        -->
        <set var="v" value="${c:expname()}" />

        <!--
            will store something like '0.1.0.0.3' (the position of the
            expression in the process definition 'tree')
        -->
        <set var="v" value="${c:expid()}" />

        <!--
            inserting the name of current flow followed by a colon and its
            revision
        -->
        <set var="v" value="${c:wfdname()} : ${c:wfdrevision()}" />

        <!--
            will put the length of the function argument (string) into the 
            variable 'v', here, the value will be '4'.
        -->
        <set var="v" value="${c:len('toto')}" />

        <!-- 
            will store 7 % 2  (thus "1") into the variable 'v'.
        -->
        <set var="v" value="${c:mod(7, 2)}" />

        <!--
            v will yield value 'afald'
        -->
        <set var="v" value="${c:substring('mafalda', '1', '-1')}" />

        <!--
            v will yield value '-1612183289' with is the java hashcode of
            the string "my little string".
            This function may be useful for generating short ids.
        -->
        <set var="v" value="${c:hashcode('my little string')}" />

        <!--
            various timestamps
        -->
        <set var="v" value="${c:ststamp}" />
        <set var="v" value="${c:ltstamp()}" />
        <set var="v" value="${c:timestamp()}" />
        <set var="v" value="${c:vltstamp()}" />
        <!-- did yield (at the time of writing this) :
            20060228
            20060228081113
            20060228081113492
            20060228081113497
        -->

        <set field="f" value="${c:round(${f})}" />
        <!-- will round the value found in field f -->

        <set field="f" value="${c:uppercase('Toto')}
        <set field="f" value="${c:uc('Toto')}
            <!-- putting 'TOTO' has the value of the field 'f' -->

        <set field="f" value="${c:lowercase('Toto')}
        <set field="f" value="${c:lc('Toto')}
            <!-- putting 'toto' has the value of the field 'f' -->

        <if test="${c:matches('user-toto', 'user-.*')}>
            <participant ref="a" />
            <participant ref="b" />
        </if>
            <!-- the flow will go to participant "a" as the string 'user-toto'
                 matches the regular expression 'user-.*' -->

        <set field="b" value="${c:matches('user-toto', 'agent-.*')}" />
            <!-- the value 'false' will be put in the field 'b' has 'user-toto'
                 doesn't matches the regex 'agent-.*' -->

        <!--
            the 'attsize' function returns the size of the attribute
            whose name is its first argument.
            If there is no argument, the returned value is "-2".
            If the attribute is not set, the value "-1" will be returned.
            If the attribute is atomic (an integer or a string for example),
            "1" is returned.
            If the attribute is a map or a list attribute, its size will
            be returned.
        -->
        <set field="count" value="${c:attsize(customers)}" />

    </sequence>
</process-definition>

The 'eval' function

You've perhaps noticed that you can use a 'test' attribute with the 'if' expression. This attribute is equivalent to a call to the 'eval' function.


    <sequence>

        <if test="${f:acceptance} == immediate">
            <participant ref="publisher" />
        </if>

        <!-- is equivalent to -->

        <set variable="v" value="${c:eval('${f:acceptance} == immediate')}" />
        <if test="${v}">
            <participant ref="publisher" />
        </if>

        <!-- is also equivalent to -->

        <if>
            <equals value="${f:acceptance}" other-value="immediate" />
            <participant ref="publisher" />
        </if>

    </sequence>

The 'eval' function (and hence, the 'test' attribute) accepts '==', '!=', '>', '<', '>=' and '<='. But you'd better escape > and < to '&gt; and &lt;< so that you've no problem with the XML parser.


    <if test="${f:value} &gt;= 10">
        <participant ref="supervisor" />
    </if>

    <!-- if you find the escape trick ugly, you can still do -->

    <if>
        <greater-than field-value="value" other-value="10" equals="true" />
        <participant ref="supervisor" />
    </if>

The 'range' function

A very useful function might be the 'range' one.


    <iterator
        on-value="${c:range(5)}"
        to-variable="i"
    >
        <participant ref="participant_${i}" />
    </iterator>

will send a workitem successively to participant_0 to participant_4.

The are other ways to use the 'range' function :


    <iterator
        on-value="${c:range(1, 5)}"
        to-variable="i"
    >
        <participant ref="participant_${i}" />
        <!-- participant_1 to participant_5 -->
    </iterator>

    <iterator
        on-value="${c:range(10, 5 -1)}"
        to-variable="i"
    >
        <participant ref="participant_${i}" />
        <!-- participant_10 to participant_6 -->
    </iterator>

Functions and errors

Whenever a function cannot accomplish its task, the engine will catch the exception or the error and put its message into the field named '__function_error__'. You can thus verify in your flow if this field is set to take appropriate actions. This may be very important for functions manipulating files or accessing databases (though it would be more appropriate to give such tasks to an Automatic Participant, not to a function).