JBoss Rules 5 リファレンスガイド
JBoss のプログラマーおよび JBoss Rules の開発者を対象
エディッション 5.2.0
概要
はじめに
1. 謝辞
第1章 はじめに
1.1. ルールエンジンとは
1.1.1. 説明と背景
- 推論エンジン
- 推論エンジンはファクトとデータをルールに対して一致し、アクションになる結果を推論します。
- プロダクションルール
- プロダクションルールはナレッジを表すために 1階論理を使用する 2 つ部分で構成される構造です。
when <conditions> then <actions>
- パターンマッチング
- パターンマッチングは、ファクトをルールに対して一致するプロセスです。Linear、Rete、Treat および Leaps アルゴリズムを使用して推論エンジンによって実行されます。
- ReteOO
- 使用される Rate 実装は
ReteOOと呼ばれます。これは、オブジェクト指向システム向けに向上され最適化された Rate アルゴリズムの実装です。 - 競合解決ストラテジー
- システムに大量のルールがある場合、ファクトのアサーションによっては複数のルールが true になることがあります。このような場合、これらのルールは競合状態にあると言えます。
agendaは、競合解決ストラテジーを使用してルールが実行される順番を指示し、このような状態を管理します。

図1.1 Rules エンジンの概要
- 真理維持
- 真理を強制する推論エンジンの機能。
- 論理関係
- 論理関係は、アクションの状態が true である推論に依存する場合に存在します。推論が true でなくなると、それに依存するアクションは元に戻されます。
- 前向き連鎖
- 前向き連鎖はデータ駆動型で、提供されたデータに反応します。ファクトはワーキングメモリーに挿入され、1 つまたは複数のルールが true になります。そして、アジェンダによって実行されるようスケジュールに置かれます。

図1.2 前向き連鎖
重要
JBoss Rules は前向き連鎖のエンジンです。 - 後向き連鎖
- 後向き連鎖はゴール駆動型で、システムはエンジンが満たそうとする 結論 より開始します。結論を満たすことができない場合、現在のゴールの一部を満たせるようにする サブゴール と呼ばれる結論を探します。最初の結論が満たされるか、満たされないサブゴールがなくなるまでこの処理が継続されます。Prolog は後向き連鎖エンジンの一例になります。
重要
JBoss Rules の次回のメジャーリリースに、後向き連鎖のサポートが追加されます。
図1.3 後向き連鎖
重要
1.2. 強結合と疎結合
- 疎結合
- あるルールの実行によって他のルールが実行されることがない設計。
- 強結合
- ルールが強結合である場合、あるルールが実行されると直接的に他のルールが実行されます。言い換えると、論理の明確なチェーンが存在することになります。
decision tree を使用してハードコードまたは実装することができます。
注記
第2章 クイックスタート
2.1. クイックスタートの基本
2.1.1. ステートレスナレッジセッション
- ステートレスセッション
- 推論のないセッション。
- 検証。例:「この申請者は住宅ローンの資格があるか」
- 計算。例: 「住宅ローンの保険料を計算する」
- ルーティングとフィルタリング。例:「受信メッセージをフォルダーへフィルターする」または「受信メッセージを宛先へ送信する」
- 必要なデータを収集します。これはルールに渡される ファクト のセットを形成します。この例では、1 つのデータのみが存在します。
package com.company.license; public class Applicant { private String name; private int age; private boolean valid; public Applicant (String name, int age, boolean valid) { this.name = name; this.age = age; this.valid = valid; } //add getters & setters here } - この時点でデータモデルが存在するため、最初のルールを記述します。このルールの目的は、18 歳未満の申請者を拒否することです。
package com.company.license; rule "Is of valid age" when $a : Applicant( age < 18 ) then $a.setValid( false ); endApplicantオブジェクトがルールエンジンに挿入されると、各ルールの制約が評価し、一致するものを探します (常に「オブジェクトタイプ」の暗黙的な制約があり、それ以降は明示的なフィールド制約がいくつでも存在できます)。- パターン
- 制約のコレクションはパターンと呼ばれます。
- パターンマッチング
- 各ルールの制約グループがオブジェクトを評価し、一致するものを探すプロセスです。
- 一致
- 挿入されたオブジェクトがルールのすべての制約を満たす場合、一致したと見なされます。
たとえば、Is of valid ageルールでは次の 2 つの制約があります。- 一致するファクトのタイプは
Applicantでなければならない。 Ageの値は 18 未満でなければならない。
$aはバインディング変数で、ルールの結果 (ここからオブジェクトのプロパティーを更新可能) の一致したオブジェクトの参照を可能にします。注記
ドル記号 ($) の使用は任意です。使用すると変数名とフィールド名を区別できます。注記
ここでは、ルールがクラスと同じフォルダーにあり、最初の ナレッジベース を構築するために クラスパスリソースローダー を使用できると仮定します。- ナレッジベース
- ナレッジベースは
KnowledgeBuilderによってコンパイルされたルールのコレクションです。
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClassPathResource( "licenseApplication.drl", getClass() ), ResourceType.DRL ); if ( kbuilder.hasErrors() ) { System.err.println( kbuilder.getErrors().toString() ); }上記で引用符で囲まれたコードはnewClassPathResource()メソッドを使用してlicenseApplication.drlファイルのクラスパスを検索します。ResourceTypeは Drools ルール言語 で記述されます。- Drools ルール言語
- Drools ルール言語 (DRL) は JBoss Rules のネイティブルール言語です。
- エラーは
KnowledgeBuilderをチェックします。エラーがない場合、セッションを構築できます。 - ルールに対してデータを実行します (申請者は 18 歳未満であるため、申請は「無効」とマーク付けされます。
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() ); StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); Applicant applicant = new Applicant( "Mr John Smith", 16, true ); assertTrue( applicant.isValid() ); ksession.execute( applicant ); assertFalse( applicant.isValid() );
collection など、オブジェクトを実装する iterable に対して実行することが可能です。次の例では、運転免許申請日が含まれる Application という名前の別のクラスを追加する方法を説明します。この他に、valid というブール値フィールドを Application に移動する方法についても説明します。
- コードは次の通りです。
public class Applicant { private String name; private int age; public Applicant (String name, int age) { this.name = name; this.age = age; } // getter and setter methods here } public class Application { private Date dateApplied; private boolean valid; public Application (boolean valid) { this.valid = valid; } // getter and setter methods here } - 申請が正規の期間内に行われたことを確認するために、以下のルールを追加します。
package com.company.license rule "Is of valid age" when Applicant( age < 18 ) $a : Application() then $a.setValid( false ); end rule "Application was made this year" when $a : Application( dateApplied > "01-jan-2009" ) then $a.setValid( false ); end - Java アレイは
iterableインターフェースを実装できないため、JDK コンバーターメソッド を代わりに使用します (このメソッドはArrays.asList(...)という行で始まります)。以下のコードはiterableリストに対して実行されます。各コレクション要素は、一致したルールが実行される前に挿入されます。StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() ); Applicant applicant = new Applicant( "Mr John Smith", 16 ); Application application = new Application(true); assertTrue( application.isValid() ); ksession.execute( Arrays.asList( new Object[] {application, applicant} )); assertFalse( application.isValid() );注記
execute(Object object)およびexecute(Iterable objects)メソッドは、実際はBatchExecutorインターフェースからのexecute(Command command)というメソッドに対するラッパーです。 CommandFactoryを使用して命令を作成し、以下の内容がexecute( Iterable it )と同等になるようにします。ksession.execute( CommandFactory.newInsertElements(Arrays.asList(new Object[] {application,applicant})) );- 多くのコマンドや結果出力識別子を使用する場合は、特に
BatchExecutorとCommandFactoryが便利です。List<Command> cmds = new ArrayList<Command>(); cmds.add( CommandFactory.newInsertObject(new Person("Mr John Smith"), "mrSmith")); cmds.add( CommandFactory.newInsertObject(new Person( "Mr John Doe" ), "mrDoe" )); ExecutionResults results = ksession.execute( CommandFactory.newBatchExecution(cmds) ); assertEquals( new Person("Mr John Smith"), results.getValue("mrSmith") );
注記
CommandFactory は BatchExecutor で使用できる他のコマンドを多くサポートします。 StartProcess、Query、SetGlobal などがその一例です。
2.1.2. ステートフルナレッジセッション
- ステートフルセッション
- ステートフルセッションでは、経時的にファクトに繰り返し変更を行うことが可能です。
- 監視: 例として、株式市場を監視し、購入処理を自動化できます。
- 診断: 例として、これを使用して障害検出処理を実行できます。また、医療診断処理にも使用できます。
- 物流: 例として、配達物追跡および配送提供に関連する問題に適用できます。
- 法令遵守: 例として、市場売買の合法性を検証するために使用できます。
警告
dispose() メソッドは、必ずステートフルセッションの実行後に呼び出されるようにしてください。これは、ナレッジベースは作成時にステートフルナレッジセッションへの参照を取得するためです。
StatelessKnowledgeSession の場合と同様に、StatefulKnowledgeSession は BatchExecutor インターフェースをサポートします。唯一の違いは、FireAllRules コマンドが最後に自動的に呼び出されないことです。
- 各部屋にスプリンクラーが設定されている家のモデルを作成します。いずれかの部屋で火事が発生します。
public class Room { private String name // getter and setter methods here } public class Sprinkler { private Room room; private boolean on; // getter and setter methods here } public class Fire { private Room room; // getter and setter methods here } public class Alarm { } - ルールは複数のオブジェクト間の関係を表す必要があります (特定の部屋にスプリンクラーが存在することなどを定義するため)。これには、パターンでバインディング変数を制約として使用します。これは クロス積 になります。
Fireクラスのインスタンスを作成します。このインスタンスをセッションに挿入します。以下のルールは、Fireオブジェクトの room フィールドへのバインディングを制約の一致へ追加します。これにより、その部屋のスプリンクラーのみがチェックされます。このルールが実行され、結果が実行されると、スプリンクラーが有効になります。rule "When there is a fire turn on the sprinkler" when Fire($room : room) $sprinkler : Sprinkler( room == $room, on == false ) then modify( $sprinkler ) { setOn( true ) }; System.out.println("Turn on the sprinkler for room "+$room.getName()); endステートレスセッションは標準的な Java 構文を使用してフィールドを変更しますが、上記のルールはmodifyステートメントを使用します (「with」ステートメントのように動作します)。これには、一連のコンマ区切りの Java 式が含まれています。これらはあらゆる意味で、modifyステートメントの 制御式 によって選択されたオブジェクト"setters"への呼び出しです。これらのsettersはデータを変更し、変更内容の「理由付け」を再度行えるよう、engineがその変更を認識するようにします。このプロセスが 推論 と呼ばれ、ステートフルセッションがどのように操作するかを理解するために重要になります (ステートレスセッションは推論を使用しないため、engineがデータの変更を認識する必要はありません)。注記
推論を無効にするには、シーケンシャルモード を使用します。
not のパターンは何かが存在しない場合のみ一致します。
rule "When the fire is gone turn off the sprinkler"
when
$room : Room( )
$sprinkler : Sprinkler( room == $room, on == true )
not Fire( room == $room )
then
modify( $sprinkler ) { setOn( false ) };
System.out.println("Turn off the sprinkler for room "+$room.getName());
endAlarm オブジェクトが作成されますが、火元がいくつあっても建物全体で 1 つの Alarm のみが必要になります。ここで、not の補語である exists を導入できます。exists はカテゴリーの 1 つまたは複数のインスタンスに一致します。
rule "Raise the alarm when we have one or more fires"
when
exists Fire()
then
insert( new Alarm() );
System.out.println( "Raise the alarm" );
endnot を使用します。
rule "Cancel the alarm when all the fires have gone"
when
not Fire()
$alarm : Alarm()
then
retract( $alarm );
System.out.println( "Cancel the alarm" );
endrule "Status output when things are ok"
when
not Alarm()
not Sprinkler( on == true )
then
System.out.println( "Everything is ok" );
endfireAlarm.drl という名前のファイルに格納します。このファイルはクラスパスのサブディレクトリに保存します。次に、fireAlarm.drl という新しい名前で ナレッジベース を構築します。
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClassPathResource( "fireAlarm.drl", getClass() ), ResourceType.DRL ); if ( kbuilder.hasErrors() ) System.err.println( kbuilder.getErrors().toString() ); StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
ksession.fireAllRules()を呼び出します。これにより、一致するルールに実行権限が与えられますが、実行されないため健全性に関するメッセージのみが生成されます。String[] names = new String[]{"kitchen","bedroom","office","livingroom"}; Map<String,Room> name2room = new HashMap<String,Room>(); for( String name: names ) { Room room = new Room( name ); name2room.put( name, room ); ksession.insert( room ); Sprinkler sprinkler = new Sprinkler( room ); ksession.insert( sprinkler ); } ksession.fireAllRules();> Everything is Okay
- 次に、2 つのファイルを作成し、挿入します ( ファクトハンドル は保持されます)。
- ファクトハンドル
- ファクトハンドルは挿入されたインスタンスのへの内部参照です。これにより、後でインスタンスを取り消したり変更したりすることができます。
firesがエンジンにあるため、fireAllRules()を呼び出します。火災報知器が作動し、該当するスプリンクラーがオンになります。Fire kitchenFire = new Fire( name2room.get( "kitchen" ) ); Fire officeFire = new Fire( name2room.get( "office" ) ); FactHandle kitchenFireHandle = ksession.insert( kitchenFire ); FactHandle officeFireHandle = ksession.insert( officeFire ); ksession.fireAllRules();
> Raise the alarm > Turn on the sprinkler for room kitchen > Turn on the sprinkler for room office
- 鎮火したら、火災オブジェクトが取り消され、スプリンクラーがオフになります。この時点で火災報知器はキャンセルされ、状態メッセージが再度表示されます。
ksession.retract( kitchenFireHandle ); ksession.retract( officeFireHandle ); ksession.fireAllRules();
> Turn off the sprinkler for room office > Turn off the sprinkler for room kitchen > Cancel the alarm > Everything is ok
> Turn off the sprinkler for room office > Turn off the sprinkler for room kitchen > Cancel the alarm > Everything is ok
2.2. 理論の概要
2.2.1. メソッドとルール
- 直接呼び出される
- 特定のインスタンスが渡される
- 1 つの呼び出しによって 1 つの実行が行われる
public void helloWorld(Person person)
{
if ( person.getName().equals( "Chuck" ) )
{
System.out.println( "Hello Chuck" );
}
}rule "Hello World"
when
Person( name == "Chuck" )
then
System.out.println( "Hello Chuck" );
endengineに挿入されたデータに一致することにより実行される- 直接呼び出すことができない
- 特定のインスタンスをルールに渡すことはできない
- 一致によって、ルールは一度または複数回実行され、全く実行されないこともある
2.2.2. クロス積
- クロス積
- 2 つ以上のデータセットが組み合わされた時、その結果はクロス積と呼ばれます。
rule "show sprinklers in rooms"
when
$room : Room()
$sprinkler : Sprinkler()
then
System.out.println( "room:" + $room.getName() +
" sprinkler:" + $sprinkler.getRoom().getName() );
endselect * from Room, Sprinkler への SQL (Structured Query Language) コマンドと同じで、Room テーブル内の各行が Sprinkler テーブル内の各行と結合するよう指示します。結果して、以下が出力されます。
room:office sprinker:office room:office sprinkler:kitchen room:office sprinkler:livingroom room:office sprinkler:bedroom room:kitchen sprinkler:office room:kitchen sprinkler:kitchen room:kitchen sprinkler:livingroom room:kitchen sprinkler:bedroom room:livingroom sprinkler:office room:livingroom sprinkler:kitchen room:livingroom sprinkler:livingroom room:livingroom sprinkler:bedroom room:bedroom sprinkler:office room:bedroom sprinkler:kitchen room:bedroom sprinkler:livingroom room:bedroom sprinkler:bedroom
rule "show sprinklers in rooms"
when
$room : Room()
$sprinkler : Sprinkler( room == $room )
then
System.out.println( "room:" + $room.getName() +
" sprinkler:" + $sprinkler.getRoom().getName() );
endSprinkler が各 Room に割り当てられます。SQL で書かれた通り、対応するクエリは select * from Room, Sprinkler where Room == Sprinkler.room になります。
room:office sprinkler:office room:kitchen sprinkler:kitchen room:livingroom sprinkler:livingroom room:bedroom sprinkler:bedroom
2.2.3. アクティベーション、アジェンダおよび競合セット
rule engine には結果の実行を管理する方法が必要となります。JBoss Rules は アクティベーション、アジェンダ および 競合解決ストラテジー を使用してこれを実現します。
注記
knowledge bases の作成に必要な Java コードの知識があることを前提とし、コードが繰り返されないように StatefulKnowledgeSession にファクトを投入します。
rule engine の状態を示します。
Cashflow、 Account、および AccountPeriod の 3 つのクラスから構成されます。
public class Cashflow
{
private Date date;
private double amount;
private int type;
long accountNo;
// getter and setter methods here
}
public class Account
{
private long accountNo;
private double balance;
// getter and setter methods here
}
public AccountPeriod
{
private Date start;
private Date end;
// getter and setter methods here
}knowledge bases の作成方法と、ファクトをインスタンス化して StatefulKnowledgeSession に投入する方法は理解されているはずです。したがって、説明を明確にするため、表を使用して挿入されたデータの状態を示します。下表は単一のファクトが Account に対して挿入されたことを表しています。2 四半期にわたる入出金も Cashflow オブジェクトとして Account に挿入されています。

図2.1 Cash Flow および Account
rule "increase balance for credits"
when
ap : AccountPeriod()
acc : Account( $accountNo : accountNo )
CashFlow( type == CREDIT,
accountNo == $accountNo,
date >= ap.start && <= ap.end,
$amount : amount )
then
acc.setBalance(acc.getBalance() + $amount);
endrule "decrease balance for debits"
when
ap : AccountPeriod()
acc : Account( $accountNo : accountNo )
CashFlow( type == DEBIT,
accountNo == $accountNo,
date >= ap.start && <= ap.end,
$amount : amount )
then
acc.setBalance(acc.getBalance() - $amount);
endCashflow オブジェクトに制約されます。

図2.2 Cash Flow および Account
- データは挿入段階で照合されますが、これはステートフルセッションであるため、ルールの結果は即座に実行されません。一致したルールと対応するデータは アクティベーション と呼ばれます。
- 各アクティベーションは アジェンダ と呼ばれるリストへ追加されます。
- アジェンダ上の各アクティベーションは、
fireAllRules()メソッドが呼び出された時に実行されます。指定のない限り、アクティベーションは任意の順番で実行されます。

図2.3 Cash Flow および Account

図2.4 Cash Flow および Account
アジェンダ のアクティベーションも 1 つになります。

図2.5 Cash Flow および Account

図2.6 Cash Flow および Account
agenda に 1 つまたは複数のアクティベーションがある場合、アクティベーションは「競合」状態であると言えます。この場合、実行の順序を決定するため、競合解決ストラテジー が使用されます。最も単純なレベルでは、デフォルトのストラテジーは salience を使用してルールの優先度を決定します。各ルールのデフォルトの salience 値はゼロで、この値が大きいほど優先度が高くなります。salience に負の値を使用することも可能です。これにより、相対的にルールの実行順序を適用することができます。
注記
rule "Print balance for AccountPeriod"
salience -50
when
ap : AccountPeriod()
acc : Account()
then
System.out.println( acc.getAccountNo() + " : " + acc.getBalance() );
endagenda を表しています。3 つの入出金ルールが任意の順番で、出力ルールは最後であるため、入出金ルールの後に実行されます。
重要

図2.7 Cash-Flow および Account
2.2.4. 推論
age フィールドと年齢ポリシー制御を提供するルールを持つ Person ファクトの場合、Person が成人または子供であるかを推論し、その推論に基づいたアクションを実行できます。
例2.1 成人の推論
rule "Infer Adult" when $p : Person( age >= 18 ) then insert( new IsAdult( $p ) ) end
Person には、IsAdult のインスタンスが挿入されます。このようなファクトは関係と呼ばれます。この推論された関係はすべてのルールで使用できます。
$p : Person() IsAdult( person == $p )
2.2.4.1. 使用する推論

図2.8 モノリシック決定テーブル

図2.9 ルールテーブルの年齢ポリシー
IsAdult ファクトはポリシールールから推論されます。中央政府が IsAdult ファクトを保持するため、ID 部門は成人であるかどうかのみを知る必要があり、ルールが現在のポリシーに適合するよう維持する必要はありません。

図2.10 ルールテーブルの ID カード
2.2.5. 推論および TruthMaintenance
logicalInsert) は、関心の分離を提供するために使用されます。以下の例は、子供または成人用のバス乗車パスを発行します。
rule "Issue Child Bus Pass" when $p : Person( age < 16 ) then insert(new ChildBusPass( $p ) ); end rule "Issue Adult Bus Pass" when $p : Person( age >= 16 ) then insert(new AdultBusPass( $p ) ); end
logicalInsert を使用して実現できます。
rule "Infer Child" when
$p : Person( age < 16 )
then
logicalInsert( new IsChild( $p ) )
end
rule "Infer Adult" when
$p : Person( age >= 16 )
then
logicalInsert( new IsAdult( $p ) )
endwhen 節の真偽に基づいて論理的に挿入されます。when 節の真偽が false に変更された場合、ファクトは自動的に取り消しされます。これは、相互排他的なルールには適切です。たとえば、年齢が 15 から 16 に変わった場合、IsChild ファクトは自動的に取り消され、IsAdult ファクトが挿入されます。
rule "Issue Child Bus Pass" when
$p : Person( )
IsChild( person =$p )
then
logicalInsert(new ChildBusPass( $p ) );
end
rule "Issue Adult Bus Pass" when
$p : Person( age >= 16 )
IsAdult( person =$p )
then
logicalInsert(new AdultBusPass( $p ) );
endLogicalInsert を not 条件要素と組み合わせて通知を処理できます。この場合、バス乗車パスの返却に対する要求を送信することができます。ChildBusPass オブジェクトが取り消されると、ルールがトリガーされ、要求が対象者に送信されます。
rule "Return ChildBusPass Request "when
$p : Person( )
not( ChildBusPass( person == $p ) )
then
requestChildBusPass( $p );
end2.3. 構築およびデプロイに関するその他のコメント
2.3.1. Change-Sets を使用したルールの追加
knowledge bases を構築してきました。各ルールは手作業で追加しました。 JBoss Rules では、リソースを knowledge bases に追加するよう XML ファイルより宣言することもできます。この機能は change-set と呼ばれます。
XML ファイルには、knowledge base に追加できるルールリソースのリストが含まれています。このファイルを他のファイルに示すことも可能です。
重要
<change-set xmlns='http://drools.org/drools-5.0/change-set'
xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
xs:schemaLocation='http://drools.org/drools-5.0/change-set
drools-change-set-5.0.xsd' >
<add>
<resource source='http://hostname/myrules.drl' type='DRL' />
</add>
</change-set>classpath と呼ばれるプロトコルの使用も可能です。このプロトコルは、リソースの current processes クラスパスを参照します。
重要
注記
CHANGE_SET に変更されていること以外は、前述のコードとほぼ同じであることに注意してください。
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newClasspathResource( "myChangeSet.xml",
getClass() ), ResourceType.CHANGE_SET );
if ( kbuilder.hasErrors() ) {
System.err.println( kbuilder.getErrors().toString() );
}<change-set xmlns='http://drools.org/drools-5.0/change-set'
xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' >
<add>
<resource source='http://hostname/myrules.drl' type='DRL' />
<resource source='classpath:data/IntegrationTest.xls' type="DTABLE">
<decisiontable-conf input-type="XLS" worksheet-name="Tables_2" />
</resource>
</add>
</change-set><change-set xmlns='http://drools.org/drools-5.0/change-set'
xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' >
<add>
<resource source='file://rules/' type='DRL' />
</add>
</change-set>2.3.2. ナレッジエージェント
KnowledgeAgent は自動的にルールリソースをロード、再ロード、およびキャッシュします。 properties ファイルより設定します。
knowledge base によって使用されるリソースに変更があった場合、KnowledgeAgent は knowledge base を更新または再構築できます。これらの更新にストラテジーを設定するには、KnowledgeAgentFactory を再設定します。
KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent("MyAgent");
kagent.applyChangeSet( ResourceFactory.newUrlResource( url ) );
KnowledgeBase kbase = kagent.getKnowledgeBase();KnowledgeAgent は、追加された各リソースをスキャンします。デフォルトのポーリング間隔は60 秒です。リソースの「最終変更」日が変更された場合、 KnowledgeAgent は knowledge base を再構築します (ディレクトリがリソースの 1 つとして設定された場合は、そのディレクトリのすべての内容が変更に対してスキャンされます)。
重要
knowledge base の参照は変更後も存在するため、新たに構築された knowledge base へアクセスするには getKnowledgeBase() を呼び出す必要があります。
KnowledgeAgent と knowledge bases がどのように対話するか説明しました。これにより、本ソフトウェアの仕組みをより深く理解していただけたと思います。また、ステートレスセッションとステートフルセッションについてより包括的な知識を得られたことと思います。
第3章 ユーザーガイド
3.1. 構築

図3.1 org.drools.builder
3.1.1. コードによる構築
Knowledge Builder はソースデータを取得し、knowledge package に変換します。knowledge package には Knowledge Base が消費するルールおよびプロセス定義が含まれています。
注記
ResourceType オブジェクトクラスは構築されるリソースのタイプを示します。
ResourceFactory は、reader、クラスパス、URI、ファイルまたは ByteArray など含む複数のソースよりリソースをロードする機能を提供します。
重要

図3.2 KnowledgeBuilder
注記
Knowledge Builder は KnowledgeBuilderFactory によって作成されます。

図3.3 KnowledgeBuilderFactory
Knowledge Builder を作成します。
例3.1 新しい Knowledge Builder の作成
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
KnowledgeBuilderFactory を使用して設定を作成します。このような設定により Knowledge Builder の挙動を変更できるようになります。
注記
Knowledge Builder オブジェクトが解決できるようにするカスタム クラスローダー を提供するために、この設定の作成を行います。
例3.2 カスタムクラスローダーを使用した新しい Knowledge Builder の作成
KnowledgeBuilderConfiguration kbuilderConf =
KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration(
null, classLoader );
KnowledgeBuilder kbuilder =
KnowledgeBuilderFactory.newKnowledgeBuilder(kbuilderConf);.drl ファイルが追加されます。
注記
Knowledge Builder は、JBoss Rules 4.0 Package Builder では不可能であった複数の名前空間の処理を行います。そのため、名前空間に関係なく、リソースの追加を継続することが可能です。
例3.3 DRL リソースの追加
kbuilder.add( ResourceFactory.newFileResource( "/project/myrules.drl" ),
ResourceType.DRL);重要
hasErrors() メソッドをチェックしてください。エラーがある時は、リソースを追加したり Knowledge Package を読み出したりしないでください (エラーがある場合、getKnowledgePackages() は空のリストを返します)。
例3.4 検証
if( kbuilder.hasErrors() )
{
System.out.println( kbuilder.getErrors() );
return;
}Knowledge Package の コレクション を取得します (「コレクション」と表現したのは、パッケージの名前空間ごとに 1 つの Knowledge Package があるからです)。これらの Knowledge Package は シリアライズ可能 で、頻繁にデプロイメントの単位として使用されます。
例3.5 Knowledge Package の取得
Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();
例3.6 全要素の組み合わせ
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
if( kbuilder.hasErrors() ) {
System.out.println( kbuilder.getErrors() );
return;
}
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newFileResource( "/project/myrules1.drl" ),
ResourceType.DRL);
kbuilder.add( ResourceFactory.newFileResource( "/project/myrules2.drl" ),
ResourceType.DRL);
if( kbuilder.hasErrors() )
{
System.out.println( kbuilder.getErrors() );
return;
}
Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();3.1.2. 設定および Change-Set XML を用いた構築
警告
例3.7 Change-Set XML のスキーマ (非「規範的」)
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://drools.org/drools-5.0/change-set"
targetNamespace="http://drools.org/drools-5.0/change-set">
<xs:element name="change-set" type="ChangeSet"/>
<xs:complexType name="ChangeSet">
<xs:choice maxOccurs="unbounded">
<xs:element name="add" type="Operation"/>
<xs:element name="remove" type="Operation"/>
<xs:element name="modify" type="Operation"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="Operation">
<xs:sequence>
<xs:element name="resource" type="Resource"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Resource">
<xs:sequence>
<!-- To be used with <resource type="DTABLE"...>> -->
<xs:element name="decisiontable-conf" type="DecTabConf"
minOccurs="0"/>
</xs:sequence>
<!-- java.net.URL, plus "classpath" protocol -->
<xs:attribute name="source" type="xs:string"/>
<xs:attribute name="type" type="ResourceType"/>
<xs:attribute name="basicAuthentication" type="xs:string"/>
<xs:attribute name="username" type="xs:string"/>
<xs:attribute name="password" type="xs:string"/>
</xs:complexType>
<xs:complexType name="DecTabConf">
<xs:attribute name="input-type" type="DecTabInpType"/>
<xs:attribute name="worksheet-name" type="xs:string"
use="optional"/>
</xs:complexType>
<!-- according to org.drools.builder.ResourceType -->
<xs:simpleType name="ResourceType">
<xs:restriction base="xs:string">
<xs:enumeration value="DRL"/>
<xs:enumeration value="XDRL"/>
<xs:enumeration value="DSL"/>
<xs:enumeration value="DSLR"/>
<xs:enumeration value="DRF"/>
<xs:enumeration value="DTABLE"/>
<xs:enumeration value="PKG"/>
<xs:enumeration value="BRL"/>
<xs:enumeration value="CHANGE_SET"/>
</xs:restriction>
</xs:simpleType>
<!-- according to org.drools.builder.DecisionTableInputType -->
<xs:simpleType name="DecTabInpType">
<xs:restriction base="xs:string">
<xs:enumeration value="XLS"/>
<xs:enumeration value="CSV"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
注記
<xs:attribute name="basicAuthentication" type="xs:string"/>
<xs:attribute name="username" type="xs:string"/>
<xs:attribute name="password" type="xs:string"/>
重要
.drl ファイルをロードします。
例3.8 単純な Change-Set XML
<change-set xmlns='http://drools.org/drools-5.0/change-set'
xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' >
<add>
<resource source='file:/project/myrules.drl' type='DRL' />
</add>
</change-set>
file や http など java.net.URL によって提供されるすべてのプロトコルをサポートし、classpath の追加バージョンもサポートします。
重要
ClassPath resource loader を使用して、リソースの特定に使用される class loader を指定します (XML では不可能です)。デフォルトでは、Knowledge Builder によって使用される class loader が使用されます (Change-Set XML が ClassPath リソースによってロードされる場合を除きます。この場合、リソースに対して指定された class loader が使用されます)。
例3.9 Change-Set XML のロード
kbuilder.add(ResourceFactory.newUrlResource(url),ResourceType.CHANGE_SET);
例3.10 リソース設定を用いた Change-Set XML
<change-set xmlns='http://drools.org/drools-5.0/change-set'
xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' >
<add>
<resource source='http:org/domain/myrules.drl' type='DRL' />
<resource source='classpath:data/IntegrationExampleTest.xls'
type="DTABLE">
<decisiontable-conf input-type="XLS" worksheet-name="Tables_2" />
</resource>
</add>
</change-set>Knowledge Base を再構築するため、Change-Set は Knowledge Agent を使用する時に便利です (これらの機能の詳細は Knowledge Agent の項の「デプロイメント」に記載されています)。
Knowledge Agent を使用する場合、リソースへの変更を継続的にスキャンします。また、キャッシュされた Knowledge Base も再構築します。
注記
Knowledge Agent と併用することも可能です。詳細は 「KnowledgeAgent」 を参照してください。
例3.11 ディレクトリの内容を追加するための Change-Set XML コード
<change-set xmlns='http://drools.org/drools-5.0/change-set'
xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' >
<add>
<resource source='file:/projects/myproject/myrules' type='DRL' />
</add>
</change-set>3.2. デプロイメント
3.2.1. KnowledgePackage とナレッジ定義
KnowledgePackage は KnowledgeBuilder によって作成されます。KnowledgePackage は自己充足的で、シリアライズ可能です。現在の基本的なデプロイメントユニットを形成します。

図3.4 KnowledgePackage
重要
KnowledgePackage は Knowledge Base に追加されますが、KnowledgePackage インスタンスは追加されると再使用できないことに注意してください。別の knowledge base に追加するには、最初に シリアライズ を行い、「クローン」された結果を使用します。 JBoss Rules の今後のバージョンでは、この制限がなくなる予定です。
3.2.2. ナレッジベース

図3.5 ナレッジベース
knowledge base は、アプリケーションすべての ナレッジ定義 が格納されるレポジトリです。これには、ルール、プロセス、関数およびタイプモデルが含まれます。knowledge base 自体には「インスタンス」データ (ファクト) は含まれません。この代わりに、セッションは Knowledge Base より作成されます。facts は Knowledge Base へ挿入され、process instances は Knowledge Base より開始します。
重要
knowledge base の作成は比較的リソースを多く使用するプロセスですが、セッションの作成はそうではありません。よって、セッションを繰り返し作成できるようにするため、Red Hat は可能な限り knowledge bases をキャッシュすることを推奨します。
knowledge bases オブジェクトも シリアライズ可能 であるため、構築して保存する方がよいでしょう。こうすることで、knowledge packages ではなくデプロイメントの単位として取り扱うことが可能です。
knowledge base を作成する方法の 1 つが KnowledgeBaseFactory クラスを使用する方法です。

図3.6 KnowledgeBaseFactory
例3.12 新しいナレッジベースの作成
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
Knowledge Builder と共にカスタマイズされた class-loader を使用してデフォルトの loader にない types を解決したい場合、これを Knowledge Base に設定します (このテクニックは Knowledge Builder に適用されるものと同じです)。
例3.13 カスタム Class-Loader を用いた新しいナレッジベースの作成
KnowledgeBaseConfiguration kbaseConf =
KnowledgeBaseFactory.newKnowledgeBaseConfiguration( null, cl );
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase( kbaseConf );3.2.3. インプロセス構築およびデプロイメント
knowledge base に追加されます。
重要
drools-${module}-${version}.jar ファイルが必ずクラスパス上にあるようにしてください。
例3.14 Knowledge Packages の Knowledge Base への追加
Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages(); KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( kpkgs );
注記
addKnowledgePackages(kpkgs) メソッドは繰り返し呼び出せることを理解してください。これはナレッジを追加するために行います。
3.2.4. 別プロセスとしての構築およびデプロイメント
Knowledge Base と KnowledgePackage は両方ともデプロイメントの単位であるため、シリアライズすることが可能です。そのため、drools-compiler.jar に必要な構築を実行するため 1 つのマシンを割り当て、すべてをデプロイおよび実行するために他のマシンを確保することができます。2 つ目のマシンは drools-core.jar のみを必要とします。
例3.15 KnowledgePackage を出力ストリームへ書き込む
ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( fileName ) ); out.writeObject( kpkgs ); out.close();
例3.16 入力ストリームから KnowledgePackage を読み取る
ObjectInputStream in = new ObjectInputStream( new FileInputStream( fileName ) );
// The input stream might contain an individual
// package or a collection.
@SuppressWarnings( "unchecked" )
Collection<KnowledgePackage> kpkgs =
()in.readObject( Collection<KnowledgePackage> );
in.close();
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages( kpkgs );
knowledge base 自体もシリアライズ可能であるため、ナレッジパッケージではなく構築と保存を行うことが推奨されます。
注記
knowledge packages を URL へコンパイルおよびパブリッシュした後、このアドレスリソースタイプを使用してこれらをロードできます。
3.2.5. ステートフルナレッジセッションとナレッジベースの変更
Knowledge Base によって作成され、返されます。また、任意で参照を保持することも可能です。Knowledge Base が変更されると、変更はセッションのデータに適用されます。これは弱い任意の参照で、ブール値フラグによって制御されます。
3.2.6. KnowledgeAgent
KnowledgeAgent は、リソースの自動ロード、キャッシュ、および再ロードを提供するクラスで、プロパティーファイルより設定されます。使用するリソースが変更されると、KnowledgeAgent は Knowledge Base を更新または再構築することができます。factory の設定は使用されるストラテジーを決定します (通常はプルベースで、標準のポーリングを使用します)。
注記
KnowledgeAgent は、デフォルトのポーリング間隔 (60 秒) で追加されたリソースをすべて継続してスキャンします。最後の変更の日付が更新されると、キャッシュされた Knowledge Base は新しいリソースを使用して自動的に再構築されます。

