Finite State Machine behaviour for domain classes

  • Tags: domain
  • Latest: 0.6.9
  • Last Updated: 28 February 2014
  • Grails version: 1.1.1 > *
  • Authors: Jorge Uriarte
3 votes
Dependency:
compile ":fsm:0.6.9"
Custom repositories:
mavenRepo "http://snapshots.repository.codehaus.org/"
mavenRepo "http://repository.codehaus.org/"
mavenRepo "http://download.java.net/maven/2/"
mavenRepo "http://repository.jboss.com/maven2/"

 Documentation

Summary

This plugin allow definition of simple workflows attached to domain classes, including states, events, transitions and conditions. Current workflow's state will be held in domain class' property that must be defined. Multiple workflows can be defined on every domain class.

Installation

grails install-plugin fsm

Description

Overview

This plugin injects the ability to define and execute simple workflows into domain classes. The current state of the flows will be held in class-properties, that must be explicitely defined in the class (like 'status' and 'mood' in the example below).

Flow definition will be in a static block called 'fsm_def', which can hold several flow definition, each of them tied to one property in the class.

Syntax:

General aspect of the flow definition block is:

static fsm_def = [
    <flow-property> : [
        <initial-value> : { <flow-definition-closure> }
        ],
    <another-flow> : [
        <initial-value-2> : { <flow-definition-closure> }
        ]
    ]

Every closure might use the "on", "from", "to", "when" and "act" clauses to define the desired behaviour:

Structure of a flow definition follows:

= { flow ->
  flow.on(<event>) {
    from(<source>)[.when({
        <condition to allow the transition, must return boolean>
        })].to(<destiny>)[.act({
            <code to run if success>
        })]
  }
}

A bigger sample code

/**
 * Domain class to held some test of the FsmSupport
 *
 */
class SampleFSMEnabledClass {

def name def status def mood = "ignored" // fsm_def will override this value // with 'none' in this example) // and a Warning will be traced

def amount // A number > 0 will allow the status flow to start

static fsm_def = [ mood : [ none : { flow -> flow.on ('up') { from('none').when({ status == 'running' // Depends on second flow ! }).to('high') from('low').to('high').act({ doSomethingInteresting() }) from('high').to('high') from('none').to('none') // Order is CRITICAL ! } flow.on('down') { from('none').when({ status == 'running' }).to('low') from('low').to('low') from('high').to('low') from('none').to('none') // Order is CRITICAL ! } } ], status : [ initial : { flow -> flow.on('launch') { from('initial').when({ isSomethingPending() && amount > 0 }).to('running') from('initial').to('initial') } flow.on('stop') { from('running').to('stopped') } flow.on('continue') { from('stopped').to('running') } flow.on('finish') { from('stopped').to('finished') from('running').to('finished') } } ] ]

/* * Sample method to call from conditions! */ def isSomethingPending() { return true }

def doSomethingInteresting() { [...] } }

Firing events

Every domain class that has the FSM definition will get a set of methods defined:

  • fire(<flow>, <event>): Will launch the <event> on <flow> definition. Example:
    fire('status', 'continue')
  • fire_<flow>('event'): Will launch the <event> on <flow> definition. Example:
    fire_status('continue')

Mocking FSM in Unit Tests (since 0.6.1)

integration tests will get the behaviour, but if you need to test fsm behaviour in Unit Testing, you might try mocking with the new (0.6.1) mockFsm method call. JUST REMEMBER TO RESET THE METACLASS TO null AFTER THE TEST, SO FURTHER INTEGRATION TESTS WILL GET THE PROPER METACLASS AND NOT THE MOCKED ONE !

protected void setUp() {
    grails.plugin.fsm.FsmUtils.mockFsm(FsmSupportDummy)
    super.setUp()
}

protected void tearDown() { FsmSupportDummy.metaClass = null super.tearDown() }

Support

Add your issues, problems, bugs, patches in the JIRA tracker

Source code now hosted at GitHub

Changelog

0.6.9: 2013-02-28

  • Fixed "is_fireable" to take "when" conditions into account, from highwayman's pull requests 3 and 4

0.6.8: 2013-11-04

  • Added support for better mocking from highwayman's pull requests 1 and 2

0.6.6: 2011-10-13

  • Fixes some bugs, adds support to ask the domain classes if they are allowed to be fired a given event, using 'fireable'.

0.6.1: 2009-10-07

  • Fixed bug with initial value in domain classes with multiple FSM definitions.
  • You can now call "FsmGrailsPlugin.mockFsm(MyDomainClass)" to test basic FSM in your unit tests (integration tests recommended for whole testing, though...)
  • Testing resources now excluded from runtime, so no more dummy tables created in your database ;)

0.6: 2009-08-25 - Initial value will now work as intended. A warning will be emitted if it's setted in both the property and the FSM definition, but the latter will prevail.

0.5: 2009-08-24 - Workaround for problem with properties already initialized in the domain class

0.4: 2009-08-13 - Fixed a regression.

0.3: 2009-08-13 - Fixed bug in multiple actions in transitions with common 'from' state. Thanks to Alex Kochnev

0.2: 2009-08-11 - Added support for triggering code after transitions

0.1: 2009-08-10 - Initial release