jBPM PVM の使用

jBPM PVM(プロセス仮想マシン)を Groovy から使ってみる。
jBPM PVM は、グラフ構造を構築して実行するためのシンプルな Java ライブラリで、ワークフローや BPM を実現するための共通基盤となる。

使うための準備は以下のとおり。

  1. jBPM PVM のサイトから zip ファイル(現時点では jbpm-pvm-1.0.alpha2.zip)をダウンロード
  2. zip ファイルを適当なディレクトリに解凍
  3. 解凍先ディレクトリ内の jbpm-pvm.jar ファイルを CLASSPATH に追加

これで基本機能は利用できるようになる。

簡単なプロセス定義の作成と実行

プロセス定義は基本的に ProcessFactory のメソッドを使って構築する。実行するための処理は Activity インターフェースの実装クラスに定義する。

ProcessFactory の主なメソッドは以下のとおり。

  • node() : ノードの定義
  • initial() : カレントノードを初期ノードに設定
  • behaviour() : カレントノードの振る舞い(処理)を設定
  • transition() : カレントノードの状態を作成
  • to() : 遷移先のノード指定
  • done() : ProcessDefinition オブジェクトを生成

ここで、「first -> second -> third」 という単純な 3つの処理を順次実行するだけのスクリプトを作成してみた。

サンプルスクリプト sample.groovy
import org.jbpm.pvm.Activity
import org.jbpm.pvm.Execution
import org.jbpm.pvm.ProcessDefinition
import org.jbpm.pvm.ProcessFactory

//処理クラスの定義
class SampleActivity implements Activity {
    def name

    public void execute(Execution exec) {
        println "$name : node name=${exec.node.name}"
    }
}

//プロセス定義
pd = ProcessFactory.build()
    .node("first").initial().behaviour(new SampleActivity(name: "FirstAct"))
        .transition().to("second")
    .node("second").behaviour(new SampleActivity(name: "SecondAct"))
        .transition().to("third")
    .node("third").behaviour(new SampleActivity(name: "ThirdAct"))
.done()

//プロセスの実行
exec = pd.startExecution()

//プロセスの状況を出力
println "node name = ${exec.node.name}, ended=${exec.ended}, active=${exec.active}, finished=${exec.finished}"

上記のスクリプトを実行すると以下のような結果となる。

実行結果
>groovy sample.groovy
FirstAct : node name=first
SecondAct : node name=second
ThirdAct : node name=third
node name = third, ended=true, active=false, finished=true

待機状態を用いたプロセス定義の作成と実行

ExternalActivity インターフェースの実装クラスを使うと、待機状態と待機からの復帰を実現するプロセスを実現することができる。
具体的には、execute メソッドの中で Execution オブジェクトの waitForSignal メソッドを呼び出すと、Execution の signal メソッドが呼ばれるまで待機状態になる。Execution の signal メソッドが呼ばれると ExternalActivity の signal メソッドが呼び出され、Execution の take メソッドで遷移先の状態を指定することができる。

そんなわけで、ExternalActivity を使って単純なループ処理を実装してみた。

サンプルスクリプト loop.groovy
import org.jbpm.pvm.Activity
import org.jbpm.pvm.ExternalActivity
import org.jbpm.pvm.Execution
import org.jbpm.pvm.ProcessDefinition
import org.jbpm.pvm.ProcessFactory
import org.jbpm.pvm.SignalDefinition

class NullActivity implements Activity {
    void execute(Execution exec) {
    }
}

class LoopCounter implements ExternalActivity {

    def loopCount

    void execute(Execution exec) {
        exec.waitForSignal();
    }

    void signal(Execution exec, String signal, Map<String, Object> params) {
        def count = (exec.hasVariable("count"))? exec.getVariable("count"): 0;
        count++
        exec.setVariable("count", count)

        if (count < loopCount) {
            //continue に移行(1stノードに戻る)
            exec.take("continue")
        }
        else {
            //next に移行(2ndノードに移る)
            exec.take("next")
        }
    }

    Set<SignalDefinition> getSignals(Execution exec) {
    }
}

printExec = {
    println "${exec.node.name} : ended=${exec.ended}, active=${exec.active}, finished=${exec.finished}"
}

//プロセス定義
pd = ProcessFactory.build()
    .node("1st").initial().behaviour(new LoopCounter(loopCount: 3))
        .transition("continue").to("1st")
        .transition("next").to("2nd")
    .node("2nd").behaviour(new NullActivity())
.done()

exec = pd.startExecution()

while(exec.active) {
    printExec()
    exec.signal()
}

printExec()

上記のスクリプトを実行すると以下のような結果となる。

実行結果
>groovy loop.groovy
1st : ended=false, active=true, finished=false
1st : ended=false, active=true, finished=false
1st : ended=false, active=true, finished=false
2nd : ended=true, active=false, finished=true

JRuby で書いてみると

sample.groovy と同じような処理を JRuby 1.1.2 では以下のように書ける。(ブロックを使ったインターフェースの実装方法を使ってみた)
なお、プロセス定義の部分で . の後に改行している点に注意。(こうしないと SyntaxError が発生)

sample.rb
require "java"

import org.jbpm.pvm.Activity
import org.jbpm.pvm.Execution
import org.jbpm.pvm.ProcessDefinition
import org.jbpm.pvm.ProcessFactory

def createActivity(name)
    #Activity インターフェースの execute メソッド実装オブジェクト生成
    Activity.impl(:execute) do |method, exec|
        puts "#{name} : node name: #{exec.node.name} - call #{method}"
    end
end

pd = ProcessFactory.build.
    node("first").initial.behaviour(createActivity :FirstAct).
        transition.to("second").
    node("second").behaviour(createActivity :SecondAct).
        transition.to("third").
    node("third").behaviour(createActivity :ThirdAct).
done

exec = pd.startExecution

puts "node name = #{exec.node.name}, ended=#{exec.ended}, active=#{exec.active}, finished=#{exec.finished}"