図3.7 KnowledgeAgent
KnowledgeBuilderFactory オブジェクトは Knowledge Builder を作成するために使用されます。ログファイルが必要とするため、エージェントは名前を指定する必要があります (ログエントリを正しいエージェントへ関連付けできるようにするためです)。
例3.17 KnowledgeAgent の作成
KnowledgeAgent kagent =
KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent" );

図3.8 KnowledgeAgentFactory
knowledge base を構築するエージェントを構築します。
注記
注記
KnowledgeAgent は、change set から追加されたリソースを 60 秒ごと (デフォルトの間隔) にポーリングして、更新されたか確認します。変更が検出されると、新しい Knowledge Base を構築します。また、ディレクトリがリソースとして指定された場合は、その内容がスキャンされます。
例3.18 KnowledgePackage を出力ストリームへ書き込む
KnowledgeAgent kagent =
KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent" );
kagent.applyChangeSet( ResourceFactory.newUrlResource( url ) );
KnowledgeBase kbase = kagent.getKnowledgeBase();
ResourceFactory を用いてアクティベートします。
例3.19 スキャンおよび通知サービスの開始
ResourceFactory.getResourceChangeNotifierService().start(); ResourceFactory.getResourceChangeScannerService().start();
ResourceChangeScannerService クラスより、デフォルトのリソーススキャン期間を変更します (更新された ResourceChangeScannerConfiguration オブジェクトはサービスの configure() メソッドに渡されるため、サービスは要求に応じて再設定できるようになります)。
例3.20 スキャン間隔の変更
ResourceChangeScannerConfiguration sconf =
ResourceFactory.getResourceChangeScannerService().
newResourceChangeScannerConfiguration();
// Set the disk scanning interval to 30s, default is 60s.
sconf.setProperty( "drools.resource.scanner.interval", "30" );
ResourceFactory.getResourceChangeScannerService().configure( sconf );
KnowledgeAgents は、空の Knowledge Bases と値が入力されたKnowledge Bases の両方を処理できます。値が入力された Knowledge Bases が提供された場合、KnowledgeAgent が内部から イテレーター を実行し、見つかった各リソースをサブスクライブします。
警告
KnowledgeBuilder がディレクトリのリソースをすべて構築するようにすることは可能ですが、この情報は失われます。つまり、これらのディレクトリは連続してスキャンされません。applyChangeSet(Resource) メソッドによって指定されたディレクトリのみが監視されます。
注記
Knowledge Base を土台として使用する利点の 1 つは、KnowledgeBaseConfiguration クラスで Knowledge Base を提供できることです。リソースの変更が検出され、新しい Knowledge Base がインスタンス化されると、以前の Knowledge Base オブジェクトに属する KnowledgeBaseConfiguration クラスが使用されます。
例3.21 既存のナレッジベースの使用
KnowledgeBaseConfiguration kbaseConf =
KnowledgeBaseFactory.newKnowledgeBaseConfiguration( null, cl );
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase( kbaseConf );
// Populate kbase with resources here.
KnowledgeAgent kagent =
KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent", kbase );
KnowledgeBase kbase = kagent.getKnowledgeBase();
Knowledge Base が構築されるまで getKnowledgeBase() メソッドは同じ Knowledge Base インスタンスを返します。これは、以前の Knowledge Base へ提供された KnowledgeBaseConfiguration を用いて行われます。
例3.22 ディレクトリの内容を追加する Change-Set XML
<change-set xmlns='http://drools.org/drools-5.0/change-set'
xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' >
<add>
<resource source='file:/projects/myproject/myrules' type='PKG' />
</add>
</change-set>
注記
drools-compiler 依存関係は、PKG という名前のリソースタイプには必要ありません。KnowledgeAgent は drools-core のみを用いてこのようなリソースタイプに対応できます。
KnowledgeAgentConfiguration を使用して KnowledgeAgent のデフォルトの動作を変更します。これは、変更に対するディレクトリの連続スキャンを抑制しながらディレクトリよりリソースをロードするために行います。
例3.23 スキャンの挙動変更
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
KnowledgeAgentConfiguration kaconf =
KnowledgeAgentFactory.newKnowledgeAgentConfiguration();
// Do not scan directories, just files.
kaconf.setProperty( "drools.agent.scanDirectories", "false" );
KnowledgeAgent kagent =
KnowledgeAgentFactory.newKnowledgeAgent( "test agent", kaconf );
Knowledge Packages を構築およびパブリッシュする方法と、Change-Set XML が URL とパッケージの両方を処理する方法を見てきました。これらの方法は、Knowledge Agent の重要なデプロイメントのシナリオを形成します。
3.3. 実行
3.3.1. ナレッジベース
Knowledge Base 自体にはインスタンスデータ (ファクト と呼ばれます) は含まれません。ファクトが挿入でき、プロセスインスタンスが起動できる KnowledgeBase よりセッションが作成されます。
注記
Knowledge Base の作成はリソースを大量に消費するプロセスですが、セッションの作成はリソースを大量に消費しません。繰り返しセッションを作成できるようにするため、可能な限り Knowledge Bases をキャッシュするようにしてください。
例3.24 新しいナレッジベースの作成
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
3.3.2. StatefulKnowledgeSession
StatefulKnowledgeSession はランタイムデータを格納し、実行します。StatefulKnowledgeSession は KnowledgeBase より作成されます。

図3.9 StatefulKnowledgeSession
例3.25 KnowledgeBase より StatefulKnowledgeSession を作成
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
3.3.3. KnowledgeRuntime
3.3.3.1. WorkingMemoryEntryPoint
WorkingMemoryEntryPoint は、ファクトを挿入、更新、および読み出しするメソッドを提供します。
注記
working memory に複数のパーティションが存在し、ファクトが挿入されるパーティションを選択できることに関連しています。しかし、このユースケースはイベント処理を対象とし、ほとんどのルールベースのアプリケーションはデフォルトのエントリーポイントのみを使用します。
KnowledgeRuntime インターフェースは engine と主な対話を行い、ルールの結果とプロセスのアクションで使用可能です。メソッドとインターフェースに関連するルールに焦点を置きますが、KnowledgeRuntime は WorkingMemory と ProcessRuntime の両方よりメソッドを継承します。これは、プロセスとルールに対応する統合 API を提供します。ルールを用いて作業する時、WorkingMemoryEntryPoint、WorkingMemory、および KnowledgeRuntime の 3 つのインターフェースが KnowledgeRuntime を形成します。

図3.10 WorkingMemoryEntryPoint
3.3.3.1.1. 挿入 (insert)
WorkingMemory にファクトについて通知する行為です (ksession.insert(yourObject) など)。挿入が行われると、システムはルールに対する一致があるか各ファクトを調べます。ルール実行の有無に関する決定はすべて挿入時に行われます。ただし、fireAllRules() が呼び出されるまでルールは実行されません。必ずすべてのファクトが挿入された後に行います。
注記
fireAllRules() が呼び出された時に条件評価が行われるという誤った考えを持ったユーザーが過去に存在しました。
注記
FactHandle は working memory 内で挿入されたオブジェクトを示すために使用されるトークンです。 オブジェクトが変更または取り消された時に、working memory との対話にも使用されます。
Cheese stilton = new Cheese("stilton");
FactHandle stiltonHandle = ksession.insert( stilton );working memory は、equality と identity の 2 つのアサーションモードのいずれかで操作します (デフォルトは identity です)。
Identityが使用される場合、working memoryはIdentityHashMapを使用してアサートされたオブジェクトをすべて格納します。インスタンスが新たにアサートされると、常に新しいFactHandleが返されます。同じインスタンスを繰り返し挿入すると、元のファクトハンドルが返されます。Equalityが使用される場合、working memoryはHashMapを使用してアサートされたオブジェクトをすべて格納します。同等のオブジェクトがアサートされていない場合、インスタンスが新たにアサートされると、新しいFactHandleのみが返されます。
3.3.3.1.2. 取り消し
working memory よりファクトを削除することを意味します。ファクトの追跡やルールの一致は行われないようになります。さらに、そのファクトに依存するアクティベートされたルールは、キャンセルされます。取り消しは、アサート時に返された FactHandle を使用して実行されます。
注記
not および exist キーワードを使用)。このような場合にファクトを取り消すと、ルールがアクティベートされる原因となることがあります。
Cheese stilton = new Cheese("stilton");
FactHandle stiltonHandle = ksession.insert( stilton );
ksession.retract( stiltonHandle );3.3.3.1.3. 更新
rule engine に通知し、変更されたファクトを再処理できるようにする必要があります。ファクトが更新済みであると見なされる場合、working memory より自動的に取り消され、再度挿入されます。
working memory 自体に通知できない場合、update メソッドを使用して通知します。update メソッドは常に変更されたオブジェクトをセカンダリパラメーターとして取ります。これにより、新しいインスタンスを 不変オブジェクト に対して指定できます。
注記
update メソッドは、シャドウプロキシ が有効になったオブジェクトのみに使用できます。
重要
update メソッドは Java コードと併用する場合のみ使用できます。オブジェクトの setter メソッドへの呼び出しを提供するため、ルール内で modify キーワードを使用します。
Cheese stilton = new Cheese("stilton");
FactHandle stiltonHandle = workingMemory.insert( stilton );
...
stilton.setPrice( 100 );
workingMemory.update( stiltonHandle, stilton );3.3.3.2. ワーキングメモリー
working memory は agenda へのアクセスを提供します。また、クエリの実行を許可し、名前付きの entry points へのアクセスを許可します。

図3.11 ワーキングメモリー
3.3.3.2.1. クエリ
Knowlege Base にクエリを定義します。Knowlege Base よりクエリを呼び出して一致する結果を返します。結果コレクション上で繰り替えされる間、get(String identifier) メソッドを使用してクエリのバインド識別子へアクセスできます。getFactHandle(String identifier) を使用すると、その識別子の FactHandle を読み出すことができます。

図3.12 クエリ結果

図3.13 QueryResultsRow
例3.26 簡単なクエリの例
QueryResults results =
ksession.getQueryResults( "my query", new Object[] { "string" } );
for ( QueryResultsRow row : results ) {
System.out.println( row.get( "varName" ) );
}3.3.3.3. ライブクエリ
例3.27 ViewChangedEventListener の実装
final List updated = new ArrayList();
final List removed = new ArrayList();
final List added = new ArrayList();
ViewChangedEventListener listener = new ViewChangedEventListener() {
public void rowUpdated(Row row) {
updated.add( row.get( "$price" ) );
}
public void rowRemoved(Row row) {
removed.add( row.get( "$price" ) );
}
public void rowAdded(Row row) {
added.add( row.get( "$price" ) );
}
};
// Open the LiveQuery
LiveQuery query = ksession.openLiveQuery( "cheeses",
new Object[] { "cheddar", "stilton" },
listener );
...
...
query.dispose() // make sure you call dispose when you want the query to close3.3.3.4. KnowledgeRuntime
KnowledgeRuntime は、ルールとプロセスの両方へ適用可能なその他のメソッドを提供します。グローバルを設定したり、ExitPoints を登録するメソッドがこの一例となります。

図3.14 KnowledgeRuntime
3.3.3.4.1. グローバル
rule engine へ渡すことができる名前付きオブジェクトです。挿入する必要はありません。統計情報やルールの右側で使用されるサービスに対して最も頻繁に使用されます。また、rule engine よりオブジェクトを返す手段としても使用されます。
- 不変であることを確認します。
- セッションに設定する前に
rulesファイルで宣言します。global java.util.List list
- これで、
Knowlege Baseがグローバル識別子とそのタイプを認識するようになったため、任意セッションのksession.setGlobalを呼び出します。警告
最初にグローバルタイプと識別子を宣言しないと、例外がスローされます。 - セッションにグローバルを設定するには、
ksession.setGlobal(identifier, value)を使用します。List list = new ArrayList(); ksession.setGlobal("list", list);警告
設定される前にルールがグローバルを評価すると、NullPointerException例外がスローされます。
3.3.3.5. StatefulRuleSession
NullPointerException は StatefulKnowledgeSession によって継承されます。これは、engine 外部に適用可能なルール関連のメソッドを提供します。

図3.15 StatefulRuleSession
3.3.3.5.1. アジェンダ フィルター

図3.16 AgendaFilters
filter インターフェースの実装です。アジェンダフィルターを使用して実行権利の有効化を許可または拒否します (フィルターできるかは実装に完全依存します)。
注記
fireAllRules() を呼び出す時に指定します。次の例では、 Test という文字列で終わるルールのみ実行が許可されます。他のルールはフィルターによって除外されます。
ksession.fireAllRules( new RuleNameEndsWithAgendaFilter( "Test" ) );
3.3.4. アジェンダ
working memory でアクションが実行される時、ルールが完全一致すると実行可能になります。単一の working memory アクションによって複数のルールを実行可能にできます。ルールが完全一致すると、アクティベーションが作成されます。これはルールと、一致するファクトの両方を参照し、Agenda 上に置かれます。次に Agenda は、競合解決ストラテジを介してアクティベーションの順序を決定します。
engine は 2 つの段階を繰り返します。
- 最初の段階は ワーキングメモリーアクション段階 と呼ばれます。ほとんどの作業はこの段階で行われ、 結果 (右側) または主要な Java アプリケーションプロセスのいずれかになります。結果が終了したり、主要の Java アプリケーションが
fireAllRules()を呼び出すと、engineがアジェンダの第 2 段階へ切り替えられます。 - 第 2 段階は アジェンダ評価段階 と呼ばれます。この段階でシステムは実行するルールを検索します。何も検出されないと終了します。検出されたルールがある場合はそのルールを実行し、その後ワーキングメモリーアクション段階へ切り替えます。

図3.17 2段階の実行
- プロセスは
agendaが消去されるまで繰り替えされ、消去された時点で時間制御が呼び出しアプリケーションへ返されます。注記
ワーキングメモリーアクションの実行中、ルールは実行されません。
図3.18 アジェンダ
3.3.4.1. 競合の解決
agenda に複数のルールがある場合、競合解決ストラテジが必要となります。ルールの実行はワーキングメモリーに影響を与えることがあるため、rule engine はルールが実行される順序を認識する必要があります (たとえば、ruleA を実行すると ruleB がアジェンダより削除される原因となることがあります)。
- Salience
- LIFO (後入れ先出し)
working memory の action counter 値を基に優先度を決定します。同じアクションが同じ値を受け取る間に各ルールが作成されます (実行のセットが同じ優先度値を持つ場合、実行順序は任意になります)。
重要
注記
3.3.4.2. AgendaGroup

図3.19 AgendaGroup
agenda 上のアクティベーションを分割します。常に 1 つのグループのみが「フォーカス」を持つことができ、そのグループに属するアクティベーションのみを有効にできます。
注記
Agenda groups は最も一般的に使用されます。
agenda group が一致するとフォーカスされます)。
setFocus() が呼び出されるたびに、agenda group が スタック にプッシュされます。フォーカスグループが空である場合、スタックから削除され、次のフォーカスグループ (この時点で一番上のグループ) を評価することが許可されます。
注記
agenda group はスタックの複数の場所に表示できます。
ksession.getAgenda().getAgendaGroup( "Group A" ).setFocus();
agenda group グループは MAIN と呼ばれます。これがスタックの最初のグループで、最初にフォーカスを持ちます。agenda group のないルールは自動的にこのグループに置かれます。
3.3.4.3. アクティベーショングループ

図3.20 ActivationGroup
activation group は activation-group ルール属性によってバインドされるルールのセットです。このグループでは 1 つのルールのみが実行できます。そのルールが実行した後、他のルールはすべてキャンセルされます。
注記
clear() メソッドを呼び出し、アクティベーションが実行する前にすべてのアクティベーションをキャンセルします。
ksession.getAgenda().getActivationGroup( "Group B" ).clear();
3.3.5. イベントモデル
event package は rule engine イベントの1つに通知します。これを使用して、アプリケーションの主な部分やルールから、ロギングおよび監査のアクティビティーを切り離します。
KnowledgeRuntimeEventManager インターフェースは KnowledgeRuntime クラスによって実装されます。このクラスは、WorkingMemoryEventManager と ProcessEventManager の 2 つのインターフェースを提供します。
注記
WorkingMemoryEventManager のみ取り上げます。

図3.21 KnowledgeRuntimeEventManager
WorkingMemoryEventManager を使用してリスナーを追加および削除します。リスナーを追加すると、working memory および agenda に影響するイベントを「リッスン」できます。

図3.22 WorkingMemoryEventManager
agenda listener を宣言し、セッションにアタッチする方法を表しています。アクティベーションが実行された後に、アクティベーションを出力します。
例3.28 AgendaEventListener の追加
ksession.addEventListener( new DefaultAgendaEventListener() {
public void afterActivationFired(AfterActivationFiredEvent event) {
super.afterActivationFired( event );
System.out.println( event );
}
});DebugWorkingMemoryEventListener および DebugAgendaEventListener と呼ばれる 2 つのクラスも提供します。すべての working memory イベントを出力するには、これらリスナーの 1 つを追加します。
例3.29 新しい KnowledgeBuilder の作成
ksession.addEventListener( new DebugWorkingMemoryEventListener() );
KnowledgeRuntime を読み出すには、KnowledgeRuntimeEvent インターフェースを使用します。

図3.23 KnowledgeRuntimeEvent
| ActivationCreatedEvent | ActivationCancelledEvent |
| BeforeActivationFiredEvent | AfterActivationFiredEvent |
| AgendaGroupPushedEvent | AgendaGroupPoppedEvent |
| ObjectInsertEvent | ObjectRetractedEvent |
| ObjectUpdatedEvent | ProcessCompletedEvent |
| ProcessNodeLeftEvent | ProcessNodeTriggeredEvent |
| ProcessStartEvent |
3.3.6. KnowledgeRuntimeLogger
KnowledgeRuntimeLogger は JBoss Rules の event system を使用して監査ログを作成します。JBoss Rules IDE の Audit Viewer などのツールを使用してこのログを調査します。

図3.24 KnowledgeRuntimeLoggerFactory
例3.30 FileLogger
KnowledgeRuntimeLogger logger =
KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "logdir/mylogfile");
...
logger.close();newFileLogger() メソッドを使用して、自動的にファイル拡張子 .log をファイルへ追加します。
3.3.7. StatelessKnowledgeSession
StatelessKnowledgeSession は StatefulKnowledgeSession をラッピングし、決定サービスタイプのシナリオに関連して使用されます。この存在により、dispose() の呼び出しが軽減されます。
fireAllRules() メソッドを呼び出したりすることはできません。 execute() メソッドは内部で StatefullKnowledgeSession をインスタンス化し、ユーザーデータをすべて追加してユーザーコマンドを実行します。その後、fireAllRules() および dispose() メソッドを呼び出します。
BatchExecution コマンドよりこのクラスを使用します (CommandExecutor インターフェースによってサポートされます)。しかし、2 つの 簡便性 (convenience) メソッド も提供されています。これらのメソッドは、簡単なオブジェクト挿入が必要な場合のみ使用します (CommandExecutor および BatchExecution は独自の項で詳細に説明されています)。

図3.25 StatelessKnowledgeSession
Convenience API を使用して stateless session を実行することを表しています。コレクションを繰り返し処理し、各要素を順に挿入します。
例3.31 コレクションを用いた簡単な StatelessKnowledgeSession の実行
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newFileResource( fileName ), ResourceType.DRL );
if (kbuilder.hasErrors() ) {
System.out.println( kbuilder.getErrors() );
} else {
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
ksession.execute( collection );
}例3.32 InsertElements コマンドを用いた簡単な StatelessKnowledgeSession の実行
ksession.execute( CommandFactory.newInsertElements( collection ) );
CommandFactory.newInsert(collection) を使用します。
CommandFactory にはサポートされるコマンドの詳細が含まれています。これらのコマンドのいずれかをマーシャリングするには XStream および BatchExecutionHelper を使用します。 また、BatchExecutionHelper を使用して、使用される XML 形式の詳細について学びます。JBoss Rules Pipeline を使用して、自動的に BatchExecution および ExecutionResults をマーシャリングします。
StatelessKnowledgeSession はさまざまなやり方でグローバルをスコープ指定できるようにします。最初はコマンドではない方法です。コマンドは特定の実行呼び出しへスコープ指定されます (グローバルは 3 つの方法で解決されます)。
StatelessKnowledgeSessionのgetGlobals()メソッドはGlobalsインスタンスを返します。名前の通り、このメソッドはセッションのグローバルへのアクセスを提供します。セッションのグローバルは すべての 実行呼び出しによって共有されます。警告
実行呼び出しは異なるスレッドで同時に実行できるため、可変グローバル を扱う場合は注意が必要です。例3.33 セッションスコープグローバル
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); // sets a global hibernate session, that can be used // for DB interactions in the rules. ksession.setGlobal( "hbnSession", hibernateSession ); // Execute while being able to resolve the "hbnSession" identifier. ksession.execute( collection );
- 委譲を使用してグローバル解決を実行する方法もあります。グローバルに値を割り当てると (
setGlobal(String, Object)を使用)、値は内部コレクションに格納されます。これは、識別子を値へマッピングすることが目的です。これらの識別子は提供される委譲よりも優先されます。識別子が見つからない場合のみ委譲グローバル (存在する場合) が使用されます。 - グローバルを解決する 3 つ目の方法は、 実行スコープグローバル を使用することです。この場合、グローバルを設定するコマンドは
CommandExecutorへ渡されます。
CommandExecutor インターフェースは out パラメーターを用いてデータをエクスポートする機能も提供します。挿入されたファクト、グローバル、およびクエリの結果はすべて返すことが可能です。
例3.34 out 識別子
// Set up a list of commands List cmds = new ArrayList(); cmds.add( CommandFactory.newSetGlobal( "list1", new ArrayList(), true ) ); cmds.add( CommandFactory.newInsert( new Person( "jon", 102 ), "person" ) ); cmds.add( CommandFactory.newQuery( "Get People" "getPeople" ); // Execute the list ExecutionResults results = ksession.execute( CommandFactory.newBatchExecution( cmds ) ); // Retrieve the ArrayList results.getValue( "list1" ); // Retrieve the inserted Person fact results.getValue( "person" ); // Retrieve the query as a QueryResults instance. results.getValue( "Get People" );
3.3.7.1. シーケンシャルモード
engine は簡素化された方法で稼働できます。以下の手順に従ってください。
- ルールセットの salience と position によりルールの順番を決定します (
rule terminalノードの sequence 属性を設定します)。 - 可能なルールアクティベーションごとに 1 つの要素を持つアレイを作成します。要素のポジションが実行順序を表します。
- right-input オブジェクトメモリー 以外の ノードメモリー をすべてオフにします。
Left Input Adapter Node伝播の接続を切断し、コマンドオブジェクトがオブジェクトとノードを参照できるようにします。後で実行するために、このコマンドオブジェクトはworking memoryのリストに追加されます。- すべてのオブジェクトをアサートします。アサートが実行され、right-input ノードメモリーにデータが投入されると、コマンドリストを確認し、各項目を順番に実行します。
- ルールに対して決定されたシーケンス番号に従って、結果となるすべてのアクティベーションをアレイに格納します。繰り返し処理の範囲を縮小するため、最初と最後に投入された要素を記録します。
- アクティベーションのアレイを繰り返し処理し、投入された要素を順番に実行します。
- 許可される最大ルール実行数が存在する場合、アレイのルールをすべて実行するため、ネットワーク評価を早期に終了します。
注記
LeftInputAdapterNode によって実行されないようになりました。代わりに、コマンドオブジェクトが作成され、working memory のリストに追加されるようになりました。このオブジェクトには、LeftInputAdapterNode と伝播されたオブジェクト両方への参照が含まれます。これにより、 挿入時に left-input 伝播が発生しないようにするため、left-input で結合を実行しようとする right-input 伝播はありません (よって、left-input メモリーが不必要になります)。
LeftInputAdapterNode コマンドオブジェクトを順に呼び出し、LeftInputAdapterNode コマンドオブジェクトのリストを繰り返し処理します。これらのコマンドオブジェクトはネットワークへ渡され、right-input オブジェクトと結合しようとしますが、right-input メモリーへアサートまたは伝播されるオブジェクトはこれ以上ないため、left-input には記録されません。
注記
agenda はなくなりました。代わりに、ルールの数に対する簡単なアレイが存在します。RuleTerminalNode のシーケンス番号はアクティベーションを格納するアレイ内の要素を表します。
重要
RuleTerminalNode にシーケンス番号が割り当てられ、ネットワークが構築されます。この番号は、salience 番号とネットワークに追加された順序に基づいています。
重要
RuleBaseConfiguration.setSequential(true) を呼び出すか、ルールベース設定の drools.sequential プロパティーを true に設定します。
注記
SequentialAgenda.DYNAMIC を用いて setSequentialAgenda を呼び出し、シーケンシャルモードが動的アジェンダへフォールバックするようにします。また、drools.sequential.agenda プロパティーを sequential または dynamic に設定することもできます。
3.3.8. コマンドと CommandExecutor
working memory を使用します。ステートレスセッションは、提供されたデータセットを用いて working memory を一度だけ実行します。結果がいくつか返される可能性があり、対話が繰り返し行われないようにするためセッションは最後に破棄されます。ステートレスセッションは、任意の結果を返す関数として rule engine を処理する方法であると考えてください。

図3.26 CommandExecutor

図3.27 ExecutionResults
CommandFactory はコマンドをステートフルおよびステートレスセッションで実行できるようにします (ステートレスナレッジセッションは破棄される前に fireAllRules() を最後に実行することが唯一の違いとなります)。現在サポートされているコマンドは次の通りです。
| FireAllRules | GetGlobal |
| SetGlobal | InsertObject |
| InsertElements | クエリ |
| StartProcess | BatchExecution |
InsertObject は任意の out 識別子を用いて単一オブジェクトを挿入します。InsertElements は繰り返し処理が可能なオブジェクトを確認し、各要素を挿入します。その結果、ステートレスナレッジセッションへオブジェクトを挿入するだけでなく、プロセスを開始したりクエリを実行したりして任意の順番でこれを実行できるようになります。
例3.35 insert コマンド
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); ExecutionResults bresults = ksession.execute( CommandFactory.newInsert( new Cheese( "stilton" ), "stilton_id" ) ); Stilton stilton = bresults.getValue( "stilton_id" );
ExecutionResults インスタンスを返します。これにより、上記の stilton_id などの out 識別子が指定されていると、すべてのコマンドの結果にアクセスできます。
例3.36 InsertElements コマンド
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
Command cmd = CommandFactory.newInsertElements(
Arrays.asList(new Object[] {
new Cheese("stilton"), new Cheese("brie"), new Cheese("cheddar")}
));
ExecutionResults bresults = ksession.execute( cmd );重要
BatchExecution は命令リストを取る複合コマンドで、これらの命令を順番に繰り返し処理し、実行します。そのため、オブジェクトをいくつか挿入してプロセスを開始し、fireAllRules を呼び出して単一の execute(...) 呼び出しでクエリを実行できるため、大変強力なコマンドになります。
fireAllRules() メソッドを自動的に実行しますが、FireAllRules コマンドも許可されます。このコマンドを使用すると、最後に自動実行が無効になります。これは手動のオーバーライドです。
ExecutionResults インスタンスにその結果を追加します。次の例はこの仕組みを表しています。
例3.37 BatchExecution コマンド
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); List cmds = new ArrayList(); cmds.add( CommandFactory.newInsertObject( new Cheese( "stilton", 1), "stilton") ); cmds.add( CommandFactory.newStartProcess( "process cheeses" ) ); cmds.add( CommandFactory.newQuery( "cheeses" ) ); ExecutionResults bresults = ksession.execute( CommandFactory.newBatchExecution( cmds ) ); Cheese stilton = ( Cheese ) bresults.getValue( "stilton" ); QueryResults qresults = ( QueryResults ) bresults.getValue( "cheeses" );
ExecutionResults にデータを投入します。query コマンドはデフォルトではクエリ名と同じ識別子を使用しますが、異なる識別子へマッピングすることも可能です。
XStream マーシャラーを JBoss Rules Pipeline と共に使用すると XML スクリプトを提供できるため、サービスに最適です。以下は、BatchExecution と ExecutionResults 向けの 2 つの簡単な XML の例になります。
例3.38 簡単な BatchExecution XML
<batch-execution>
<insert out-identifier='outStilton'>
<org.drools.Cheese>
<type>stilton</type>
<price>25</price>
<oldPrice>0</oldPrice>
</org.drools.Cheese>
</insert>
</batch-execution>
例3.39 簡単な ExecutionResults XML
<execution-results>
<result identifier='outStilton'>
<org.drools.Cheese>
<type>stilton</type>
<oldPrice>25</oldPrice>
<price>30</price>
</org.drools.Cheese>
</result>
</execution-results>
stage オブジェクトを使用できます。これらを組み合わせると、より簡単にデータをセッション内やセッション外で移動できます。
CommandExecutor インターフェースを実装する stage があります。これを使用して、パイプラインスクリプトをステートフルまたはステートレスセッションにします。次のように設定を行います。
例3.40 CommandExecutor のパイプライン
Action executeResultHandler = PipelineFactory.newExecuteResultHandler();
Action assignResult = PipelineFactory.newAssignObjectAsResult();
assignResult.setReceiver( executeResultHandler );
Transformer outTransformer =
PipelineFactory.newXStreamToXmlTransformer(
BatchExecutionHelper.newXStreamMarshaller() );
outTransformer.setReceiver( assignResult );
KnowledgeRuntimeCommand cmdExecution =
PipelineFactory.newCommandExecutor();
batchExecution.setReceiver( cmdExecution );
Transformer inTransformer =
PipelineFactory.newXStreamFromXmlTransformer(
BatchExecutionHelper.newXStreamMarshaller() );
inTransformer.setReceiver( batchExecution );
Pipeline pipeline =
PipelineFactory.newStatelessKnowledgeSessionPipeline( ksession );
pipeline.setReceiver( inTransformer );
BatchExecutionHelper を使用して、command のカスタムコンバーターと新しい BatchExecutor ステージを持つ、特別に設定された XStream を提供します。
pipeline を使用するには、ResultHandler の実装を提供します。これは、pipeline が ExecuteResultHandler ステージを実行する時に呼び出されます。

図3.28 パイプライン ResultHandler
例3.41 簡単なパイプライン ResultHandler
public static class ResultHandlerImpl implements ResultHandler {
Object object;
public void handleResult(Object object) {
this.object = object;
}
public Object getObject() {
return this.object;
}
}例3.42 パイプラインの使用
ResultHandler resultHandler = new ResultHandlerImpl(); pipeline.insert( inXml, resultHandler );
BatchExecution を使用してオブジェクトを挿入し、クエリを実行します。以下の pipeline の例では XML 表現が使用されます。パラメーターはクエリに追加されています。
例3.43 XML へマーシャリングされた BatchExecution
<batch-execution>
<insert out-identifier="stilton">
<org.drools.Cheese>
<type>stilton</type>
<price>1</price>
<oldPrice>0</oldPrice>
</org.drools.Cheese>
</insert>
<query out-identifier='cheeses2' name='cheesesWithParams'>
<string>stilton</string>
<string>cheddar</string>
</query>
</batch-execution>
CommandExecutor は ExecutionResults を返し、pipeline コードスニペットによって処理されます。
例3.44 XML へマーシャリングされた ExecutionResults
<execution-results>
<result identifier="stilton">
<org.drools.Cheese>
<type>stilton</type>
<price>2</price>
</org.drools.Cheese>
</result>
<result identifier='cheeses2'>
<query-results>
<identifiers>
<identifier>cheese</identifier>
</identifiers>
<row>
<org.drools.Cheese>
<type>cheddar</type>
<price>2</price>
<oldPrice>0</oldPrice>
</org.drools.Cheese>
</row>
<row>
<org.drools.Cheese>
<type>cheddar</type>
<price>1</price>
<oldPrice>0</oldPrice>
</org.drools.Cheese>
</row>
</query-results>
</result>
</execution-results>
BatchExecutionHelper は事前設定された XStream を提供します。これを使用して一括実行のマーシャリングをサポートします (結果となる XML は上記の通り、メッセージ形式として使用できます)。Command Factory よりサポートされるコマンドのみに事前設定されたコンバーターが存在します。ユーザーオブジェクトに他のコンバーターを追加することもできます (特にサービスが関与する場合、ステートレスまたはステートフルナレッジセッションのスクリプティングに大変便利です)。
drools-transformer-xstream モジュールには drools-transformer-xstream と呼ばれる単体テストがあります。ルート要素は <batch-execution> と命名され、任意の数の command 要素を含めることが可能です。
例3.45 ルート XML 要素
<batch-execution> ... </batch-execution>
Command Factory によって提供されるコマンドに限定されます。最も基本的なものが <insert> 要素で、オブジェクトを挿入します。insert 要素の内容はユーザーオブジェクトで、XStream によって決まります。
例3.46 Insert
<batch-execution>
<insert>
...<!-- any user object -->
</insert>
</batch-execution>
insert 要素は out-identifier と呼ばれる属性を特徴とします。これは、挿入されたオブジェクトが結果ペイロードの一部として返されることを要求します。
例3.47 out 識別子コマンドを用いた挿入
<batch-execution>
<insert out-identifier='userVar'>
...
</insert>
</batch-execution>
out-identifier をサポートしません (org.domain.UserClass は XStream によるシリアライズが可能な例示のユーザーオブジェクトです)。
例3.48 Insert Elements コマンド
<batch-execution>
<insert-elements>
<org.domain.UserClass>
...
</org.domain.UserClass>
<org.domain.UserClass>
...
</org.domain.UserClass>
<org.domain.UserClass>
...
</org.domain.UserClass>
</insert-elements>
</batch-execution>
例3.49 Insert Elements コマンド
<batch-execution>
<set-global identifier='userVar'>
<org.domain.UserClass>
...
</org.domain.UserClass>
</set-global>
</batch-execution>
例3.50 Set Global コマンド
<batch-execution>
<set-global identifier='userVar1' out='true'>
<org.domain.UserClass>
...
</org.domain.UserClass>
</set-global>
<set-global identifier='userVar2' out-identifier='alternativeUserVar2'>
<org.domain.UserClass>
...
</org.domain.UserClass>
</set-global>
</batch-execution>
例3.51 Get Global コマンド
<batch-execution>
<get-global identifier='userVar1' />
<get-global identifier='userVar2' out-identifier='alternativeUserVar2'/>
</batch-execution>
例3.52 Query コマンド
<batch-execution>
<query out-identifier='cheeses' name='cheeses'/>
<query out-identifier='cheeses2' name='cheesesWithParams'>
<string>stilton</string>
<string>cheddar</string>
</query>
</batch-execution>
注記
<start-process> コマンドは任意のパラメーターも許可します。
例3.53 Start Process コマンド
<batch-execution>
<startProcess processId='org.drools.actions'>
<parameter identifier='person'>
<org.drools.TestVariable>
<name>John Doe</name>
</org.drools.TestVariable>
</parameter>
</startProcess>
</batch-execution
例3.54 Signal Event コマンド
<signal-event process-instance-id='1' event-type='MyEvent'> <string>MyValue</string> </signal-event>
例3.55 Complete Work Item コマンド
<complete-work-item id='" + workItem.getId() + "' >
<result identifier='Result'>
<string>SomeOtherString</string>
</result>
</complete-work-item>
例3.56 About Work Item コマンド
<abort-work-item id='21' />
注記
3.3.9. マーシャリング
MarshalerFactory を使用して stateful knowledge sessions のマーシャリングおよびアンマーシャリングを行います。

