Ruleのライフサイクル
jBossRulesでは用語として、Fact(事実)というのがよくでてくる。1行概要。jBossRulesでは、推論エンジンがルール(仕様)に事実(データ)をMatchingして解を導くといったことをします。
jBossRulesの利用は大きく2つのpartに分かれる。ルールのコンパイルと、実行。主な登場人物は以下のとおり。
PackageBuilder: drl,dslをReaderとして読み込み、Packageオブジェクトを作成する。
Package: Ruleの集まりみたいなもの。
RuleBase:実行コンポーネントでPackageオブジェクトを保持。
WorkingMemory:RuleBaseのSession.(HibernateのSessionに近い)
依存jarは以下のとおり。
drools-core.jar:コア
drools-compiler.jar:Ruleをプレコンパイルしてしかつかわない場合は必要ない。
drools-jsr94.jar:JSR94用impl.
drools-decisiontables.jar:decisionTableを利用する場合、必要。
まずはサンプル(恒例のハロワー)
public class HelloWorld { public static final void main(final String[] args) { try { //RuleBaseのコンパイル/読み込み final RuleBase ruleBase = readRule(); final WorkingMemory workingMemory = ruleBase.newWorkingMemory(); //ハロワー用のPOJO final Message message = new Message(); message.setMessage( "Hello World" ); message.setStatus( Message.HELLO ); //オブジェクトをWorkingMemoryに渡す。 workingMemory.assertObject( message); //ルール実行依頼。 workingMemory.fireAllRules(); //状態を変更 message.setStatus( Message.GOODBYE ); //状態が変更したことを明示的に通知 workingMemory.modifyObject(workingMemory.getFactHandle(message), message); //再度ルール実行依頼。 workingMemory.fireAllRules(); } catch ( final Throwable t ) { t.printStackTrace(); } } /** * Ruleのコンパイル/読み込み処理. */ private static RuleBase readRule() throws Exception { final Reader source = new InputStreamReader( HelloWorld.class.getResourceAsStream( "HelloWorld.drl" ) ); final Reader dsl = new InputStreamReader( HelloWorld.class.getResourceAsStream( "helloWorld.dsl" ) ); //PackageをつくるBuilderを用意. final PackageBuilder builder = new PackageBuilder(); //Packageを読み込んだReaderを元に作成 builder.addPackageFromDrl( source,dsl ); //Package取得 final Package pkg = builder.getPackage(); //RuleBaseの作成. final RuleBase ruleBase = RuleBaseFactory.newRuleBase(); //Packageの追加 ruleBase.addPackage( pkg ); return ruleBase; } public static class Message { public static final int HELLO = 0; public static final int GOODBYE = 1; private String message; private int status; public String getMessage() { return this.message; } public void setMessage(final String message) { this.message = message; } public int getStatus() { return this.status; } public void setStatus(final int status) { int oldStatus = this.status; this.status = status; this.changes.firePropertyChange( "status", oldStatus, this.status ); } }
仕様はこちら
package org.drools.examples expander helloWorld.dsl import org.drools.examples.HelloWorld.Message; rule "Hello World" when m : Message( status == Message.HELLO, message : message ) then System.out.println( message ); m.setMessage( "Goodbye cruel world" ); end rule "GoodBye" no-loop true when m : Message( status == Message.GOODBYE, message : message ) then m.setMessage( message ); System.out.println( message ); end
出力されるのは
Hello World Goodbye cruel world
です。
2回、fireしているのでこう出力されましたが、実は、workingMemory.modifyObjectで明示的にしているのがポイントです。
1回fireされると、そのRuleはAgendaGroup(Activation)に、その状態を保持してFactのオブジェクトの属性に変更がないかぎりRuleを再Fireしません。
(HibernateのSessionにオブジェクトがPersistentている状態みたい)
んで、そのひとつの方法がこのworkingMemory.modifyObjectで明示的に属性の変更通知をするやり方です。
または、
workingMemory.assertObject( message, true);
にして、Messageクラスにも
private final PropertyChangeSupport changes = new PropertyChangeSupport( this ); public void addPropertyChangeListener(final PropertyChangeListener l) { this.changes.addPropertyChangeListener( l ); } public void removePropertyChangeListener(final PropertyChangeListener l) { this.changes.removePropertyChangeListener( l ); }
って追加したPropertyChangeListenerを利用するやり方でもOK。workingMemory.modifyObjectしなくてすみます。
また、drl(Rule)上でもm.setStatus(),m.modify()と記述しても実はOK。
ただ、これは仕様にデータの制御が入っていていかがなものかなと。
ちなみにHibernateのSessionみたい、といったように、detachに対応するメソッドもあります。