Tiny Workflow Reference

This document is the reference documentation of TinyWorkflow.

This document presents all aspects of TinyWorkflow in a systematic way.

If you need an introduction or overview of TinyWorkflow, it's better to read the following document first:

The WorkflowXmlReference.html document is a complete description of the XML syntax of a workflow definition.

Table of Content

TinyWorkflow Distribution

Organization

TinyWorkflow is organized in several components:

  • TinyWorkflow-Core: The core workflow engine. This is a plain java implementation. This core implementation comes with memory persistence. The dependencies of this core are reduced to the strict minimum.
  • TinyWorkflow-XmlUtil: This utility package contains generic classes for XML serialization support.
  • TinyWorkflow-Hibernate2: Persistence layer allowing to save/load workflow in a relational database using Hibernate 2.x.
  • TinyWorkflow-Hibernate3: Persistence layer allowing to save/load workflow in a relational database using Hibernate 3.x.
  • TinyWorkflow-EJB: Stateless Session Bean facade for usage of TinyWorkflow in a J2EE container.
  • TinyWorkflow-GuiUtils: Various Swing utilities.
  • TinyWorkflow-Gui: Set of java-swing components that can be used to visualise workflows (as tree, graph, property sheets ...).
  • TinyWorkflow-Editor: Workflow Editor tool. This tool can be used to visually edit, layout and test workflows.
  • TinyWorkflow-Launcher: Utility used to launch the editor tool.
  • TinyWorkflow-Struts: Support for a struts web interface.

You can have more info about these component by browsing their specific documentaion (under modules if the left menu)

<i>Diagram 1: TinyWorkflow components dependencies</i>

Dependencies shown in this diagram are transitivies.

To use TinyWorkflow, you have to use the TinyWorkflow-Core project and one persitency implementation (note: the default in-memory persitency is bundled in the core project). All other components are optional.

Note:

  • the utilities projects (XmlUtils, GuiUtils, Launcher) are not specific to TinyWorkflow (so they have no dependency to it) , they can be reused in other projects.
  • The Gui projects are independent from model, they are not required to simply run workflows.

Requirements

TinyWorkflow Core and XmlUtils require:

  • Java 1.5 (or higher)
  • Beanshell 1.2 (or higher)
  • Apache commons-beanutils 1.7.0 (or higher)
  • Apache commons-logging 1.0.4 (or higher)

For other components, see the specific dependency list of those components in their documentation.

For a typical usage of the TinyWorkflow engine with Hibernate3 persitence you need to have the following jars in your classpath:

The TinyWorkflow components:

  • TinyWorkflow-XmlUtils-x.y.jar
  • TinyWorkflow-Core-x.y.jar
  • TinyWorkflow-Hibernate3-x.y.jar

The dependencies of the core

  • commons-logging-1.0.4.jar
  • commons-beanutils-1.7.0.jar
  • bsh-1.2b7.jar

The dependencies of Hibernate3

  • ... TODO

Packaging