図3.29 MarshalerFactory
MarshalerFactory を使用する最も簡単な方法になります。
例3.57 簡単なマーシャラーの例
// ksession is the StatefulKnowledgeSession // kbase is the KnowledgeBase ByteArrayOutputStream baos = new ByteArrayOutputStream(); Marshaller marshaller = MarshallerFactory.newMarshaller( kbase ); marshaller.marshall( baos, ksession ); baos.close();
ObjectMarshalingStrategy インターフェースが追加されています。このインターフェースの 2 つの実装が提供され、ユーザーは独自の実装を追加できます。提供されている 2 つの実装は次の通りです。
- IdentityMarshalingStrategy
- SerializeMarshalingStrategy
SerializeMarshalingStrategy です (上記の例で使用されています)。これは、ユーザーインスタンス上で Serializable または Externalizable メソッドを呼び出しします。
IdentityMarshalingStrategy は ID がストリームに書き込まれる間に、各ユーザーオブジェクトに対して整数識別番号を作成し、マップに格納します。アンマーシャリングが行われている間、IdentityMarshalingStrategy マップへアクセスし、インスタンスを読み出します (そのため、IdentityMarshalingStrategy が使用されると、Marshaller インスタンスが生存している間はステートフルになり、識別子を作成し、マーシャリングを行いたい各オブジェクトへの参照を保持します)。IdentityMarshalingStrategy に使用するコードは次の通りです。
例3.58 IdentityMarshallingStrategy
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectMarshallingStrategy oms = MarshallerFactory.newIdentityMarshallingStrategy()
Marshaller marshaller =
MarshallerFactory.newMarshaller( kbase, new ObjectMarshallingStrategy[]{ oms } );
marshaller.marshall( baos, ksession );
baos.close();ObjectMarshalingStrategyAcceptor インターフェースも提供されています。各 Object Marshaling Strategy にはこのインターフェースが含まれています。マーシャラーは一連のストラテジーを持ち、ユーザーオブジェクトへ読み書きしようとすると、ストラテジーを繰り返し、ユーザーオブジェクトをマーシャリングする責任を受け入れるかどうか「依頼」します。提供される実装の 1 つは ClassFilterAcceptor と呼ばれます。これは、文字列とワイルドカードを使用してクラス名を照合できるようにします。デフォルトは *.* であるため、上記の例では使用される IdentityMarshalingStrategy にはデフォルトの *.* が含まれます。
例3.59 アクセプターを用いた IdentityMarshalingStrategy
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectMarshallingStrategyAcceptor identityAcceptor =
MarshallerFactory.newClassFilterAcceptor( new String[] { "org.domain.pkg1.*" } );
ObjectMarshallingStrategy identityStrategy =
MarshallerFactory.newIdentityMarshallingStrategy( identityAcceptor );
ObjectMarshallingStrategy sms = MarshallerFactory.newSerializeMarshallingStrategy();
Marshaller marshaller =
MarshallerFactory.newMarshaller( kbase,
new ObjectMarshallingStrategy[]{ identityStrategy, sms } );
marshaller.marshall( baos, ksession );
baos.close();注記
第4章 ルール言語
4.1. 概要
DRL.g 内のルール言語に対する Antlr3 グラマーを参照することもできますが、必須ではありません。Rule Workbench を使用する場合、コンテンツアシスタントはルール構造の大部分に対応します。例えば、「ru」を入力し、ctrl キーとスペースキーを同時に押すと、ルール構造が構築されます。
4.1.1. ルールファイル
.drl 拡張子を持つファイルです。DRL ファイルには複数のルール、クエリ、および関数を含めることができ、またルールやクエリによって割り当てられ使用されるインポート、グローバルおよび属性などの一部のリソース宣言も含めることができます。 複数のルールファイルにまたがってルールを分散することもできます。この場合、拡張子 .rule が推奨されますが必須ではありません。ファイルにまたがってルールを分散すると、大量のルールを管理する場合に役立ちます。DRL ファイルは簡単なテキストファイルです。
package package-name imports globals functions queries rules
4.1.2. ルールの構造
rule "name"
attributes
when
LHS
then
RHS
end
4.2. キーワード
| true | false | accumulate |
| collect | from | null |
| over | then | when |
| lock-on-active | date-effective | date-expires |
| no-loop | auto-focus | activation-group |
| agenda-group | ruleflow-group | entry-point |
| duration | package | import |
| dialect | salience | enabled |
| attributes | rule | extend |
| template | query | declare |
| function | global | eval |
| not | in | or |
| and | exists | forall |
| action | reverse | result |
| end | init |
notSomething() や accumulateSomething() のようにメソッド名に使用することが可能です。
Holiday( `when` == "july" )
rule "validate holiday by eval"
dialect "mvel"
when
h1 : Holiday( )
eval( h1.when == "july" )
then
System.out.println(h1.name + ":" + h1.when);
end
rule "validate holiday"
dialect "mvel"
when
h1 : Holiday( `when` == "july" )
then
System.out.println(h1.name + ":" + h1.when);
end
4.3. コメント
rule engine によって無視されるテキストの部分のことです。セマンティックコードブロック 内にコメントがある場合以外は、コメントが検出されると、ルールの右側のように無視されます。コメントには 単一行コメント と 複数行コメント の 2 種類があります。

図4.1 単一行コメント
# または // のいずれかを使用して単一行コメントを作成します。パーサーは行のコメント記号後の内容をすべて無視します。
rule "Testing Comments"
when
# this is a single line comment
// this is also a single line comment
eval( true ) # this is a comment in the same line of a pattern
then
// this is a comment inside a semantic code block
# this is another comment in a semantic code block
end

図4.2 複数行コメント
rule "Test Multi-line Comments"
when
/* this is a multi-line comment
in the left hand side of a rule */
eval( true )
then
/* and this is a multi-line comment
in the right hand side of a rule */
end
4.4. エラーメッセージ

図4.3 エラーメッセージの形式
- 最初のブロック
- この領域はエラーコードを特定します。
- 2 番目のブロック
- この領域には行および列の情報が表示されます。
- 3 番目のブロック
- この行行には問題を説明するテキストが表示されます。
- 4 番目のブロック
- 最初のコンテキストです。通常、エラーが発生したルール、関数、テンプレート、またはクエリを示します (このブロックは必須ではありません)。
- 5 番目のブロック
- エラーが発生したパターンを特定します (このブロックは必須ではありません)。
4.4.1. 101: No viable alternative
rule one
when
exists Foo()
exits Bar()
then
end
[ERR 101] Line 4:4 no viable alternative at input 'exits' in rule one
exits != exists であるため、実際は有効ではありません。
package org.drools;
rule
when
Object()
then
System.out.println("A RHS");
end
[ERR 101] Line 3:2 no viable alternative at input 'WHEN'
rule simple_rule
when
Student( name == "Andy )
then
end
[ERR 101] Line 0:-1 no viable alternative at input '<eof>' in rule simple_rule in pattern Student
注記
0:-1 の位置を生成する場合があります。この場合、引用符、アポストロフィ、および括弧がすべて閉じられているか確認してください。
4.4.2. 102: Mismatched input
rule simple_rule
when
foo3 : Bar(
[ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule simple_rule in pattern Bar
注記
0:-1 位置は parser がソースの最後に到達したことを意味します。
package org.drools;
rule "Avoid NPE on wrong syntax"
when
not(Cheese((type=="stilton",price==10)||(type=="brie",price==15))
from $cheeseList)
then
System.out.println("OK");
end
[ERR 102] Line 5:36 mismatched input ',' expecting ')' in rule
"Avoid NPE on wrong syntax" in pattern Cheese
[ERR 101] Line 5:57 no viable alternative at input 'type' in
rule "Avoid NPE on wrong syntax"
[ERR 102] Line 5:106 mismatched input ')' expecting 'then' in
rule "Avoid NPE on wrong syntax",) を && 演算子に置き換えます。
注記
4.4.3. 103: Failed predicate
false として評価されたことを意味します。通常、これらのセマンティック述語は「ソフト」キーワードを識別するために使用されます。次の例でも同様です。
package nesting;
dialect "mvel"
import org.drools.Person
import org.drools.Address
fdsfdsfds
rule "test something"
when
p: Person( name=="Michael" )
then
p.name = "other";
System.out.println(p.name);
end
[ERR 103] Line 7:0 rule 'rule_key' failed predicate:
{(validateIdentifierKey(DroolsSoftKeywords.RULE))}? in rulefdsfdsfds は無効なテキストで、parser はそれをソフトキーワードの rule として識別できません。
注記
102: Mismatched input と大変よく似ていますが、通常はソフトキーワードが関係します。
4.4.4. 104: Trailing semi-colon not allowed
rule simple_rule
when
eval(abc();)
then
end
[ERR 104] Line 3:4 trailing semi-colon not allowed in rule simple_rule
4.4.5. 105: Early Exit
recognizer がどの代替とも一致しない文法 (grammar) のサブルールを検出すると発生します。これは、parser が出口のないブランチに入ったことを意味します。この状態を表す例は次のようになります。
template test_error
aa s 11;
end
[ERR 105] Line 2:2 required (...)+ loop did not match anything at input 'aa' in template test_error
4.5. パッケージ

図4.4 package
注記
4.5.1. import

図4.5 import
java.lang パッケージより自動的にクラスをインポートします。
4.5.2. global

図4.6 global
- ルールファイルにグローバル変数を宣言し、ルールで使用します。例は次の通りです。
global java.util.List myGlobalList; rule "Using a global" when eval( true ) then myGlobalList.add( "Hello World" ); end - ワーキングメモリーにグローバル値を設定します。ワーキングメモリーにファクトをアサートする前にすべてのグローバル値を設定することが推奨されます。例は次の通りです。
List list = new ArrayList(); WorkingMemory wm = rulebase.newStatefulSession(); wm.setGlobal( "myGlobalList", list );
4.6. 関数

図4.7 関数
helper クラス以上のことは実行できませんが (実際にコンパイラーが「背後」で helper クラスを生成します)、ロジックをすべて 1 つの場所に集められることが主な利点となります。また、必要に応じて関数を変更することもできます (これには良い面と悪い面の両方があります)。
function String hello(String name) {
return "Hello "+name+"!";
}
注記
function キーワードが使用されます。関数にとってパラメーターは通常のメソッドと同様です (また、必要でない場合はパラメーターを使用する必要はありません)。戻り値 は通常のメソッドと同様です。
Foo.hello() のように helper クラスで静的メソッドを使用することもできます。JBoss Rules では 関数インポート を使用できます。次のコード例は使用方法を表しています。
import function my.package.Foo.hello
rule "using a static function"
when
eval( true )
then
System.out.println( hello( "Bob" ) );
end
4.7. タイプ宣言

図4.8 meta_data

図4.9 type_declaration
- 新しいタイプの宣言: JBoss Rules はそのままの状態でプレーン POJO をファクトとして動作します。Java などの下位言語でモデルを作成することを懸念せずに直接モデルをルールエンジンへ定義したい時があります。また、ドメインモデルが既に構築されている状態で、主に理由付け処理中に使用される追加エンティティーを用いてこのモデルを補完したい場合や補完する必要がある場合もあります。
- メタデータの宣言: ファクトに、関連するメタ情報があることがあります。メタ情報の例には、ファクト属性によって表されず、そのファクトタイプのすべてのインスタンス間で一貫するあらゆる種類のデータが含まれます。メタ情報はエンジンによってランタイム時にクエリされ、理由付け処理で使用されることがあります。
4.7.1. 新しいタイプの宣言
例4.1 新しいファクトタイプの宣言: Address
declare Address number : int streetName : String city : String end
例4.2 新しいファクトタイプ Person の宣言
declare Person name : String dateOfBirth : java.util.Date address : Address end
java.util.Date タイプで、address は以前に定義されたファクトタイプ Address です。
例4.3 import を使用して完全修飾クラス名の使用を省略する
import java.util.Date declare Person name : String dateOfBirth : Date address : Address end
例4.4 以前の Person ファクトタイプ宣言に対して生成された Java クラス
public class Person implements Serializable { private String name; private java.util.Date dateOfBirth; private Address address; // getters and setters // equals/hashCode // toString }
例4.5 ルールで宣言されたタイプの使用
rule "Using a declared Type" when $p : Person( name == "Bob" ) then System.out.println( "The name of the person is "+ ) // lets insert Mark, that is Bob's mate Person mark = new Person(); mark.setName("Mark"); insert( mark ); end
4.7.2. メタデータの宣言
@matadata_key( metadata_value )
例4.6 任意のメタデータ属性に宣言
@author( Bob )
例4.7 ファクトタイプと属性に対するメタデータ属性の宣言
import java.util.Date declare Person @author( Bob ) @dateOfCreation( 01-Feb-2009 ) name : String @key @maxLength( 30 ) dateOfBirth : Date address : Address end
4.7.3. 既存タイプに対するメタデータの宣言
例4.8 既存のタイプに対するメタデータの宣言
import org.drools.examples.Person declare Person @author( Bob ) @dateOfCreation( 01-Feb-2009 ) end
例4.9 完全修飾クラス名を使用したメタデータの宣言
declare org.drools.examples.Person @author( Bob ) @dateOfCreation( 01-Feb-2009 ) end
4.7.4. アプリケーションコードからの宣言されたタイプへのアクセス
例4.10 org.drools.examples パッケージでのタイプの宣言
package org.drools.examples import java.util.Date declare Person name : String dateOfBirth : Date address : Address end
例4.11 API を介した宣言されたファクトタイプの処理
// get a reference to a knowledge base with a declared type:
KnowledgeBase kbase = ...
// get the declared FactType
FactType personType = kbase.getFactType( "org.drools.examples",
"Person" );
// handle the type as necessary:
// create instances:
Object bob = personType.newInstance();
// set attributes values
personType.set( bob,
"name",
"Bob" );
personType.set( bob,
"age",
42 );
// insert fact into a session
StatefulKnowledgeSession ksession = ...
ksession.insert( bob );
ksession.fireAllRules();
// read attributes
String name = personType.get( bob, "name" );
int age = personType.get( bob, "age" );
4.8. ルール

図4.10 ルール
例4.12 ルール構文の概要
rule "<name>"
<attribute>*
when
<conditional element>*
then
<action>*
end例4.13 単純なルール
rule "Approve if not rejected"
salience -100
agenda-group "approval"
when
not Rejection()
p : Policy(approved == false, policyState:status)
exists Driver(age > 25)
Process(status == policyState)
then
log("APPROVED: due to no objections.");
p.setApproved(true);
end注記
4.8.1. ルール属性
Ruleflow など複雑なサブシステムの一部であるものもあります。JBoss Rules を活用するには、本項を読んで各属性をよく理解するとよいでしょう。

図4.11 ルール属性
表4.1 ルール属性
| 属性 | デフォルト値 | タイプ | コメント |
|---|---|---|---|
| no-loop |
false
| ブール値 |
ルールの結果がファクトを変更すると、ルールが再度アクティベートされることがあり、再帰が行われる原因となります。no-loop を
true に設定して、現在のデータセットに対する activation の作成が無視されるようにします。
|
| ruleflow-group |
N/A
| 文字列 |
ルールフロー 機能を使用して、ルールの実行を制御します (同じ ruleflow-group 識別子によってアセンブルされたルールはグループがアクティブな時のみ実行されます)。
|
| lock-on-active |
false
| ブール値 |
uleflow-group がアクティブになるか、agenda-group がフォーカスを受けると、lock-on-active が true に設定されたそのグループ内のルールがアクティブでなくなります。更新元に関係なく、一致するルールのアクティベーションは破棄されます。これは、変更がルール自体のみによって引き起こされるわけではないため、no-loop の強いバージョンになります。これは、ファクトを変更する複数のルールが存在し、ルールの再照合や再実行を希望しない場合の計算ルールに最適です。ruleflow-group がアクティブでなくなるか、agenda-group がフォーカスを失った時のみ、lock-on-active が true に設定されたルールがアジェンダに配置されるアクティベーションに対して再び有効になります。
|
| salience |
0
| 整数 |
各ルールは整数を割り当てることができる salience 属性を持ちます。デフォルトはゼロです。salience は優先度の形式で、
activation queue で順序付けされる時に大きい値を持つルールに高い優先度が割り当てられます。
|
| agenda-group |
MAIN
| 文字列 |
アジェンダグループを用いるとアジェンダを分割できるため、実行制御を強化できます。フォーカスを取得したアジェンダグループのルールのみ実行が許可されます。
|
| auto-focus |
false
| ブール値 | auto-focus 値が true のルールがアクティベートされた場合や、ルールの agenda グループがフォーカスを取得していない場合、フォーカスがグループに与えられ、ルールを実行できるようになります。
|
| activation-group |
N/A
| 文字列 |
この文字列は同じ
activation グループに属するルールを識別します。このようなグループのルールはお互いを排他的に実行します。つまり、実行する activation グループの最初のルールは他のルールのアクティベーションをキャンセルし、実行を阻止します。
注記
これは以前 Xor グループと呼ばれていましたが、厳密には Xor の定義を満たしていません。
|
| dialect |
パッケージの指定通り
|
文字列で、可能な値は
java と mvel
|
この属性を使用して、左側または右側のいずれかにあるコード式に使用される言語を指定します。現在、Java と MVFLEX 式言語 の 2 つのダイアレクトを使用できます (ダイアレクトはパッケージレベルで指定できますが、この属性を使用すると、ルールのパッケージ定義を上書きできます)。
|
| date-effective |
N/A
|
日付と時間の定義が含まれる文字列
|
この属性で設定されたタイムスタンプよりも現在の日時が後であることを示す場合にのみルールをアクティベートできます。
|
| date-expires |
N/A
|
日付と時間の定義が含まれる文字列
|
この属性で設定されたタイムスタンプよりも現在の日時が後であるとルールをアクティベートできません。
|
| duration |
デフォルト値なし
|
long
|
この属性を使用すると、指定された期間後に属性が
true のままであった場合にルールが実行されます。
|
rule "my rule" salience 42 agenda-group "number 1" when ...
4.8.2. タイマーとカレンダー
例4.14 timer 属性の使用例
timer ( int: <initial delay> <repeat interval>? ) timer ( int: 30s ) timer ( int: 30s 5m ) timer ( cron: <cron expression> ) timer ( cron:* 0/15 * * * ? )
例4.15 cron の例
rule "Send SMS every 15 minutes"
timer (cron:* 0/15 * * * ?)
when
$a : Alarm( on == true )
then
channels[ "sms" ].insert( new Sms( $a.mobileNumber, "The alarm is still on" ));
end例4.16 クオーツ式カレンダーの適応
Calendar weekDayCal = QuartzHelper.quartzCalendarAdapter(org.quartz.Calendar quartzCal)
例4.17 カレンダーの登録
ksession.getCalendars().set( "week day", weekDayCal );
例4.18 カレンダーとタイマーを一緒に使用
rule "weekdays are high priority"
calendars "weekday"
timer (int:0 1h)
when
Alarm()
then
send( "priority high - we have an alarm” );
end
rule "weekend are low priority"
calendars "weekend"
timer (int:0 4h)
when
Alarm()
then
send( "priority low - we have an alarm” );
end4.8.3. 左側条件要素
eval(true) と書き換えられます。そのため、ルールの条件は常に true のままになります)。
working memory セッションが作成された時に一度だけアクティベートされます。

図4.12 左側
conditional element のないルールになります。
rule "no CEs"
when
then
<action>*
end
rule "no CEs"
when
eval( true )
then
<action>*
end
and で、ルールの左側に複数の完全に無関係なパターンがある場合に暗示されます。
注記
or パターンとは異なり、and は 主導宣言バインディング を持つことができません。これは、宣言は 1 つのファクトへのみ参照でき、 and が満たされると複数のファクトを照合するため、どのファクトへバインドするのか分からなくなるためです。
4.8.3.1. パターン
conditional element の最も重要な型です。以下のエンティティー関係図はパターンの制約を構成するさまざまな部分の概要と、これらの部分がどのように連携するかを表しています。各部分の詳細は、図やコード例と共に本項の後半で取り上げます。

図4.13 パターンエンティティー関係図

図4.14 パターン
Cheese であるため、パターンは working memory の各 Cheese オブジェクトに対して照合されます。
注記
Cheese( )
$c などのパターンバインディング変数を使用して一致するオブジェクトを参照します。$ プレフィックスの使用は任意ですが、このプレフィックスを使用すると変数をフィールドを区別しやすくなるため、複雑なルールに対処するする場合に便利です。
$c : Cheese( )
,、&&、|| のいずれかを使用して制約を区切ります。ただし、これらの記号は若干ことなる機能を持つことに注意してください。

図4.15 制約

図4.16 制約

図4.17 constraintGroup
,) は制約グループを区切るために使用されます。これは暗黙的で接続的な意味論を持ちます。
# Cheese type is stilton and price < 10 and age is mature. Cheese( type == "stilton", price < 10, age == "mature" )
type == "stilton"の通り、型は stilton です。price < 10の通り、価格 (price) は 10 未満です。age == "mature"の通り、熟成度 (age) は mature です。
&& と || を用いると、グループは複数の制約を持つことができます。例は次の通りです。
// Cheese type is "stilton" and price < 10, and age is mature Cheese( type == "stilton" && price < 10, age == "mature" ) // Cheese type is "stilton" or price < 10, and age is mature Cheese( type == "stilton" || price < 10, age == "mature" )
- &&
- ||
- ,
# Cheese type is stilton and ( price is less than 20 or age is mature ). Cheese( type == "stilton" && ( price < 20 || age == "mature" ) )
|| は && の前に評価されるようになります。
&& と , は同じ意味論を持ちますが、異なる優先度で解決されることに注意してください。そのため、複合制約式に , を組み込むことはできません。
// invalid as ',' cannot be embedded in an expression: Cheese( ( type == "stilton", price < 10 ) || age == "mature" ) // valid as '&&' can be embedded in an expression: Cheese( ( type == "stilton" && price < 10 ) || age == "mature")
4.8.3.1.1. フィールド制約

図4.18 fieldConstraint
- 単一値制限
- 複合値制限
- 複数制限

図4.19 制限
4.8.3.1.2. ファクトとしての Java Bean
getXXX メソッドか isXXX メソッドを使用してフィールドが公開されます。この場合、これらのメソッドは引数を取りませんが、何らかの値を返します。
getType は type としてアクセスされ、JBoss Rules は標準的な Java Development Kit の Introspector クラスを使用してこのマッピング処理を実行します)。
Cheese クラスの例では、Cheese(type == "brie") パターンは getType() メソッドを Cheese インスタンスへ適用します。フィールド名が見つからない場合、コンパイラーは最終的に引数のないメソッドとして名前を使用します。そのため、Cheese(toString == "cheddar") 制約により toString() が呼び出されます。この場合、大文字と小文字を正しく区別したメソッドの完全名を使用しますが、括弧は省略します。必ず、アクセスされるメソッドがパラメーターを取らず、これらのメソッドはルールに影響する方法でオブジェクトの状態を変更しない accessors であるようにしてください (パフォーマンス上の理由で、呼び出し間の照合の結果を rule engine がキャッシュすることに注意してください)。
4.8.3.1.3. 値
- リテラル
- qualifiedIdentifiers (列挙値)
- 変数
- returnValues

図4.20 リテラル

図4.21 qualifiedIdentifier

図4.22 変数

図4.23 returnValue
== と != を使用します。リテラルの null キーワード (Cheese(type != null) のように使用) は、evaluator が例外をスローしない場合、値が null であると true を返します。
evaluator に ten を文字列として提供すると、例外が発生しますが、10 を提供すると数値の 10 に強制します (強制は常に値型ではなくフィールド型を優先します)。
4.8.3.1.4. 単一値制限

図4.24 singleValueRestriction
4.8.3.1.5. 演算子

図4.25 演算子
== および != 演算子はすべての型で有効です。型値が順序付けされている時は他のリレーショナル演算子を使用することもできます。データフィールドでは < は 「前」を意味します。 matches と not matches のペアは文字列フィールドへのみ適用し、contains と not contains には Collection 型のフィールドが必要になります (evaluator とフィールドに対して正しい値を強制しようとします)。
matches演算子- これは有効な Java 正規表現があるフィールドと一致します。正規表現は通常は文字列リテラルですが、有効な正規表現を解決する変数を使用することもできます。
重要
Java とは異なり、文字列リテラルとして書かれる正規表現内にエスケープは必要ありません。Cheese( type matches "(Buffalo)?\S*Mozarella" )
not matches演算子- この演算子は文字列が正規表現と一致しない時に
trueを返します。matches演算子でも同じルールが適用されます。使用例は次の通りです。Cheese( type not matches "(Buffulo)?\S*Mozerella" )
contains演算子- この演算子を使用して、 collection または array フィールドに指定された値が含まれていることを確認します。
CheeseCounter( cheeses contains "stilton" ) // contains with a String literal CheeseCounter( cheeses contains $var ) // contains with a variable
not contains演算子- この演算子を使用して、collection または array フィールドに指定された値がないことを確認します。
CheeseCounter( cheeses not contains "cheddar" ) // not contains with a String literal CheeseCounter( cheeses not contains $var ) // not contains with a variable
注記
後方互換性を維持するため、excludes演算子もサポートされています。この演算子はnot containsと同等です。 memberOf演算子- この演算子を使用してフィールドが collection または array のメンバーであるかどうかを確認します。コレクションは変数である必要があります。
CheeseCounter( cheese memberOf $matureCheeses )
not memberOf演算子- この演算子を使用して、フィールドが collection または array のメンバーでないかどうかを確認します。コレクションは変数である必要があります。
CheeseCounter( cheese not memberOf $matureCheeses )
soundslike演算子- この演算子は
matchesと似ていますが、言葉が指定値と似た発音であるかどうかを確認します。Soundex アルゴリズムを使用し、英語の発音を基にして言葉の発音を確認します。// match cheese "fubar" or "foobar" Cheese( name soundslike 'foobar' )
4.8.3.1.6. リテラル制限

図4.26 literalRestriction
- 数値
- 標準的な Java 数値プリミティブがすべてサポートされます。
Cheese( quantity == 5 )
- 日付
- デフォルトの日付形式は
dd-mmm-yyyyです。これを変更するには、drools.dateformat プロパティーの別の日付形式マスクを提供します (制御を強化する必要がある場合は、inline-eval制約を使用します)。Cheese( bestBefore < "27-Oct-2013" )
- 文字列
- 有効な Java 文字列を使用します。
Cheese( type == "stilton" )
- ブール値
trueかfalseのみを使用できます。0 と 1 は使用できません。単一のブール値フィールド (例Cheese( smelly )) は許可されません。ブール値リテラルの比較対象となる必要があります。Cheese( smelly == true )
- 修飾識別子
- Java Development Kit 1.4 および 1.5 形式の
enumsはサポートされますが、1.5 は JDK 5 の環境でのみ使用可能です。Cheese( smelly == SomeClass.TRUE )
4.8.3.1.7. バインドされた変数制限

図4.27 variableRestriction
fied constraints で使用することが可能です。バインドされた変数は 宣言 と呼ばれます。制約されるフィールドの型によって有効な演算子が決定されます。可能な場合、強制が試行されます。パフォーマンスを高速化するには、== 演算子を使用して変数制限をバインドします。
Person( likes : favouriteCheese ) Cheese( type == likes )
Person インスタンスの favouriteCheese フィールドへ likes 変数がバインドされます (次のパターンで Cheese の型を制約します)。有効な Java 変数名を使用することができ、フィールドと宣言を区別するために使用される $ プレフィックスを使用できます。
$stilton の宣言を表しています。contains 演算子と共に使用されます (任意の $ の使用方法に注目してください)。
$stilton : Cheese( type == "stilton" ) Cheesery( cheeses contains $stilton )
4.8.3.1.8. 戻り値制限

図4.28 returnValueRestriction
Person( girlAge : age, sex == "F" ) Person( age == ( girlAge + 2) ), sex == 'M' )
4.8.3.1.9. 複合値制限
in および not in evaluators のみがサポートしています)。
evaluators は実際は「シンタクティックシュガー」で、!= および == 演算子を使用して複数制限のリストとして内部で書き直されます。

図4.29 compoundValueRestriction
Person( $cheese : favouriteCheese ) Cheese( type in ( "stilton", "cheddar", $cheese )
4.8.3.1.10. 複数制限
&& または || 分離記号を使用)。括弧を用いたグループ化は許可され、結果的に再帰的な構文パターンになります。

図4.30 multiRestriction

図4.31 restrictionGroup
// Simple multi restriction using a single &&
Person( age > 30 && < 40 )
// Complex multi restriction using groupings of multi restrictions
Person( age ( (> 30 && < 40) ||
(> 20 && < 25) ) )
// Mixing muti restrictions with constraint connectives
Person( age > 30 && < 40 || location == "london" )
4.8.3.1.11. インライン評価制約

図4.32 インライン評価式
注記
age 変数は auto-vivification によって 2 つ目のパターンで自動作成されます。
Person( girlAge : age, sex = "F" ) Person( eval( age == girlAge + 2 ), sex = 'M' )
4.8.3.1.12. ネストされたアクセサー
inline-eval 制約として書き換えられます)。
警告
working memory はネストされた値を認識せず、ネストされた値がいつ変更するかも認識しないため、nested accessors を使用する時は十分注意してください。親参照が working memory に存在する間は常に不変の値として見なしてください。
modify とその ブロックセッター を使用し、ネストされたアクセサーの割り当てを書くことができます。この場合、ルートの親オブジェクトを必要に応じて取り消したり挿入したりすることができます (Nested accessors は演算子記号のどちらの側でも使用できます)。
// Find a pet older than its owners first-born child $p : Person( ) Pet( owner == $p, age > $p.children[0].age )
inline eval として書き換えられます。
// Find a pet older than its owners first-born child $p : Person( ) Pet( owner == $p, eval( age > $p.children[0].age ) )
警告
nested accessors は direct field accesses よりもパフォーマンスへの影響がかなり大きいため、注意して使用してください。
4.8.3.2. and 条件要素
and conditional element を使用して他の conditional elements を論理積にグループ化します。
root element は暗黙的な and プレフィックスです。これは指定する必要はありません。JBoss Rules は and をプレフィックスとインフィックスの両方としてサポートしますが、暗黙的なグループ化により混乱を避けることができるため、プレフィックスの使用が推奨されます。

図4.33 prefixAnd
(and Cheese( cheeseType : type ) Person( favouriteCheese == cheeseType ) )
when Cheese( cheeseType : type ) Person( favouriteCheese == cheeseType )
and インフィックスと括弧による明示的なグループ化がサポートされます。
注記
and の代わりに && 記号を使用できます。この記号は廃止されましたが、レガシーサポートの理由で現在でも使用可能です。

図4.34 infixAnd
//infixAnd Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType ) //infixAnd with grouping ( Cheese( cheeseType : type ) and ( Person( favouriteCheese == cheeseType ) or Person( favouriteCheese == cheeseType ) )
4.8.3.3. or 条件要素
or conditional element を使用して他の conditional element を論理和にグループ化します。
注記
or をプレフィックスまたはインフィックスとして使用できますが、暗黙的なグループ化により混乱を避けることができるため、プレフィックスの使用が推奨されます。
conditional element の挙動は、フィールド制約や制限に対する結合記号 || とは異なります。engine は実際には or を理解しません。複数の異なる論理変換を使用することにより、or を使用するルールは複数のサブルールとして書き換えられます。この結果、ルールは単一の root node である or と、各 conditional elements に対する 1 つのサブルールを持ちます。各サブルールは通常のルールと同様にアクティベートおよび実行できます。特別な挙動や対話はありませんが、それが新しい開発者が混乱する原因となることがあります。

図4.35 prefixOr
(or Person( sex == "f", age > 60 ) Person( sex == "m", age > 65 )
or インフィックスと括弧による暗示的なグループ化がサポートされます。
注記
or の代わりに || 記号を使用できます。この記号は廃止されましたが、レガシーサポートの理由で現在でも使用可能です。

図4.36 infixOr
//infixOr Cheese( cheeseType : type ) or Person( favouriteCheese == cheeseType ) //infixOr with grouping ( Cheese( cheeseType : type ) or ( Person( favouriteCheese == cheeseType ) and Person( favouriteCheese == cheeseType ) )
or を用いて pattern binding を使用することもできます。これは、結果となる各サブルールがパターンにバインドされることを意味します。次の例のように、eponymous 変数を使用して各パターンを別々にバインドする必要があります。
(or pensioner : Person( sex == "f", age > 60 ) pensioner : Person( sex == "m", age > 65 ) )
or を使用すると、可能な結果ごとに 1 つサブルールが作成されます。上記の単純な例では、2 つのルールが生成されます。これらのルールは working memory 内で独立して機能するため、両方とも照合、アクティベートおよび実行することが可能です。ショートカットはありません。
or を 2 つ以上の同様のルールを生成する方法と考えると分かりやすいかもしれません。2 つ以上の項の論理和が true の場合、単一のルールが複数のアクティベーションを持つことがあります。
4.8.3.4. eval 条件要素

図4.37 eval
eval conditional element はプリミティブなブール値を返す意味論コードを実行できるようにする「catch-all」です。このコードはルールの左側にバインドされた変数か、ルールパッケージの関数を参照できます。
警告
eval を使用しすぎないようにしてください。ルールの宣言性が低下し、engine のパフォーマンスが低下することがあります。eval はパターンのどこにでも使用できますが、ルールの左側で最後の条件要素として追加することが推奨されます。
Evals に索引を付けることはできません。そのため、フィールド制約 ほど効率的ではありません。しかし、時間とともに変化する値を返す関数として使用することに適しています (フィールド制約にはこの機能はありません)。
p1 : Parameter() p2 : Parameter() eval( p1.getList().containsKey(p2.getItem()) ) // call function isValid in the LHS eval( isValid(p1, p2) )
4.8.3.5. not 条件要素

図4.38 not
not conditional element は、1 階論理の 非存在数量詞 (non-existential quantifier) です。この目的は、working memory に存在しないものを確認することです。
not キーワードは、括弧で囲まれた conditional element の前に指定する必要があります (最も単純なユースケースでは、括弧を省略することが可能です)。
not Bus()
// Brackets are optional: not Bus(color == "red") // Brackets are optional: not ( Bus(color == "red", number == 42) ) // "not" with nested infix and - two patterns, // brackets are requires: not ( Bus(color == "red") and Bus(color == "blue") )
4.8.3.6. exists 条件要素

図4.39 exists
exists conditional element は 1 階論理 の 存在数量詞 (existential quantifier) です。この目的は、working memory に存在するものを確認することです。exists を「少なくとも 1 つ存在する」という意味で考えるプログラマーもいます (「それぞれに対する」のように独自のパターンを持つこととは異なります)。
exists が使用されると、その条件と一致する working memory のデータの量に関係なく、ルールは一度だけアクティベートされます。存在のみが非常に重要となるため、バインディングは確立されません。
exists キーワードは、適用する conditional elements の前に指定する必要があります。conditional elements は括弧で囲まれている必要があります (以下のような最も単純な単一のパターンでは、括弧を省略することもできます)。
exists Bus()
exists Bus(color == "red") // brackets are optional: exists ( Bus(color == "red", number == 42) ) // "exists" with nested infix and, // brackets are required: exists ( Bus(color == "red") and Bus(color == "blue") )
4.8.3.7. conditional elements

図4.40 forall
forall conditional element は JBoss Rules の 1 階論理のサポートを完全にします。最初のパターンに一致するすべてのファクトが、残りの各パターンにも一致する場合、forall は true として評価します。例は次の通りです。
rule "All English buses are red"
when
forall( $bus : Bus( type == 'english')
Bus( this == $bus, color = 'red' ) )
then
# all english buses are red
end
english の各 Bus オブジェクトを選択します。そして、このパターンに一致する各ファクトに対し、後続のパターンが評価されます。後続のパターンにも一致する場合、forall conditional element は true として評価します。
rule "All Buses are Red"
when
forall( Bus( color == 'red' ) )
then
# all asserted Bus facts are red
end
rule "all employees have health and dental care programs"
when
forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp )
)
then
# all employees have health and dental care
end
not などの他の conditional elements 内にネストできます。
重要
rule "not all employees have health and dental care"
when
not ( forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp ) )
)
then
# not all employees have health and dental care
end
注記
not( forall( p1 p2 p3...)) は次のコードと同等です。
not(p1 and not(and p2 p3...))
重要
forall は 範囲の区切り文字 であることに注意してください。そのため、以前にバインドされた変数を使用できますが、内部でバインドされた変数は外部で使用することはできません。
4.8.3.8. from 条件要素

