2007年7月2日月曜日

grails でワークフロー (手で書く編1)

grails で jbpm を使ってみる. まずは convetion over configuration の精神に則りつつ, すべて手書きしてみる. また最初は永続化は考慮しない (つまり, サーバを再起動するとすべてのワークフローはリセットされてしまう. 普通はこれでは役に立たない)


ステップは以下の通り.



  1. 状態機械として使う


  2. 組織モデルの作り方を決める


  3. ワークフローとして使う


状態機械として使う

例えば ActionItem というドメイン・クラスに対する状態機械を jbpm で動かしてみたいとしましょう. ActionItem.groovy に以下のようなコードを追加します.



// statemachine

static states = [start:'開始', notyet:'未着手', inhand:'着手済', completed:'完了', suspended:'中断', end:'終了']

// まずは永続性は考えない

private def statemachine = new ActionItemStatemachine()

// 以下は dynamic inject する

// states がなければ state() の結果をそのまま

public def state = { states[statemachine.state()] }

public def start = { statemachine.start() }

public def begin = { statemachine.signal('begin') }

public def done = { statemachine.signal('done') }

public def suspend = { statemachine.signal('suspend') }

public def abandon = { statemachine.signal('abandon') }

public def resume = { statemachine.signal('resume') }

public def end = { statemachine.end() }


どうやら grails はドメイン・クラスのプロパティに modifier を付けると永続化の対象としないようなので (本当か?), それを利用しています.



ここでまず states という Map を定義していますが, これは定義した状態名と人間が見る状態名のマッピングをするためのものです. これはなくてもいいかもしれませんね.



次にドメイン・クラス Foo に対する状態機械 statemachine の名前を FooStatemachine と決め打ちしています (convention).



statemachine, state, start, end はすべての状態機械に共通, begin から resume はこの状態機械が定義している状態です. いずれも動的な注入が可能でしょう.



一方, ActionItemStatemachine という状態機械クラスを作ります. 今は便宜上 controllers に置きます.



import jp.co.metabolics.jbpm.grails.ProcessDefinition

import org.jbpm.graph.exe.ProcessInstance

import org.jbpm.graph.exe.Token



class ActionItemStatemachine {

// 実際には def statemachine (WorkflowBuilder で作成またはリソース名を表す文字列) だけ書けばよい.

// ここでは ProcessDefinition になっている.

def statemachine = {

def writer = new StringWriter()

def b = new groovy.xml.MarkupBuilder(writer)

b.'process-definition'(name:'action-item-statemachine') {

b.'start-state'(name:'start') { transition(to:'notyet') }

state(name:'notyet') {

transition(name:'begin', to:'inhand')

}

state(name:'inhand') {

transition(name:'done', to:'completed')

transition(name:'suspend', to:'suspended')

}

state(name:'completed') { transition(to:'end') }

state(name:'suspended') {

transition(name:'abandon', to:'completed')

transition(name:'resume', to:'inhand')

}

b.'end-state'(name:'end')

}

ProcessDefinition.parseXmlString(writer.toString())

}



// 以下は dynamic inject される

private token



ActionItemStatemachine() {

token = (new ProcessInstance(statemachine())).getRootToken()

}



def signal = { token.signal(it) }

def start = { token.signal() }

def end = start

def state = { token.getNode().getName() }



}



最初に
jp.co.metabolics.jbpm.grails.ProcessDefinition を import していますが, これは jbpm の ProcessDefinition が 'def' というパッケージに入っているために groovy から直接読めない ('def' は groovy ではキーワード) からです.



src/java の下に以下のような内容の ProcessDefinition.java を置いています. 内容は何もない単なる wrapper です.



package jp.co.metabolics.jbpm.grails;



public class ProcessDefinition extends org.jbpm.graph.def.ProcessDefinition {

public static org.jbpm.graph.def.ProcessDefinition parseXmlString(String xml) {

return org.jbpm.graph.def.ProcessDefinition.parseXmlString(xml);

}



}




次に statemachine を定義しています. ここでは groovy の MarkupBuilder を使って, jbpm の jpdl 形式の XML を直書きです. これは例えば eclipse プラグインを使って書いたものをファイルとして読み込んでも構いません. が, いずれにしろプログラマが書くしかありません.



token, コンストラクタ, signal, start, end, state はすべての状態機械に共通なコードですから, やろうと思えば動的な注入が可能です.



さてこれを動かしてみましょう.



def ai1 = new ActionItem()

assert '開始' == ai1.state()

ai1.start()

assert '未着手' == ai1.state()

ai1.begin()

assert '着手済' == ai1.state()

ai1.suspend()

assert '中断' == ai1.state()

ai1.resume()

assert '着手済' == ai1.state()

ai1.done()

assert '完了' == ai1.state()

ai1.end()

assert '終了' == ai1.state()


OK のようです.



今度は jbpm をワークフローとして使ってみましょう. そのためにはまず組織モデルを決める必要があります.