When you download TinyWorkflow you got an archive (zip, tgz) containing the following files:

  • readme.txt: simple description of the archive content (equivalent to this chapter)
  • release.txt: Description of the release content.
  • LICENSE-2.0.txt: the text of the apache 2.0 license
  • site/... : The TinyWorkflow documentation browsable locally, open site/index.html as starting point.
  • javadoc/... : The generated javadoc
  • lib/*.jar: the jars files of all TinyWorkflow libraries (see dependency graph for more information).
  • dependencies/*.jar: the jar files of external projects needed to run tinyWorkflow (see dependency graph for more information).
  • examples/...: Full code of the examples
  • demo/...: The demo application.

Running the examples

TinyWorkflow comes with a set of examples.

TODO.

Building TinyWorkflow

TinyWorkflow is build with Maven 2 (http://maven.apache.org/). It uses regular maven 2 project structure, so to build it, just install the latest Maven version and follow maven build instructions.

Each TinyWorkflow component is a maven project and all the projects are grouped as modules of the TinyWorkflow project. The TinyWorkflow project's POM is the parent for the POMs of each module. So, if you want to build everything in one command, go to the TinyWorkflow directory and type

        mvn package

Note: the site generation plugin of Maven 2 is not (yet) completely OK, so the generation of this site requires some manual interventions. TODO: describe.

TinyWorkflow definition

The definition XML file encodes the workflow state machine as explained in the GettingStarted.html.

The workflow definition is composed of:

  • The workflow declaration itself: This declares the attributes of the workflow (see Workflow Declaration) like the version (see Workflow Versioning) or the workflow peer class name (see Workflow Peer).
  • Name and description of the workflow: The name and version can be defined in various languages (see Multi-Lingual Support).
  • Extension declaration: Specify inheritance between workflow definitions (see Workflow Inheritance).
  • Meta-data: Allow to add extra data not used by the workflow core engine but exploitable by extensions or custom applications (see Meta-Data).
  • Flags: Allow to encode custom state of the workflow and save it in the database (see Workflow aggregated data).
  • Pre-functions: Allow executing code when the workflow is started (see Pre- and Post-Functions).
  • Initial transitions: Declares the set of initial transitions = transitions starting from initial state (see Initial Transitions).
  • Global transitions: Declares a set of reusable transitions that can be used in any state (see Global Transitions).
  • Set of states: The set of possible workflow states (see State Definition).
  • Post-functions: Allow executing code when the workflow is finished (see Pre- and Post-Functions).

Workflow Declaration

The workflow definition is a transcription of a Finite State Machine. The <workflow> tag is the root tag of the declaration. A workflow definition is uniquely identified by the workflow definition id (the 'id' attribute).

Workflow Versioning

In addition to the id, a workflow definition is identified by its version number. If you modify a workflow definition, it's better to increase the version number (especially if you remove states or transitions or if you modify state/transition ids).

The version number can be any String. You just have to ensure that they are correctly ordered: when a version is greater than another, the version number of this version must be greater than the other version number (when compared using the standard string comparison). Using the 'dotted notation' ('1.0.0', '1.0.1', '1.1') fit this requirement as long as you always use the same number of digits. Counterexample: '1.9.5' > '1.10.4' when you use string comparison. If you have this problem, one simple solution is to use letters (like in hexadecimal): '1.9.5' < '1.A.4'.

When you create a workflow, you can specify null as version number. In this case, the latest workflow definition version available in the repository is used. It's a good practice to always do that.

When an instance of the workflow is loaded and the repository contains a newer version of the workflow definition, the workflow is 'upgraded' to use the new workflow definition (the version id of the current workflow definition if upgraded). This upgrade is visible in the history in the form of an 'upgrade' transition. The automatic upgrade can be disabled on the WorkflowDefinitionRepository by calling setAutoUpgrade(false).

In a future version of TinyWorkflow this 'upgrade' transition will also change the workflow state when the current state of a workflow instance no more exists.

Note that the exact version of the workflow definition used to for states and transitions is kept in the workflow history. So, even if a transition or state disappeared from a workflow definition, it can be found back in the correct older versions.

Multi-Lingual Support

Many definition objects (WorkflowDefinitions, StateDefinitions, TransitionDefinitions ...) are identified by an ID. This ID is internal and should not be displayed to the user. It is used by the workflow engine to identify objects.

In addition to the ID you can define a Name and descriptions. The name and description are Strings to be displayed to the user. They are not used by the workflow engine. Doing a clear separation between Name and ID helps maintenance as the name can change but the ID should stay the same (for backward compatibility). Of course, name and description are not mandatory. They are there to improve readability of the definition or to support user interface construction. If you don't need them, just ignore them.

If your application must support multiple languages, then you can provide names and description in multiple languages. This is done like usual localisation in java. The languages are identified by a locale. The usual locale matching algorithm of java is used to find the name/description in a given locale. For example, if the user locale is "fr_BE" it will check for:

  • a name with "fr_BE" locale
  • a name with "fr" locale
  • the default name (without locale)

Note: When you have to provide names/descriptions in several languages. It's recommended to alaways define a default one (without locale) that will be used for all locales not matching the proposed one.

Name and Description elements are declared in XML and used in java exactly the same way.

You can define, for example, names of some states as following:

        <state id="someState1" name="Some State">
                ...
        </state>
        
        <state id="someState2">
                <name value="Some state" />
                ...
        </state>
        
        <state id="someState3">
                <name value="Some state" />
                <name locale="fr" value="Un état" />
        </state>
        
        <state id="someState4">
                <name>Some state</name>
                <name locale="fr">Un état</name>
        </state>
        
        <state id="someState5" name="Some State" >
                <description>
                    Some very long description
                    of my state
                    using multiple lines.
                </description>
        </state>
        

in this example:

  • someState1: Define a default name (for all locales)
  • someState2: Is exactly the same as someState1. The default name can be defined as an attribute or as a sub-element.
  • someState3: Define a french name and a default name for all other languages.
  • someState4: Is exactly the same as someState3 but the name are defined as XML element text rather than a "text" attribute of the "name" element.
  • someState4: Demonstrate that using the XML element text style is useful when you have big or multi-line text (the line breaks are preserved when parsing the XML).

In your java code, you can get the name or description from the workflow definition objects (workflowDefinition, transitionDefinitions ...).

        // Display the name of the current workflow state
        Workflow wf = ...
        Locale loc = ...
        StateDefinition stateDef = wf.getCurrentStateDefinition();
        System.out.println("The current state is: " + stateDef.getName(loc));
        

Workflow Inheritance

A basic inheritance mechanism is defined for workflow definitions.

A workflow definition can declare that it extends other workflows.

    <?xml version="1.0" encoding="ISO-8859-1" ?>
    <workflow xmlns="http://org.tinyWorkflow/2005/WorkflowDefinition" id="extensionTest" version="1.0">
            <extends id="someWorkflow" version="1.0"/>
            ...
            

This simply means that all initial transitions, global transitions and states defined in the parent workflow are available in the extended workflow (just as if they were declared in this workflow). If some global/initial transition or state of the child workflow has the same ID as one of the parent one, it simply overrides the definition of the parent.

When you extends multiple workflows redundant definitions are ignored.

Meta-Data

In some worklfow definition objects, you can add meta-data (WorkflowDefinition, StateDefinition, TransitionDefinition, parameterDefinition ...). This data is not used by the workflow engine. It's just available in the definition for use by other code.

For example, if your application generate user interface based on the workflow definition then meta-data can be a good place to put some useful information. Let's say you want to show a toolbar displaying all the transitions that can be executed by the current user on a worklfow. You can put in each transition meta-data giving the name of the icon to set in the toolbar button.

Meta-data can be customized: you can define your own meta-data object and use them in your worklfow definition. See Customisation.html for more info.

Meta-data are identified by an ID and have a value that can be nay Object. They are always defined between <meta> ... </meta> tags. There are two meta-data pre-defined in TinyWorkflow:

  • Property meta-data: XML element <property>. It contains a simple string value.
  • Multi-lingual meta-data: XML element <nameData> or <textData> (depending if you want to interpret it more as a name or as text) It's a string value provided in several locales (just as for "name" and "description" of workflow definition objects). The meta-data value returned by java code is of type org.tinyWorkflow.definition.MultiLingualText.

Examples:

    ...
    <globalTransitions>
        <transition id="t1">
                    <meta>
                        <property id="icon" value="MyIcon.png"/>
                        <nameData id="name">
                                    <name value="John"/>
                                    <name locale="fr" value="Jean"/>
                        </nameData>
                        <textData id="text">
                                    <text>
                                        Hello
                        Everybody
                    </text>
                                    <text locale="fr">
                                        Bonjour
                        tout le monde
                    </text>
                        </textData>
                    </meta>
                ...
                

You can access the meta-data defined in this example like this:

    
        // Get some meta-data values from transition "t1" definition
        WorkflowDefinition wfDef = ...
        Locale loc = ...
        TransitionDefinition transDef = wfDef.getGlobalTransitionDefinition("t1");
        String iconName = (String) transDef.getMetaData("icon");
        System.out.println("The icon to use is: " + iconName);
        MultiLingualText text = (MultiLingualText) transDef.getMetaData("text");
        System.out.println("The text is: " + text.getText(loc));
                

Pre- and Post-Functions

The functions are invocation of a piece of code. They are the link between the workflow definition and the java code.

Functions can be of 3 types:

  • Call to a reusable java object: When some code has to be executed in many transition, you can put is in a function object and call it directly. See the <startChildWorkflow> as an example.
  • Call to a beanshell script: Using beanshell you can put some scripted "java like" code directly in the workflow definition. It brings the power of java into your XML. But remember that this code is not compiled, so you will see errors only at runtime. In fact, the beanshell code is not very easy to maintain. So it's better to use it as glue code between XML and java. For example: use it only to pass the correct variables to a method of a java class.
  • Call to the workflow peer: The workflow peer is a good place to put the code associated to the workflow. You can invoke methods on it using the <peerCall> function or from a beanshell script.

Functions can be customized: you can define your own function object and use them in your worklfow definition. See Customisation.html for more info.

Examples:

            <transition id="trans1">
                ...
            <preFunctions>
                <beanshellFunction>
                    String myParam = (String) context.getParam("myParam");
                    context.getPeer().doSomeAction(myParam);
                </beanshellFunction>
                        <peerCall method="doOtherAction"/>
            </preFunctions>
                    <results>
                        <result state="state1"/>
                    </results>
            <postFunctions>
                <proxyFunction class="org.tinyWorkflow.definition.FakeFunction">
                        <meta>
                        <property id="p1" value="val1"/>
                            <property id="p2" value="val2"/>
                        </meta>
                </proxyFunction>
            </postFunctions>
        </transition>
                

The pre-functions are called before the workflow action are executed, and the post-functions are called after. So when you query, for example, the workflow state from a pre-function of a transition getCurrentStateDefinition() give you the state before the transition, in post-functions, it gives you the state after the transition.

Pre and post functions can be defined for Workflow, States and transtions. The most common used place to put functions is the pre-function of the transition. It's the usually the best place to put business logic that must be executed during a transition.

The following table shows when the pre and post fuctions are triggered depending on the container defining them:

ContainerPre-functionsPost-functions
WorkflowJust before the initial transtion is executed.After the transition leading to the final state.
StateEach time the workflow is put in that state (comming from a different one).When the workflow leave the state.
TranstionBefore the transition executed.After the transition is executed.

Initial Transitions

Initial transitions are transitions starting the workflow. Usually, the transitions are defined in their origin state. But, as in TinyWorkflow the initial state of the workflow is not defined as a state, the initial transitions are defined in the special <initialTransitions> section of the workflow definition. See Transition Definition to have details about the content of a transition definition.

Before the initial transition, the current state of the workflow is not defined (getCurrentStateDefinition() == null) and the workflow golbal state is "not started" (getGlobalStatus() == Workflow.STATUS_NOT_STARTED)

It is recommended always directlty execute an intial transition on any newly created workflow. If you use a relational database, it's better to do it in the same database transaction.

In following example, we create a workflow with definition "unitTest1" and directly execute the "init" initial transition on it (in one single database transaction).

                WorkflowPersistenceFactory factory = ...
        WorkflowDefinitionRepository repo = ...
        
        WorkflowPersistence persistence = factory.createPersistence();
        persistence.beginTransaction();
        Workflow wf = repo.createWorkflowInstance("unitTest1", null, persistence);
        wf.doTransition("init", null, "MyUser");
        persistence.commitTransaction();

Global Transitions

Global transitions are simply transition definitions that can be reused in any state. See Transition Definition to have details about the content of a transition definition.

When you define a global transition in your workflow like the "lock" transition below:

    ...
    <globalTransitions> 
        <transition id="lock" name="Lock Workflow">
            <conditions>
                <workflowHasNoOwner/>
            </conditions>
                    <results>
                        <result state="${oldStateId}" owner="${callerId}"/>
                    </results>
        </transition>
        ...
        

you can reuse it in any state of the workflow (or the in workflows extending it) with a transition reference <transitionRef>:

    
        ...
        <state id="readyForPublishing">
                        <transitions>
                    <transitionRef refId="lock"/>
                    ...
        

It allows factor out transitions appearing in several states.

Overriding global transitions

When you reference a global transition with <transitionRef> you can override any part of the transition you reference. So the referencing works also as inheritance: you can modify the transition behavior in the reference.

When you override an permissions, conditions, parameters, pre/post functions or results, you override the whole XML element content (the whole list of permission, conditions ...) at once. For those definition, there is no mix possible between what's in the global transition and what's in the transition reference.

On the other hand, for name, description and meta-data it's possible to add something or partially replace the definition done in the parent. It's because those data are accessed with a key (the "locale" for name/description or the "id" for meta-data).

You can, for example, write (reusing global transition defined above):

    
        ...
        <state id="readyForPublishing">
                        <transitions>
                    <transitionRef refId="lock"/>
                            <name locale="fr" value="Verouiller le flux"/>
                    <conditions>
                        <workflowHasNoOwner/>
                        <allowedCaller ids="Bill,Bob"/>
                    </conditions>
                </transitionRef>
                    ...
        

In this case, we add a french translation for the name and a condition allowing only Bill and Bob to perform the "lock" transition. Note that, to add a translated name, we just put it in the reference transition. But to add a condition, we had to rewrite the original condition in the new condition list.

When referencing a transition, you can also assign a new ID to it with the 'id' parameter. If no id parameter is specifed, the same id as the global transition is used. By specifiing an ID, you can reference the same global transition twice from a state and override diffrent parts of the global transition in each reference.

example:

    
        ...
        <state id="readyForPublishing">
                        <transitions>
                    <transitionRef refId="lock"/>
                    <transitionRef refId="lock" id="rootLock" name="Forced lock for root user" />
                    <conditions>
                        <allowedCaller ids="Root"/>
                    </conditions>
                </transitionRef>
                    ...
        

The first reference just enable the use of the 'lock' transition (as defined in global transitions) in the 'readyForPublishing' state. The other transition references the same global transition with another id ('rootLock') assigned (because two transitions of the same state cannot have the same ID). In this second transition the conditions are overriden to allow the 'Root' user to bypass the the 'workflowHasNoOwner' condition of the global transition.

State Definition

The state definition is definition of a state of the state "machine". It is identified by a unique ID (given as a mandatory XML attribute of the <transition> element).

States are of two types:

  • Normal state. Define the all the possible transitions starting for this state.
  • Final state. No transition can start from a final state. Once a workflow has reached such a state, it is finished and no transtion can be executed on it anymore.

A state definition contains (in this order)

  • Name and description of the state: The name and description can be defined in various languages (see Multi-Lingual Support).
  • Meta-data: Allow to add extra data not used by the workflow core engine but exploitable by extensions or custom applications (see Meta-Data).
  • pre-functions: Allow executing code when the state is entered (see Pre- and Post-Functions).
  • Set of transitions: Set of all the transitions starting from the state (see State Transitions).
  • post-functions: Allow executing code when the state is left (see Pre- and Post-Functions).

Note: there is no "initial state" representation in TinyWorkflow. It's because they have no name, descrition, functions or meta-data. The transitions virtually starting from the "initial state" are the initial transitions.

State Transitions

Each state contains the list of all transition starting from itself. They are declared in the <transitions> XML element.

The transitions in the state can be of two types:

  • Transition reference: reference to a global transition (see Global Transitions). Rather that rewriting the common transitions all over the XML definition, you can define them as global transitions and reference them from where you need. The references can work like inheritance: you can override any part of the global transition in each reference. (see Overriding global transitions).
  • State transition: transition defined in the state itself (see Transition Definition).

Transition Definition

The transition definition is definition of a transition of the "state machine". It is identified by a unique ID (given as a mandatory XML attribute of the <transition> element).

The transition can define an 'auto' attribute to enable automatic transitions (see Automatic Transitions).

Each transition definition is composed of (in this order):

  • Name and description of the transition: The name and description can be defined in various languages (see Multi-Lingual Support).
  • Meta-data: Allow to add extra data not used by the workflow core engine but exploitable by extensions or custom applications (see Meta-Data).
  • Parameters: Define the list of parameters used by the transition (see Parameters).
  • Permissions: Define the access right that must be granted to a user to be able to perform the transition (see Permissions and Conditions).
  • Conditions: Define the conditions on which the transition is available (see Permissions and Conditions).
  • pre-functions: Allow executing code when the transition is started (see Pre- and Post-Functions).
  • List of results: Define the possible target states for the transition (see Transition Results).
  • post-functions: Allow executing code when the transition is done (see Pre- and Post-Functions).

The execution of a transition can return a Map of values (see Return Values).

Inside some XML attributes of transition definitions you can define expression of the form ${expression}. See Beanutils expressions for more info and WorkflowXmlReference.html to find which parameters allow to define expressions.

Automatic Transitions

An automatic transition is a transition defined with the XML parameter auto='true'.

After a transition is done, the workflow checks all the automatic transitions of the current workflow state. If it finds a transition for witch permissions and conditions are met, the transition is performed automatically.

The caller id of the transition is set to Workflow.SYSTEM_USER_ID and the given map of parameters is null. This check for automatic transition is performed repeatedly until there is no such transition available.

If a parent workflow exists, the automatic transitions of the parent are also triggered.

External triggering

The automatic transitions can be triggered just bay calling the doAutomaticTransitions() on the workflow. In this case, the all the available automatic transition of the workflow (and possible parent workflows) are performed just as after a transition.

Controlling the automatic transitions execution

The fact that the automatic transitions are automatically executed after any transition can be disabled. Just put the special parameter Workflow.AUTO_TRANSITION_PARAM with value Boolean.FALSE in the map of parameter of a transition.

It allows controlling yourself when the automatic transitions are performed.

This is useful, for example, if you want that the main transition and the possible automatic transitions following it are executed in different Database transactions.

Just do something like:

        ...
        Workflow wf = ...
        Map input= ...
        
        // disable the automatic transition by putting a special parameter in the input Map
        inputs.put(Workflow.AUTO_TRANSITION_PARAM, Boolean.FALSE);
        
        // do the transition in one DB transaction 
        startTransaction();
        wf.doTransition("someTransition", inputs, "Billy");
        commitTransaction();
        
        // trigger the automatic transitions in a separate DB transaction
        startTransaction();
        wf.doAutomaticTransitions();
        commitTransaction();
        ...
        

Beanutils expressions

In attributes of some XML element you can put expressions in the form of ${expression}. The expression used here is an Apache/Jakarta Commons-BeanUtils expression (http://jakarta.apache.org/commons/beanutils).

Those expressions are evaluated at runtime when the transition is performed. They are relative to the current TransitionContext (of type org.tinyWorkflow.instance.TransitionContext).

Basically this API maps 'attributes' of the expression to java methods like following:

ExpressionMapped code
${someAttribute}context.getSomeAttribute()
${mappedAttr(key)}context.getMappedAttr("key")
${indexedAttr[i]}context.getIndexedAttr(i)

You can combine expression with dots:

ExpressionMapped code
${someAttribute.mappedAttr(key).indexedAttr[i]}context.getSomeAttribute().getMappedAttr("key").getIndexedAttr(i)

Here is a list most commonly used expressions:

ExpressionMapped codeDescription
${oldStateId}context.getOldStateId()The Id of the workflow state before the transition. It's useful for defining self transitions.
${oldOwnerId}context.getOldOwnerId()The Id of the owner of workflow state before the transition.
${callerId}context.getCallerId()The Id of the user executing the transition.
${param(paramId)}context.getParam("paramId")Get a parameter of the transition.
${workflow}context.getWorkflow()Get the workflow instance.
${peer}context.getPeer()Get the workflow peer.

The expressions are mostly used inside <expressionEquals> conditions or in attributes of transition results.

You can put several expressions inside a single attribute like, in this case they are transformed to string and concatenated.

        ...
        <results>
            <result state="${oldStateId}" logInfo="Setting value from ${param(oldMyParam)) to ${param(myParam)}"/>
        </results>
        ...

Where "myParam" is a parameter of the function, and "oldMyParam" has been put in the transition context in some pre-function.

Parameters

The <parameters> element lists the parameters of the transition. Parameter definitions are used to validate the transition input. It's also a way to document the expected parameters and to support GUI building automatic input forms.

A parameter definition usually contains:

  • An 'id' attribute: giving the id of the parameter. This id is used as key to store the parameter in the parameter Map.
  • A 'mandatory' attribute: specifying if the parameter is mandatory or optional (mandatory by default).
  • A 'defaultValue' attribute: specifying if the parameter default value, this is not used by the core engine. It is more a hint for external applications allowing to initialize the parameters with a meaningful value.
  • A name and description: A multi-lingual name and description just as for states, transitions, ... (see Multi-Lingual Support).
  • Meta-data: Extra data just as for states, transitions, ... (see Meta-Data).

The abstract class org.tinyWorkflow.definition.parameter.BaseParameterDefinition gives a base implementation of a parameter definition inmplementing all this usual behavior of the parameter definitions.

Parameter definitions can specify additionl constraints depending of the parameter type (max. length, max. value, enumerations, ...)

Parameters can be customized: you can define your own parameter types and use them in your worklfow definition. See Customisation.html for more info.

Example of defining parameters:

        ...
        <transition id="sample1">
                    ...
                        <parameters>
                                <stringParameter id="param1" minLength="1" maxLength="256" defaultValue="enter a value"> 
                                        <name value="Parameter 1" />
                                        <name locale="fr" value="Paramètre 1" />
                                </stringParameter> 
                                <booleanParameter id="param2" mandatory="false" />
                        </parameters>
        ...

for a complete list of standard parameters definitions with all their attribute description, see WorkflowXmlReference.html

Permissions and Conditions

TinyWorkflow defines permissions and conditions. Both are boolean expressions guarding the a transition execution. From the point of view of implementation they are the same: you can move everything you find in <permissions> in <conditions> (and vice-versa) without affecting the workflow behavior. The only diffrence between permission and conditions is the meaning they have:

  • Permissions are about user access right. It checks if the user has been granted the right to perform a transition. This right is usually given ob basis on roles/credentials managed outside of the workflow. The permission usually doesn't change during a user session.
  • Conditions check the preconditions that are needed to perform a transition. Those preconditions are something dependent of the current workflow state and not of the specific user about to perform the transition. The user can act on the workflow to put it in a state meeting the preconditions.

For the workflow engine, there is no difference: a transition can be executed only if all the permissions and conditions are met.

They are however separated because, usually, the user interface are doing difference between those two. What the user is not permitted to do is not shown to him (because, as it will never be able to perform some transition, he has no need to even know they exists). What can not be executed for temporary conditions is shown but disabled (So the user can see that some transitions will be available if he make the preconditions happening). To support that, TinyWorkflow provides the method getTransitionsAvailability(..). It returns a list of all the transitions defined in the current workflow state with a flag saying if it is available and if the user has the permission to execute them.

If you don't want to implement such a advanced GUI, simply call getAvailableTransitions(..) to have the list of all available transitions (for which both permissions and conditions are met). Then you can choose to use only one of <permissions> or <conditions> elements in XML (or sill use both for declaration clarity).

Both <permissions> and <conditions> contains a list of boolean expressions that must be true (AND combination) for the transition to be allowed. Various pre-defined boolean expressions (simply called 'Conditions' though they can also be used in <permissions>) are provided by TinyWorkflow. For a complete list see WorkflowXmlReference.html

Conditions can be customized: you can define your own condition types and use them in your worklfow definition. See Customisation.html for more info.

Conditions can also be combined with the usual "AND", "OR" and "NOT" operators:

        ...
        <conditions>
                <or>
                    <callerIsWorkflowOwner/>
                <and>
                    <callerIsSystem/>
                    <paramEquals paramId="myParam" value="abc"/>
                </and>
            </or>
        </conditions>
        ...

Don't forget that the <conditions> itself act like a <and> condition.

Transition Results

The <results> section of a transition define what will be the state of the workflow after the transition is executed.

The result define:

  • The id of next state
  • The owner of next state (none by default). See The workflow owner chapter
  • The workflow definition id and version for next state. This is rarely used, it allows to move from one defintion to another or to move to a newer version of the same definition. By default, you stay in the same definition.
  • Log info: the info string to log in workflow history.

The result list can define more than one result. In this case the result must be <conditionalResult> XML elements. Thos results have an additional condition allowing the workflow engine to choose what will be the actual result. The last result of the result list is always not conditional because it's the default one that is chossen when all the conditions of the previous results are false.

The conditional result is just like a normal result (same attributes) with a condition inside it. The conditions used here are exactly the same as the one used to guard transition execution (see Permissions and Conditions). Of course, some conditions (like <workflowHasNoOwner>) are not usefull in this context. You will usually check an expression on the current context (using for example <expressionEquals ... >) that have been set during pre-functions to choose what will be the result.

The workflow engine simply evaluate the conditions of the result list in the order they are defined. The first result with a true condition is used.

Example of result list with condituional result:

        ...
        <results>
            <conditionalResult state="Processing" owner="${oldOwnerId}">
                <or>
                    <expressionEquals eval="${oldStateId}" value="Valid"/>
                    <expressionEquals eval="${oldStateId}" value="Invalid"/>
                </or>
            </conditionalResult>
            <result state="${oldStateId}" owner="${oldOwnerId}"/>
        </results>
        ...

This it the result list of a global transition specifying that the transition doesn't change the workflow state except when it comes from the "Valid" or "Invalid" state. In this case the workflow goes in the "Processing" state. The owner of the workflow always stays the same.

Note: you can achive the same by using only the non-conditional result in the global transition and overriding the results in the transition references of the "Valid" and "Invalid" states.

The workflow owner

Each workflow can define a current owner. It allows to easily assign workflows to users. This owner is not directly used by the workflow engine (it is just logged in the workflow history) but you can define conditions using it.

Conditions checking the workflow owner are:

  • <callerIsWorkflowOwner>
  • <workflowHasNoOwner>

    Using those conditions, it's easy to define a 'locking' mechanism on the workflow. Just define the following global transitions:

            ...
        <globalTransitions>
            <transition id="lock" group="locking">
                <conditions>
                    <workflowHasNoOwner/>
                </conditions>
                <results>
                    <result state="${oldStateId}" owner="${callerId}"/>
                </results>
            </transition>
            <transition id="unlock" group="locking">
                <conditions>
                    <callerIsWorkflowOwner/>
                </conditions>
                <results>
                    <result state="${oldStateId}"/>
                </results>
            </transition>
        </globalTransitions>
            ...
    

    Those transitions have as effect to set/reset the workflow owner (without changing the current state).

    Then you can reference those transitions from some states and guard the other transition with <callerIsWorkflowOwner/> to allow that only to the current workflow owner perfroms them:

            ...
            <state id="state1">
                <transitions>
                    <transitionRef refId="lock"/>
                    <transitionRef refId="unlock"/>
                    <transition id="doSomething">
                        <conditions>
                            <callerIsWorkflowOwner/>
                        </conditions>
                        ...
    

    By setting wisely the result owner of each transition, you can control how the workflow is assigned to users during its lifetime. Doing that, you can eliminate the usual concept of 'Task' (usualy seen in other workflow engines) because you can assign the workflow itself.

Return Values

A workflow transition execution can return a Map containing useful data.

In the Usual worklow paradigm, the returned Map is null. This is because the workflow is seen as an object on which you execute transitions and the transitions are not meant to be "queries for a result" but action modifying the workflow state. After the transition, the state of the workflow (and its associated peer) can be inspected. This is the regular use of workflow and so, returning values is not encouraged.

However, sometimes it's useful to be able to pass some 'transient' values to the transition caller. Those value have no place in the object model of the workflow because they are related to one specific transition execution.

Typical example is a warning message: the transition was performed correctly but you want to display some warning to the user who performed it. In this case, you cannot put the warning message in the workflow because any other transition call (possibly made by another user) can clear it before you got a chance to display it.

To return a value from transition, just put it in the workflow TransitionContext during the transition:

        TransitionContext context = ...
        context.setResult("WARNING_MESSAGE", "This is an example of warning");

Then the code calling the transition can get the message from the returned map.

Note: when no result is set on the TransitionContext, the returned Map is null.

        Workflow wf = ...
        Map results = wf.doTransition("someTransition", null, "Billy");
        Object warningMessage = (results == null) ? null : results.get("WARNING_MESSAGE");
        if (warningMessage != null) {
            // Got a warning from transition execution
            System.out.println("Returned value: " + warningMessage);
        }

Using the workflows

Workflow Definition Repository

The central class for TinyWorkflow usage is the WorkflowDefinitionRepository. As you guessed, this class is used to hold the workflow definitions.

You can use the repository as a singleton, in this case always get the unique instance by calling:

        WorkflowDefinitionRepository repo=WorkflowDefinitionRepository.getInstance();

If you don't want a singleton, you can just create a new WorkflowDefinitionRepository instance.

Once you have a WorkflowDefinitionRepository, you can load the workflow XML definitions in it with the addXmlDefinition(..) method. This method accept a File, a URL or a Stream.

If you have some definitions extending other definitions, you have to load the base definition first and the the extension. Otherwise you will get an exception when the extension try to bind itself to the parent workflow.

Creating workflow instances

Once you have loaded all the workflow definitions you need, you are ready to create workflow instances. The workflow instances share the same workflow definition. As they contain no data related to workflow execution, they are re-entrant and can be shared beween all workflows without any concurrency problem (The definition are not supposed to be modified when running workflows).

The workflow instances are created or loaded using helper methods of the worklfow repository. It's because a workflow instance must always be attached to a workflow definition. The repository does that for you.

To instantiate a workflow just do:

        WorkflowDefinitionRepository repo=null;
        try {
            // Get the respository singleton and add a XML worklfow definition in it.
            repo = WorkflowDefinitionRepository.getInstance();
            repo.addXmlDefinition(getClass().getResource("doc/sampleDoc.xml"));
        } catch(Exception e) {
            System.err.println("Unable to configure the workflow repository");
            e.printStackTrace();
        }
        // create a new workflow instance
        Workflow myNewInstance = repo.createWorkflowInstance("myDefinition", "1.0");
        Object instanceId = myNewInstance.getId();        

To get back (load) a workflow that was previously persisted (saved in database) you also have to use the WorkflowDefinitionRepository. The workflow are identified by a unique id (a database key), just give the key to the WorkflowDefinitionRepository and it will give you back your workflow. The workflow id can be obtained with the getId() method. The id itself is generated by the persistence layer.

Just do:

        WorkflowDefinitionRepository repo = ...
        Object instanceId = ...
        // get back a previously saved workflow instance
        Workflow loadedInstance = repo.getWorkflowInstance(instanceId);

The workflow repository get the workflow from the persistence layer. It checks the workflow definition id and version of the loaded workflow and it attach the correct definition to it. The definition used by the loadaed workflow must be present in the repository.

Note: in these examples we did not configure the WorkflowPersistence, so it uses the default MemoryPersistence. See the Workflow Persistence chapter for more info.

Executing transitions

When you have a workflow instance, just call the "doTransition" method to execute a transition.

This method takes 3 parameters:

  • the transitionId: Id of the transition to execute. It must be a transition defined in the current workflow state.
  • parameters: A Map constaing the transition parameters.
  • userId or Principal: ID (or object giving the ID) of the user performing the transition. This ID is logged in workflow history and can be used during the transition execution if configured so (for example, to check permissions).

The first thing don by TinyWorkflow when a transition is started is to build a TransitionContext. The transitionContext is an object gathering all the information available at this time. It will be passed to all objects participating to the transition execution (permissions, conditions, functions ...).

In particular, if you give a Principal object to the doTransition method, it is stored as such (and made accessible) in the principal. It allows you to give your own principal class (or a standard java security class implementing principal) and implement your own permissions based on the information availble in this principal.

The transition is executed as following:

  • The parameters are checked: The presence of all mandatory parameter are checked. The type and value are also checked (when possible). If one parameter validation failed, a ValidationException is thrown.
  • The permission and conditiond are checked: a WorkflowException is thrown if one is not met.
  • The pre-functions are executed:
    • The pre-function of the workflow are executed if the executed transition is an initial transition.
    • The pre-function of the transition are executed
    • The result is evaluated (to find the new state).
    • The post-functions of the current (old) state are executed if this state is left.
  • The state, owner and last updator of the workflow are updated. A new item is added to the workflow history.
  • The post-functions are executed:
    • The pre-functions of the new state are executed if this state has changed.
    • The post-function of the transition are executed
    • The post-function of the workflow are executed if the new state is a final state.
  • The aggregated data (priority, flags) is re-evaluated.
  • The condition of the automatic transitions are evaluated, the first automatic transition found is executed.
  • The parent workflows are (recursively) notified so the can:
    • Update their aggregated data (priority, flags)
    • Check their automatic transitions.

Available transitions list

It is possible to check what are the actions that can be executed by one user on a workflow. Just call the getAvailableTransitions(..) method to have the list of the transitions fullfilling the permissions and conditions (for a specific user)

In the following example, we print all the transition of the workflow 'wf' available for a user. For each available transition, the transition id and all the transition parameters are printed.

        Workflow wf = ...
        String userId = ...
        System.out.print("  > Available transitions for " + userId + " are:");
        List transitions = wf.getAvailableTransitions(null, userId);
        for (Iterator it = transitions.iterator(); it.hasNext();) {
            TransitionDefinition trans = (TransitionDefinition) it.next();
            System.out.print(" ");
            System.out.print(trans.getId());
            // display parameters of the transition
            Iterator params = trans.getParameterDefinitions();
            if (params.hasNext()) {
                System.out.print("(");
                for (; params.hasNext();) {
                    TransitionParameterDefinition param = (TransitionParameterDefinition) params.next();
                    System.out.print(param.getId());
                    if (params.hasNext()) System.out.print(",");
                }
                System.out.print(")");
            }
        }
        System.out.println("");

Note: we usually pass null instead of the parameter Map to the getAvailableTransitions(..) method. This is because the conditions and permissions rarely depends of the parameters. But it's possible to put such conditions (for example with <paramIsDefined> condition). In this case, you have to give the parameters to have a correct evaluation of the conditions.

If you want to make a difference between permissions and conditions, you have to call the getTransitionsAvailability(..) method. It will return you a list of TransitionAvailability objects on which you can check if the permissions are OK and/or if the conditions are OK.

Workflow history

Each transition executed on a workflow is recorded in the workflow history.

TODO ...

Workflow aggregated data.

The workflow data (the set of all the instance data of the workflow) can be seen as 'primary data' and 'aggregated data'.

  • Primary data: is the data that cannot be deduced from other data. The primary data is the content of the workflow history, the relations to the parent and child workflows and, optionally, data kept in the workflow peer.
  • Aggregated data: is the data that is redundant. It can be re-calculated from the primary data.

The aggregated data is there to help usage of the workflow by giving meaningful information 'aggregated' from the workflow history. So the user can simply access this information, there's no need to re-calculate it at each access. Another advantage of the aggregated data is that this data is saved in the database (when you use database persistence); this allows performing workflow selection based on this aggregated data.

The aggregated data contains:

  • Workflow current state + owner + definition id and version. This data is present directly in the workflow instance (and hence the corresponding database table) but you can also find it as the result state + owner + definition id and version of the last item of the history.
  • Global status: This status tells if the workflow is 'not started', 'running' or 'finished'. You can deduce this status the history (To know if the workflow is finished, you have to check if the result state of the last item of the history is defined as a final state in the workflow definition).
  • Start date: Date when the workflow was started. It's the execution time of the first transition in the history.
  • End date: Date when the workflow was finished. It's the execution time of the last transition in the history when the last transition result in a final state.
  • Priority: priority of the workflow. This priority is calculated from various other data by the PriorityCalculator. You can plug your own calculator to calculate the priority as you want.
  • Flags: Flags are simple values (typically boolean or enumerated values) encoding complex state of the workflow. Something like: "this workflow contains a sub-workflow in some particular state". It is not used directly by the workflow core. It is there just to support business applications developed on top of the workflow.
  • Last updator and last update time: It's the time and caller of the last history transition.

The workflow flags

When watching a list of workflows in an application, the user usually wants some specific information about the workflow in addition to the current state. For example: "Did the workflow ever go to a specific state ?", "does the workflow have active sub-workflows ?", "Does the workflow have a child workflow in some specific state ?", "Does the workflow peer contain some specific object ?". To answer to those questions, you need to perform a complex check (like scanning the history or evaluating the state of each sub-workflow). This is too heavy to do when listing the workflows, so the solution is to calculate those flag each time the workflow is modified and store it in a generic way. Then we can retrieve it with a simple select and display those flags values in workflow lists.

Let's look, for example, at the subWorkflowFlag.

This flag has a boolean value. It is true when the workflow has child workflows defined.

If you define the following flag list in the workflow XML definition:

        ...
        <flags>
            <subWorkflowFlag id="activeChildren" mask="1" noSub="None" someSub="Some"/>
        </flags>
        ...

It means that you define a flag with id="activeSubCount" and value "None" when there is no active child workflow or value "Some" when there are active child workflows.

When this flag is defined you can query it value at any moment from the workflow:

        myWorkflow.getFlagValue("activeChildren");

All the flag values are encoded in a single int value. It's the reason why they can only have enumerated values. The mask attribute gives the bits where the flag values are encoded inside the int. You can retrieve the int encoding the flags (directly from the database or using the getFlagsIntValue() method) and deduce all the flags values by using the FlagList object of the workflow definition.

You can create your custom flag objects with the standard extension mechanism provided by TinyWorkflow: see Customisation.html for more information.

Workflow Persistence

TinyWorkflow uses a set of interface to manage interactions with the storage backend (the database). This set of interface is called the 'persistence'. Several implementations of the persistence interfaces are provided:

Available persitence types

Memory Persistence

The memory persistence is a simple implementation of the persistence interface of the workflow. This persistence only keeps everything in memory.

This persistence is useful only for tests and demos.

Hibernate Persistence

The Hibernate persistence simply provide concrete classes for the TinyWorkflow abstract implementation and maps them to database tables.

Abstract Class
from org.tinyWorkflow.instance
Concrete Class
from org.tinyWorkflow.persistence.hibernate
Database Table
WorkflowHibernateWorkflowWF_INSTANCE
WorkflowHistoryTransitionHibernateWorkflowHistoryTransitionWF_HIST_TRANS
WorkflowHistoryStateHibernateWorkflowHistoryStateWF_HIST_STATE

The are two Hibernate persistence available one using Hibernate 2.x and one using the new 3.x version. To have more information about those persistences, go to the documentation of the Hibernate2 / Hibernate3 modules.

The WorkflowPersistenceFactory

The WorkflowPersistenceFactory adds another degree of abstraction in persistence management. This factory simply provides a method to create WorkflowPersistence instances. It allows to write code independent from the persistence implementation.

You can set such a factory on the WorkflowDefinitionRepository, so it can create persistence itself when it loads/creates workflows. The drawback of this 'automated' usage of persistence is that you have less control on database transaction management. In particular, TinyWorkflow-Core classes never begin/commit/rollback transactions.