図4.41 from
from conditional element を使用して、左側のパターンによって一致されるデータの任意のソースを指定します。これを行うことにより、 engine が working memory で発見されなかったデータを推論できます。データソースはバインドされた変数上のサブフィールドや、メソッド呼び出しの結果であることがあります。
rule "validate zipcode"
when
Person( $personAddress : address )
Address( zipcode == "23920W") from $personAddress
then
# zip code is ok
end
rule "validate zipcode"
when
$p : Person( )
$a : Address( zipcode == "23920W") from $p.address
then
# zip code is ok
end
from を使用してオブジェクトのコレクションを返すこともできます。この場合、from がコレクションの各オブジェクトを繰り返し、個別に照合しようとします。以下は、注文の各商品を 10 % 割引するためのルールが含まれる例になります。
rule "apply 10% discount to all items over $ 100,00 in an order"
when
$order : Order()
$item : OrderItem( value > 100 ) from $order.items
then
# apply discount to $item
end
from を lock-on-active ルール属性と共に使用する場合、予期しない結果がもたらされることがあるため特に注意してください。次のように若干変更が加えられている前述の例を考慮してください。
rule "Assign people in Queensland (QLD) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$p : Person( )
$a : Address( state == "QLD") from $p.address
then
modify ($p) {} #Assign person to sales region 1 in a modify block
end
rule "Apply a discount to people in the city of Brisbane"
ruleflow-group "test"
lock-on-active true
when
$p : Person( )
$a : Address( city == "Brisbane") from $p.address
then
modify ($p) {} #Apply discount to person in a modify block
end
from を使用すると目的に関係なく、評価される度に新しいファクトが返されます)。
- 前述のパターンを使用する必要性について検討します。これは異なるルールフローグループ全体で多くのルールが存在するためです。ルールが
working memoryおよび該当するルールフローのダウンストリームにある他のルールを変更し、必要性を再評価しなければならない場合、modifyを使用することが重要となります。しかし、同じルールフローグループの他のルールがお互いにアクティベーションを再帰的に配置しないようにしてください。この場合、ルールがルール自体を再帰的にアクティベートできないようになるため、no-loop 属性では効果がありません。したがって、lock-on-active を使用してください。 - この問題に対応する方法はいくつかあります。
- すべてのファクトを
working memoryにアサートできる場合にfromを使用しないようにするか、制約式でネストされたオブジェクト参照を使用します (下記を参照)。 modify blockで使用するために割り当てられた変数を、左側の条件の最後にある文として配置します。- 同じルールフローグループ内のルールがお互いにアクティベーションを配置する方法を明示的に管理できる場合、lock-on-active を使用しないようにします。
この中で推奨される解決法は、working memoryへすべてのファクトを直接アサートできる場合にfromの使用を最小限に抑える方法になります。上記の例では、PersonおよびAddressインスタンスの両方をworking memoryにアサートすることが可能です。グラフは比較的単純であるため、次のようにルールを変更するとさらに簡単です。rule "Assign people in Queensland (QLD) to sales region 1" ruleflow-group "test" lock-on-active true when $p : Person(address.state == "QLD" ) then modify ($p) {} #Assign person to sales region 1 in a modify block end rule "Apply a discount to people in the city of Brisbane" ruleflow-group "test" lock-on-active true when $p : Person(address.city == "Brisbane" ) then modify ($p) {} #Apply discount to person in a modify block end - 両方のルールは想定通り実行されるようになりますが、この方法で常にネストされたファクトへアクセスできるわけではありません。
Personが 1 つまたは複数のAddressesを保持し、存在数量詞 を使用して特定条件を満たす住所を最低でも 1 つ person に一致させたい例を考えてみましょう。この場合、コレクションを推論するためfromを使用する必要があります。これを実現する方法は複数ありますが、すべてで lock-on-active 使用時の問題が発生するわけではありません。たとえば、次のようにfromを使用すると、両方のルールが想定通りに実行されます。rule "Assign people in Queensland (QLD) to sales region 1" ruleflow-group "test" lock-on-active true when $p : Person($addresses : addresses) exists (Address(state == "QLD") from $addresses) then modify ($p) {} #Assign person to sales region 1 in a modify block end rule "Apply a discount to people in the city of Brisbane" ruleflow-group "test" lock-on-active true when $p : Person($addresses : addresses) exists (Address(city == "Brisbane") from $addresses) then modify ($p) {} #Apply discount to person in a modify block endしかし、若干異なる方法では問題が発生します。rule "Assign people in Queensland (QLD) to sales region 1" ruleflow-group "test" lock-on-active true when $assessment : Assessment() $p : Person() $addresses : List() from $p.addresses exists (Address( state == "QLD") from $addresses) then modify ($assessment) {} #Modify assessment in a modify block end rule "Apply a discount to people in the city of Brisbane" ruleflow-group "test" lock-on-active true when $assessment : Assessment() $p : Person() $addresses : List() from $p.addresses exists (Address( city == "Brisbane") from $addresses) then modify ($assessment) {} #Modify assessment in a modify block endこの場合、fromを使用すると $addresses が返されます。またこの例では、可能な解決法を示す新しいオブジェクトassessmentが導入されます。$addresses 変数が移動され、各ルールの最後の条件となると、両方のルールは想定通り実行されます。上記の例は、ルールのアクティベーションを損失せずにfromを lock-on-active と組み合わせて使用する方法を表していますが、左側の条件の配置順序に依存するという欠点があります。またこれにより、問題を起こす可能性のある条件を追跡する必要があるため、ルールの作成が極めて複雑になります。 - 代わりに、ファクトをさらに
working memoryへアサートする方がよいでしょう。この場合、人の住所がworking memoryへアサートされると、fromを使用する必要がなくなります。
注記
working memory にアサートするのは現実的ではなく、他の方法を使用する必要がある場合があります。
working memory が変更された時にお互いを再帰的にアクティベートしないようにする条件が各ルールに含まれるようにして、同じルールフローグループ内のルールがお互いをアクティベートするよう、直接管理する方法もあります。たとえば、前述の例の場合、割引が既に適用されているか確認し、適用されている場合はルールがアクティベートしないようにする条件をルールに追加できます。
4.8.3.9. collect 条件要素

図4.42 collect
collect conditional element を使用して、指定のソースまたは working memory のいずれかより取得されたオブジェクトのコレクションをルールが推定するようにします。
注記
import java.util.ArrayList
rule "Raise priority if system has more than 3 pending alarms"
when
$system : System()
$alarms : ArrayList( size >= 3 )
from collect( Alarm( system == $system, status == 'pending' ) )
then
# Raise priority, because system $system has
# 3 or more alarms pending. The pending alarms
# are $alarms.
end
working memory で該当する各システムの保留のアラームを探し、ArrayLists でグループ化します。1 つのシステムに対して 3 つ以上のアラームが見つかった場合にルールが実行されます。
collect の結果パターンは、 java.util.Collection インターフェースを実装し、引数のないデフォルトの公開コンストラクターを提供する「具体的」なクラスになります。これは java.util.Collection を実装し、これらの条件を満たす限り、ArrayList、LinkedList、HashSet、またはカスタムクラスなどの Java 使用できることを意味します。
注記
collect conditional element の前にバインドされた変数は、ソースと結果パターン両方のスコープ内にあります。このような変数を使用してこれらのパターンを制約します。しかし、collect はバインディングの scope delimiter であるため、内部のバインディングは外部では使用できません。
collect はネストされた from conditional elements を許可します。そのため、以下は collect の有効な使用例になります。
import java.util.LinkedList;
rule "Send a message to all mothers"
when
$town : Town( name == 'Paris' )
$mothers : LinkedList()
from collect(
Person( gender == 'F', children > 0 )
from $town.getPeople()
)
then
# send a message to all mothers
end
4.8.3.10. accumulate 条件要素

図4.43 accumulate
accumulate conditional element は collect よりも柔軟で強力な条件要素です。ルールはオブジェクトのコレクションにまたがって繰り返しでき、各要素に対してカスタムのアクションを実行できます。終了すると結果オブジェクトを返します。
accumulate conditional element の一般的な構文は次の通りです。
<result pattern> from accumulate(<source pattern>,
init( <init code> ),
action( <action code> ),
reverse( <reverse code> ),
result( <result expression> ) )
<source pattern>:engineが各ソースオブジェクトとの一致を試みる通常のパターンです。<init code>: 選択されたダイアレクトのコードにある意味論ブロックです。ソースオブジェクトで繰り返しする前に各タプルに対して一度実行されます。<action code>: 各ソースオブジェクトに対して実行される選択されたオブジェクトにあるコードの意味論ブロックです。<reverse code>: 選択されたダイアレクトにあるコードの任意の意味論ブロックです。存在する場合、ソースパターンと一致しなくなった各ソースオブジェクトに対して実行されます。ソースオブジェクトが変更または取り消された時にengineが減分計算を行えるよう、<action code>ブロックで実行された計算を元に戻すことが目的です。このような操作のパフォーマンスを大幅に向上します。<result expression>: すべてのソースオブジェクトが繰り返し処理された後に実行される選択されたダイアレクトの意味論の式です。<result pattern>:engineが<result expression>から返されたオブジェクトと一致しようとする通常のパターンです。一致する場合、accumulate条件要素がtrueとして評価し、engineはルールにある次の条件要素を評価します。一致しない場合、accumulate条件要素はfalseとして評価し、ルールの条件要素を評価することを停止します。
rule "Apply 10% discount to orders over US$ 100,00"
when
$order : Order()
$total : Number( doubleValue > 100 )
from accumulate( OrderItem( order == $order, $value : value ),
init( double total = 0; ),
action( total += $value; ),
reverse( total -= $value; ),
result( total ) )
then
# apply discount to $order
end
engineはworking memoryの各orderに対する init のコード を実行します。これは、 total 変数をゼロに初期化します。- 次に、該当する注文に対する
OrderItemオブジェクトすべてで繰り返し処理を行い、各オブジェクトに対してアクションを実行します (この場合、全商品の合計値が計算され、total変数に置かれます)。 result expressionに対応する値が返されます (total変数の値)。- エンジンは結果を
Numberパターンに一致しようとします。double の値が 100 よりも大きい場合にルールが実行されます。
重要
init、action、および reverse コードブロックではセミコロンをステートメントの区切り文字として使用しなければならないことに注意してください。結果は式であり、; は許可されません。他のダイアレクトを使用する場合は、常に特定の構文に従ってください。
重要
reverse code は任意ですが、update および retract の使用時にパフォーマンスが向上するため、Red Hat は reverse code の使用を強く推奨します。
accumulate conditional element を使用してソースオブジェクト上のアクションを実行します。次項の例は、カスタムオブジェクトをインスタンス化し、値を投入します。
4.8.3.10.1. Accumulate 関数
accumulate conditional element は非常に強力な CE ですが、累積関数 と呼ばれる事前定義された関数を使用する場合、特に簡単に使用することができます。累積関数は accumulate とほぼ同様に動作しますが、accumulate conditional element すべてにカスタムコードを明示的に記述する代わりに、一般的な操作に対して事前定義されたコードを使用できることが異なります。
accumulate 関数を用いて、次のように注文に割引を適用するルールをプログラミングできることを表しています。
rule "Apply 10% discount to orders over US$ 100,00" when $order : Order() $total : Number( doubleValue > 100 ) from accumulate( OrderItem( order == $order, $value : value ), sum( $value ) ) then # apply discount to $order end
sum は accumulate 関数です。名前の通り、各 OrderItem の $value の合計を計算し、結果を返します。
accumulate 関数が内蔵されています。
averageminmaxcountsum
average 関数を使用してルールを記述します。
rule "Average profit"
when
$order : Order()
$profit : Number()
from accumulate( OrderItem( order == $order, $cost : cost, $price : price )
average( 1 - $cost / $price ) )
then
# average profit for $order is $profit
end
accumulate 関数はすべて プラグ可能 です。そのため、必要な場合はカスタマイズされたドメイン固有の関数を比較的簡単に engine へ追加することが可能です。追加後、ルールは制限なしでこれらの関数を使用できます。新しい accumulate 関数を実装するには、以下の手順に従います。
org.drools.base.acumulators.AccumulateFunctionインターフェースを実装する Java クラスを作成します。- 設定ファイルに行を追加するか、システムプロパティーを設定し、
engineに新しい関数について知らせます。次の例は、average関数の実装を示しています。/* * Copyright 2007 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Created on Jun 21, 2007 */ package org.drools.base.accumulators; /** * An implementation of an accumulator capable of calculating average values * * @author etirelli * */ public class AverageAccumulateFunction implements AccumulateFunction { protected static class AverageData { public int count = 0; public double total = 0; } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#createContext() */ public Object createContext() { return new AverageData(); } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#init(java.lang.Object) */ public void init(Object context) throws Exception { AverageData data = (AverageData) context; data.count = 0; data.total = 0; } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#accumulate(java.lang.Object, * java.lang.Object) */ public void accumulate(Object context, Object value) { AverageData data = (AverageData) context; data.count++; data.total += ((Number) value).doubleValue(); } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#reverse(java.lang.Object, * java.lang.Object) */ public void reverse(Object context, Object value) throws Exception { AverageData data = (AverageData) context; data.count--; data.total -= ((Number) value).doubleValue(); } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#getResult(java.lang.Object) */ public Object getResult(Object context) throws Exception { AverageData data = (AverageData) context; return new Double( data.count == 0 ? 0 : data.total / data.count ); } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#supportsReverse() */ public boolean supportsReverse() { return true; } }統合の作業はすべてengineによって実行されるため、コードは非常に簡単です。 - 機能を
engineへプラグするには、configurationファイルへ追加します。drools.accumulate.function.average = org.drools.base.accumulators.AverageAccumulateFunction重要
必ずdrools.accumulate.function.プレフィックスを使用してください。averageは関数がルールファイルでどのように使用されるかを制御します。org.drools.base.accumulators.AverageAccumulateFunctionは関数の動作を実装するクラスの完全修飾名です。
4.8.4. 右側
4.8.4.1. 使用法
重要
working memory のデータを挿入、取り消し、または変更します。これを行うには、最初に working memory を参照せずに working memory を変更する次の 便宜的なメソッド (convenience mehtod) を利用します。
update(object, handle)を使用して、オブジェクト (左側にあるものにバインドされている) が変更されたため、ルールの「再検討」が必要になる可能性があることをengineに伝えます。update(object)を使用して、ナレッジヘルパー が必要なfacthandleをルックアップするようにします。これは、渡されたオブジェクトの ID チェックを使用して行われます (Java bean にproperty change listenersを提供する場合、engineに挿入するため、オブジェクト変更時にupdate()を呼び出す必要はありません)。insert(new Something ())を使用して、新しく作成されたオブジェクトをworking memoryに置きます。insertLogical(new Something())はinsertと似ていますが、現在実行されているルールの真実をサポートするファクトがこれ以上存在しない場合に、オブジェクトが自動的に取り消される点が異なります。retract(handle)を使用してworking memoryからオブジェクトを削除します。
convenience methods は、実際には Knowledge Helper インスタンスへショートカットを提供するマクロにすぎません。これを行うことにより、rules ファイルから working memory へのアクセスが可能になります。
drools.halt()を使用して、ルールの実行を即座に終了します。これは、現在のセッションがfireUntilHalt()で開始された時点へ制御を戻すために行います。insert(Object o)、update(Object o)、およびretract(Object o)メソッドを呼び出すこともできます (頻繁に使用されるため、オブジェクト参照なしで呼び出すことが可能です)。drools.getWorkingMemory()を使用してworking memoryオブジェクトを返します。drools.setFocus( String s)を使用して、指定されたagenda groupにフォーカスを設定します。drools.getRule().getName()を使用してルール名を返します。drools.getTuple()を使用して現在実行しているルールに一致する タプル を返します。drools.getActivation()は対応するactivationを返します (これらの呼び出しはデバッグの処理に便利です)。
Knowlege Runtime アプリケーションプログラミングインターフェースは、KnowledgeContext 型の事前定義された変数 kcontext を介して公開されます。この getKnowledgeRuntime() メソッドが KnowledgeRuntime 型のオブジェクトを送信し、多数のメソッドへのアクセスを提供します。これらのメソッドは、右側の論理をコーディングする時に便利です。
kcontext.getKnowledgeRuntime().halt()呼び出しを使用して、ルールの実行を即座に終了します。getAgenda()アクセサーを使用して、このセッションのagendaへの参照を返します。これは、さまざまなアクティベーション、アジェンダ、およびルールフローグループへのアクセスを提供します。以下ようなアジェンダグループのアクティベーションは比較的一般的な使用例になります。// give focus to the agenda group CleanUp kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus();
注記
drools.setFocus( "CleanUp" )を使用して上記を実現することも可能です。- クエリを実行するには、
getQueryResults(String query)を呼び出します。その後、「クエリ」 の通り、結果を処理することが可能です。 working memoryとagendaevent listenersを追加および削除できるevent managementに対応するためのメソッドがあります。getKnowledgeBase()メソッドを使用してKnowledgeBaseオブジェクトを返します。このオブジェクトはシステムのバックボーンで、現在のセッションの発信元です。setGlobal(...)、getGlobal(...)、およびgetGlobals()を用いてグローバルを管理します。getEnvironment()を使用してrun-timeの 環境 を返します (これはオペレーティングシステムの環境と同様です)。
4.8.4.2. modify ステートメント
fact の更新を実行する構造化された方法を提供する言語拡張です。更新操作と、オブジェクトのフィールドを変更する複数の setter 呼び出しを組み合わせます。構文スキーマは次の通りです。
modify ( <fact-expression> ) {
<expression> [ , <expression> ]*
}
重要
<fact-expression> は ファクトオブジェクト参照 を生成しなければなりません。必ず、ブロックの式リストが該当するオブジェクトの setter 呼び出しで構成されるようにしてください (これらは、コンパイラーによって自動的に事前終了される通常のオブジェクト参照を用いずに記述されます)。
rule "modify stilton"
when
$stilton : Cheese(type == "stilton")
then
modify( $stilton ){
setPrice( 20 ),
setAge( "overripe" )
}
end
4.9. クエリ

図4.44 query
KnowledgeBase に対してグローバルであるため、同じ名前のクエリを同じ RuleBase の異なるナレッジパッケージに追加しないでください。
ksession.getQueryResults("name") を使用し、"name" はクエリ名に置き換えます。これは、クエリと一致したオブジェクトを読み出せるクエリ結果のリストを返します。
例4.19 年齢が 30 を超える人を問い合わせるクエリー
query "people over the age of 30"
person : Person( age > 30 )
end例4.20 年齢が x を超え、y に住む人を問い合わせるクエリー
query "people over the age of x" (int x, String y)
person : Person( age > x, location == y )
endQueryResultsRow で、タプルの各列へアクセスするために使用します。これらの列は、バインドされた宣言名または索引の位置よりアクセスできます。
例4.21 年齢が 30 を超える人を問い合わせるクエリー
QueryResults results = ksession.getQueryResults( "people over the age of 30" );
System.out.println( "we have " + results.size() + " people over the age of 30" );
System.out.println( "These people are are over 30:" );
for ( QueryResultsRow row : results ) {
Person person = ( Person ) row.get( "person" );
System.out.println( person.getName() + "
" );
}4.10. ドメイン固有言語
4.10.1. ドメイン固有言語の使用
- ルールの作成と、
engineが操作するドメインオブジェクトを区別するレイヤーとして機能します。これは、ドメインの専門家 (ビジネスアナリストなど) によってルールが読まれ、検証される必要がある場合に便利です。DSL は実装の詳細を隠し、ルール論理に集中します。 - DSL センテンスは、ルールで繰り返し使用される条件要素と結果アクションのテンプレートとしても機能します。
- DSL の実行時、ルールエンジンは影響を受けません。DSL はコンパイル時の機能であり、特別なパーサーとトランスフォーマーを必要とします。
4.10.2. ドメイン固有言語の作成
- ドメイン固有言語の作成には、技術者とドメインの専門家の協力が必要となります。
- 最初に、大きさと複雑さを把握するために、アプリケーションが必要なルールの例を記述します。
- ルールにおける類似のステートメントと繰り返しのステートメントを特定し、変数の部分をパラメーターとしてマーク付けします。
- 言語が開発されたら、ルールをテストします。
- アプリケーションのデータモデルがデータ型をファクトとして表す場合、ルールの記述は通常容易になります。
- 行の最初に大なり記号 (">") を付けて条件要素とアクションを DRL 形式のままにすると、初期設計段階で条件とアクションに関する実装の決定を延期することができます (これはデバッグのステートメントを挿入する際にも便利です)。
- 既存の DSL 定義を再使用したり、既存の条件または結果エントリーにパラメーターを追加したりしてルールを記述することが可能です。
- DSL エントリーの数をできるだけ少なくするようにしてください。パラメーターを使用すると、似たようなルールパターンや制約に同じ DSL センテンスを適用することができます。
4.10.3. ドメイン固有言語の管理
[when]Something is {color}=Something(color=="{color}")[when]キーワードは、式のスコープを示します (ルールの LHS (左側) または RHS (右側) に有効であるかどうかなど)。- 括弧で囲まれたキーワードの後の部分は、ルールで使用する式になります。通常は自然言語の式ですが、自然言語以外の式でも可能です。
- 最初の等号
=の右部分は、式のルール言語へのマッピングになります。この文字列の形式は、宛先、RHS、または LHS によって異なります。LHS の場合、通常の LHS 構文に基づいた用語となり、RHS の場合は Java ステートメントとなることがあります。
{color} など)。次に、括弧で囲まれた名前がマッピングの右側にある場合に、これらのキャプチャーから取得された値が補間されます。最後に、DSL ルールファイルの行にある式全体と一致する値を、補間された文字列が置き換えます。
? を使用して先行する文字が任意であることを示すことができます。これにより、DSL の自然言語のフレーズの変異を解消できます。ただし、これらの式は正規表現のパターンであるため、Java のパターン構文の マジック 文字はすべてバックスラッシュ (\) でエスケープする必要があります。
注記
[when]This is "{something}" and "{another}"=Something(something=="{something}", another=="{another}")
[when]This is {also} valid=Another(something=="{also}"){ および }) を使用する必要があります。波括弧をそのまま式や右側の置換テキスト内で使用したい場合は、前にバックスラッシュ (\) を付けてエスケープする必要があります。
[then]do something= if (foo) \{ doSomething(); \}注記
{ および }) を DSL 定義の置換文字列で表示する場合は、バックスラッシュ (\) でエスケープする必要があります。
例4.22 DSL マッピングエントリー
#This is a comment to be ignored.
[when]There is a Person with name of "{name}"=Person(name=="{name}")
[when]Person is at least {age} years old and lives in "{location}"=Person(age >= {age}, location=="{location}")
[then]Log "{message}"=System.out.println("{message}");
[when]And = and例4.23 DSL マッピングエントリーの拡張
There is a Person with name of "kitty"
==> Person(name="kitty")
Person is at least 42 years old and lives in "Atlanta"
==> Person(age > 42, location="Atlanta")
Log "boo"
==> System.out.println("boo");
There is a Person with name of "Bob" and Person is at least 30 years old and lives in "Atlanta"
==> Person(name="kitty") and Person(age > 30, location="Atlanta")4.10.4. ファクトへの制約の追加
-) を DSL 式の先頭に追加することでパターンに制約を追加することができます。式がハイフンで始まる場合、式はフィールド制約と見なされ、その前にある最後のパターン行に追加されます。
Cheese クラスに type、price、age、および country のフィールドがある場合、次のように左側の条件の一部を通常の DRL ファイルに表現することが可能です。
例4.24 DRL の LHS 条件
Cheese(age < 5, price == 20, type=="stilton", country=="ch")
例4.25 制約の追加
[when]There is a Cheese with=Cheese()
[when]- age is less than {age}=age<{age}
[when]- type is '{type}'=type=='{type}'
[when]- country equal to '{country}'=country=='{country}'例4.26 制約の記述
There is a Cheese with
- age is less than 42
- type is 'stilton'- で始まる行を見つけ、制約として先行のパターンに追加し、必要な場合にコンマを挿入します。 例4.26「制約の記述」 の例では、結果となる DRL は次のようになります。
Cheese(age<42, type=='stilton')
[when][]is less than or equal to=<= [when][]is less than=< [when][]is greater than or equal to=>= [when][]is greater than=> [when][]is equal to=== [when][]equals=== [when][]There is a Cheese with=Cheese()
注記
注記
- を押した後に Ctrl+Space を押してこのリストから項目を選択します。
[when][org.drools.Cheese]- age is less than {age} のように変更します。同じことを、上記の例の全項目に対して行います。
[org.drools.Cheese] コードは、文が直上の主要な制約にのみ適用されることを表しています (この場合は There is a Cheese with)。
Cheese というクラスが存在し、Content Assistance を用いた方法で追加される場合、com.yourcompany.Something オブジェクトスコープでマーク付けされた項目のみが有効となり、リストに表示されます。これは完全に任意となります。
4.10.5. DSL および DSLR 参照
#または//始まる行はコメントとして扱われます (先頭に空白文字がある場合とない場合の両方)。#/で始まるコメント行は、デバッグオプションを要求する単語に対してスキャンされます。- 左角括弧
[で始まる行は DSL エントリー定義の最初の行と見なされます。 - 他の行はすべて先行する DSL エントリー定義に付加され、行端が空白文字に置き換えられます。
- スコープの定義
[condition]または[when][consequence]または[then][keyword](ruleまたはendなど)。キーワードは、グローバルであるかどうか (DSLR ファイル全体で認識されるなど)、エントリーのスコープを示します。
- Java クラス名として記述されたタイプ定義は括弧で囲まれます。次の部分が左括弧で始まる場合を除き、この部分は任意となります。括弧の中が空の場合は無効になります。
- DSL 式は (Java) 正規表現で構成され、任意数の組み込み 点数定義 を持ち、等号
=で終了します。変数定義は波括弧{および}で囲まれます。変数定義は変数名と 2 つの任意のアタッチメントで構成され、コロン:で区切られます。アタッチメントが 1 つある場合、変数に割り当てられるテキストを一致する正規表現となります。2 つのアタッチメントがある場合、最初のアタッチメントは GUI エディターのヒントで、2 つ目が正規表現になります。正規表現で magic である文字を式の中で文字通り使用する場合、その文字の前にバックスラッシュ\を付けてエスケープする必要があることに注意してください。 - 区切り等号の後にある行の残りの部分は、正規表現に一致するすべての DSLR テキストの置換テキストになります。これには、波括弧で囲まれた変数名など、変数参照が含まれるようにすることもできます。任意で、変数名の後に感嘆符 (
!) と変換関数を使用することもできます。置換文字列内で波括弧{および}を文字通り使用する場合、括弧の前にバックスラッシュ\を付けてエスケープする必要があることに注意してください。
#/ で始まるコメント行を使用して選択的に有効にすることができます。このコメント行には下表の単語が 1 つ以上含まれることがあります。結果となる出力は標準出力に書き込まれます。
表4.2 DSL 拡張のデバッグオプション
| 単語 | 説明 |
|---|---|
| result | 結果となる DRL テキストを行番号と共に出力します。 |
| steps | 条件および結果行の各拡張ステップを出力します。 |
| keyword | スコープ keyword を持つすべての DSL エントリーの内部表現をダンプします。 |
| when | スコープ when または * を持つすべての DSL エントリーの内部表現をダンプします。 |
| then | スコープ then または * を持つすべての DSL エントリーの内部表現をダンプします。 |
| usage | すべての DSL エントリーの使用統計を表示します。 |
# Comment: DSL examples
#/ debug: display result and usage
# keyword definition: replaces "regula" by "rule"
[keyword][]regula=rule
# conditional element: "T" or "t", "a" or "an", convert matched word
[when][][Tt]here is an? {entity:\w+}=
${entity!lc}: ${entity!ucfirst} ()
# consequence statement: convert matched word, literal braces
[then][]update {entity:\w+}=modify( ${entity!lc} )\{ \}
4.10.6. DSLR ファイルのトランスフォーメーション
- テキストがメモリーへ読み込まれます。
- 各
keywordエントリーがテキスト全体に適用されます。最初に、空白文字シーケンスを任意数の空白文字と一致するパターンに置き換え、変数定義をデフォルトの".*?または定義で提供される正規表現より作成されたキャプチャーに置き換え、キーワード定義からの正規表現が変更されます。次に、DSLR テキストにて、変更された正規表現と一致する文字列が徹底的に検索されます。さらに、変数キャプチャーに対応する一致する文字列のサブ文字列が抽出され、対応する置換テキストの変数参照を置き換えます。DSLR テキストの一致する文字列はこのテキストによって置き換えられます。 whenとthenの間にある DSLR テキストは、均一に行ごとに検索されます。thenとendの間にある DSLR テキストは、均一に行ごとに処理されます。行では、行のセクションに関係する各 DSL エントリーが、DSL ファイルに記述されている順番通りに取得されます。正規表現の部分は変更されます。空白文字は任意数の空白文字と一致するパターンによって置き換えられ、正規表現を持つ変数定義はこの正規表現を持つキャプチャーによって置き換えられます (デフォルトは.*?になります)。結果となる正規表現が行のすべてまたは一部と一致すると、一致した部分が適切に変更された置換テキストによって置き換えられます。変数参照を正規表現のキャプチャーに対応するテキストに置き換えることで置換テキストが変更されます。このテキストは、変数参照に提供される文字列変換関数に従って変更することができます。同じエントリーで定義されない変数を命名する変数参照がある場合、その名前の変数にバインドされた値をエキスパンダーが置換します (現在のルールの前にある行の 1 つに定義されている場合)。- 条件の DSLR 行がハイフンで始まる場合、拡張された結果は最後の行に挿入されます。最後の行にはパターン CE (例: 型名の後に左右の括弧が続く) が含まれる必要があります。括弧内が空白である場合、拡張された行 (有効な制約が含まれている) が挿入されます。これ以外の場合、コンマ (
,) がその前に挿入されます。結果の DSLR 行がハイフンで始まる場合、拡張された結果は最後の行に挿入されます。最後の行にはmodifyステートメントが含まれるはずで、左右の波括弧{および}で終わります。括弧内が空白である場合、拡張された行 (有効なメソッド呼び出しが含まれる) が挿入されます。これ以外の場合、コンマ (,) がその前に挿入されます。
注記
accumulate など) にテキストを挿入することは できません。最初の挿入のみ実行されることはあります (eval など)。
4.10.7. 文字列トランスフォメーション関数
表4.3 文字列トランスフォーメーション関数
| 名前 | 説明 |
|---|---|
| uc | すべての文字を大文字に変換します。 |
| lc | すべての文字を小文字に変換します。 |
| ucfirst | 最初の文字を大文字に変換し、他の文字はすべて小文字に変換します。 |
| num | すべての数字と - を文字列から抽出します。元の文字列の最後にある 2 つの数字の前に . または , がある場合、少数点が対応する場所に挿入されます。 |
| a?b/c | 文字列と文字列 a を比較し、同等であれば b と置き換え、同等でなければ c と置き換えます。ただし、c は別の a、b、c の 3 つ組である可能性もあるため、構造全体が実際にはトランスレーションテーブルになります。 |
例4.27 DSL 文字列トランスフォーメーション関数
# definitions for conditions
[when][]There is an? {entity}=${entity!lc}: {entity!ucfirst}()
[when][]- with an? {attr} greater than {amount}={attr} <= {amount!num}
[when][]- with a {what} {attr}={attr} {what!positive?>0/negative?%lt;0/zero?==0/ERROR}.dsl が拡張子して使用されます。これは、ResourceType.DSL で Knowledge Builder へ渡されます。DSL 定義を使用するファイルでは、.dslr を拡張子として使用すべきです。Knowledge Builder は ResourceType.DSLR を想定します。しかし、 IDE はファイル拡張子に依存してルールファイルを正しく認識し、ルールファイルと動作します。
KnowledgeBuilder kBuilder = new KnowledgeBuilder(); Resource dsl = ResourceFactory.newClassPathResource( dslPath, getClass() ); kBuilder.add( dsl, ResourceType.DSL ); Resource dslr = ResourceFactory.newClassPathResource( dslrPath, getClass() ); kBuilder.add( dslr, ResourceType.DSLR );
4.10.8. BRMS および IDE におけるドメイン固有言語
重要
{token} へのみデータの入力が許可されます)。
drools-ant タスクを使用するか、「XML ルール言語」 のようにコードを組み込みます。
4.11. XML ルール言語
4.11.1. XML を使用する場合
4.11.2. XML 形式
<?xml version="1.0" encoding="UTF-8"?>
<package name="com.sample"
xmlns="http://drools.org/drools-4.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://drools.org/drools-4.0 drools-4.0.xsd">
<import name="java.util.HashMap" />
<import name="org.drools.*" />
<global identifier="x" type="com.sample.X" />
<global identifier="yada" type="com.sample.Yada" />
<function return-type="void" name="myFunc">
<parameter identifier="foo" type="Bar" />
<parameter identifier="bada" type="Bing" />
<body>System.out.println("hello world");</body>
</function>
<rule name="simple_rule">
<rule-attribute name="salience" value="10" />
<rule-attribute name="no-loop" value="true" />
<rule-attribute name="agenda-group" value="agenda-group" />
<rule-attribute name="activation-group" value="activation-group" />
<lhs>
<pattern identifier="foo2" object-type="Bar" >
<or-constraint-connective>
<and-constraint-connective>
<field-constraint field-name="a">
<or-restriction-connective>
<and-restriction-connective>
<literal-restriction evaluator=">" value="60" />
<literal-restriction evaluator="<" value="70" />
</and-restriction-connective>
<and-restriction-connective>
<literal-restriction evaluator="<" value="50" />
<literal-restriction evaluator=">" value="55" />
</and-restriction-connective>
</or-restriction-connective>
</field-constraint>
<field-constraint field-name="a3">
<literal-restriction evaluator="==" value="black" />
</field-constraint>
</and-constraint-connective>
<and-constraint-connective>
<field-constraint field-name="a">
<literal-restriction evaluator="==" value="40" />
</field-constraint>
<field-constraint field-name="a3">
<literal-restriction evaluator="==" value="pink" />
</field-constraint>
</and-constraint-connective>
<and-constraint-connective>
<field-constraint field-name="a">
<literal-restriction evaluator="==" value="12"/>
</field-constraint>
<field-constraint field-name="a3">
<or-restriction-connective>
<literal-restriction evaluator="==" value="yellow"/>
<literal-restriction evaluator="==" value="blue" />
</or-restriction-connective>
</field-constraint>
</and-constraint-connective>
</or-constraint-connective>
</pattern>
<not>
<pattern object-type="Person">
<field-constraint field-name="likes">
<variable-restriction evaluator="==" identifier="type"/>
</field-constraint>
</pattern>
<exists>
<pattern object-type="Person">
<field-constraint field-name="likes">
<variable-restriction evaluator="==" identifier="type"/>
</field-constraint>
</pattern>
</exists>
</not>
<or-conditional-element>
<pattern identifier="foo3" object-type="Bar" >
<field-constraint field-name="a">
<or-restriction-connective>
<literal-restriction evaluator="==" value="3" />
<literal-restriction evaluator="==" value="4" />
</or-restriction-connective>
</field-constraint>
<field-constraint field-name="a3">
<literal-restriction evaluator="==" value="hello" />
</field-constraint>
<field-constraint field-name="a4">
<literal-restriction evaluator="==" value="null" />
</field-constraint>
</pattern>
<pattern identifier="foo4" object-type="Bar" >
<field-binding field-name="a" identifier="a4" />
<field-constraint field-name="a">
<literal-restriction evaluator="!=" value="4" />
<literal-restriction evaluator="!=" value="5" />
</field-constraint>
</pattern>
</or-conditional-element>
<pattern identifier="foo5" object-type="Bar" >
<field-constraint field-name="b">
<or-restriction-connective>
<return-value-restriction evaluator="==" >
a4 + 1
</return-value-restriction>
<variable-restriction evaluator=">" identifier="a4" />
<qualified-identifier-restriction evaluator="==">
org.drools.Bar.BAR_ENUM_VALUE
</qualified-identifier-restriction>
</or-restriction-connective>
</field-constraint>
</pattern>
<pattern identifier="foo6" object-type="Bar" >
<field-binding field-name="a" identifier="a4" />
<field-constraint field-name="b">
<literal-restriction evaluator="==" value="6" />
</field-constraint>
</pattern>
</lhs>
<rhs>
if ( a == b ) {
assert( foo3 );
} else {
retract( foo4 );
}
System.out.println( a4 );
</rhs>
</rule>
</package>import 要素はルールで使用したい型をインポートします。
global 要素はルールで参照できるグローバルオブジェクトを定義します。
function にはルールで使用される関数のための関数宣言が含まれます。戻り型、一意の名前、およびパラメーターをコードのスニペットのボディーに指定する必要があります。
例4.28 rule 要素の詳細
<rule name="simple_rule">
<rule-attribute name="salience" value="10" />
<rule-attribute name="no-loop" value="true" />
<rule-attribute name="agenda-group" value="agenda-group" />
<rule-attribute name="activation-group" value="activation-group" />
<lhs>
<pattern identifier="cheese" object-type="Cheese">
<from>
<accumulate>
<pattern object-type="Person"></pattern>
<init>
int total = 0;
</init>
<action>
total += $cheese.getPrice();
</action>
<result>
new Integer( total ) );
</result>
</accumulate>
</from>
</pattern>
<pattern identifier="max" object-type="Number">
<from>
<accumulate>
<pattern identifier="cheese" object-type="Cheese"></pattern>
<external-function evaluator="max" expression="$price"/>
</accumulate>
</from>
</pattern>
</lhs>
<rhs>
list1.add( $cheese );
</rhs>
</rule>4.11.3. 形式 (XML と DRL) 間の自動転換
XmlDumper - XML のエキスポート。 DrlDumper - DRL のエキスポート DrlParser - DRL の読み取り。 XmlPackageReader - XML の読み取り。
第5章 スプレッドシートのデシジョンテーブルの使用
CSV や .XLS などのスプレッドシート形式でルールを保存し、ルールを管理できるようにします。
5.1. デシジョンテーブルを使用する場合
5.2. 概要

図5.1 Excel を使用してデシジョンテーブルを編集

図5.2 ルール行の複数のアクション

図5.3 OpenOffice.org Calc の使用
注記
注記
重要
agenda を消去し、最初の一致が存在する場所に大変単純なデシジョンテーブルをシミュレートすることが可能です。デシジョンテーブルは基本的に DRL パッケージを自動的に生成するツールです。
注記

図5.4 複数のテーブルを使用して同様のルールをグループ化する実例
5.3. デシジョンテーブルの仕組み
重要

図5.5 行と列
注記

図5.6 ルールテンプレートの展開
RuleSet キーワードは、ルールがすべてグループ化される rule package で使用される名前を示します (名前は任意です。デフォルト値もありますが、RuleSet キーワードが存在する必要があります)。C 列に表示される他のキーワードは Import と Sequential です。これらについては本章で後ほど説明します。この段階では、通常、キーワードが名前と値のペアを作成します。
RuleTable は重要になります。
RuleTable キーワードの後には名前があります。この名前は生成されたルール名の先頭に追加するために使用されます (一意のルール名を作成するために行番号が付加されます)。RuleTable の列はルールが開始する列を示します (左側の列は無視されます)。
CONDITION および ACTION キーワードは、下の列にあるデータがルールの LHS または RHS 部分であることを示しています (このように任意に設定できる他の属性もあります)。
Person(age=="42") (42 は 18 行から取得) が生成されます。この場合、== は暗黙的で、フィールド名のみを指定すると完全一致の検索と見なされます。
注記
ObjectType の宣言は複数の列にまたがることができます (マージされたセルにより)。そのため、マージされた範囲の下にあるすべての列が 1 つの制約セットにまとめられます。
$para プレースホルダーを使用して、セルからのデータが投入される場所を示します。$param または $1、$2 などを使用して下のセルにあるコンマ区切りリストからのパラメーターを示します。
RuleTables を使用できます。
RuleTable または RuleSet キーワードが表示される場所と同じである必要があります (この例では、C 列を選択しましたが、代わりに A 列を選択することも可能です)。
ObjectType 行が使用されるため)。
//row 18
rule "Cheese_fans_18"
when
Person(age=="42")
Cheese(type=="stilton")
then
list.add("Old man stilton");
end
[age=="42"] と [type=="stilton"]は上のセルにある対象の ObjectType に追加される単一の制約として解釈されます (上のセルが複数にまたがる場合は、1 つの「列」に対して複数の制約が存在することがあります)。
5.4. キーワードと構文
5.4.1. テンプレートの構文
CONDITION または ACTION であるかによって異なります。ほとんどの場合、LHS (CONDITIONの場合) または RHS (ACTION の場合) の 「vanilla」DRL と同じです。そのため、LHS では制約言語を使用する必要があり、RHS は実行目的のコードのスニペットになります。
$param プレースホルダーが使用されます。$1 を使用することも可能です。セルに値のコンマ区切りリストが含まれる場合、$1 や $2 などのシンボルを使用して、セルの値リストよりどの位置パラメーターを使用するか示すことができます。forall(DELIMITER){SNIPPET} 関数を使用して、使用できるコンマ区切りの値をすべてループすることが可能です。
テンプレートが [Foo(bar == $param)] で、セルが [ 42 ] である場合、結果は [Foo(bar == 42)] になります。 テンプレートが [Foo(bar < $1, baz == $2)] で、セルが [42,43] である場合、結果は [Foo(bar > 42, baz ==43)] になります。
ObjectType 宣言が存在するかどうかによって異なります。存在する場合、スニペットは ObjectType の個別の制約としてレンダリングされます。存在しない場合、そのままレンダリングされます (値は置換されます)。上記の例のようにプレーンフィールドのみが入力された場合、相等であると見なされます。スニペットの最後に別の演算子がある場合、制約の最後で値が補間されます。スニペットの最後に別の演算子がない場合は、前述の通り $param を探します。

図5.7 またがった列
Person ObjectType 宣言がどのように 2 つのスプレッドシート列にまたがっているかを表しています。そのため、両方の制約は Person(age == ... , type == ...) として表示されます。これまでと同様に、フィールド名のみがスニペットに存在するため、等価テストを意味します。

図5.8 パラメーターの使用
Person(age == "42") になります)。

図5.9 演算子の補完

図5.10 バインディングの使用
ObjectType 行にはどのような値でも入力することができます (たとえば、後続するスプレッドシートの列に対する事前条件など)。

図5.11 結果
5.4.2. キーワード
表5.1 キーワード
| キーワード | 説明 | 含まれる状態 |
|---|---|---|
| RuleSet | この右側のセルにはルールセット名が含まれます。 | 1 つのみ (指定しない場合はデフォルト値が使用されます) |
| Sequential | この右側のセルは true または false になります。true の場合は salience が使用され、ルールが上から順に実行されるようにします。 | 任意 |
| Import | 右側のセルには、インポートする Java クラスのコンマ区切りリストが含まれます。 | 任意 |
| RuleTable | RuleTable はルールテーブルの定義の始まりを示します (実際のルールテーブルは次の行で開始します)。ルールテーブルは次の空白行まで、左から右、そして上から下の順に読み取られます。 | 最低 1 つ。複数ある場合は、1 つのルールセットにすべてが追加されます。 |
| CONDITION | この列がルール条件用であることを示します。 | ルールテーブルごとに最低 1 つ。 |
| ACTION | この列がルール結果用であることを示します。 | ルールテーブルごとに最低 1 つ。 |
| PRIORITY | この列の値がルール行に対して salience 値を設定することを示します。Sequential フラグを上書きします。 | 任意 |
| DURATION | この列の値がルール行に対して期間の値を設定することを示します。 | 任意 |
| NAME | この列の値がその行から生成されたルールに対して名前を設定することを示します。 | 任意 |
| Functions | すぐ右側のセルには、ルールスニペットで使用できる関数を指定できます。JBoss Rules は DRL で定義された関数をサポートし、ハードコーディングせずに論理をルールに組み込んだり、論理を変更したりすることができます。使用する際は注意して使用してください。構文は通常の DRL と同じです。 | 任意 |
| Variables | すぐ右側のセルには JBoss Rules がサポートするグローバル宣言を指定できます。タイプの後に変数名を指定したものです。複数の変数が必要な場合はコンマで区切ります。 | 任意 |
| No-loop または Unloop | no-loop または unloop は、テーブルのヘッダーに指定され、ルール (行) のループを許可しない同じ関数を補完します。このオプションが適切に機能するには、セルに値 (true または false) が存在しなければなりません。セルが空白のままであると、このオプションは行に対して設定されません。 | 任意 |
| XOR-GROUP | この列のセル値は、ルール行が指定のアクティベーショングループに属することを意味します。アクティベーショングループとは、名前付きグループの 1 つのルールのみが実行されることを意味します (最初に実行されるルールが他のルールのアクティベーションをキャンセルします)。 | 任意 |
| AGENDA-GROUP | この列のセル値は、ルール行が指定のアジェンダグループに属することを意味します (ルールのグループ間のフローを制御する方法の 1 つです。「ルールフロー」を参照してください)。 | 任意 |
| RULEFLOW-GROUP | この列のセル値は、ルール行が指定のルールフローグループに属することを意味します。 | 任意 |
| Worksheet | デフォルトでは、デシジョンテーブルで最初のワークシートだけが参照されます。 | 該当なし |
HEADER キーワードのユースケースは次の通りです。ほとんどの場合でヘッダー名が最も重要になります。下のセルに値がないと、特定の行に属性が適用されません。

図5.12 キーワードの使用例
Import (コンマ区切り)、Variables (グローバル、コンマ区切り)、および function block (複数の関数で構成されることもあり、普通の DRL 構文を使用) の例になります。RuleSet キーワードと同じ列に指定したり、すべてのルール行の下に指定することもできます。

図5.13 関数などのキーワードの使用例
5.5. スプレッドシートベースのデシジョンテーブルの作成と統合
drools-decisiontables モジュールのスプレッドシートベースのデシジョンテーブルと共に使用されるアプリケーションプログラミングインターフェースを探します。1 つのクラスのみが関連しますが、そのクラスが SpreadsheetCompiler です。このクラスはさまざまな形式のスプレッドシートを取得し、通常通りに使用できる DRL ルールを生成します。
SpreadsheetCompiler を使用して部分的なルールファイルを生成し、後で完全なルールパッケージへアセンブルすることができます (これを使用してルールの技術的な側面と非技術的な側面を区別します)。
Wizard を使用してテンプレートよりスプレッドシートを生成し、XLS 対応のスプレッドシートアプリケーションを用いて編集します。

図5.14 統合開発環境の使用
5.6. デシジョンテーブルにおけるビジネスルールの管理
5.6.1. ワークフローとコラボレーション
- ビジネスアナリストがテンプレートのデシジョンテーブルを取得します (リポジトリまたは IT 部門から)。
- ビジネスアナリストがデシジョンテーブルのビジネス言語の説明をテンプレートに入力します。
- デシジョンテーブルルール (行) が入力されます (下書きとして)。
- デシジョンテーブルが、ビジネス言語 (説明) をスクリプトへマッピングするプログラマーへ渡されます (新しいアプリケーションやデータモデルの場合、ソフトウェアの開発が関係することがあります)。
- プログラマーがビジネスアナリストと共に変更を確認します。
- ビジネスアナリストは必要に応じてルール行の編集を継続して行えます (列の移動など)。
- プログラマーは、システム稼働後にルールの変更を検証するために使用するルールのテストケースを開発します。
5.6.2. スプレッドシート機能の使用

図5.15 スプレッドシートリストの使用
重要
第6章 Java ルールエンジンアプリケーションプログラミングインターフェース
6.1. はじめに
rule engine アプリケーションプログラミングインターフェース (API) を提供します。複数のルールエンジンをこの単一の API で実行することができます。本章を読んで、この API の機能について詳しく学びましょう。
注記
rule engine によって提供される機能の一部が無効になります。
globals、DRL、DSL、XML ファイルなど) へアクセスするには、property maps を使用します。これにより、移植不可能な機能が導入されることに注意してください。さらに、JSR94 はルール言語を提供しないため、ルールエンジンを切り替えても複雑さはほとんど解消されません。よって、ほとんど利点がありません。そのため、JSR94 の使用が要求される場合に Red Hat はサポートを行いますが、プログラマーは JBoss Rules API を使用することが強く推奨されます。
6.2. API の使用方法
Administrative API で、RuleExecutionSets のビルドおよび登録に使用されます。2 つ目は run-time session で、同じ RuleExecutionSets を実行します。
6.2.1. RuleExecutionSets のビルドと登録
RuleServiceProviderManager は RuleExecutionSets の登録と読み出しを管理します。JBoss Rules RuleServiceProvider 実装は、Class.forNamem を使用してクラスがロードされた時に 静的ブロック によって自動的に登録されます (これは JDBC ドライバーとほぼ同様です)。
例6.1 RuleServiceProvider の自動登録
// RuleServiceProviderImpl is registered to "http://drools.org/"
// via a static initialization block
Class.forName("org.drools.jsr94.rules.RuleServiceProviderImpl");
// Get the rule service provider from the provider manager.
RuleServiceProvider ruleServiceProvider =
RuleServiceProviderManager.getRuleServiceProvider("http://drools.org/");
RuleServiceProvider は、RuleRuntime および RuleAdministration API へのアクセスを提供します。RuleAdministration は、RuleExecutionSets を管理するための管理 API を提供します。これにより、RuleRuntime より読み出しできる RuleExecutionSet の登録が可能になります。
RuleExecutionSet を作成し登録するには、次の手順に従います。
RuleExecutionSetを作成します。RuleAdministratorは、空のLocalRuleExecutionSetProviderまたはRuleExecutionSetProviderを返すファクトリメソッドを提供します。LocalRuleExecutionSetProviderを使用して、streamなどのローカルのシリアライズ不可能なソースよりLocalRuleExecutionSetProviderをロードします。RuleExecutionSetProviderを使用して、DOM 要素やナレッジパッケージなどのシリアライズ可能なソースよりRuleExecutionSetsをロードできます。
注記
ruleAdministrator.getLocalRuleExecutionSetProvider( null ); および ruleAdministrator.getRuleExecutionSetProvider( null ); メソッドは両方 null をパラメーターとして許可します。これは、これらのメソッドの properties map は現在使用されていないからです。
例6.2 RuleAdministrator API を用いた LocalRuleExecutionSet の登録
// Get the RuleAdministration
RuleAdministrator ruleAdministrator = ruleServiceProvider.getRuleAdministrator();
LocalRuleExecutionSetProvider ruleExecutionSetProvider =
ruleAdministrator.getLocalRuleExecutionSetProvider( null );
// Create a Reader for the drl
URL drlUrl = new URL("http://mydomain.org/sources/myrules.drl");
Reader drlReader = new InputStreamReader( drlUrl.openStream() );
// Create the RuleExecutionSet for the drl
RuleExecutionSet ruleExecutionSet =
ruleExecutionSetProvider.createRuleExecutionSet( drlReader, null );
ruleExecutionSetProvider.createRuleExecutionSet( reader, null ) が properties map に対して null のパラメーターを取ります (しかし、実際は受信ソースの設定情報を提供するために使用されます)。null が渡されると、DRL ファイルより入力をロードするためデフォルトが使用されます。マップに使用できるキーは source と dsl です。source は drl または xml を値として取ります。source を drl に設定して DRL ファイルをロードします。また同様に、source を xml に設定して XML ファイルをロードします (xml は dsl のキーと値の設定をすべて無視します) 。dsl キーは reader または string (ドメイン固有言語の内容) を値として使用します。
例6.3 LocalRuleExecutionSet の登録時におけるドメイン固有言語の指定
// Get the RuleAdministration
RuleAdministration ruleAdministrator = ruleServiceProvider.getRuleAdministrator();
LocalRuleExecutionSetProvider ruleExecutionSetProvider =
ruleAdministrator.getLocalRuleExecutionSetProvider( null );
// Create a Reader for the drl
URL drlUrl = new URL("http://mydomain.org/sources/myrules.drl");
Reader drlReader = new InputStreamReader( drlUrl.openStream() );
// Create a Reader for the dsl and a put in the properties map
URL dslUrl = new URL("http://mydomain.org/sources/myrules.dsl");
Reader dslReader = new InputStreamReader( dslUrl.openStream() );
Map properties = new HashMap();
properties.put( "source", "drl" );
properties.put( "dsl", dslReader );
// Create the RuleExecutionSet for the drl and dsl
RuleExecutionSet ruleExecutionSet =
ruleExecutionSetProvider.createRuleExecutionSet( reader, properties );
重要
RuleExecutionSet を読み出すために使用する名前は、登録時に指定する必要があります (プロパティーを渡せるようにするためのフィールドもありますが、現在使用されていないため、null を渡します)。
例6.4 RuleExecutionSet の登録
// Register the RuleExecutionSet with the RuleAdministrator String uri = ruleExecutionSet.getName(); ruleAdministrator.registerRuleExecutionSet(uri, ruleExecutionSet, null);
6.2.2. 「ステートフル」および「ステートレス」ルールセッションの使用
run-time は RuleServiceProvider より取得できます。これは、ステートフル および ステートレス rule engine セッションを作成するために使用されます。
例6.5 RuleRunTime の取得
RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime();
RuleRuntimeの 2 つのパブリック定数である RuleRuntime.STATEFUL_SESSION_TYPE および RuleRuntime.STATELESS_SESSION_TYPE のいずれかを使用します。RuleSessionをインスタンス化するために使用されるRuleExecutionSetの URI (Uniform Resource Indicator) を提供します。properties mapをnullに設定するか、properties mapを使用してグローバルを指定します (次項に記載されています)。createRuleSession(....)メソッドはRuleSessionインスタンスを返します。これをStatefulRuleSessionまたはStatelessRuleSessionへキャストします。
例6.6 ステートフルルール
(StatefulRuleSession) session =
ruleRuntime.createRuleSession( uri,
null,
RuleRuntime.STATEFUL_SESSION_TYPE );
session.addObject( new PurchaseOrder( "lots of cheese" ) );
session.executeRules();
StatelessRuleSession は非常に単純な API を持ちます。これを使用して executeRules(List list) (オブジェクトのリストを渡す) を呼び出し、任意でフィルター呼び出します。その後、結果となるオブジェクトが返されます。
例6.7 ステートレスルール
(StatelessRuleSession) session =
ruleRuntime.createRuleSession( uri,
null,
RuleRuntime.STATELESS_SESSION_TYPE );
List list = new ArrayList();
list.add( new PurchaseOrder( "even more cheese" ) );
List results = new ArrayList();
results = session.executeRules( list );
6.2.3. グローバル
properties map を渡すメソッドを RuleSession ファクトリに渡します。最初に、DRL または XML ファイルにグローバルを定義し、例外がスローされないようにします。
DRL または XML ファイルに宣言された識別子を表します。このキーの値は、実行で使用するインスタンスになります。次の例では、結果はグローバル java.util.List リストに収集されます。
例6.8 グローバル
java.util.List globalList = new java.util.ArrayList( );
java.util.Map map = new java.util.HashMap( );
map.put( "list", globalList );
//Open a stateless Session
StatelessRuleSession srs =
(StatelessRuleSession) runtime.createRuleSession( "SistersRules",
map,
RuleRuntime.STATELESS_SESSION_TYPE );
...
// Persons added to List
// call executeRules( ) giving a List of Objects as parameter
// There are rules which will put Objects in the List
// fetch the list from the map
List list = (java.util.List) map.get("list");
DRL ファイルにグローバルリストを宣言してください。グローバルリストの宣言は次のように行います。
例6.9 グローバルリスト
package SistersRules;
import org.drools.jsr94.rules.Person;
global java.util.List list
rule FindSisters
when
$person1 : Person ( $name1:name )
$person2 : Person ( $name2:name )
eval( $person1.hasSister($person2) )
then
list.add($person1.getName() + " and " + $person2.getName() +" are sisters");
assert( $person1.getName() + " and " + $person2.getName() +" are sisters");
end
6.3. 参考文献
- Java Rule Engine API (JSR 94) の公式の JCP 仕様
- Java Rule Engine API ドキュメント
- Jess and the javax.rules API、E Friedman-Hill 著、TheServerSide.com 発行、2003 年
- Getting Started With the Java Rule Engine API (JSR 94): Toward Rule-Based Applications、Q. H. Mahmoud 著、Sun Developer Network 発行、2005 年
- The Logic From The Bottom Line: An Introduction to The Drools Project、N. A. Rupp 著、TheServiceSide.com 発行、2004 年
第7章 JBoss Developer Studio
注記
注記

図7.1 概要
7.1. 概要
- コンテンツアシスタント 機能 (outline view を含む) を提供する
DRL構文認識エディター - コンテンツアシスタント 機能を提供する ドメイン固有言語 拡張認識エディター
- ルールフローグラフ (プロセスを表す) を編集するための Rule-Flow Graphical Editor。ルールフローグラフをルールパッケージに適用し、命令型制御 を許可できます。
- 以下を作成するウィザード
- 「rules」プロジェクト
- ルールリソース (
DRLまたはBRLファイルの形式) - ドメイン固有言語
- ディシジョンテーブル
- ルールフロー
- カスタム言語とルール言語間のマッピングを作成および管理するための domain-specific language editor
- ルール検証は、変更が行われるたびにルールを再構築します。Problem View よりエラーを報告します。
7.2. Drools ランタイム
7.2.1. Drools ランタイムの定義
- Drools Eclipse プラグインに含まれるようにデフォルトの jar ファイルを使用するには、「Create a new Drools 5 runtime ...」ボタンをクリックすると新しい Drools ランタイムを自動的に作成することができます。ファイルブラウザーが表示され、このランタイムを作成するファイルシステム上のフォルダーを選択するよう求められます。プラグインが必要な依存関係を自動的に指定されたフォルダーへコピーします。このフォルダーの選択後、ダイアログは下図のようになるはずです。
- 特定リリースの Drools プロジェクトを使用したい場合は、必要なすべての Drools ライブラリおよび依存関係が含まれるファイルシステム上にフォルダーを作成する必要があります。上記のように、新しい Drools ランタイムを作成する代わりにランタイムに名前を割り当て、必要な jar がすべて含まれるこのフォルダーの場所を選択します。
7.2.2. Drools プロジェクトに対するランタイムの選択
7.3. ルールプロジェクトの作成
Hello World ルールを学習してください。

図7.2 新しいルールプロジェクトの結果
注記
重要
JBoss Rules Builder と呼ばれる機能を提供します。 を使用してプロジェクトが作成されると、この機能はデフォルトで有効になります。また、他のプロジェクトに対してこの機能を手作業で有効にすることも可能です。
重要
.rule ファイルに移動する方法もあります。これらのファイルはビルダーによって無視されますが、ファイルに含まれるルールを検証するために単体テストで実行する必要があります。
7.4. 新しいルールの作成とウィザード
.drl ファイル拡張子を持つ空ののテキストファイルを生成するか、Wizard を使用します。Control+N を押すか、ツールバーの JBoss Rules アイコンをクリックして Wizard のメニューを起動します。

図7.3 ウィザードメニュー
src/rules というディレクトリを作成し、適切に命名されたサブディレクトリを追加します。パッケージ名は必須で、Java のパッケージ名と似ていることに注意してください (関係するルールをグループ化する名前空間を確立します)。

図7.4 新しいルールウィザード
7.5. テキストルールエディタ ー

図7.5 使用中のルールエディター
.drl または .rule 拡張子を持つファイルを開くことができます。通常、これらのファイルには関連する複数のルールが含まれますが、個別のファイルに各ルールが含まれるようにし、同じパッケージ名前空間に存在する利点を生かしてグループ化することも可能です。
注記
DRL ファイルのデータはプレーンテキスト形式で保存されます。
expander キーワードが存在することに注意してください。これは、ルール言語を解決するため、その名前の .dsl ファイルを検索するようルールコンパイラーに指示します。ドメイン固有言語が使用可能であっても、ルールはプレーンテキストとして格納され、画面上で表示できる内容を反映します。これにより、バージョンを比較する場合など、ルールの管理が非常に簡単になります。

図7.6 ルールアウトラインビュー
7.6. ガイド付きエディター

図7.7 ガイド付きエディター
- メニューをクリックします。
.brlファイルを作成し、Guided Editor で開きます。注記
Editor は、.brlファイルと同じディレクトリにある.packageを使用して動作します。このファイルには、通常の.drlファイルの上部にあるようなパッケージ名とインポートステートメントが含まれます。- パッケージファイルに必要な
factクラスを追加します。 - この情報を追加したら、Guided Editor によって示されたプロンプトに従います (ファクトと関連するフィールドが表示されます)。
model または fact クラスが提供されたら、Guided Editor はルールの視覚的な表現をレンダリングすることができます。また、これを使用し、ビジネスルール言語を直接使用してルールを構築することもできます。これを実現する方法の 1 つが drools-ant モジュールを使用する方法です。このモジュールは、ルールアセットをディレクトリのルールパッケージとして作成するため、バイナリファイルとしてデプロイすることが可能になります。また、以下のコードスニペットを使用して、BRL ファイルを .drl ルールに変換する方法もあります。
例7.1 変換コード
BRXMLPersitence read = BRXMLPersitence.getInstance(); BRDRLPersistence write = BRDRLPersistence.getInstance(); String brl = ... // read from the .brl file as needed... String outputDRL = write.marshall(read.unmarshal(brl)); // Pass the outputDRL to the PackageBuilder, as usual
7.7. JBoss Rules ビュー
working memory を起動するコードでブレークポイントを作成します (workingMemory.fireAllRules() を呼び出す行が適しています)。デバッガーが joinpoint で停止した場合、Debugging Variables ビューで working memory 変数を選択します。次に、次の機能を使用して選択されたworking memory の詳細を表示します。
- Working Memory View には JBoss Rules の
working memoryにある要素がすべて表示されます。 - 名前の通り、Agenda View には
agendaの要素がすべて表示されます (各ルールの名前とバインドされた変数が表示されます)。 - Global Data View には、現在 JBoss Rules の
working memoryに定義されているグローバルデータがすべて表示されます。 - Audit View には、
rules engineが実行された時に生成された監査ログがツリー形式で表示されます。
7.7.1. Working Memory View

working memory にある各要素の 論理構造 の表示と、要素の詳細表示を切り替えます。論理構造では要素のセットを簡単に視覚化できます。AgendaItems の論理構造は、ルールとそのルールによって使用される全パラメーターの値を表示します。
7.7.2. Audit View

例7.2 監査ログの設定
WorkingMemory workingMemory = ruleBase.newWorkingMemory(); // Create a new Working Memory Logger, that logs to file. WorkingMemoryFileLogger logger = new WorkingMemoryFileLogger(workingMemory); // An event.log file is created in the subdirectory log (which must exist) // of the working directory. logger.setFileName( "log/event" ); workingMemory.assertObject(...); workingMemory.fireAllRules(); // stop logging logger.writeToDisk();
- 挿入されたオブジェクト (緑色の正方形)
- 更新されたオブジェクト (黄色の正方形)
- 削除されたオブジェクト (赤色の正方形)
- 作成されたアクティベーション (右矢印)
- キャンセルされたアクティベーション (左矢印)
- 実行されたアクティベーション (青色の菱形)
- 開始または終了したルールフロー (「プロセス」アイコン)
- アクティベートまたはアクティベート解除されたルールフローグループ (「アクティビティ」アイコン)
- 追加または削除されたルールパッケージ (「JBoss Rules」アイコン)
- 追加または削除されたルール (「JBoss Rules」アイコン)
id と toString 表現が含まれます。アクティベーションイベントでは (作成済み、キャンセル済み、実行済みなど)、詳細にルール名とアクティベーションにバインドされたすべての変数が含まれます。
注記
注記
7.8. ドメイン固有言語
- ビジネスアナリストが独自の言葉でどのように記述するか注意してください。
- ルールコンストラクトよりこれをオブジェクトモデルへマッピングします (ドメインオブジェクトとルール自体の間で隔離レイヤーを提供できることも利点となります)。
注記
注記
Properties ファイル形式の若干改良されたバージョンを提供する利点があります)。
.dsl 拡張子を持つ任意のファイル上で起動されます (.dsl ファイルのサンプルを作成する wizard もあります)。
7.8.1. 言語の編集

図7.8 ドメイン固有言語エディター
rule resource ファイルが開かれた時に Rule Editor はこの設定をロードします)。ルールの言語マッピングは、rule engine によって言語式がコンパイルされる「コード」を定義します。
scope 項目は式がどこに属するかを示します。when は左側を示し、then は右側を示します。* は「どこでも」という意味です。
注記
警告

図7.9 言語マッピングエディターのダイアログ
- パーサーが
DSLファイルのルールテキストを読み取り、スコープに応じて言語式に対して行ごとに一致しようとします。 - 一致した場合、括弧内のプレースホルダー (
{age}など) に対応する値がルールソースより抽出されます。 - 「Rule Expression」のプレースホルダーは、対応する値に置き換えられます (上記の例では、自然言語式は「age」および「location」フィールドと、元のルールテキストより抽出された
{age}および{location}値に基づいて、「Person」型のファクトの 2 つの制約へマッピングします)。
注記
.drl ファイルの特定のルールに対して言語マッピングを使用したくない場合は、式の先頭に > を付けるとコンパイラーによって無視されます。また、ドメイン固有言語は任意であることにも注意してください。
.dsl ファイルも使用可能である必要があります。
7.9. Rete View
.drl ファイルに対する Rete Network を表示します。表示するには、DRL Editor ウインドウの下部にある Rete Tree というタブをクリックします。表示されると、個別のノードを「ドラッグアンドドロップ」し、最適な概観を調整します (これらのノード上で長方形をドラッグし、複数のノードを選択することもできます。こうすることで、グループ全体を移動することができます)。JBoss Rules IDE ツールバーの拡大アイコンは、慣習的なやり方で使用できます。
注記

重要
7.10. 大型の .drl ファイル
-XX:MaxPermSize=###m を用いて JBoss Rules IDE を起動します。例は次の通りです。
c:\Eclipse\Eclipse.exe -XX:MaxPermSize=128m
128 Mb に設定してください。
注記
.rule 拡張子を持つファイルにルールを格納することもできます。これを行うと、background builder は変更のたびにルールをコンパイルしないようになります。これにより、パフォーマンスが向上されます (特に、非常に大量のルールを処理する時に IDE の動作が遅い場合)。
7.11. ルールのデバッグ
consequences にブレークポイントを追加できます。ルール実行中にこのようなブレークポイントが検出されると処理が停止されるため、そのポイントで既知の変数をチェックし、デフォルトのデバッグアクションを使用して次に行う処理を決定することができます。また、Debugging View を使用して working memory と agenda の内容を調査することも可能です。
7.11.1. ブレークポイントの作成
- ブレークポイントを追加する行で DRL Editor の Ruler をダブルクリックします。
重要
このようなブレークポイントは、ルールのconsequenceのみで作成できます。ブレークポイントが許可されない行をダブルクリックしても何も行われません。ブレークポイントを削除するには、再度 Ruler をダブルクリックします。 - ルーラーを右クリックすると、「Toggle breakpoint」アクションが含まれるポップアップメニューが表示されます。ルールブレークポイントはルールの結果でのみ作成できることに注意してください。ルールブレークポイントがその行で許可されない場合、アクションは自動的に無効になります。アクションをクリックすると、選択された行にブレークポイントが追加されますが、ブレークポイントがすでに存在する場合はブレークポイントが削除されます。
7.11.2. ルールのデバッグ
JBoss Rules Application としてデバッグする必要があります。

図7.10 JBoss Rules アプリケーションとしてのデバッグ
- アプリケーションのメインクラスを選択し、右クリックします。 サブメニューを選択した後に を選択します。この代わりに メニューアイテムを選択することもできます。この場合、デバッグ設定を作成、管理、および実行するための新しいダイアログボックスが表示されます (以下のスクリーンショットを参照)。
- 左側の tree で アイテムを選択し、New Launch Configuration ボタン (tree の上にあるツールバーの一番左にあるアイコン) をクリックします。これにより、すでに一部のプロパティーが設定された (プロジェクトやメインクラスなど) 新しい設定が作成されます (最初に選択したメインクラスに基づきます)。これらすべてのプロパティーは標準的な Java プログラムのプロパティーと同じです。
- デバッグの設定の名前を意味のある名前に変更します。
- アプリケーションのデバッグは開始するには、ウインドウの下にある Debug ボタンをクリックします。デバッグ設定は一度だけ設定する必要があります。次回デバッグが必要な時は同じ設定を使用します。
注記
JBoss Rules IDE ツールバーには、以前の設定の 1 つを即座に再実行するショートカットボタンも含まれています (少なくとも Java、Java Debug、または JBoss Rules の 1 つが選択された時)。

図7.11 「Debug as JBoss Rules Application」の設定
working memory と agenda の内容を調査することも可能です (現在実行されているものが自動的に表示されるため、この時点で working memory を選択する必要はありません)。

図7.12 デバッグ
第8章 例
Examples ZIP アーカイブファイルをダウンロードしてください。
8.1. HelloWorld の例
| 名前: | HelloWorld の例 |
| メインクラス: | org.drools.examples.helloworld.HelloWorldExample |
| タイプ: | Java アプリケーション |
| 目的: | 簡単な Rules の使用方法を示すチュートリアル |
knowledge bases および sessions をビルドする方法を説明し、監査のロギングとデバッグの出力を実証します (両方とも他の例では省略されています)。
Knowledge Builder を使用して、Drools ルール言語 (DRL) ソースファイルを knowledge base が消費できる複数の Package オブジェクトへ変換します。
add メソッドは、Resource インターフェースと Resource Type の両方をパラメーターとして取ります。Resource インターフェースを使用して、ResourceFactory を介して DRL ソースファイルをクラスパスより読み出します。
注記
Resource を使用して DRL ファイルを URL アドレスなどの他の場所から読み出すことも可能です。必要に応じて複数のファイルを追加できます。
DRL ファイルを追加することも可能です (この場合、Knowledge Builder は各名前空間に対して 1 つのパッケージを作成します)。異なる名前空間を持つ複数のナレッジパッケージを同じ knowledge base に追加することができます。
DRL をすべて追加したら、Knowledge Builder のエラーをチェックします (knowledge base がパッケージを検証しますが、文字列形式のエラー情報にしかアクセスできません。そのため、Knowledge Builder インスタンスよりデバッグを行う必要があります)。
Knowledge Builder コレクションを取得し、KnowledgeBaseFactory より Knowledge Builder をインスタンス化してナレッジパッケージのコレクションを追加します。
例8.1 HelloWorld の例: Knowledge Base および Session の作成
final KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
// this will parse and compile in one step
kbuilder.add(ResourceFactory.newClassPathResource
("HelloWorld.drl",HelloWorldExample.class), ResourceType.DRL);
// Check the builder for errors
if (kbuilder.hasErrors())
{
System.out.println(kbuilder.getErrors().toString());
throw new RuntimeException("Unable to compile \"HelloWorld.drl\".");
}
// get the compiled packages (which are serializable)
final Collection<KnowledgePackage> pkgs = kbuilder.getKnowledgePackages();
// add the packages to a knowledgebase (deploy the knowledge packages).
final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages(pkgs);
final StatefulKnowledgeSession ksession =
kbase.newStatefulKnowledgeSession();
JBoss Rules の イベントモデル は、独自の内部プロセスのほとんどを公開します。DebugAgendaEventListener と DebugWorkingMemoryEventListener の 2 つのデフォルトデバッグ listeners が提供されます。これらのリスナーは、デバッグ情報を Error Console へ出力します (listeners をセッションに追加するのは簡単です。この処理については、後で説明します)。
KnowledgeRuntimeLogger は、Agenda と Working Memory listeners から特別に派生されたものです。実行監査 を提供し、この出力はグラフィック表示されます。
重要
logger.close() を呼び出す必要があります。
注記
JBoss Rules の監査ロギング機能を使用し、今後の検査のために実行フローを記録します。
例8.2 HelloWorld の例: イベントのロギングと監査
// setup the debug listeners
ksession.addEventListener( new DebugAgendaEventListener() );
ksession.addEventListener( new DebugWorkingMemoryEventListener() );
// setup the audit logging
KnowledgeRuntimeLogger logger =
KnowledgeRuntimeLoggerFactory.newFileLogger(ksession,"log/helloworld");
HELLO または GOODBYE のどちらかになる status) 単一クラスの単純な例です。
例8.3 HelloWorld の例: メッセージクラス
public static class Message
{
public static final int HELLO = 0;
public static final int GOODBYE = 1;
private String message;
private int status;
...
}
Hello World という言葉が含まれ、HELLO の状態を持つ単一の Message オブジェクトを作成します。作成後、このオブジェクトはエンジンへ挿入され、その時点で fireAllRules() が実行されます。
注記
fireAllRules() メソッド呼び出しに到達するまでに、完全一致し、適切に実行できるルールをエンジンが認識します。
例8.4 実行
final Message message = new Message();
message.setMessage("Hello World");
message.setStatus(Message.HELLO);
ksession.insert(message);
ksession.fireAllRules();
logger.close();
ksession.dispose();
- JBoss Rules IDE で
org.drools.examples.helloworld.HelloWorldExampleクラスを開きます。 - クラスを右クリックし、[] を選択した後、[context menu] から [] を選択します。
注記
fireAllRules() にブレイクポイントを追加し、ksession 変数を選択すると、Hello World がすでにアクティベートされ、Agenda に追加されたことが分かるはずです (これにより、パターン一致の作業がすべて挿入中に実行されたことが確認されます)。

図8.1 fireAllRules Agenda ビュー
System.out へ送られ、debug listener の出力は debug listener へ送られます。
例8.5 コンソールウインドウの System.out
Hello World Goodbye cruel world
例8.6 コンソールウインドウの System.err
==>[ActivationCreated(0): rule=Hello World; tuple=[fid:1:1:org.drools.examples.HelloWorldExample$Message@17cec96]] [ObjectInserted: handle=[fid:1:1:org.drools.examples.HelloWorldExample$Message@17cec96]; object=org.drools.examples.HelloWorldExample$Message@17cec96] [BeforeActivationFired: rule=Hello World; tuple=[fid:1:1:org.drools.examples.HelloWorldExample$Message@17cec96]] ==>[ActivationCreated(4): rule=Good Bye; tuple=[fid:1:2:org.drools.examples.HelloWorldExample$Message@17cec96]] [ObjectUpdated: handle=[fid:1:2:org.drools.examples.HelloWorldExample$Message@17cec96]; old_object=org.drools.examples.HelloWorldExample$Message@17cec96; new_object=org.drools.examples.HelloWorldExample$Message@17cec96] [AfterActivationFired(0): rule=Hello World] [BeforeActivationFired: rule=Good Bye; tuple=[fid:1:2:org.drools.examples.HelloWorldExample$Message@17cec96]] [AfterActivationFired(4): rule=Good Bye]
when 以降) は、working memory への挿入時に、Message.HELLO 状態の各 Message オブジェクトに対してアクティベートされることを示しています。
message (message 属性へバインドされる) と m (一致した Message オブジェクト自体にバインドされる) の 2 つの変数バインディングが作成されるよう指示します。
then 以降) はルールの「結果」部分です。ルールの dialect 属性で宣言された通り、MVEL で書かれていることを確認してください。ルールのこの部分は、bound variable message の内容を System.out へ送信します。この後、MVEL の modify ステートメントより、 m にバインドされた message object に含まれる message および status 属性を変更します。このステートメントにより、割り当てのブロックを一度に適用できます (ブロックが完了すると、エンジンは自動的に変更を通知されます)。
例8.7 Hello World のルール
rule "Hello World"
dialect "mvel"
when
m : Message( status == Message.HELLO, message : message )
then
System.out.println( message );
modify (m) { message="Goodbye cruel world", status=Message.GOODBYE };
end
DRLファイルのmodify呼び出し上にブレイクポイントを設定します。- JBoss Rules IDE の
org.drools.examples.HelloWorldクラスを開きます。 - コンテキストメニューへ移動し、[ ] をクリックした後に [] を選択し、実行を開始します。
Good Bye という別のルールは Java を使用します。このルールはここでアクティベートされ、アジェンダに置かれます。

図8.2 Hello World ルールの Agenda ビュー
Good Bye ルールは Hello World ルールと似ていますが、Message.GOODBYE 状態の Message オブジェクトと一致します。
例8.8 Good Bye ルール
rule "Good Bye" dialect "java" when Message( status == Message.GOODBYE, message : message ) then System.out.println( message ); end
KnowledgeRuntimeLoggerFactory メソッドの newFileLogger を使用して KnowledgeRuntimeLogger を作成した Java コードをもう一度思い出してください。このコードは最後に logger.close() を呼び出しました。このようにして、Audit に表示される監査ログファイルが作成されました。
注記
- オブジェクトが挿入され、
Hello Worldルールのアクティベーションが作成されます。 - アクティベーションが実行され、
Messageオブジェクトが更新されます。その結果、Good Byeルールがアクティベートされ、実行されます。 - Audit ビューでイベントを選択すると、元のイベントが緑色で強調表示されます (この例では、
Activation executedイベントの元となるActivation createdイベントが緑色で強調表示されます)。

図8.3 Audit ビュー
8.2. 状態の例
| 名前: | 状態の例 |
| 状態クラス: | org.drools.examples.state.StateExampleUsingSalience |
| タイプ: | Java アプリケーション |
| Rules ファイル: | StateExampleUsingSalience.drl |
| 目的: | 基本的なルールの使用とルール実行優先度における競合の解決方法を実証します。 |
working memory のファクトへの変更に基づいてルールを順に評価、アクティベート、および実行する engine の機能のことです。
8.2.1. 状態の例について
org.drools.examples.state.State クラスには、名前と現在の状態の 2 つのフィールドがあります。現在の状態は以下のいずれかになります。
NOTRUNFINISHED
例8.9 状態クラス
public class State {
public static final int NOTRUN = 0;
public static final int FINISHED = 1;
private final PropertyChangeSupport changes =
new PropertyChangeSupport( this );
private String name;
private int state;
... setters and getters go here...
}
org.drools.examples.state.State を無視すると (理由は後で説明します)、A、 B、 C、および D という名前の 4 つの State オブジェクトが作成されたことが分かります。当初これらの状態は、使用されるコンストラクターのデフォルト値である NOTRUN に設定されます。各インスタンスが順に session にアサートされ、fireAllRules() が呼び出されます。
例8.10 Salience 状態の実行例
State a = new State( "A" );
State b = new State( "B" );
State c = new State( "C" );
final State d = new State( "D" );
// By setting dynamic to TRUE, JBoss Rules will use JavaBean
// PropertyChangeListeners so you do not have to call modify or update().
boolean dynamic = true;
session.insert( a, dynamic );
session.insert( b, dynamic );
session.insert( c, dynamic );
session.insert( d, dynamic );
session.fireAllRules();
session.dispose(); // Stateful rule session must always be disposed when finished</programlisting>
- JBoss Rules IDE で
org.drools.examples.state.StateExampleUsingSalienceクラスを開きます。 - class を右クリックし、[] を選択した後、[] を選択します。
例8.11 Salience 状態のコンソール出力
A finished B finished C finished D finished
Bootstrap ルールが最初に実行され、A が FINISHED 状態に設定されると、B の状態も FINISHED になります。(C と D は両方とも B に依存するため、一時的に競合が発生しますが、salience 値によって解決されます)。
- Audit View が表示されていない場合、[] をクリックし、[]、[]、[]、[] と順番に選択します。
- Audit View で Open Log ボタンをクリックし、
drools-examples-drl-dir>/log/state.logというファイルを選択します。

図8.4 Salience 状態の Audit View 例
working memory に書き込まれた変更が記録されているのが分かります。そのため、状態が NOTRUN の State オブジェクト A をアサートすると、Bootstrap ルールがアクティベートされますが、他の State オブジェクトをアサートしてもすぐに影響はありません。
例8.12 Salience 状態: Bootstrap ルール
rule Bootstrap when a : State(name == "A", state == State.NOTRUN ) then System.out.println(a.getName() + " finished" ); a.setState( State.FINISHED ); end
Bootstrap ルールを実行すると、A の状態が FINISHED に変わり、A to B ルールがアクティベートされます。
例8.13 A to B ルール
rule "A to B" when State(name == "A", state == State.FINISHED ) b : State(name == "B", state == State.NOTRUN ) then System.out.println(b.getName() + " finished" ); b.setState( State.FINISHED ); end
A to B ルールを実行すると B の状態が FINISHED に変わり、B to C ルールと B to D ルールの両方がアクティベートされ、アクティベーションが agenda に置かれます。
engine の agenda はどちらのルールを実行するか決定することができます。B to C ルールの salience 値の方が大きいため (10、デフォルト値は 0) 最初に実行され、オブジェクト C が FINISHED 状態に設定されます。
A to B ルールの State オブジェクトへの変更を表しています。Audit View が開かれている間にルール内にデバッグポイントを配置することができるため、Agenda View を使用してアジェンダの状態を調査することも可能です。以下のスクリーンショットは、A to B ルールのブレークポイントを表しています。また、2 つのルールが競合状態にある時の agenda の状態も表しています。

図8.5 状態の例: Agenda View
例8.14 B to C ルール
rule "B to C" salience 10 when State(name == "B", state == State.FINISHED ) c : State(name == "C", state == State.NOTRUN ) then System.out.println(c.getName() + " finished" ); c.setState( State.FINISHED ); end
B to D ルールは最後に実行され、オブジェクト D の状態を FINISHED に変更します。
例8.15 B to D ルール
rule "B to D" when State(name == "B", state == State.FINISHED ) d : State(name == "D", state == State.NOTRUN ) then System.out.println(d.getName() + " finished" ); d.setState( State.FINISHED ); end
engine が停止します。
PropertyChangeListener オブジェクトに基づいた 動的ファクト です。エンジンが fact プロパティーの変更を「確認」し、反応するには、アプリケーションがこれらの情報をエンジンに通知する必要があります。これは、ルール (modify ステートメント) を介して明示的に行うか、暗黙的 (ファクトが実装された PropertyChangeSupport よりエンジンに通知) に行うことが可能です。
注記
PropertyChangeSupport は Java Beans Specification に定義されます。
PropertyChangeSupport を使用する方法について学びましょう (これを使用すると、ルールが modify ステートメントだらけにならないようにすることが可能です)。この機能を使用するには、org.drools.example.State クラスと同じ方法で、最初にファクトが PropertyChangeSupport を実装するようにします。その後、次のコードを使用してファクトを working memory に挿入します。
例8.16 動的ファクトの挿入
// By setting dynamic to TRUE, JBoss Rules will use JavaBean // PropertyChangeListeners so that one does not have to call modify or update(). final boolean dynamic = true; session.insert( fact, dynamic );
PropertyChangeListener オブジェクトが使用される場合、各 セッター は追加のコード (通知のため) を実装する必要があります。org.drools.examples クラスの State のセッターは次の通りです。
例8.17 「セッター」の PropertyChangeListener サポート
public void setState(final int newState) {
int oldState = this.state;
this.state = newState;
this.changes.firePropertyChange( "state",oldState,newState );
}
StateExampleUsingAgendGroup と StateExampleWithDynamicRules と呼ばれます。上記の通り、これらのクラスは A to B から C to D へ実行されます。
StateExampleUsingAgendGroupクラスはagenda groupsクラスを使用してルールの競合を制御し、最初に実行するルールを決定します。StateExampleWithDynamicRulesクラスは、実行しているworking memoryセッションへルールを追加する方法を表します。
MAIN というアジェンダグループに含まれます。agenda-group 属性により、ルールの異なる agenda group を指定することができます。最初に、MAIN agenda group は working memory によって使用されます。
重要
setFocus() メソッドまたは auto-focus ルール属性を使用します (auto-focus ルール属性を使用する場合、ルールが一致し、アクティベートされると自動的にフォーカスが agenda group に設定されるため、auto-focus という名前になっています。このメソッドは、B to D ルールの前に B to C ルールを実行できるようにします)。
例8.18 アジェンダグループの状態の例: B to C ルール
rule "B to C"
agenda-group "B to C"
auto-focus true
when
State(name == "B", state == State.FINISHED )
c : State(name == "C", state == State.NOTRUN )
then
System.out.println(c.getName() + " finished" );
c.setState( State.FINISHED );
kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "B to D" ).setFocus();
end
B to C は B to D agenda group 上で setFocus() を呼び出します。これにより、アクティブなルールを実行でき、B to D に属するルールをトリガーします。
例8.19 アジェンダグループの状態の例: B to D ルール
rule "B to D" agenda-group "B to D" when State(name == "B", state == State.FINISHED ) d : State(name == "D", state == State.NOTRUN ) then System.out.println(d.getName() + " finished" ); d.setState( State.FINISHED ); end
StateExampleWithDynamicRules の例は、fireAllRules() の実行後、他のルールをベースに追加します。この新しいルールは他の 状態遷移 です。
例8.20 動的状態の例: D to E ルール
rule "D to E" when State(name == "D", state == State.FINISHED ) e : State(name == "E", state == State.NOTRUN ) then System.out.println(e.getName() + " finished" ); e.setState( State.FINISHED ); end
例8.21 動的状態の出力例
A finished B finished C finished D finished E finished
8.3. フィボナッチの例
| 名前: | フィボナッチ |
| メインクラス: | org.drools.examples.fibonacci.FibonacciExample |
| タイプ: | Java アプリケーション |
| Rules ファイル: | Fibonacci.drl |
| 目的: | 再帰およびクロス積の一致を実証します。 |
Fibonacci 単一ファクトクラスを使用します。このクラスには sequence と value の 2 つのフィールドがあります。sequence フィールドは、フィボナッチ数列のオブジェクトの位置を示すために使用されます。value フィールドは、特定の数列位置に対する Fibonacci オブジェクトの値を示します (算出する必要がある値を示すために -1 を使用)。
例8.22 フィボナッチクラス
public static class Fibonacci {
private int sequence;
private long value;
public Fibonacci( final int sequence ) {
this.sequence = sequence;
this.value = -1;
}
... setters and getters go here...
}
- JBoss Rules の統合開発環境で
org.drools.examples.FibonacciExampleクラスを開きます。 - このクラスを右クリックし、 [] を選択した後に [] を選択します。
...snip... は行が省略されていることを示しています)。
例8.23 フィボナッチの例: コンソールの出力
recurse for 50 recurse for 49 recurse for 48 recurse for 47 ...snip... recurse for 5 recurse for 4 recurse for 3 recurse for 2 1 == 1 2 == 1 3 == 2 4 == 3 5 == 5 6 == 8 ...snip... 47 == 2971215073 48 == 4807526976 49 == 7778742049 50 == 12586269025
fifty を持つ単一の Fibonacci オブジェクトを挿入します。その後、再帰ルールが実行され、他の 49 個のオブジェクトが自動的に挿入されます。
注記
PropertyChangeSupport を使用しません。この代わりに、MVFLEX 式言語を使用するため、modify キーワードを使用できます。このキーワードにより、ブロックセッターアクション を使用できます (エンジンに変更も通知します)。
例8.24 フィボナッチの例: 実行
ksession.insert( new Fibonacci( 50 ) ); ksession.fireAllRules();
Recurse ルールは非常に単純です。このルールは、-1 を値として持つアサートされた Fibonacci オブジェクトを照合するため、現在よりも 1 つ前の値を持つ新しい Fibonacci オブジェクトが作成され、アサートされます。数列フィールドが 1 である Fibonacci オブジェクトが存在しない限り、Fibonacci オブジェクトが追加されるたびにルールが実行されます。
Bootstrap ルールを実行する前に 50 個の Fibonacci オブジェクトをすべてアサートする必要があるため、ルールは salience 値も持っています。
例8.25 フィボナッチの例:「再帰」ルール
rule Recurse salience 10 when f : Fibonacci ( value == -1 ) not ( Fibonacci ( sequence == 1 ) ) then insert( new Fibonacci( f.sequence - 1 ) ); System.out.println( "recurse for " + f.sequence ); end
50) を表示します。その後、アサートされた各 Fibonacci オブジェクトによって Recurse ルールが何度も実行される、ルールの連続的な再帰を表します。

図8.6 フィボナッチの例: Recurse Audit ビュー 1
2 である Fibonacci オブジェクトがアサートされると、Bootstrap ルールと一致し、Bootstrap ルールと共にアクティベートされます。
注記
Bootstrap フィールドには複数の制限があります。これらの制限は、フィールドの値が 1 または 2 と等しいか確認します。
例8.26 フィボナッチの例: Bootstrap ルール
rule Bootstrap
when
f : Fibonacci( sequence == 1 || == 2, value == -1 )
// this is a multi-restriction || on a single field
then
modify ( f ){ value = 1 };
System.out.println( f.sequence + " == " + f.value );
endRecurse ルールの salience 値の方が大きいため、Bootstrap ルールは実行されません。

図8.7 フィボナッチの例: Recurse Agenda ビュー 1
1 の Fibonacci オブジェクトがアサートされると、再度 Recurse ルールに対して一致し、2 度アクティベートされます。
注記
1 である Fibonacci オブジェクトが存在すると即座にルールの一致が行われないようになるため、Recurse は一致せず、アクティベートされません。

図8.8 フィボナッチの例: Recurse Agenda ビュー 2
-1 と等しくない値を持つ 2 つのオブジェクトが存在すると、Calculate ルールが実行されます (Bootstrap ルールがオブジェクトに 1 と 2 から 1 の数列値を設定したことに注意してください)。
working memory に 50 個の Fibonacci オブジェクトが存在します。適切な「3 つ組」を選択して順に値を計算します。
Calculate ルールはフィールド制約を使用して、3 つのフィボナッチパターンを正しい順番に制限します。この方法は クロス積の一致 と呼ばれます。この仕組みは次の通りです。
- 最初のパターンは、値が
!= -1の Fibonacci オブジェクトを見つけ、パターンとフィールドの両方をバインドします。 - 2 つ目の Fibonacci オブジェクトも同じ事を行いますが、別のフィールド制約を追加します。これは、f1 にバインドされた Fibonacci オブジェクトよりも数列が大きくなるようにするためです。このルールが最初に実行される時、最初と 2 番目の数列のみ値が
1になります。2 つの制約は、 f1 が最初の数列を参照し、f2が 2 番目の数列を参照するようにします。 - 最後のパターンは、
-1と等しい値を持ち、f2に含まれる値よりも 1 大きい数列値を持つ Fibonacci オブジェクトを見つけます。
f3 へバインドされる) を計算できます。
例8.27 フィボナッチの例: Calculate ルール
rule Calculate
when
// Bind f1 and s1
f1 : Fibonacci( s1 : sequence, value != -1 )
// Bind f2 and v2; refer to bound variable s1
f2 : Fibonacci( sequence == (s1 + 1), v2 : value != -1 )
// Bind f3 and s3; alternative reference of f2.sequence
f3 : Fibonacci( s3 : sequence == (f2.sequence + 1 ), value == -1 )
then
// Note the various referencing rechniques.
modify ( f3 ) { value = f1.value + v2 };
System.out.println( s3 + " == " + f3.value );
end
Modify ステートメントは f3 にバインドされるオブジェクトの値を更新します。その結果、値が -1 と等しくない別の新しいオブジェクトが存在することになります。このオブジェクトが作成されると、Calculate ルールが再度一致します。その後、次のフィボナッチ数を処理します。次図の Audit ビューはこの処理の概要を表しています。これは、最後に実行される Bootstrap ルールがどのように Fibonacci オブジェクトを変更し、Calculate ルールをトリガーするかを表しています。その後、別の Fibonacci オブジェクトが変更され、Calculate ルールが再実行されます。このサイクルは、オブジェクトすべてに値が設定されるまで継続されます。

図8.9 フィボナッチの例: Bootstrap Audit ビュー
8.4. 銀行取引のチュートリアル
| 名前: | 銀行取引のチュートリアル |
| メインクラス: | org.drools.tutorials.banking.BankingExamplesApp.java |
| タイプ: | Java アプリケーション |
| Rules ファイル: | org.drools.tutorials.banking.*.drl |
| 目的: | パターンの一致、基本的なソート、および計算ルールを実証します。 |
重要
RuleRunner クラスは、データのセットに対して 1 つまたは複数の DRL ファイルを実行して、ナレッジパッケージをコンパイルし、各実行に対して knowledge base を作成します。これは、テストおよびチュートリアルの目的で使用できますが、knowledge base を一度だけ構築してキャッシュする必要がある実稼働システムでは適切でないことに注意してください。
例8.28 銀行取引のチュートリアル: RuleRunner
public class RuleRunner {
public RuleRunner() {}
public void runRules(String[] rules,Object[] facts) throws Exception
{
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
KnowledgeBuilder kbuilder =
KnowledgeBuilderFactory.newKnowledgeBuilder();
for ( int i = 0; i < rules.length; i++ ) {
String ruleFile = rules[i];
System.out.println( "Loading file: " + ruleFile );
kbuilder.add( ResourceFactory.newClassPathResource
( ruleFile, RuleRunner.class ),ResourceType.DRL );
}
Collection<KnowledgePackage> pkgs = kbuilder.getKnowledgePackages();
kbase.addKnowledgePackages( pkgs );
StatefulKnowledgeSession ksession =
kbase.newStatefulKnowledgeSession();
for ( int i = 0; i < facts.length; i++ ) {
Object fact = facts[i];
System.out.println( "Inserting fact: " + fact );
ksession.insert( fact );
}
ksession.fireAllRules();
}
}Example.drl という単一の DRL ファイルをロードし、実行しますが、データは挿入しません。
例8.29 銀行取引のチュートリアル: Java の例 1
public class Example1
{
public static void main(String[] args) throws Exception
{
new RuleRunner().runRules(new String[]{"Example1.drl"},
new Object[0] );
}
}
true になります。そのため、常に一致し、起動後に一度「実行」します。
例8.30 銀行取引のチュートリアル: Example1.drl のルール
rule "Rule 01" when eval( 1==1 ) then System.out.println( "Rule 01 Works" ); end
print ステートメントに一致し、実行することを表しています。
例8.31 銀行取引のチュートリアル: Example1.java の出力
Loading file: Example1.drl Rule 01 Works
例8.32 銀行取引のチュートリアル: Java の例 2
public class Example2
{
private static Integer wrap(int i) {return new Integer(i);}
public static void main(String[] args) throws Exception
{
Number[] numbers = new Number[] {wrap(3), wrap(1), wrap(4),
wrap(1), wrap(5)};
new RuleRunner().runRules(new String[] { "Example2.drl" },numbers);
}
}
java.lang.Integer クラスのセットを使用します (コレクション番号が「fact」でも「thing 」でもないため、最良の方法ではありません。銀行の口座には残高を表す数字があります。そのため、この場合、口座が「fact」になります。しかし、最初の実証の目的を満たすには整数をアサートすれば十分です)。
例8.33 銀行取引のチュートリアル: Example2.drl のルール
rule "Rule 02"
when
Number( $intValue : intValue )
then
System.out.println("Number found with value: " + $intValue);
end
例8.34 銀行取引のチュートリアル: Example2.drl の出力
Loading file: Example2.drl Inserting fact: 3 Inserting fact: 1 Inserting fact: 4 Inserting fact: 1 Inserting fact: 5 Number found with value: 5 Number found with value: 1 Number found with value: 4 Number found with value: 1 Number found with value: 3
Cashflow クラスを日付順に適用するタスクがあるため、ここでプロセスについて説明します。
例8.35 銀行取引のチュートリアル: Example3.java
public class Example3
{
private static Integer wrap(int i) {return new Integer(i);}
public static void main(String[] args) throws Exception
{
Number[] numbers = new Number[] {wrap(3), wrap(1), wrap(4),
wrap(1), wrap(5)};
new RuleRunner().runRules(new String[]{ "Example3.drl"},numbers);
}
}
例8.36 銀行取引のチュートリアル: Example3.drl のルール
rule "Rule 03"
when
$number : Number( )
not Number( intValue < $number.intValue )
then
System.out.println("Number found with value: "+$number.intValue() );
retract( $number );
end
例8.37 銀行取引のチュートリアル: Example3.java の出力
Loading file: Example3.drl Inserting fact: 3 Inserting fact: 1 Inserting fact: 4 Inserting fact: 1 Inserting fact: 5 Number found with value: 1 Number found with value: 1 Number found with value: 3 Number found with value: 4 Number found with value: 5
Cashflow クラスオブジェクトを作成します。
例8.38 銀行取引のチュートリアル: Cashflow クラス
public class Cashflow
{
private Date date;
private double amount;
public Cashflow() {}
public Cashflow(Date date,double amount)
{
this.date = date;this.amount = amount;
}
public Date getDate() { return date; }
public void setDate(Date date) { this.date = date; }
public double getAmount() { return amount; }
public void setAmount(double amount) { this.amount = amount; }
public String toString()
{
return "Cashflow[date=" + date + ",amount=" + amount + "]";
}
}
Cashflow クラスには、日付と金額の 2 つの簡単な属性があります。toString メソッドが追加されているため、出力することができます (浮動小数点形式は多くの数型を正確に表せないため、通貨単位に double 型を使用することは通常推奨されません)。
Cashflow オブジェクトを挿入します。
例8.39 銀行取引のチュートリアル: Example4.java
public class Example4
{
public static void main(String[] args) throws Exception
{
Object[] cashflows = {
new Cashflow(new SimpleDate("01/01/2007"), 300.00),
new Cashflow(new SimpleDate("05/01/2007"), 100.00),
new Cashflow(new SimpleDate("11/01/2007"), 500.00),
new Cashflow(new SimpleDate("07/01/2007"), 800.00),
new Cashflow(new SimpleDate("02/01/2007"), 400.00),
};
new RuleRunner().runRules(new String[] {"Example4.drl"},cashflows;
}
}
SimpleDate 「コンビニエンス」クラスは、入力文字列を取り日付形式を定義するコンストラクターを提供し、java.util.Date クラスを拡張します。コードは次の通りです。
例8.40 銀行取引のチュートリアル: クラス SimpleDate
public class SimpleDate extends Date
{
private static final SimpleDateFormat format =
new SimpleDateFormat("dd/MM/yyyy");
public SimpleDate(String datestr) throws Exception
{
setTime(format.parse(datestr).getTime());
}
}
cashflow がどのように出力されるかを学ぶため、cashflow ファイルを検証します。
例8.41 銀行取引のチュートリアル: Example4.drl のルール
rule "Rule 04"
when
$cashflow : Cashflow( $date : date, $amount : amount )
not Cashflow( date < $date)
then
System.out.println("Cashflow: "+$date+" :: "+$amount);
retract($cashflow);
end
cashflow を識別し、日付と金額を抽出できます。ルールの 2 行目では、見つかった cashflow よりも日付が古い cashflow が存在しないようにします。結果として、ルールを満たす cashflow を出力して取り消し、日付が次に新しい cashflow を処理できるようにします。
例8.42 銀行取引のチュートリアル: Example4.java の出力
Loading file: Example4.drl Inserting fact: Cashflow[date=Mon Jan 01 00:00:00 GMT 2007,amount=300.0] Inserting fact: Cashflow[date=Fri Jan 05 00:00:00 GMT 2007,amount=100.0] Inserting fact: Cashflow[date=Thu Jan 11 00:00:00 GMT 2007,amount=500.0] Inserting fact: Cashflow[date=Sun Jan 07 00:00:00 GMT 2007,amount=800.0] Inserting fact: Cashflow[date=Tue Jan 02 00:00:00 GMT 2007,amount=400.0] Cashflow: Mon Jan 01 00:00:00 GMT 2007 :: 300.0 Cashflow: Tue Jan 02 00:00:00 GMT 2007 :: 400.0 Cashflow: Fri Jan 05 00:00:00 GMT 2007 :: 100.0 Cashflow: Sun Jan 07 00:00:00 GMT 2007 :: 800.0 Cashflow: Thu Jan 11 00:00:00 GMT 2007 :: 500.0
TypedCashflow を作成するため Cashflow クラスを拡張します (入金または出金操作のいずれかになります)。通常、Cashflow に追加するのが最も簡単な方法ですが、ここでは以前のバージョンのクラスを保持するために拡張が使用されます。
public class TypedCashflow extends Cashflow {
public static final int CREDIT = 0;
public static final int DEBIT = 1;
private int type;
public TypedCashflow() { }
public TypedCashflow(Date date, int type, double amount)
{
super( date, amount );
this.type = type;
}
public int getType()
{
return type;
}
public void setType(int type)
{
this.type = type;
}
public String toString()
{
return "TypedCashflow[date=" + getDate()
+ ",type=" + (type == CREDIT ? "Credit" : "Debit")
+ ",amount=" + getAmount()
+ "]";
}
}
例8.43 銀行取引のチュートリアル: Example5.java
public class Example5
{
public static void main(String[] args) throws Exception
{
Object[] cashflows = {
new TypedCashflow(new SimpleDate("01/01/2007"),
TypedCashflow.CREDIT, 300.00),
new TypedCashflow(new SimpleDate("05/01/2007"),
TypedCashflow.CREDIT, 100.00),
new TypedCashflow(new SimpleDate("11/01/2007"),
TypedCashflow.CREDIT, 500.00),
new TypedCashflow(new SimpleDate("07/01/2007"),
TypedCashflow.DEBIT, 800.00),
new TypedCashflow(new SimpleDate("02/01/2007"),
TypedCashflow.DEBIT, 400.00),
};
new RuleRunner().runRules(
new String[] { "Example5.drl" }, cashflows );
}
}
Cashflow オブジェクトのセットが作成され、各オブジェクトは入金または出金操作のいずれかになります。これらのオブジェクトは Example5.drl とともに RuleEngine へ提供されます。
Cashflow オブジェクトを出力します。
例8.44 銀行取引のチュートリアル: Example5.drl のルール
rule "Rule 05"
when
$cashflow : TypedCashflow( $date : date, $amount : amount,
type == TypedCashflow.CREDIT )
not TypedCashflow( date < $date, type == TypedCashflow.CREDIT )
then
System.out.println("Credit: "+$date+" :: "+$amount);
retract($cashflow);
end
Cashflow ファクトを識別し、日付と金額を抽出することができます。ルールの 2 行目では、見つかった Cashflow よりも日付が古い同じ型の Cashflow が存在しないようにします。結果として、ルールを満たす Cashflow を出力して取り消し、日付が次に新しい CREDIT 型の Cashflow を処理できるようにします。
例8.45 銀行取引のチュートリアル: Example5.java の出力
Loading file: Example5.drl Inserting fact: TypedCashflow[date=Mon Jan 01 00:00:00 GMT 2007,type=Credit,amount=300.0] Inserting fact: TypedCashflow[date=Fri Jan 05 00:00:00 GMT 2007,type=Credit,amount=100.0] Inserting fact: TypedCashflow[date=Thu Jan 11 00:00:00 GMT 2007,type=Credit,amount=500.0] Inserting fact: TypedCashflow[date=Sun Jan 07 00:00:00 GMT 2007,type=Debit,amount=800.0] Inserting fact: TypedCashflow[date=Tue Jan 02 00:00:00 GMT 2007,type=Debit,amount=400.0] Credit: Mon Jan 01 00:00:00 GMT 2007 :: 300.0 Credit: Fri Jan 05 00:00:00 GMT 2007 :: 100.0 Credit: Thu Jan 11 00:00:00 GMT 2007 :: 500.0
Account オブジェクトを 2 つ作成し、RuleEngine へ渡す前に Cashflow クラスへインジェクトします (helper を用いずに正しい口座へ簡単にアクセスできるようにするため、この作業を行います)。
Account クラスを見てみましょう。これは単純な Java オブジェクトで、口座番号と残高の両方を持ちます。
public class Account
{
private long accountNo;
private double balance = 0;
public Account() { }
public Account(long accountNo)
{
this.accountNo = accountNo;
}
public long getAccountNo()
{
return accountNo;
}
public void setAccountNo(long accountNo)
{
this.accountNo = accountNo;
}
public double getBalance()
{
return balance;
}
public void setBalance(double balance)
{
this.balance = balance;
}
public String toString()
{
return "Account[" + "accountNo=" + accountNo
+ ",balance=" + balance + "]";
}
}
AllocatedCashflow クラスを作成するため TypedCashflow を拡張し、Account 参照が含まれるようにします。
例8.46 AllocatedCashflow クラス
public class AllocatedCashflow extends TypedCashflow
{
private Account account;
public AllocatedCashflow() {}
public AllocatedCashflow(Account account, Date date,
int type, double amount)
{
super( date, type, amount );
this.account = account;
}
public Account getAccount()
{
return account;
}
public void setAccount(Account account)
{
this.account = account;
}
public String toString()
{
return "AllocatedCashflow["
+ "account=" + account
+ ",date=" + getDate()
+ ",type=" + (getType() == CREDIT ? "Credit" : "Debit")
+ ",amount=" + getAmount()
+ "]";
}
}
Example5.java は 2 つの Account オブジェクトを作成します。コンストラクターの呼び出しにより Account オブジェクトの 1 つが各 Cashflow に渡されます。
例8.47 銀行取引のチュートリアル: Example5.java
public class Example6
{
public static void main(String[] args) throws Exception
{
Account acc1 = new Account(1);
Account acc2 = new Account(2);
Object[] cashflows =
{
new AllocatedCashflow(acc1,new SimpleDate("01/01/2007"),
TypedCashflow.CREDIT, 300.00),
new AllocatedCashflow(acc1,new SimpleDate("05/02/2007"),
TypedCashflow.CREDIT, 100.00),
new AllocatedCashflow(acc2,new SimpleDate("11/03/2007"),
TypedCashflow.CREDIT, 500.00),
new AllocatedCashflow(acc1,new SimpleDate("07/02/2007"),
TypedCashflow.DEBIT, 800.00),
new AllocatedCashflow(acc2,new SimpleDate("02/03/2007"),
TypedCashflow.DEBIT, 400.00),
new AllocatedCashflow(acc1,new SimpleDate("01/04/2007"),
TypedCashflow.CREDIT, 200.00),
new AllocatedCashflow(acc1,new SimpleDate("05/04/2007"),
TypedCashflow.CREDIT, 300.00),
new AllocatedCashflow(acc2,new SimpleDate("11/05/2007"),
TypedCashflow.CREDIT, 700.00),
new AllocatedCashflow(acc1,new SimpleDate("07/05/2007"),
TypedCashflow.DEBIT, 900.00),
new AllocatedCashflow(acc2,new SimpleDate("02/05/2007"),
TypedCashflow.DEBIT, 100.00)
};
new RuleRunner().runRules(new String[]{"Example6.drl"},cashflows);
}
}
Example6.drl ファイルのルールを確認して、各 Cashflow がどのように日付順で適用されるか確認し、残高を計算および出力します。
rule "Rule 06 - Credit"
when
$cashflow : AllocatedCashflow( $account : account,
$date : date, $amount : amount, type==TypedCashflow.CREDIT )
not AllocatedCashflow( account == $account, date < $date)
then
System.out.println("Credit: " + $date + " :: " + $amount);
$account.setBalance($account.getBalance()+$amount);
System.out.println("Account: " + $account.getAccountNo() +
" - new balance: " + $account.getBalance());
retract($cashflow);
end
rule "Rule 06 - Debit"
when
$cashflow : AllocatedCashflow( $account : account,
$date : date, $amount : amount, type==TypedCashflow.DEBIT )
not AllocatedCashflow( account == $account, date < $date)
then
System.out.println("Debit: " + $date + " :: " + $amount);
$account.setBalance($account.getBalance() - $amount);
System.out.println("Account: " + $account.getAccountNo() +
" - new balance: " + $account.getBalance());
retract($cashflow);
end
Cashflow をチェックする時に型を指定しないでください。これは、型に関係なくすべての Cashflow が日付順にチェックされるようにするためです。作業する口座を識別するために conditions が使用され、Cashflow の金額で更新するために consequences が使用されます。
Loading file: Example6.drl Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Mon Jan 01 00:00:00 GMT 2007,type=Credit,amount=300.0] Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Mon Feb 05 00:00:00 GMT 2007,type=Credit,amount=100.0] Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Sun Mar 11 00:00:00 GMT 2007,type=Credit,amount=500.0] Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Wed Feb 07 00:00:00 GMT 2007,type=Debit,amount=800.0] Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Fri Mar 02 00:00:00 GMT 2007,type=Debit,amount=400.0] Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Sun Apr 01 00:00:00 BST 2007,type=Credit,amount=200.0] Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Thu Apr 05 00:00:00 BST 2007,type=Credit,amount=300.0] Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Fri May 11 00:00:00 BST 2007,type=Credit,amount=700.0] Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Mon May 07 00:00:00 BST 2007,type=Debit,amount=900.0] Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Wed May 02 00:00:00 BST 2007,type=Debit,amount=100.0] Debit: Fri Mar 02 00:00:00 GMT 2007 :: 400.0 Account: 2 - new balance: -400.0 Credit: Sun Mar 11 00:00:00 GMT 2007 :: 500.0 Account: 2 - new balance: 100.0 Debit: Wed May 02 00:00:00 BST 2007 :: 100.0 Account: 2 - new balance: 0.0 Credit: Fri May 11 00:00:00 BST 2007 :: 700.0 Account: 2 - new balance: 700.0 Credit: Mon Jan 01 00:00:00 GMT 2007 :: 300.0 Account: 1 - new balance: 300.0 Credit: Mon Feb 05 00:00:00 GMT 2007 :: 100.0 Account: 1 - new balance: 400.0 Debit: Wed Feb 07 00:00:00 GMT 2007 :: 800.0 Account: 1 - new balance: -400.0 Credit: Sun Apr 01 00:00:00 BST 2007 :: 200.0 Account: 1 - new balance: -200.0 Credit: Thu Apr 05 00:00:00 BST 2007 :: 300.0 Account: 1 - new balance: 100.0 Debit: Mon May 07 00:00:00 BST 2007 :: 900.0 Account: 1 - new balance: -800.0
8.5. 価格ルールのデシジョンテーブルの例
| 名前: | 価格決定のポリシー例 |
| メインクラス: | org.drools.examples.decisiontable.PricingRuleDTExample |
| タイプ: | Java アプリケーション |
| Rules ファイル: | ExamplePolicyPricing.xls |
| 目的: | スプレッドシートベースのデシジョンテーブルの実証。 |
8.5.1. 例の実行
PricingRuleDTExample.java ファイルを開き、Java アプリケーションとして実行します。次の出力が Console ウインドウに表示されます。
Cheapest possible BASE PRICE IS: 120 DISCOUNT IS: 20
stateless session が作成されます。違いはルールが追加される方法にあります。
DecisionTableConfiguration dtableconfiguration =
KnowledgeBuilderFactory.newDecisionTableConfiguration();
dtableconfiguration.setInputType( DecisionTableInputType.XLS );
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
Resource xlsRes = ResourceFactory.newClassPathResource( "ExamplePolicyPricing.xls",
getClass() );
kbuilder.add( xlsRes,
ResourceType.DTABLE,
dtableconfiguration );
DecisionTableConfiguration オブジェクトの使用に注目してください。この入力型は DecisionTableInputType.XLS に設定されます。
注記
Driver と Policy の 2 つの fact types が使用されます。両方のファクトタイプのデフォルト値が使用されます。Driver の年齢は 30 歳で、以前に保険の請求がなく、リスクプロファイルは LOW になります。ドライバーが申請している Policy は COMPREHENSIVE で、まだ承認されていません。
8.5.2. ディシジョンテーブル

図8.10 デシジョンテーブルの設定
RuleSet 宣言がパッケージ名を提供することに注意してください。Variables (グローバル変数用) や Imports (クラスのインポートに使用) など、他のオプション項目をここに追加することも可能です。この例では、ルールの名前空間は fact classes の名前空間と同じであるため、省略されています。
RuleTable 宣言があります。後続の Pricing Bracket が、生成されるルールの名前のプレフィックスとして割り当てられます。
CONDITION または ACTION があります。これは、列の目的を示しています。列が条件の一部または生成されたルールの結果を形成するかどうかを決定します。
$1 および $2 (コンマ区切りの値で入力)、locationRiskProfile および priorClaims (それぞれの列に存在) を使用します。
action 列に設定されます。ここにメッセージのログを記録することも可能です。

図8.11 基本保険料の計算
注記
120 になります。

図8.12 割引の計算
COMPREHENSIVE 契約を申請しています。これにより、20 パーセントの割引が適応されます。
注記
重要
8.6. ペットストアの例
| 名前: | ペットストア |
| メインクラス: | org.drools.examples.petstore.PetStoreExample |
| タイプ: | Java アプリケーション |
| Rules ファイル: | PetStore.drl |
| 目的: | アジェンダグループ、グローバル変数、およびグラフィカルユーザーインターフェースの統合 (ルール内からのコールバックを含む) の使用について実証します。 |
Rules ファイル内には アジェンダグループ および 自動フォーカス 機能の使用方法を説明する例があります。さらに、この例は Java と MVFLEX 式言語のダイアレクトを組み合わせる方法も説明し、accumulate の使用やルールセット内から Java 関数を読み出す方法についても実証します。
PetStore.java ファイルに格納されます。このコードは次のプリンシパルクラスを定義します (このコードには、Swing イベントを処理するために使用されるマイナークラスも複数含まれます)。
Petstore-main()メソッドが含まれます。PetStoreUI- Swing ベースのグラフィカルユーザーインターフェースを作成および表示します。マウスやボタンのクリックなど、主にさまざまな GUI イベントに応答する小型のクラスが複数含まれています。TableModel- テーブルデータを保持します。SwingAbstractTableModelクラスを拡張する Java Bean として考慮してください。CheckoutCallback- このクラスにより、グラフィカルユーザーインターフェースがルールと対話できます。Ordershow- ユーザーが購入を希望するアイテムを保持します。Purchase- 注文と購入済み製品の詳細を保存します。Product- 購入可能な製品の価格情報やその他の情報を保持する Java Bean です。
注記
Java Beans 形式になります。ここでは Swing について詳しく説明しませんが、 Sun Microsystems の Web サイト http://java.sun.com/docs/books/tutorial/uiswing/ に使用方法のチュートリアルがあります。
Petstore.java ファイルの Java コードの一部は次の通りです。
例8.48 PetStore.main にペットストアの RuleBase を作成
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newClassPathResource( "PetStore.drl",
PetStore.class ),
ResourceType.DRL );
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
// Create the stock.
Vector<Product> stock = new Vector<Product>();
stock.add( new Product( "Gold Fish", 5 ) );
stock.add( new Product( "Fish Tank", 25 ) );
stock.add( new Product( "Fish Food", 2 ) );
// A callback is responsible for populating the
// Working Memory and for firing all rules.
PetStoreUI ui = new PetStoreUI( stock,
new CheckoutCallback( kbase ) );
ui.createAndShowGUI();
DRL ファイルからルールをロードします。ファクトが即座にアサートされ実行される他の例とは異なり、この例ではファクトのアサートと実行が遅延されます。これは、Vector オブジェクトである stock を許可するコンストラクターを使用して PetStoreUI オブジェクトが作成される最後から 2 つ目の行によって決定されます。これは製品と、この直前にロードされた rule-base が含まれる CheckoutCallback クラスのインスタンスを収集します。
CheckoutCallBack.checkout() メソッドによって呼び出されます。これは、ユーザーが Checkout ボタンをクリックするとトリガーされます。
public String checkout(JFrame frame, List<Product> items) {
Order order = new Order();
// Iterate through list and add to cart
for ( Product p: items ) {
order.addItem( new Purchase( order, p ) );
}
// Add the JFrame to the ApplicationData to allow for user interaction
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
ksession.setGlobal( "frame", frame );
ksession.setGlobal( "textArea", this.output );
ksession.insert( new Product( "Gold Fish", 5 ) );
ksession.insert( new Product( "Fish Tank", 25 ) );
ksession.insert( new Product( "Fish Food", 2 ) );
ksession.insert( new Product( "Fish Food Sample", 0 ) );
ksession.insert( order );
ksession.fireAllRules();
// Return the state of the cart
return order.toString();
}
TableModel からこのリストが取得されます。
for ループは注文商品のリストを Order Java Bean に変換します (PetStore.java にあります)。
注記
注記
TableModel の内容がセッションの working memory にコピーされます。
working memory への呼び出しが 9 つあります。最初の呼び出しは新しい working memory ステートフルナレッジセッション (Knowledge Base 内) を作成します (この Knowledge Base は、main() メソッドで作成された CheckoutCallBack クラスで渡されることに注意してください)。
working memory と注文リストの両方に格納されます。最後の呼び出しは標準の fireAllRules() メソッドです。次に、ルールが実行された時にこのメソッドが何を行うか見てみましょう。
例8.49 パッケージ、インポート、グローバル、およびダイアレクト - PetStore.drl より抜粋
package org.drools.examples
import org.drools.WorkingMemory
import org.drools.examples.PetStore.Order
import org.drools.examples.PetStore.Purchase
import org.drools.examples.PetStore.Product
import java.util.ArrayList
import javax.swing.JOptionPane;
import javax.swing.JFrame
global JFrame frame
global javax.swing.JTextArea textArea
PetStore.drl ファイルの最初の部分には標準の package および import ステートメント (さまざまな Java クラスをルールが使用できるようにする) が含まれています。この他に、2 つの グローバル変数、frame および textArea があります。これらのグローバル変数は、Swing の JFrame コンポーネントおよび Textarea コンポーネントへの参照を保持します (Rules 変数は実行後すぐに期限切れとなりますが、グローバル変数はセッションの有効期間中、値が保持されます)。
PetStore.drl ファイルの最後の部分から抜粋したものです。これには、ルールによって参照される 2 つの関数が含まれています (これら 2 つの関数については次の項で取り上げます)。
function void doCheckout(JFrame frame, WorkingMemory workingMemory)
{
Object[] options = {"Yes","No"};
int n = JOptionPane.showOptionDialog(frame,
"Would you like to checkout?","",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,options,options[0]);
if (n == 0) {workingMemory.setFocus( "checkout" );}
}
function boolean requireTank(JFrame frame, WorkingMemory workingMemory,
Order order, Product fishTank, int total)
{
Object[] options = {"Yes","No"};
int n = JOptionPane.showOptionDialog(frame,
"Would you like to buy a tank for your " +
total + " fish?",
"Purchase Suggestion",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,options,options[0]);
System.out.print( "SUGGESTION: Would you like to buy a tank for your "
+ total + " fish? - " );
if (n == 0) {
Purchase purchase = new Purchase( order, fishTank );
workingMemory.insert( purchase );
order.addItem( purchase );
System.out.println( "Yes" );
} else {
System.out.println( "No" );
}
return true;
}
注記
Rules ファイルに格納すると、ペットストアの例がコンパクトになります。実際には、同じルールパッケージ内の個別のファイルに関数を格納するか、標準の Java クラスの静的メソッドとして個別のファイルに関数を格納する必要があります。これらの関数は import function my.package.Foo.hello でインポートします。
doCheckout()は、ユーザーに支払い (チェックアウト) するかどうかを尋ねるダイアログボックスを表示します。支払いしたい場合、フォーカスがcheckOutアジェンダグループに設定され、そのグループ内のルールに潜在的な実行パーミッションが与えられます。requireTank()は、水槽を購入したいかどうかをユーザーに尋ねるダイアログボックスを表示します。購入したい場合、working memoryの注文リストに追加されます。
auto-focus 属性が true に設定されていることが部分的に関係します)。
例8.50 アイテムをワーキングメモリーに格納 - PetStore.drl ファイルより抜粋
/// Insert each item in the shopping cart into the Working Memory
// Insert each item in the shopping cart into the Working Memory
rule "Explode Cart"
agenda-group "init"
auto-focus true
salience 10
dialect "java"
when
$order : Order( grossTotal == -1 )
$item : Purchase() from $order.items
then
insert( $item );
kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "show items" ).setFocus();
kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "evaluate" ).setFocus();
end
PetStore.drl) が算出されていないすべての注文に対して一致します。順番に各購入アイテムをループします。ルール名、salience (ルールが実行される順番を提示)、ダイアレクト (Java に設定) など、Explode Cart ルールにはすでに説明した項目があります。また、ルールには新しいアイテムが 3 つあります。
agenda-group "init"- アジェンダグループの名前です。この例では、グループに 1 つのルールしか存在しません。しかし、Java コードとルール結果はどちらもフォーカスをこのグループに設定しないため、実行する機会を得られるかどうかは次の属性に依存します。auto-focus trueは、Java コードからfireAllRules()が呼び出された時に、アジェンダグループの唯一のルールが実行する機会を得られるようにします。drools.setFocus()はshow itemsおよびevaluateアジェンダグループに順番にフォーカスを提供し、ルールの実行を許可します (実際には、確実に注文アイテムがメモリーに挿入されるよう、注文アイテムはすべてループされます。他のルールはその後順次実行されます)。
show items および evaluate アジェンダグループのルールを表しています。
例8.51 GUI のアイテムを表示 - PetStore.drl ファイルより抜粋
rule "Show Items" agenda-group "show items" dialect "mvel" when $order : Order( ) $p : Purchase( order == $order ) then textArea.append( $p.product + "\n"); end
show items アジェンダグループが最初に呼び出されます。これには、Show Items (小文字と大文字の違いに注意してください) と呼ばれるルールのみがあります。このルールは、各購入のログ詳細をテキストエリア (GUI 画面の下部) へ移動します (この操作に使用される textArea 変数は先に説明したグローバル変数の 1 つです)。
evaluate アジェンダグループも先にリストされた explode cart よりフォーカスを取得します。このアジェンダグループには、Free Fish Food Sample と Suggest Tank の 2 つのルールがあります。
例8.52 Evaluate アジェンダグループ: PetStore.drl ファイルより抜粋
// Free Fish Food sample when we buy a Gold Fish if we have not already //bought Fish Food and dont already have a Fish Food Sample rule "Free Fish Food Sample" agenda-group "evaluate" dialect "mvel" when $order : Order() not ( $p : Product( name == "Fish Food") && Purchase( product == $p ) ) not ( $p : Product( name == "Fish Food Sample") && Purchase( product == $p ) ) exists ( $p : Product( name == "Gold Fish") && Purchase( product == $p ) ) $fishFoodSample : Product( name == "Fish Food Sample" ); then System.out.println( "Adding free Fish Food Sample to cart" ); purchase = new Purchase($order, $fishFoodSample); insert( purchase ); $order.addItem( purchase ); end // Suggest a tank if we have bought more than 5 gold fish and do not // already have one rule "Suggest Tank" agenda-group "evaluate" dialect "java" when $order : Order() not ( $p : Product( name == "Fish Tank") && Purchase( product == $p ) ) ArrayList( $total : size > 5 ) from collect( Purchase ( product.name == "Gold Fish" ) ) $fishTank : Product( name == "Fish Tank" ) then requireTank(frame, drools.getWorkingMemory(), $order, $fishTank, $total); end
Free Fish Food Sample ルールは以下の場合のみ実行されます。
- ユーザーが魚の餌を持っていない場合。
- ユーザーが魚の餌の無料サンプルを持っていない場合。
- ユーザーの注文に金魚が含まれる場合。
Fish Food Sample) が生成され、Fish Food Sample の注文に追加されます。
Suggest Tank ルールは次の 2 つの条件が満たされた場合のみ実行されます。
- ユーザーが水槽を注文していない場合。
- ユーザーが金魚関係の製品を 6 つ以上注文した場合。
requireTank() 関数が呼び出されます。確認後、水槽が注文に追加されます。ルールが requireTank() 関数を呼び出す時、ルールがグローバル frame 変数を渡すことに注意してください。これは、関数が Swing グラフィカルユーザーインターフェースのハンドルを持つからです。
do checkout と呼ばれます。
例8.53 チェックアウトの実行: PetStore.drl ファイルより抜粋
rule "do checkout" dialect "java" when then doCheckout(frame, drools.getWorkingMemory()); end
do checkout ルールには設定されたアジェンダグループや auto-focus 属性がありません。そのため、デフォルトのアジェンダグループの一部と見なされ、明示的なフォーカスを受け取るよう設定されたルールがすべて完了したときに、フォーカスを自動的に受け取ります。
doCheckout() 関数を呼び出します。この場合、ルールはグローバル frame 変数を渡し、Swing グラフィカルユーザーインターフェースのハンドルを関数に付与します (上記の通り、doCheckout() 関数はユーザーに確認ダイアログボックスを表示します。確認後、関数はフォーカスを checkout アジェンダグループに渡し、次のルールセットを実行できるようにします)。
例8.54 チェックアウトのルール: PetStore.drl ファイルより抜粋
rule "Gross Total"
agenda-group "checkout"
dialect "mvel"
when
$order : Order( grossTotal == -1)
Number( total : doubleValue ) from accumulate( Purchase ( $price : product.price ),sum( $price ) )
then
modify( $order ) { grossTotal = total };
textArea.append( "\ngross total=" + total + "\n" );
end
rule "Apply 5% Discount"
agenda-group "checkout"
dialect "mvel"
when
$order : Order( grossTotal >= 10 && < 20 )
then
$order.discountedTotal = $order.grossTotal * 0.95;
textArea.append("discountedTotal total="+$order.discountedTotal+"\n");
end
rule "Apply 10% Discount"
agenda-group "checkout"
dialect "mvel"
when
$order : Order( grossTotal >= 20 )
then
$order.discountedTotal = $order.grossTotal * 0.90;
textArea.append("discountedTotal total="+$order.discountedTotal+"\n");
end
checkout アジェンダグループには 3 つのルールがあります。
Gross Total- まだ実行されていない場合、製品価格を累積し、合計をworking memoryに保存して、SwingText Areaに表示します (textAreaグローバル変数を使用)。- 総合計が 10 と20 の間になる場合、
Apply 5% Discountが割引後の合計を計算し、working memoryへ追加してテキストエリアに表示します。 - 総合計が 20 を越える場合、
Apply 10% Discountが割引後の合計を計算し、working memoryへ追加してテキストエリアに表示します。
PetStore.java というファイルには main() メソッドが含まれているため、コマンドラインまたは IDE 内より標準の Java アプリケーションとして実行することができます (クラスパスが適切に設定されていることを前提とします)。

図8.13 起動直後の Pet Store Demo
main()メソッドが実行され、ルールベースがロードされますが、ルールはまだ実行されません (ここまで、このコードがルールと何らかの関係がある唯一のコードになります)。- 新しい
PetStoreUIオブジェクトが作成され、ルールベースのハンドルが付与されます。このハンドルは後で使用します。 - さまざまな Swing コンポーネントが操作を実行します。この時点で初めて上記の画面が表示され、ユーザーによる入力を待ちます。

図8.14 製品が選択された Pet Store Demo
注記
TableModel オブジェクトへ追加して右上の部分に表示します (これは Model View Controller 設計パターンの従来の実装であることに注意してください)。
- Checkout ボタンがクリックされると、Swing クラスによって
CheckOutCallBack.checkout()メソッドが呼び出されます。このメソッドはTableModelオブジェクト (グラフィカルユーザーインターフェースの右上に表示されます) よりデータを挿入し、さらにセッションのworking memoryに置きます。その後、ルールが実行されます。 Explode Cartルールは、自動フォーカス設定がtrueであるため、最初に実行されます。カート内のすべての製品をループし、確実にこれらの製品がworking memoryに存在するようにします。次に、Show ItemsおよびEvaluationアジェンダグループに実行パーミッションを与えます。これらのグループにあるルールは、カートの内容をテキストエリア (ウインドウの下部) に追加します。さらに、魚の餌をユーザーに無料で提供するか決定し、水槽を購入するかどうかをユーザーに尋ねます。水槽購入の画面は次のようになります。
図8.15 水槽を購入しますか?
Do Checkoutルールが次に実行されます。これは、現在フォーカスを持つアジェンダグループが他になく、デフォルトのアジェンダグループの一部であるためです。このルールは、次の質問が含まれるダイアログボックスを表示するdoCheckout()関数を常に呼び出します。"Would you like to Checkout?"
doCheckout()関数はフォーカスをcheckoutアジェンダグループに設定し、このグループのルールが実行する選択肢を与えます。checkoutアジェンダグループのルールは「カート」の内容を表示し、適切な割引を適用します。- 下図の通り、さらに製品をチェックアウトするか (これによりルールが再度実行されます) またはグラフィカルユーザーインターフェースが閉じられるかに応じて、Swing はユーザーの入力を待ちます。

図8.16 ルールがすべて実行され、アプリケーションが終了
System.out 呼び出しをさらに追加して、イベントの流れを実証することもできます。上記の例に表された Console の出力は次のようになります。
例8.55 ペットストア GUI 実行後のコンソール出力
Adding free Fish Food Sample to cart SUGGESTION: Would you like to buy a tank for your 6 fish? - Yes
8.7. 数独の例
| 名前: | 数独 |
| メインクラス: | org.drools.examples.sudoku.Main |
| タイプ: | Java アプリケーション |
| Rules ファイル: | sudokuSolver.drl, sudokuValidator.drl |
| 目的: | 論理的な問題を解決する方法を実証し、複雑なパターンマッチングを使用する方法を説明します。 |
working memory への変更に基づいて画面を更新し、 JBoss Rules とグラフィカルユーザーインターフェースベースのアプリケーションを統合する方法についても説明します。
8.7.1. 数独の概要
8.7.2. 例の実行
drools-examples ファイルをダウンロードし、インストールします。その後、 java org.drools.examples.sudoku.Main を実行します (この例には Java 5 が必要です)。比較的簡単な一部が埋められたグリッドがウインドウに表示されます。

図8.17 一部が埋められたグリッド
rules engine が残りの値を埋めます。Console はルールが実行されるとルールの詳細情報を表示し、パズルの解き方を実証します。
Rule #3 determined the value at (4,1) could not be 4 as this value already exists in the same column at (8,1) Rule #3 determined the value at (5,5) could not be 2 as this value already exists in the same row at (5,6) Rule #7 determined (3,5) is 2 as this is the only possible cell in the column that can have this value Rule #1 cleared the other PossibleCellValues for (3,5) as a ResolvedCellValue of 2 exists for this cell. Rule #1 cleared the other PossibleCellValues for (3,5) as a ResolvedCellValue of 2 exists for this cell. ... Rule #3 determined the value at (1,1) could not be 1 as this value already exists in the same zone at (2,1) Rule #6 determined (1,7) is 1 as this is the only possible cell in the row that can have this value Rule #1 cleared the other PossibleCellValues for (1,7) as a ResolvedCellValue of 1 exists for this cell. Rule #6 determined (1,1) is 8 as this is the only possible cell in the row that can have this value
engine は 2 番目の rule base を処理します。これにより、解答が完全で有効であることがチェックされます。この例では、すべてが完全で有効であるため、結果として Solve ボタンが無効になり、以下のメッセージが表示されます。
Solved (1052ms)

図8.18 解決済みのグリッド
5 が 2 つあります)。

図8.19 無効なグリッド
Validation Rule Set が発見した問題をすべて Console に出力することを確認してください。
There are two cells on the same column with the same value at (6,0) and (4,0) There are two cells on the same column with the same value at (4,0) and (6,0) There are two cells on the same row with the same value at (2,4) and (2,2) There are two cells on the same row with the same value at (2,2) and (2,4) There are two cells on the same row with the same value at (6,3) and (6,8) There are two cells on the same row with the same value at (6,8) and (6,3) There are two cells on the same column with the same value at (7,4) and (0,4) There are two cells on the same column with the same value at (0,4) and (7,4) There are two cells on the same row with the same value at (0,8) and (0,0) There are two cells on the same row with the same value at (0,0) and (0,8) There are two cells on the same column with the same value at (1,2) and (3,2) There are two cells on the same column with the same value at (3,2) and (1,2) There are two cells in the same zone with the same value at (6,3) and (7,3) There are two cells in the same zone with the same value at (7,3) and (6,3) There are two cells on the same column with the same value at (7,3) and (6,3) There are two cells on the same column with the same value at (6,3) and (7,3)
engine が実際に見つけられないものがあります。例を確認するには、[]、[]、[] の順にクリックします。一部が埋められたグリッドがロードされます。
engine に提供する必要があります。
8.7.3. Java ソースとルールの概要
/src/main/java/org/drools/examples/sudoku ディレクトリにあります。2 つの DRL ルール定義ファイルは /src/main/rules/org/drools/examples/sudoku ディレクトリにあります。
org.drools.examples.sudoku.swing パッケージには、数独パズルのフレームワークを実装するクラスセットが含まれています。
注記
SudokuGridModel インターフェースを実装します (値はセルの値が決定していないことを示す null である場合があります)。
SudokuGridView は Swing コンポーネントです。SudokuGridModel の実装をグラフィカルに表すことができます。
SudokuGridEvent および SudokuGridListener はモデルのステート変更をビューに通知するため使用されます。セルの値が解決または変更されると、イベントが実行されます。JTable など、他の Swing コンポーネントで使用されるモデルビューコントローラーパターンの知識があるユーザーは、この挙動を理解できるはずです (このような概念を実証するため、SudokuGridSamples は部分的に完成されたパズルを複数提供します)。
org.drools.examples.sudoku.rules パッケージには、 JBoss Rules に基づいた SudokuGridModel の実装が含まれています。AbstractCellValue を拡張し、グリッドの特定セルの値を表す 2 つの Java オブジェクトが使用されます。この値には、セルの行と列の場所、値が含まれる 3x3 ゾーンのインデックス番号、およびセルに保持される実際の番号が含まれます。
PossibleCellValue はセルの値が現在不明であることを示しています (どのセルにも 2 から 9 個の可能な値があります)。
ResolvedCellValue は、セルの値が決定されたことを示します。セルの解決された値は 1 つのみです。
DroolsSudokuGridModel は SudokuGridModel を実装します。これは、一部指定されたセルのアレイ (最初は 2 次元) を CellValue Java オブジェクトのセットに変換します。これにより、solverSudoku.drl ルールファイルに基づいて working memory セッションが作成されます。また、CellValue オブジェクトも working memory に挿入します。
solve() メソッドが呼び出されると、パズルを解こうとする fireAllRules() が呼び出されます。
DroolsSudokuGridModel は WorkingMemoryListener を working memory にアタッチします。これにより、パズルが解決された時に挿入および取り消しイベントで呼び戻すことが可能です。新しい ResolvedCellValue が working memory に挿入されると、このコールバックにより実装は SudokuGridEvent を SudokuGridListener クライアントに対して実行することができ、リアルタイムで更新します。
"solver" working memory によってルールがすべて実行されると、DroolsSudokuGridModel が 2 番目のルールセットを実行します (これらのルールは validatorSudoku.drl ファイルから取得されます)。これらのルールは同じ Java オブジェクトのセットと動作し、結果となるグリッドが有効で完全な解答であるかを判断することが目的です。
注記
org.drools.examples.sudoku.Main クラスは上記のコンポーネントを組み合わせる Java アプリケーションを実装します。
org.drools.examples.sudoku パッケージには、solverSudoku.drl と validator.drl の 2 つの DRL ファイルが含まれています。solverSudoku.drl は数独パズルを解くために使用されるルールを定義します。validator.drl は、working memory の現在の状態が有効な解答を表すかどうかをテストするルールを定義します。これらのルールセットはPossibleCellValue および ResolvedCellValue オブジェクトをファクトとして使用します。また、両方とも実行時に Console ウィンドウへデータを出力します。
注記
WorkingMemoryListener を使用して出力をユーザーに表示します。
8.7.4. 検証ルール
validatorSudoku.drl ファイルにある検証ルールが従うプロセスは次の通りです。
- 最初のルールは、
working memoryにPossibleCellValueオブジェクトがないことを確認します (パズルが解かれると、各セルにResolvedCellValueオブジェクト 1 つのみが存在するはずです)。 - 他の 3 つのルールは
ResolvedCellValueオブジェクトすべてと一致し、$resolved1 という変数へバインドされます。そして、同じ値が含まれ、同じ行、列および 3x3 ゾーンにあるResolvedCellValuesを探します。 - これらのルールが実行されると、解答が無効である理由を示すメッセージが文字列のグローバルリストに追加されます。
DroolsSudokoGridModelはルールセットを実行する前にこのリストをインジェクトします (また、fireAllRules()を呼び出した後にこのリストが空であるかどうかを確認します。空でない場合、リストに全文字列を出力します。空でない場合、すべての文字列がリストに出力され、グリッドが解決していないことを示すフラグが設定されます)。
8.7.5. ルールの解決
solverSudoku.drl ファイルにある「解決」ルールの前に実行されるさらに複雑なプロセスです。
- ルール
#1は、無効な解答が存在する行と列に対応するPossibleCellValuesのworking memoryを消去します。他の複数のルールは、セルに値が必要であると判断した後、特定行および列のworking memoryにResolvedCellValuesを挿入します。このルールは重要であるため、他のルールよりも大きな salience の値が指定されます。そのため、左側がtrueになると、即座にactivationsがアジェンダの最上部に移動され、実行されます。これにより、他のルールが誤って実行されないようにします。このルールはResolvedCellValue上でupdate()も実行します (これは、working memoryにアタッチされたWorkingMemoryListenersへ JBoss Rules がイベントを送信するようにルールが変更されていなくても発生します。このようなイベントを実行すると、ルールがルール自体を更新できます)。これにより、グラフィカルユーザーインターフェースがグリッドの新しい状態を表示します。 - ルール
#2は、可能な値が 1 つのみであるグリッドのセルを識別します。when句の最初の行は、working memoryのPossibleCellValueオブジェクトすべてと一致します。2 行目はnotキーワードの使用法を表しています。注記
このルールは、異なる値を持つ同じ行と列のPossibleCellValueが他にない場合のみ実行されます。このルールが実行されると、その行と列の単一のPossibleCellValueがworking memoryから取り消され、同じ値のResolvedCellValueに置き換えられます。 - ルール
#3は、ResolvedCellValueと同じ値がある場合に行からPossibleCellValuesを削除します。つまり、解決された値がセルに含まれる場合、パズルのルールに従うため同じ値が含まれる同じ行の他のセルを消去する必要があります。when句の最初の行は、working memoryのResolvedCellValueオブジェクトをすべて見つけます。2 番目の行は、これらのResolvedCellValueオブジェクトと同じ行と値を持つPossibleCellValuesを見つけます。 このようなPossibleCellValuesが見つかると、ルールがアクティベートされ、実行された時に取り消されます。これは、これらのセルに対する適切な解答ではないためです。 - ルール
#4と#5はルール#3と同じように動作しますが、ResolvedCellValuesと競合する冗長なPossibleCellValuesがないか列とゾーンをチェックします。 - ルール
#6は、セルの可能な値が該当する行に 1 つだけある場合をチェックします。左側の最初の行はworking memoryのPossibleCellValueファクトすべてと一致し、結果を複数のローカル変数に格納します。2 行目は、同じ値を持つ他のPossibleCellValueオブジェクトが同じ行に存在しないことをチェックします。3 - 5 行目は、同じ値を持つResolvedCellValueが同じゾーン、行、または列に存在しないことをチェックします (これは、このルールが不完全な状態で実行されないようにするためです)。注記
また、3 - 5 行目を行を削除し、ルール#3、#4、および#5の salience の値を大きくしてこれらのルールが常にルール#6、#7、および#8の前に実行されるようにすることも可能です。ルールが実行された場合、$possibleはセルの値を表す必要があります。そのため、これを取り消し、同等のResolvedCellValueに置き換えます (これはルール #2 のプロセスと同じです)。 - ルール
#7と#8はルール#2と同じように動作しますが、#7はグリッドの該当列にある単一のPossibleCellValuesをチェックし、#8はグリッドの該当ゾーンにあるPossibleCellValuesをチェックします。 - ルール
#9は最も複雑です。これは「該当する値のペアが特定行の 2 つのセルにのみあり (たとえば、4と6の値はセル [0,3] および [0,5] の最初の行のみに存在できることを判断しました)、このセルのペアが他の値を保持できない場合、4 が含まれるペアと 6 が含まれるペアがどれであるは分かりませんが、これらの値がこれらの 2 つのセルになければならないことはわかっています。そのため、これらの値が同じ行の他の場所で発生する可能性を排除することができます」ということです。 - ルール
#10と#11は ルール#9と同じように動作しますが、#10は該当列に可能な値が 2 つのみ存在することをチェックし、#8は該当ゾーンに可能な値が 2 つのみ存在することをチェックします。
8.7.6. 将来的な開発に関する提言
- Agenda Groups: 段階的な実行 に 宣言ツール を使用します。この例では、「解決」 と 「検証」の 2 つの段階があることが分かるはずです。現在、これらは 2 つのルールベースファイルより実行されます。「解決」と「検証」のカテゴリーに分類し、すべてのルールに対してアジェンダグループを定義することが推奨されます。その後、すべてが単一の
rule-baseよりロードされます。engineはこれらを順番に即座に実行します。 - 自動フォーカス: このメソッドを使用し、通常のルール実行の例外に対応します。現時点では、入力データーまたは解決ルールのどちらかに不整合があった場合、そのまま無駄にルールを実行せずに、即座に報告した方がよいでしょう。すでに単一の
rule-baseが作成されているため、パズルの一貫性の検証に使用される各ルールに対して auto-focus 属性を定義します。 - 論理挿入: 不整合は
working memoryに誤ったデータがある場合のみ発生します。そのため、したがって、検証ルールが不整合を 理論的に挿入 すると言えます (誤ったデータが取り消されると、すぐに不整合はなくなります)。 session.iterateObjects(): 現時点では、グローバルリストは問題を記録するために使用されますが、session.iterateObjects( new ClassObjectFilter( Inconsistency.class ) )を介して必要な問題を呼び出すようstateful sessionに要求すると、より興味深いでしょう。inconsistencyを使用すると、不正なセルに色 (赤など) を付けて見やすくすることもできます。kcontext.getKnowledgeRuntime().halt(): ソフトウェアが検出したエラーを即座に報告した場合でも、ルールの評価を停止するようエンジンに伝える方法が必要です。これは、不整合が存在する時にhalt()コードを呼び出すルールをプログラミングして作成します。- Queries:
DroolsSudokuGridModelのgetPossibleCellValues(int row, int col)メソッドを見てください。実際に必要なCellValueオブジェクトを探しながらすべてのCellValueオブジェクトを繰り返すことが分かります。JBoss Rules クエリを使用すると、この処理の効率を向上することができます。必要なオブジェクトを返し、きれいに繰り返すクエリを定義します (必要に応じて他のクエリを定義します)。 - サービスとしてのグローバル: この変更の主な目的は、これに続くものを容易にすることですが、これ自体にも便利です。サービスとしてのグローバル の使用法について学ぶには、最初に
callbackを設定することが理想的です。この変更は、該当のセルに対してResolvedCellValueを見つける各ルールがグラフィカルユーザーインターフェースを「呼び出し」て、更新することができ、ユーザーにフィードバックを即座に提供することを意味します。また、最後に見つかったセルの番号を違う色に「塗り替え」、異なるルールの結果を素早く識別できるようにすることもできます。 - ステップバイステップ実行: 即座にユーザーフィードバックを提供できるようコールバックを設定すると、JBoss Rules の 制約実行 機能を使用できます。グラフィカルユーザーインターフェースにボタンを追加し、クリックした時に
fireAllRules( 1 )を呼び出して 1 つのルールを実行します。このようにして、ユーザーはエンジンが行っていることをステップごとに確認できます。
8.8. 数字当て
| 名前: | 数字当て |
| メインクラス: | org.drools.examples.numberguess.NumberGuessExample |
| タイプ: | Java アプリケーション |
| Rules ファイル: | NumberGuess.drl |
| 目的: | ルールを整理するためルールフローを使用する方法を実証します。 |
Number Guess の例を用いて、ルールの実行順序を制御するための rule-flow の使用方法について学びましょう。ルールのグループを実行する順序を明確に示すため、標準的なワークフロー図が使用されます。
例8.56 数字当てルールベースの作成
final KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClassPathResource( "NumberGuess.drl", ShoppingExample.class ),ResourceType.DRL ); kbuilder.add( ResourceFactory.newClassPathResource( "NumberGuess.rf", ShoppingExample.class ),ResourceType.DRF ); final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
add() メソッドを用いてルールをロードするプロセスは、前述の例と同じですが、任意の手順が 1 つ追加されます。この追加手順では、1 つの knowledge base でさまざまな rule-flow を使用する機能を指定するため、現在の rule-flow に NumberGuess.rf 行を追加します (この手順を省略すると、knowledge base が前述の例と同じ方法で作成されます)。
例8.57 ルールフローの開始
final StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger (ksession, "log/numberguess"); ksession.insert( new GameRules( 100,5 ) ); ksession.insert( new RandomNumber() ); ksession.insert( new Game() ); ksession.startProcess( "Number Guess" ); ksession.fireAllRules(); logger.close(); ksession.dispose();
knowledge base を使用して「ステートフル」セッションを取得します。このセッションに ファクト (標準的な Java オブジェクトのこと) を挿入します。簡単にするため、この例ではすべてのクラスが NumberGuessExample.java という 1 つのファイルに含まれています。
GameRulesクラスは最大範囲と回答可能な回数を提供します。RandomNumberクラスは、自動的にゼロから 100 までの数字を生成し、getValue()メソッドを用いて挿入した後にルールで使用できるようにします。Gameクラスは以前回答した答えと、現在の回答数を追跡します。
重要
fireAllRules() メソッドを呼び出す前に、startProcess() メソッドを用いて以前ロードされたプロセスを開始します。
注記
working memory セッションが dispose() メソッドへの呼び出しによって消去されるようにすれば十分です。

図8.20 数字当ての例のルールフロー
NumberGuess.rf ファイルを開きます。外見上、アイコンは JBoss jBPM Workflow のアイコンと大変似ていて (同じではない)、このイメージは標準的なフローチャートと非常に似ています。
X-Stream より、人言が判読できる XML 形式で保存されます。
rule-flow 上のアイテムを選択する 前に 行います (または rule-flow の空白スペースをクリックした後)。その後、次のプロパティーが使用できるようになります。

図8.21 文字当てルールフローのプロパティー
注記
session.startprocess() メソッドが最初に実行した時に開始された rule-flow プロセスの ID 番号が表示されています。
Number Guess の rule-flow のノード型は次の通りです。
- start (緑の矢印) および end ノード (赤いボックス) は
rule-flowが開始および終了する場所を示します。 - RuleFlowGroup (シンプルな黄色のボックスによって示されます) は、本項の後半で説明する
DRLファイルのRuleFlowGroupsへマップします。たとえば、フローがToo HighRuleFlowGroupに達すると、補足的なruleflow-groupToo High 属性でマーク付けされたルールのみが実行を許可されます。 - action nodes (歯車のようなエンブレムを持つ黄色のボックスで示されます) は、標準的な Java メソッドの呼び出しを実行できます。この例では、ほとんどの
action nodesがSystem.out.printlnを呼び出し、ユーザーに何が起こっているか伝えます。 Guess CorrectやMore Guesses Joinなどの split および join nodes (青の楕円) は、制御のフローを分割 (さまざまな条件による) または再結合できる場所を示します。- arrows はさまざまなノード間のフローの方向を示します。
Guess RuleFlowGroup は、実行する Get User Guess ルールのみを許可します。これは、一致する ruleflow-group "Guess" の属性を持つ唯一のルールであるからです。
例8.58 ルールフローの特定場所でのみ実行されるルールの例
rule "Get user Guess"
ruleflow-group "Guess"
no-loop
when
$r : RandomNumber()
rules : GameRules( allowed : allowedGuesses )
game : Game( guessCount < allowed )
not ( Guess() )
then
System.out.println( "You have " + ( rules.allowedGuesses - game.guessCount )
+ " out of " + rules.allowedGuesses
+ " guesses left.\nPlease enter your guess from 0 to "
+ rules.maxRange );
br = new BufferedReader( new InputStreamReader( System.in ) );
i = br.readLine();
modify ( game ) { guessCount = game.guessCount + 1 }
insert( new Guess( i ) );
end
guessCount が allowedGuesses の数よりも小さく (GameRules クラスより読み取り)、ユーザーが正しい数字を回答していない場合に、左側 (when 句) は RandomNumber オブジェクトが working memory に挿入されるたびにアクティベートされるよう指示します。
then が使用されます) はユーザーにメッセージを出力し、System.in よりユーザーの応答が到達するのを待ちます。この入力を受け取った後 (return キーが押されるまで System.in がブロックします)、回答数と実際の回答を更新または変更し、これらが working memory で使用可能になります。
Rules の残りの部分は比較的簡単です。ダイレクトが MVEL に設定され、さまざまな Java クラスがインポートされることをパッケージが宣言します。このファイルには合わせて 5 つのルールが存在します。
Get User Guess(上記で検証されたルール)- 最大の回答を記録するルール
- 最小の回答を記録するルール
- 回答を検査し、不正解の場合にメモリーから取り消すルール。
- すべての回答が使用されたことをユーザーに通知するルール。
rule-flow との間を統合する 1 つのポイントが ruleflow-group 属性です。2 つ目のポイントは DRL ルールファイルと rule-flow.rf ファイルの間です。split nodes (青の楕円) はワーキングメモリーの値を使用して (ルールによって更新される) 実行するアクションのフローを決定します。この挙動を確認するには、Guess Correct Node をクリックします。Properties ビュー内より Constraints Editor を開きます (Constraints プロパティー行をクリックした後に表示される右側のボタンを押します)。以下が表示されます。

図8.22 GuessCorrect ノードの制約を編集
working memory のオブジェクトを参照できます。consequence (右側にコード化) として、左側の式が true である場合に制御のフローがこのノードに従います。

図8.23 GuessCorrect の Constraints Editor
NumberGuess.java の例には main() メソッドが含まれるため、標準の Java アプリケーション (BASH から、または IDE 経由) として実行できます。典型的数字当てゲームでは次の対話が行われます (数字はユーザーによって入力されます)。
例8.59 数字当てプログラムが人間に勝った場合のコンソールの出力例
You have 5 out of 5 guesses left. Please enter your guess from 0 to 100 50 Your guess was too high You have 4 out of 5 guesses left. Please enter your guess from 0 to 100 25 Your guess was too low You have 3 out of 5 guesses left. Please enter your guess from 0 to 100 37 Your guess was too low You have 2 out of 5 guesses left. Please enter your guess from 0 to 100 44 Your guess was too low You have 1 out of 5 guesses left. Please enter your guess from 0 to 100 47 Your guess was too low You have no more guesses The correct guess was 48
NumberGuessExample.javaのMain()メソッドはRuleBaseをロードしてStatefulSessionを取得し、Game、GameRules、およびRandomNumber(ターゲット番号が含まれる) オブジェクトを挿入します。このメソッドは使用されるプロセスフローを設定し、すべてのルールを実行します。そして、制御がRuleFlowへ移ります。NumberGuessExample.rfRuleFlowがStartノードで開始されます。- 制御が
Guessノードに移り、More Guesses結合ノードより移行します。 GuessノードでGet User GuessRuleFlowGroupが有効になります。この場合、Guessルール (NumberGuess.drlファイルに存在する) がトリガーされます。ユーザーにメッセージを表示し、応答を受け取って、メモリーに格納します。制御が次のノードであるGuess Correctに移ります。- このノードでは、制約が現在のセッションを検証し、取るべきパスを決定します。4. で回答した数字が大きすぎるまたは小さすぎる場合、フローは以下の両方を持つパスに移ります。
- 通常の Java コードを使用して下記のステートメントのいずれかを出力するアクションノード。
Too high
Too low
Rulesファイル内より最大または最小の回答ルールがトリガーされるようにするRuleFlowGroup。
フローはこれらのノードから 6. で指定されたノードに移ります。4. の回答が正しい場合、通常の Java コードを持つアクションコードが次のステートメントを出力します。You guessed correctly.
ここには、結合ノードがあるため (ルールフロー終了の直前) 、no-more-guessesパス (7.) がRuleFlowも終了します。 - 制御が結合ノードを介して
RuleFlowへ移り、Guess IncorrectRuleFlowGroup(working memoryより回答を取り消すようルールをトリガーします) へ移動します。その後、More Guesses決定ノードへ移動します。 More Guesses決定ノード (ルールフローの右側) は制約を使用して (ルールがworking memoryに格納した値を確認して)、これ以上回答できるかどうかを決定します。回答可能な場合は 3. に戻ります。これ以上回答できない場合は、以下を表示するルールをトリガーするRuleFlowGroupを介して、制御がワークフローの最後に移動します。You have no more guesses
- 正しい数字が回答されるか、回答回数を使い果たすまで、3. から 7. のループが継続します。
8.9. Miss Manners とベンチマーキング
| 名前: | Miss Manners |
| メインクラス: | org.drools.benchmark.manners.MannersBenchmark |
| タイプ: | Java アプリケーション |
| Rules ファイル: | manners.drl |
| 目的: | Manners ベンチマークについて説明し、深さの競合解決 について取り上げます。 |
8.9.1. はじめに
8.9.1.1. ベンチマーキングのスクリプト
- Miss Manners は 深さ優先 探索を使用して座席の配置を決定します。男性と女性を交互に配置し、共通する趣味を持つ人が隣同士になるようにします。
- Waltz は線画を 3 次元にします。3 次元にするため、線をラベル付けし、制約伝播 を使用します。
- WaltzDB は
Waltzの汎用バージョンで、4 本以上の線による接合点をサポートし、データベースを使用します。 - 自動ルートプランナー (ARP) は、ロボット車両向けに設計されたルートプランナーです。
A*探索アルゴリズムを使用します。 - Weavera は、チャネルとボックス用の VLSI (Very Large Scale Integration) ルーターです。ブラックボード 技術を使用します。
注記
Miss Manners は、ルールエンジン業界の実質上の業界標準となるベンチマーキングテストです。この挙動は現在では広く知られるようになり、このテストを効率的に実行するよう多くのエンジンが最適化されたため、有用性は低くなっています。そのため、Waltz の人気が以前よりも高くなっています。
8.9.1.2. Miss Manners の実行フロー
Miss Manners は context インスタンスを使用し実行フローを制御します。次のアクティビティ図は、ルール実行と context の現状との関係を示すために分割されています。

図8.24 Miss Manners のアクティビティ図
8.9.1.3. データと結果
8.9.1.4. データ
(guest (name n1) (sex m) (hobby h1) ) (guest (name n2) (sex f) (hobby h1) ) (guest (name n2) (sex f) (hobby h3) ) (guest (name n3) (sex m) (hobby h3) ) (guest (name n4) (sex m) (hobby h1) ) (guest (name n4) (sex f) (hobby h2) ) (guest (name n4) (sex f) (hobby h3) ) (guest (name n5) (sex f) (hobby h2) ) (guest (name n5) (sex f) (hobby h1) ) (last_seat (seat 5) )
8.9.1.5. 結果
Assign Seat ルールが実行されると出力されます。各行に前の行よりも 1 大きい pid 値 があることに注意してください (この重要性については、本項の後半の Assign Seat ルールの説明で取り上げます)。 ls、rs、ln、および rn の略語は、左および右側の座席とこれらの座席に割り当てられたゲストの名前を表します。実際の実装では、長い属性名 (leftGuestName など) を使用しますが、本 ガイド では元の実装で使用された表記法が保持されます。
[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] [Seating id=2, pid=1, done=false, ls=1, ln=n5, rs=2, rn=n4] [Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3] [Seating id=4, pid=3, done=false, ls=3, rn=n3, rs=4, rn=n2] [Seating id=5, pid=4, done=false, ls=4, ln=n2, rs=5, rn=n1]
8.9.2. 深い分析
8.9.2.1. 不正行為
Miss Manners は、クロス積の結合 と Agenda のアクティビティを実行するよう設計されています。これを理解していないユーザーが、パフォーマンスを改善するために例を「微調整」してしまうことがありますが、これにより Manners ベンチマークのポートは意味がなくなります。Miss Manners における既知の不正行為とポートエラーのリストは次の通りです。
- ゲストの趣味を 1 つずつ 1 つのファクトとしてアサートせずに、アレイを使用。これにより、
cross productsが大幅に減少します。 - データシーケンスの変更。これにより、一致する量が減少し、実行速度が向上します。
- テストアルゴリズムが
first-best-matchコードのみを使用するようnot条件要素を変更。これは、基本的に 後向き連鎖 を実行するようテストテストアルゴリズムを変換します。このエリアの結果は、他の後向き連鎖ルールエンジンまたはMiss Mannersのポートのみに相当します。 rule engineがゲストと座席を時期尚早に一致するよう、contextを削除。適切なポートは、context startによりファクトが一致しないようにします。rule engineが 組み合わせ パターンマッチングを実行しないようにする。NOT CEの結果として 推論サイクル で取り消されるファクトがない場合、ポートが不完全。
8.9.2.2. 最初の座席の割り当て
START_UP に変わった後、アサートされたゲストごとに activations が作成されます。各 activation は単一の working memory アクションの結果として作成されるため、すべて同じ activation time タグを持ちます (アサートされる最後のゲストは、さらに大きな fact time タグを持ちます)。
注記
Assign Seat ルールには大きく影響します。
path がアサートされます。次に、findSeating ルールのアクティベーションを作成するため、Context 属性のステートが設定されます。
rule assignFirstSeat
when
context : Context( state == Context.START_UP )
guest : Guest()
count : Count()
then
String guestName = guest.getName();
Seating seating = new Seating( count.getValue(),
1,
true,
1,
guestName,
1,
guestName);
insert( seating );
Path path = new Path( count.getValue(), 1, guestName );
insert( path );
modify( count ) { setValue ( count.getValue() + 1 ) }
System.out.println( "assign first seat : "+seating+" : "+path);
modify( context ) { setState( Context.ASSIGN_SEATS ) }
end8.9.2.3. "findSeating" ルール
findSeating ルールは座席の配置を決定します。実行されると、アサートされた各ゲストに対して、アサートされた各座席配置の cross-product ソリューションを生成します (ルール自体とすでに割り当てられた選択済みソリューションを除く)。
rule findSeating
when
context : Context( state == Context.ASSIGN_SEATS )
$s : Seating( pathDone == true )
$g1 : Guest( name == $s.rightGuestName )
$g2 : Guest( sex != $g1.sex, hobby == $g1.hobby )
count : Count()
not ( Path( id == $s.id, guestName == $g2.name) )
not ( Chosen( id == $s.id, guestName == $g2.name, hobby == $g1.hobby) )
then
int rightSeat = $s.getRightSeat();
int seatId = $s.getId();
int countValue = count.getValue();
Seating seating = new Seating( countValue,
seatId,
false,
rightSeat,
$s.getRightGuestName(),
rightSeat + 1,
$g2.getName()
);
insert( seating );
Path path = new Path( countValue, rightSeat + 1, $g2.getName() );
insert( path );
Chosen chosen = new Chosen( seatId, $g2.getName(), $g1.getHobby() );
insert( chosen );
System.err.println( "find seating : "+seating+" : "+path+" : "+chosen);
modify( count ) {setValue( countValue + 1 )}
modify( context ) {setState( Context.MAKE_PATH )}
end=>[ActivationCreated(35): rule=findSeating [fid:19:33]:[Seating id=3, pid=2, done=true, ls=2, ln=n4, rs=3, rn=n3] [fid:4:4]:[Guest name=n3, sex=m, hobbies=h3] [fid:3:3]:[Guest name=n2, sex=f, hobbies=h3] =>[ActivationCreated(35): rule=findSeating [fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] [fid:5:5]:[Guest name=n4, sex=m, hobbies=h1] [fid:2:2]:[Guest name=n2, sex=f, hobbies=h1] =>[ActivationCreated(35): rule=findSeating [fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] [fid:9:9]:[Guest name=n5, sex=f, hobbies=h1] [fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]
注記
activations を作成することは意味がないように見えますが、Miss Manners の作成では優良なルール設計を目的としていないことを思い出してください。これは、cross-product matching 処理と Agenda 機能に対して完全にストレステストを行うために、意図的にルールセットが下手に記述されています。
注記
time タグ値は同じ 35 であることに注意してください。これは、ASSIGN_SEATS へ Context オブジェクトが変更になり、すべてアクティベートされたためです。OPS5 および LEX では、最後にアサートされた座席のアクティベーションが適切に実行されます。Depth では、accumulated fact time により、最後にアサートされる座席のアクティベーションが確実に実行されます。
8.9.2.4. "makePath" および "pathDone" ルール
makePath ルールは必ず pathDone の前に実行する必要があります。path オブジェクトは各座席の配置に対して最後までアサートされます (pathDone の条件は makePath のサブセットであることに注意してください。そのため、どのように makePath を最初に実行させるのか疑問に思う人がいるかもしれません)。
rule makePath when Context( state == Context.MAKE_PATH ) Seating( seatingId:id, seatingPid:pid, pathDone == false ) Path( id == seatingPid, pathGuestName:guestName, pathSeat:seat ) not Path( id == seatingId, guestName == pathGuestName ) then insert( new Path( seatingId, pathSeat, pathGuestName ) ); end
rule pathDone
when
context : Context( state == Context.MAKE_PATH )
seating : Seating( pathDone == false )
then
modify( seating ) {setPathDone( true )}
modify( context ) {setState( Context.CHECK_DONE)}
end
図8.25 Rete 図
activation time タグを持ちますが、makePath ルールの accumulate fact time タグの方が大きいため、makePath ルールが優先されます。
8.9.2.5. "Continue" および "Are We Done?" ルール
Are We Done は、最後の座席が割り当てられた時のみアクティベートします。この時点で、両方のルールが実行されます。makePath は常に pathDone よりも優先されますが、同じ理由で Are We Done は Continue よりも優先されます。
rule areWeDone
when
context : Context( state == Context.CHECK_DONE )
LastSeat( lastSeat: seat )
Seating( rightSeat == lastSeat )
then
modify( context ) {setState(Context.PRINT_RESULTS )}
endrule continue when context : Context( state == Context.CHECK_DONE ) then context.setState( Context.ASSIGN_SEATS ); update( context ); end
8.9.3. 出力の概要
Assign First Seat =>[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] =>[fid:14:14]:[Path id=1, seat=1, guest=n5] ==>[ActivationCreated(16): rule=findSeating [fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] [fid:9:9]:[Guest name=n5, sex=f, hobbies=h1] [fid:1:1]:[Guest name=n1, sex=m, hobbies=h1] ==>[ActivationCreated(16): rule=findSeating [fid:13:13]:[Seating id=1 , pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] [fid:9:9]:[Guest name=n5, sex=f, hobbies=h1] [fid:5:5]:[Guest name=n4, sex=m, hobbies=h1]* Assign Seating =>[fid:15:17] :[Seating id=2 , pid=1 , done=false, ls=1, lg=n5, rs=2, rn=n4] =>[fid:16:18]:[Path id=2, seat=2, guest=n4] =>[fid:17:19]:[Chosen id=1, name=n4, hobbies=h1] =>[ActivationCreated(21): rule=makePath [fid:15:17] : [Seating id=2, pid=1, done=false, ls=1, ln=n5, rs=2, rn=n4] [fid:14:14] : [Path id=1, seat=1, guest=n5]* ==>[ActivationCreated(21): rule=pathDone [Seating id=2, pid=1, done=false, ls=1, ln=n5, rs=2, rn=n4]* Make Path =>[fid:18:22:[Path id=2, seat=1, guest=n5]] Path Done Continue Process =>[ActivationCreated(25): rule=findSeating [fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] [fid:7:7]:[Guest name=n4, sex=f, hobbies=h3] [fid:4:4] : [Guest name=n3, sex=m, hobbies=h3]* =>[ActivationCreated(25): rule=findSeating [fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] [fid:5:5]:[Guest name=n4, sex=m, hobbies=h1] [fid:2:2]:[Guest name=n2, sex=f, hobbies=h1], [fid:12:20] : [Count value=3] =>[ActivationCreated(25): rule=findSeating [fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] [fid:9:9]:[Guest name=n5, sex=f, hobbies=h1] [fid:1:1]:[Guest name=n1, sex=m, hobbies=h1] Assign Seating =>[fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, lnn4, rs=3, rn=n3]] =>[fid:20:27]:[Path id=3, seat=3, guest=n3]] =>[fid:21:28]:[Chosen id=2, name=n3, hobbies=h3}] =>[ActivationCreated(30): rule=makePath [fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3] [fid:18:22]:[Path id=2, seat=1, guest=n5]* =>[ActivationCreated(30): rule=makePath [fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3] [fid:16:18]:[Path id=2, seat=2, guest=n4]* =>[ActivationCreated(30): rule=done [fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3]* Make Path =>[fid:22:31]:[Path id=3, seat=1, guest=n5] Make Path =>[fid:23:32] [Path id=3, seat=2, guest=n4] Path Done Continue Processing =>[ActivationCreated(35): rule=findSeating [fid:19:33]:[Seating id=3, pid=2, done=true, ls=2, ln=n4, rs=3, rn=n3] [fid:4:4]:[Guest name=n3, sex=m, hobbies=h3] [fid:3:3]:[Guest name=n2, sex=f, hobbies=h3], [fid:12:29]* =>[ActivationCreated(35): rule=findSeating [fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] [fid:5:5]:[Guest name=n4, sex=m, hobbies=h1] [fid:2:2]:[Guest name=n2, sex=f, hobbies=h1] =>[ActivationCreated(35): rule=findSeating [fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] [fid:9:9]:[Guest name=n5, sex=f, hobbies=h1], [fid:1:1] : [Guest name=n1, sex=m, hobbies=h1] Assign Seating =>[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]] =>[fid:25:37]:[Path id=4, seat=4, guest=n2]] =>[fid:26:38]:[Chosen id=3, name=n2, hobbies=h3] ==>[ActivationCreated(40): rule=makePath [fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2] [fid:23:32]:[Path id=3, seat=2, guest=n4]* ==>[ActivationCreated(40): rule=makePath [fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2] [fid:20:27]:[Path id=3, seat=3, guest=n3]* =>[ActivationCreated(40): rule=makePath [fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2] [fid:22:31]:[Path id=3, seat=1, guest=n5]* =>[ActivationCreated(40): rule=done [fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]* Make Path =>fid:27:41:[Path id=4, seat=2, guest=n4] Make Path =>fid:28:42]:[Path id=4, seat=1, guest=n5]] Make Path =>fid:29:43]:[Path id=4, seat=3, guest=n3]] Path Done Continue Processing =>[ActivationCreated(46): rule=findSeating [fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] [fid:5:5]:[Guest name=n4, sex=m, hobbies=h1], [fid:2:2] [Guest name=n2, sex=f, hobbies=h1] =>[ActivationCreated(46): rule=findSeating [fid:24:44]:[Seating id=4, pid=3, done=true, ls=3, ln=n3, rs=4, rn=n2] [fid:2:2]:[Guest name=n2, sex=f, hobbies=h1] [fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]* =>[ActivationCreated(46): rule=findSeating [fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] [fid:9:9]:[Guest name=n5, sex=f, hobbies=h1] [fid:1:1]:[Guest name=n1, sex=m, hobbies=h1] Assign Seating =>[fid:30:47]:[Seating id=5, pid=4, done=false, ls=4, ln=n2, rs=5, rn=n1] =>[fid:31:48]:[Path id=5, seat=5, guest=n1] =>[fid:32:49]:[Chosen id=4, name=n1, hobbies=h1]
Miss Manners ベンチマーキングスクリプトの仕組みや、注意すべき警告や落とし穴について理解できたと思います。
8.10. ライフゲーム (Conway's Game Of Life) の例
| 名前: | Conway's Game Of Life |
| メインクラス: | org.drools.examples.conway.ConwayAgendaGroupRun org.drools.examples.conway.ConwayRuleFlowGroupRun |
| タイプ: | Java アプリケーション |
| Rules ファイル: | conway-ruleflow.drl conway-agendagroup.drl |
| 目的: |
accumulate、collect、および from を実証します。
|

図8.26 新しいゲームの開始
- 生きているセルに隣接する生きたセルが 1 つ以下の場合、孤独により死滅します。
- 生きているセルに隣接する生きたセルが 4 つ以上の場合、過密により死滅します。
- 死んでいるセルに隣接する生きたセルがちょうど 3 つある場合、死んでいるセルが生き返ります。

図8.27 進行中のゲーム
AgendaGroups (ConwayAgendaGroupRun) と RuleFlowGroups (ConwayRuleFlowGroupRun) による 2 つの方法を表しています。これら方法を比較することは大変有益です。本章では、読者のほとんどが使用するルールフローバージョンについて説明します。
session に挿入されます。ルールフロープロセスは、register neighbour と呼ばれるグループのルールに実行パーミッションを与えます。ルールグループは Neighbour Relation クラスを使用して、北東、北、北西、西に隣接するセルを登録します。この関係は両方向であるため、南方向のセルにルールを作成する必要がないことに注意してください。また制約により、セルが最後から 1 つ前の列と一番上から 1 つ前の行に置かれることにも注意してください。アクティベーションがすべて実行されるまでに、すべてのセルが隣接するすべてのセルに関係します。
例8.60 隣接するセルの関係をすべて登録
rule "register north east"
ruleflow-group "register neighbour"
when
CellGrid( $numberOfColumns : numberOfColumns )
$cell: Cell( $row : row > 0, $col : col <
( $numberOfColumns - 1 ) )
$northEast : Cell( row == ($row - 1), col == ( $col + 1 ) )
then
insert( new Neighbor( $cell, $northEast ) );
insert( new Neighbor( $northEast, $cell ) );
end
rule "register north"
ruleflow-group "register neighbour"
when
$cell: Cell( $row : row > 0, $col : col )
$north : Cell( row == ($row - 1), col == $col )
then
insert( new Neighbor( $cell, $north ) );
insert( new Neighbor( $north, $cell ) );
end
rule "register north west"
ruleflow-group "register neighbour"
when
$cell: Cell( $row : row > 0, $col : col > 0 )
$northWest : Cell( row == ($row - 1), col == ( $col - 1 ) )
then
insert( new Neighbor( $cell, $northWest ) );
insert( new Neighbor( $northWest, $cell ) );
end
rule "register west"
ruleflow-group "register neighbour"
when
$cell: Cell( $row : row >= 0, $col : col > 0 )
$west : Cell( row == $row, col == ( $col - 1 ) )
then
insert( new Neighbor( $cell, $west ) );
insert( new Neighbor( $west, $cell ) );
end
Generation ルールフローが実行されます。このルールフローは繰り返しサイクルごとにセルの変更をすべて管理します。

図8.28 生成ルールフロー
evaluate グループより実行されます。その結果、このグループのアクティブなルールはすべて実行可能になります (このグループのルールは、本項の最初に説明したメイン Game ルールを適用します。これらのルールは、死滅するセルと生き返るセルを決定します)。
RuleFlowGroup の 1 つと関係します。
例8.61 ステート変更でセルを評価
rule "Kill The Lonely"
ruleflow-group "evaluate"
no-loop
when
# A live cell has fewer than 2 live neighbors
theCell: Cell(liveNeighbors < 2, cellState ==
CellState.LIVE, phase == Phase.EVALUATE)then
theCell.setPhase(Phase.KILL);
update( theCell );
end
rule "Kill The Overcrowded"
ruleflow-group "evaluate"
no-loop
when
# A live cell has more than 3 live neighbors
theCell: Cell(liveNeighbors >; 3, cellState ==
CellState.LIVE, phase == Phase.EVALUATE)then
theCell.setPhase(Phase.KILL);
update( theCell );
end
rule "Give Birth"
ruleflow-group "evaluate"
no-loop
when
# A dead cell has 3 live neighbors
theCell: Cell(liveNeighbors == 3, cellState ==
CellState.DEAD, phase == Phase.EVALUATE)then
theCell.setPhase(Phase.BIRTH);
update( theCell );
end
reset calculate ルールは、以前のデータ変更によって発生した計算アクティベーションを calculate グループから消去します。次に、「split」が入力されます。これにより、「kill」および「birth」グループの両方にあるアクティベーションを実行できます (これらのルールは状態の変更を適用します)。
例8.62 変更の適用
rule "reset calculate"
ruleflow-group "reset calculate"
when
then
WorkingMemory wm = drools.getWorkingMemory();
wm.clearRuleFlowGroup( "calculate" );
end
rule "kill"
ruleflow-group "kill"
no-loop
when
theCell: Cell( phase == Phase.KILL )
then
modify( theCell ){
setCellState( CellState.DEAD ),
setPhase( Phase.DONE );
}
end
rule "birth"
ruleflow-group "birth"
no-loop
when
theCell: Cell( phase == Phase.BIRTH )
then
modify( theCell ){
setCellState( CellState.LIVE ),
setPhase( Phase.DONE );
}
neighbour relation が使用されるため、生きている隣接するセルの数が増加または減少します。セルの数が変わったセルはすべて evaluate フェーズに設定され、ルールフロープロセスの次の段階で評価されます。
evaluate がクリックされた場合はエンジンがルールフロープロセスを再度実行します。
例8.63 ステート変更があったセルの評価
rule "Calculate Live"
ruleflow-group "calculate"
lock-on-active
when
theCell: Cell( cellState == CellState.LIVE )
Neighbor( cell == theCell, $neighbor : neighbor )
then
modify( $neighbor ){
setLiveNeighbors( $neighbor.getLiveNeighbors() + 1 ),
setPhase( Phase.EVALUATE );
}
end
rule "Calculate Dead"
ruleflow-group "calculate"
lock-on-active
when
theCell: Cell( cellState == CellState.DEAD )
Neighbor( cell == theCell, $neighbor : neighbor )
then
modify( $neighbor ){
setLiveNeighbors( $neighbor.getLiveNeighbors() - 1 ),
setPhase( Phase.EVALUATE );
}
end
付録A © 2011
付録B 改訂履歴
| 改訂履歴 | |||
|---|---|---|---|
| 改訂 5.2.0-0.1.400 | 2013-10-31 | ||
| |||
| 改訂 5.2.0-0.1 | Mon Jun 10 2013 | ||
| |||
| 改訂 5.2.0-0.1 | Thu May 9 2013 | ||
| |||
| 改訂 5.2.0-0 | Wed Dec 15 2010 | ||
| |||
| 改訂 5.1.0-0 | Wed Dec 15 2010 | ||
| |||
| 改訂 5.0.2-0 | Tue Jun 29 2010 | ||
| |||
| 改訂 5.0.2-0 | Wed May 5 2010 | ||
| |||
| 改訂 5.0.1-0 | Tue Oct 6 2009 | ||
| |||
| 改訂 5.0.0-0 | Mon May 18 2009 | ||
| |||
