Menu Close

JBoss Rules 5 リファレンスガイド

JBoss Enterprise BRMS Platform 5

開発者向け

エディッション 5.3.1

David Le Sage

Red Hat Engineering Content Services

概要

本書では、開発者向けに JBoss Rules を説明します。

前書き

第1章 はじめに

1.1. 対象読者

本書は、JBoss Rules のツールの使用方法を学習したいシステム管理者を対象としています。新規プロジェクトの作成、プロジェクトのデバッグ、エディターの使用方法について説明しています。

1.2. 本書の目的

本書は、JBoss Rules の使用方法に関する概要を説明することを目的としています。また、基本用語を詳しく説明するとともに、1 からルールを作成する方法も説明しています。プロジェクトのルール作成を支援するチュートリアルも複数含まれています。

第2章 クイックスタート

2.1. JBoss Rules

JBoss Rules は、JBoss Enterprise SOA Platform 製品に同梱されているビジネルルールエンジンの名称です。

2.2. JBoss Rules エンジン

JBoss Rules エンジンは、ルールを適用して知識表現と推論 (KRR: Knowledge Representation and Reasoning) 機能を開発者に提供するコンピュータープログラムです。

2.3. プロダクションルール

プロダクションルールは、二部構成となっており、ナレッジの表現に一階論理を使用します。このルールは、以下の形式を取ります。
when
	<conditions>
then
	<actions>

2.4. 推論エンジン

推論エンジンは、JBoss Rules エンジンに含まれているエンジンで、プロダクションのファクトとデータをルールと照合します。その後、情報からの推測をもとにアクションを実行します。プロダクションルールシステムの推論エンジンはステートフルで、真理維持を行います。

2.5. ReteOO

JBoss Rules で使用される Rete 実装は、ReteOO と呼ばれ、オブジェクト指向システム向けに Rete アルゴリズムを拡張、最適化した実装です。

2.6. プロダクションメモリ

プロダクションメモリとは、ルールを保存する場所のことです。

2.7. ワーキングメモリ

ワーキングメモリは、JBoss Rules エンジンの一部でファクトがアサートされるところです。ここから、ファクトを変更または削除することができます。

2.8. 競合解消 (Conflict Resolution) 戦略

競合解消は、アジェンダに複数のルールがある場合に必要です。ルールを発火 (fire) すると、ワーキングメモリに悪影響が及ぶ可能性があるため、ルールエンジンはルールを発火する順番を把握しておく必要があります (例えば、ルール A を発火するとルール B がアジェンダから削除されてしまう可能性があるなど)。

2.9. ハイブリッドルールシステム

ハイブリッドルールシステムは、前向き連鎖と後向き連鎖の両方を使用してルールを処理します。

2.10. 前向き連鎖

前向き連鎖はプロダクションルールのシステムのことです。前向き連鎖は、渡されたデータに反応するデータ駆動型となっています。ファクトがワーキングメモリに挿入され、1 つまたは複数のルールが true となります。その後、アジェンダで実行されるようにスケジュールに設定されます。
JBoss Rules は前向き連鎖エンジンです。

2.11. 後向き連鎖

後向き連鎖のルールシステムは結論駆動型で、システムはエンジンが充足しようとする結論から開始します。結論を充足できない場合、サブゴール (現在のゴールの一部を完結する結論) を探します。このプロセスは、最初の結論が充足されるか、充足されていないサブゴールがなくなるまで継続します。Prolog は後向き連鎖エンジンの一例になります。

重要

後向き連鎖は、JBoss BRMS 5.2 で実装されました。

2.12. 推論機能

JBoss Rules は、後向き連鎖の推論機能を使用してデータからどのルールを適用するかを推論しやすくします。

2.13. エキスパートシステム

エキスパートシステムは、プロダクションルールシステムというフレームワークでドメインを表現するためのオントロジモデルを使用して、ナレッジの獲得や説明の機能を含めることで形成されると言われています。

2.14. Rete Root ノード

Rete 00 を使用する場合、全オブジェクトがネットワークに入る場所が root ノードとなっています。ここから、ObjectTypeNode にすぐ移動します。

2.15. ObjectTypeNode

ObjectTypeNode は、ルールエンジンのワークロードを減らすのに役立ちます。オブジェクトが複数あり、ルールエンジンがオブジェクト毎にノードを 1 つずつ評価しようとする場合、多くのサイクルが無駄になります。効率化を図るため、ObjectTypeNode を使用してエンジンがオブジェクトの型に合うノードにのみオブジェクトを渡すようにします。こうすることで、アプリケーションが新しいアカウントをアサートしても、Order オブジェクトにはこのノードは伝播されません。
JBoss Rules では、アサートされたオブジェクトは、HashMap のルックアップを利用して有効な ObjectTypesNodes の一覧をオブジェクトのクラスから取得します。この一覧が存在しない場合、ObjectTypeNodes のをすべてスキャンして、マッチする有効な結果を探し出し一覧にキャッシュします。こうすることで、instanceof チェックとマッチするクラス型はどれでも JBoss Rules で照合することができるようになります。

2.16. AlphaNodes

AlphaNodes は、リテラル条件を評価する際に使用します。1 つのオブジェクト型に対して複数のリテラル条件がルール内に含まれる場合、これらのリテラル条件は関連付けられます。つまり、アプリケーションがアカウントオブジェクトをアサートした場合、最初のリテラル条件を充足してからでないと次の AlphaNode に進むことができなくなっています。
AlphaNodes は ObjectTypeNodes で伝播されます。

2.17. ハッシュ

JBoss Rules は ObjectTypeNode から AlphaNode への伝播を最適化することでハッシュを使用して Rete を拡張します。AlphaNode が ObjectTypeNode に追加されるたびに、キーとしてのリテラル値を、値として AlphaNode を持つ HashMap に追加します。新しいインスタンスが ObjectType ノードに入ると、各 AlphaNode に伝播せずに HashMap から正しい AlphaNode を取得して、必要のないリテラルチェックを回避します。

2.18. BetaNodes

BetaNodes は、2 つのオブジェクトとそのフィールドを比較する際に使用します。これらのオブジェクトの型は、同じ場合も、違う場合もあります。

2.19. アルファメモリ

アルファメモリは、BetaNode への右からの入力を指します。JBoss Rules では、この入力により、入ってきたオブジェクトをすべて記憶します。

2.20. ベータメモリ

ベータメモリは、BetaNoe の左側の入力を指します。このメモリは、入ってくるタプルをすべて記憶します。

2.21. BetaNodes を用いたルックアップ

ファクトが片方から入った場合、ハッシュルックアップを行い有効な候補を返します (インデックス)。どの時点であっても有効な結合が見つかった場合は、タプルはオブジェクトと結合して (一部合致)、次のノードに伝播します。

2.22. LeftInputNodeAdapters

LeftInputNodeAdapter は、オブジェクトを入力として受け入れ、1 つのオブジェクトタプルとして伝播します。

2.23. 終端ノード

終端ノードは、ある単一ルールが全条件に合致したこと (つまり、ルールに完全合致があったこと) を示すのに使用します。or 条件の分離結合のあるルールでは、考えられる論理分岐ごとにサブルールが生成されることになります。これにより、1 つのルールに複数の終端ノードを含めることができます。

2.24. ノード共有

ノード共有は、不必要な冗長性を避けるために使用されます。多くのルールは同じパターンを繰り返すため、ノード共有があるとこのようなパターンを減らして、インスタンスごとに再評価しなくてもよくなります。

2.25. ノード共有の例

以下では 2 つのルールが最初のパターンを共有していますが、最後のパターンは共有していません。
rule
when
    vehicle( $car : name == "car" )
    $driver: Driver( typeCar == $sedan )
then
    System.out.println( $driver.getName() + " drives sedan" );
end
rule
when
    Vehicle( $sedan : name == "sedan" )
    $driver : Driver( typeCar != $sedan )
then
    System.out.println( $driver.getName() + " does not drive sedan" );
end

2.26. 疎結合

疎結合は、ルール同士を緩く関連付け、1 つのルールが実行されても別のルールが実行されないようにします。
一般的に、より柔軟性のある疎結合の設計が推奨されます。ルールがすべて密結合されている場合、柔軟性に欠けることが多くなります。さらに、このような状況でのルールエンジンのデプロイは過剰対応であることが分かります。

2.27. 密結合

密結合は、ルールを関連付ける方法の 1 つです。ルールが密結合されている場合、1 つのルールを実行すると他の実行が引き起こされることになります。つまり、明確な論理的つながりがあるということです (明確な連鎖は、決定木を使用してハードコード化するか、実装することができます)。

2.28. 宣言型プログラミング

宣言型プログラミングは、「どのようにするか」ではなく「何をするか」をルールエンジンが宣言できるように手段を指します。宣言プログラミングの主な利点は、ルールを使用すると困難な問題に対して解決策を簡単に提示でき、結果的にこれらの解決策を検証できます。また、ルールはコードを解読するよりもはるかに簡単です。

2.29. 論理とデータの分離

論理とデータの分離は、論理とデータのコンポーネントの結合を切り離すプロセスのことです。この手法を使用して、論理を多数のドメインオブジェクトやコントローラーに展開し、1 つまたは複数の分離ルールファイルですべてを整理することができます。

2.30. ナレッジベース

ナレッジベースは、KnowledgeBuilder でコンパイルされたルールの集まりで、アプリケーションのナレッジ定義すべてに対するレポジトリでもあります。ナレッジベースには、ルール、プロセス、関数、型モデルなどが含まれている場合があります。ナレッジベース自体には、(ファクトとして知られる) インスタンスデータは含まれておらず、代わりに、データの挿入やプロセスインスタンスの開始が可能なナレッジベースからセッションが作成されます。セッションの作成を繰り返し行えるように、ナレッジベースをキャッシュできるようにすることが推奨されます。

第3章 ユーザーガイド

3.1. ステートレスナレッジセッション

ステートレスナレッジセッションは、推論なしのセッションのことです。ステートレスセッションは、セッションを使用してデータを渡し、結果を再度受け取るという点から関数のようであると言われます。
ステートレスナレッジセッションは、バリデーション、演算、ルーティング、フィルタリングを必要とする場合に便利です。

3.2. ステートレスセッションでのルールの設定

手順3.1 タスク

  1. 以下の運転免許証の例のようにデータモデルを作成します。
    public class Applicant {
        private String name;
        private int age;
        private boolean valid;
        // getter and setter methods here
    }
    
  2. 最初のルールを記述していきます。この例では、18 歳未満の申請者を拒否するためのルールを追加しています。
    package com.company.license
    
    rule "Is of valid age"
    when
        $a : Applicant( age < 18 )
    then
        $a.setValid( false );
    end
    
  3. Applicant オブジェクトがルールエンジンに挿入されると、各ルールの制約がそのオブジェクトを評価して一致があるか検索します (「オブジェクト型」には常に暗黙的な制約があり、それ以降は明示的なフィールド制約をいくつでも存在させることができます)。
    Is of valid age ルールでは、以下の 2 つの制約があります。
    • 一致するファクトは、Applicant 型である必要があります。
    • Age の値は 18 未満である必要があります。
    $a はバインド変数で、これを設定することで、ルールの結果 (ここからオブジェクトのプロパティを更新可能) 内の一致オブジェクトへ参照ができるようになります。

    注記

    ドル記号 ($) の使用はオプションです。これを使用すると、変数名とフィールド名を区別できます。

    注記

    ルールがクラスと同じフォルダー内にある場合、クラスパスのリソースローダーを使用して最初のナレッジベースを構築することができます。
  4. 以下のように、KnowledgeBuilder を使用してルール一覧をナレッジベースにコンパイルします。
    KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
    kbuilder.add( ResourceFactory.newClassPathResource( "licenseApplication.drl", 
                  getClass() ), ResourceType.DRL );
    if ( kbuilder.hasErrors() ) {
        System.err.println( kbuilder.getErrors().toString() );
    }
    KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
    kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
    
    上記のコードスニペットは、newClassPathResource() メソッドを使用して、クラスパス内の licenseApplication.drl ファイルを検索します (リソースのタイプは Drools ルール言語 (DRL: Drools Rule Language) です)。
  5. KnowledgeBuider にエラーがないか確認してください。エラーがない場合は、セッションを構築できます。
  6. ルールに対してデータを実行します (申請者は 18 歳未満であるため、申請は「無効」とマークされます)。
    StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
    Applicant applicant = new Applicant( "Mr John Smith", 16 );
    assertTrue( applicant.isValid() );
    ksession.execute( applicant );
    assertFalse( applicant.isValid() );
    
結果

先ほどのコードは、ルールに対してデータを実行しました。申請者が 18 歳未満であるため、この申請は無効とマークされました。

3.3. 複数のオブジェクトを使用したルールの設定

手順3.2 タスク

  1. オブジェクト実装 iterable (コレクションなど) に対してルールを実行するには、以下のコードサンプルに記載しているように別のクラスを追加します。
    public class Applicant {
        private String name;
        private int age;
        // getter and setter methods here
    }
    
    public class Application {
        private Date dateApplied;
        private boolean valid;
        // getter and setter methods here
    }
    
  2. 申請が正規の時間枠内に行われたことを確認するために、以下のルールを追加します。
    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
    
  3. JDK コンバーターを使用して、iterable インターフェースを実装します (このメソッドは Arrays.asList(...) という行で始まります)。以下のコードは、iterable リストに対してルールを実行しています。全コレクション要素は、一致するルールが発火される前に挿入されます。
    StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
    Applicant applicant = new Applicant( "Mr John Smith", 16 );
    Application application = new Application();
    assertTrue( application.isValid() );
    ksession.execute( Arrays.asList( new Object[] { application, applicant } ) );
    assertFalse( application.isValid() );
    

    注記

    実は、execute(Object object)execute(Iterable objects) メソッドは、 BatchExecutor インターフェースからの execute(Command command) と呼ばれる別のメソッドの周りにある「ラッパー」です。
  4. CommandFactory を使用して指示を作成し、以下が execute( Iterable it ) と同じになるようにします。
    ksession.execute( CommandFactory.newInsertIterable( new Object[] { application, applicant } ) );
    
  5. 違うコマンドや結果出力の識別子を多数使用する場合は、BatchExecutorCommandFactory を使用します。
    List<Command> cmds = new ArrayList<Command>();
    cmds.add( CommandFactory.newInsert( new Person( "Mr John Smith" ), "mrSmith" );
    cmds.add( CommandFactory.newInsert( new Person( "Mr John Doe" ), "mrDoe" );
    BatchExecutionResults results = ksession.execute( CommandFactory.newBatchExecution( cmds ) );
    assertEquals( new Person( "Mr John Smith" ), results.getValue( "mrSmith" ) );
    

    注記

    CommandFactory は、BatchExecutor で使用可能な他のコマンドを多数サポートしています。例えば、StartProcessQuerySetGlobal などです。

3.4. ステートフルセッション

ステートフルセッションは、反復的な変更をファクトに継続して加えることができます。StatelessKnowledgeSession では、StatefulKnowledgeSessionBatchExecutor インターフェースをサポートしています。唯一の相違点は、最後に FireAllRules コマンドが自動的に呼び出されない点です。

警告

必ず、dispose() メソッドは、ステートフルセッションの実行後に呼び出すようにしてください。こうすることで、メモリリークが確実になくします。これは、ナレッジベースの作成時にナレッジベースがステートフルナレッジセッションへの参照を取得するのが原因です。

3.5. ステートフルセッションセッションの一般的なユースケース

モニタリング
例えば、株式市場の監視や購入プロセスの自動化が可能です。
診断
ステートフルセッションは、障害検出プロセスを実行するために使用できます。また、医療診断プロセスでも利用可能です。
輸送関係
例えば、小包追跡や配送提供に関する問題にも適用できます。
コンプライアンスの確保
例えば、市場取引の合法性を検証するために使用できます。

3.6. ステートフルセッションの監査の例

手順3.3 タスク

  1. 監査対象のモデルを作成します。火災報知に関するこの例では、家の中の部屋がリストアップされています。各部屋にはスプリンクラーがあり、火災はどの部屋からでも発生する可能性があります。
    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 
    {
    }
    
  2. このルールは、(特定の部屋のスプリンクラーの存在などを定義するため) 複数のオブジェクト間の関係を表記する必要があります。これには変数のバインドをパターンの制約として使用します。結果、クロスプロダクトになります。
  3. 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" ステートメントのように動作します)。

3.7. 一階論理

一階論理では、個別のインスタンスではなくデータセットを参照することができます。

3.8. 一階論理を使ったルール設定

手順3.4 タスク

  1. キーワード 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());
    end
    
  2. Alarm オブジェクトは、火災発生時に作成されますが、火災の数に関係なく建物全体で必要な Alarm は 1 つのみです。Not の補完である 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" );
    end
    
  3. すべて鎮火された場合はアラームを解除する必要があります。アラームをオフにするには Not を使用します。
    rule "Cancel the alarm when all the fires have gone"
    when
        not Fire()
        $alarm : Alarm()
    then
        retract( $alarm );
        System.out.println( "Cancel the alarm" );
    end
    
  4. このこーをを使用して、アプリケーションの初回起動時、アラームの解除時、全スプリンクラーの解除時に一般的なヘルスステータスのメッセージを出力します。
    rule "Status output when things are ok"
    when
        not Alarm()
        not Sprinkler( on == true ) 
    then
        System.out.println( "Everything is ok" );
    end
    
  5. fireAlarm.drl と呼ばれるファイルにルールを格納して、このファイルをクラスパスのサブディレクトリに保存します。
  6. 最後に、新しい名前 fireAlarm.drl を使用して、knowledge base をビルドします。
    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();
    

3.9. ルールシステムの設定サンプル

手順3.5 タスク

  1. 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. 火災を 2 つ作成、挿入します (ファクトハンドルは保持されます)。
  3. エンジンに火災があるため、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
    
  4. 鎮火すると火災オブジェクトが取り消され、スプリンクラーがオフになります。この時点で火災報知器はキャンセルされ、ヘルスメッセージがもう一度表示されます。
    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 okay       
    
    

3.10. JBoss Rules のメソッド

メソッドはルールとは違います。メソッドは直接呼び出され、個別のインスタンスを渡すために使用されます。呼び出しが 1 つの場合は、実行数も 1 つとなります。

3.11. メソッドの例

メソッドは、以下のように記述されます。
public void helloWorld(Person person) {
    if ( person.getName().equals( "Chuck" ) ) {
        System.out.println( "Hello Chuck" );
    }
}

3.12. ルールの例

ルールは以下のように記述されます。
rule "Hello World"
    when
        Person( name == "Chuck" )
    then
        System.out.println( "Hello Chuck" );
end

3.13. クロスプロダクト

2 つ以上のデータセットを組み合わせた場合、その結果はクロスプロダクトと呼ばれます。

3.14. クロスプロダクトの制約

手順3.6 タスク

  • ルールが大量のクロスプロダクトを出力しないように、クロスプロダクト自体を制約する必要があります。以下のような変数制約を使用して、クロスプロダクトの制約を行います。
    rule
    when
        $room : Room()
        $sprinkler : Sprinkler( room == $room )
    then
        System.out.println( "room:" + $room.getName() +
                            " sprinkler:" + $sprinkler.getRoom().getName() );
    end
    
    以下の出力が表示されます。
    room:office sprinkler:office
    room:kitchen sprinkler:kitchen
    room:livingroom sprinkler:livingroom
    room:bedroom sprinkler:bedroom
結果

4 行のみの出力で、部屋ごとに正しいスプリンクラーが表示されます。この変数がないと、Room テーブルの行と、Sprinkler テーブルの行が結合され、結果、行が多数出力されることになります。

3.15. 推論エンジン

推論エンジンは、JBoss Rules エンジンに含まれているエンジンで、プロダクションのファクトとデータをルールと照合します。その後、情報からの推測をもとにアクションを実行します。プロダクションルールシステムの推論エンジンはステートフルで、真理維持を行います。

3.16. 推論の例

この例では、年齢フィールドと年齢ポリシー制御を行うルールを持つ Person ファクトを使用しています。推論を使用して、Person が成人か未成年かを判断し、その結果に対してアクションを実施します。
rule "Infer Adult"
when
  $p : Person( age >= 18 )
then
  insert( new IsAdult( $p ) )
end
上記のスニペットでは、18 歳以上の人には IsAdult というインスタンスが挿入されています。このファクトは特別なもので、「関係」と呼ばれるものです。どのようなルールにおいても、この推論関係を使用することができます。
$p : Person()
IsAdult( person == $p )

3.17. 論理アサーション

標準のオブジェクト挿入の後、明示的にファクトの取り消しを行う必要があります。論理アサーションでは、最初にアサートした条件が true でなくなると、アサートされたファクトは自動的に取り消されます。論理アサーションをサポートする単一条件がない場合のみ、取り消されます。

3.18. 記述 (stated) 挿入

通常の挿入は、「ファクトの記述」という意味で記述挿入と呼ばれています。HashMap とカウンターを使用して、特定の等価が何回、記述されたのかを追跡できます。つまり、同等なインスタンスがいくつあるかカウントすることができます。

3.19. 正当化 (Justified) 挿入

オブジェクトが論理的に挿入されることを正当化 (justified) と呼びます。発火ルールにより正当化されます。各論理挿入に、等価のオブジェクトが 1 つのみあり、それ以降に等価の論理挿入があると、この論理アサーションに対する正当化カウンターの数が増えます。ルールの LHS の作成が true でなくなると正当化が削除され、カウンターの数もそれにあわせて減少します。正当化がなくなると、論理オブジェクトが自動的に取り消されます。

3.20. WM_BEHAVIOR_PRESERVE 設定

等価の記述 (stated) オブジェクトがある場合に論理的にオブジェクトを挿入すると失敗して null を返します。正当化 (justified) された既存の等価オブジェクトを記述するとファクトを上書きすることになります。WM_BEHAVIOR_PRESERVE 設定により、どのように上書きが行われるかが決定します。プロパティが discard に設定されている場合、既存のハンドルを使用して既存のインスタンスが新しいオブジェクトで置き換えられます。これはデフォルトの動作となっています。設定されていない場合は、これを記述 (stated) に置き換えてつつも、新しい FactHandle を作成する必要があります。

3.21. 記述挿入のフローチャート

3.22. 論理挿入のフローチャート

論理挿入フローチャートのスクリーンショット

図3.1 論理挿入フローチャートのスクリーンショット

3.23. 真理維持システム

JBoss Rules 真理維持システム (TMS: Truth Maintenance System) とは、信念とナレッジベースにあるその信念に関する依存関係や正当化を表現する方法です。

重要

真理維持 (および論理アサーション) を機能させるには、Fact オブジェクトが equals と hashCode メソッドを正しくオーバーライドする必要があります。真理維持システムは、2 つの違った物理オブジェクトが等価であることを把握している必要があるため、Java 規格に従って、equals と hashCode の両方を正しくオーバーライドする必要があります。
equals メソッドがそれぞれに true を返し、hashCode メソッドが同じ値を返す場合のみ、2 つのオブジェクトは等価となります。equals と hashCode の両方をオーバーライドする必要があります。

3.24. insertLogical ファクト

insertLogical ファクトは、JBoss Rules TMS の一部です。このファクトは、ルールが状況にあわせて機能して、変更されるように「論理を挿入」します。例えば、insertLogical ファクトはルールセットに追加され、ルールが false になった場合自動的にそのファクトが削除されるようになっています。

3.25. 推論と TMS の使用

手順3.7 タスク

  1. この例では、バスの定期券発行のシステムを使用します。以下のコードスニペットを参照してください。
    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
  2. 推論を含めるために、insertLogical プロパティを挿入します。
    rule "Infer Child" when
        $p : Person( age < 16 )
    then
        insertLogical( new IsChild( $p ) )
    end
    rule "Infer Adult" when
        $p : Person( age >= 16 )
    then
        insertLogical( new IsAdult( $p ) )
    end
  3. コードを再度入力して定期券の発行を行います。TMS は、削除のカスケードセットができるように論理挿入の連鎖に対応しているため、これらの 2 つの設定は論理的に挿入することができます。
    rule "Issue Child Bus Pass" when
      $p : Person( )
           IsChild( person == $p )
    then
        insertLogical(new ChildBusPass( $p ) );
    end
     
    rule "Issue Adult Bus Pass" when
      $p : Person( age >= 16 )
           IsAdult( person =$p )
    then
        insertLogical(new AdultBusPass( $p ) );
    end
    その人の (年齢) が 15 から 16 に変わると、IsChild のファクトとその人の ChildBusPass のファクトの両方が自動的に削除されます。
  4. オプションで、not の条件要素を挿入して通知の処理を行います (この例では定期券の返却依頼についてです)。TMS が自動的に ChildBusPass オブジェクトを削除して、このルールがその人に対する依頼をトリガーして送信します。
    rule "Return ChildBusPass Request "when
      $p : Person( )
           not( ChildBusPass( person == $p ) )
    then
        requestChildBusPass( $p );
    end

第4章 決定表

4.1. 決定表

決定表とは、条件論理を表現する手段の 1 つです。決定表は、ビジネスレベルのルールに非常に適しています。

4.2. スプレッドシート

JBoss Rules は、スプレッドシート形式でルールを管理します。対応の形式は、Excel (XLS) と CSV です。つまり、様々なスプレッドシートプログラム (Microsoft Excel、OpenOffice.org、Calc など) に対応しています。

4.3. Open Office の例

Open Office のスクリーンショット

図4.1 Open Office のスクリーンショット

上記の例では、決定表の技術的な部分は (スプレッドシートの標準機能を使用して) 非表示にしています。
ルールは行 17 から始まり、行はそれぞれルールとなります。条件は列 C、D、E などに設定し、アクションは今回は表示されていません。値の意味は、行 16 に記載されています (列 B は (条件ではなく) 単なる説明となっています)。

注記

決定表は上から順番に処理しているように見えますが、必ずしもそうであるとは限りません。ルールは行の順番など関係なしに作成するのが理想的です。こうすることで、常に行を移動させる必要がないため、メンテナンスが簡単になります。

4.4. ルールとスプレッドシート

行に挿入されるルール
各行はルールであるため、記述したコードと同じ原則が適用されます。このルールエンジンはファクトを処理するため、合致するルールはすべて発火されます。
アジェンダ
ルールが発火され、非常に単純な決定表 (最初に合致したもののみがアクションを引き起こすもの) をシミュレートするとアジェンダが削除されます。
複数の表
1 つのスプレッドシートに複数のテーブルを含めることができます。こうすることで、共通のテンプレートを共有するルールをグループ化することができます。ただ、これらのルールはすべて、依然として 1 つのルールパッケージに組み込まれています。

4.5. RuleTable のキーワード

決定表を使用すると、スプレッドシートはルール表の開始地点を示す RuleTable キーワードを検索します (開始する行と列の両方)。

重要

キーワードはすべて同じ列に含まれている必要があります。

4.6. RuleSet キーワード

RuleSet キーワードは、全ルールを含むルールパッケージで使用する名前を指します。デフォルトでは、名前はオプションですが、すぐ右のセルに RuleSet キーワードを設定する必要があります。

4.7. ルールテンプレートの例

ルールテンプレート

図4.2 ルールテンプレート

  • RuleSet キーワードは、全ルールを含むルールパッケージで使用する名前を指します。デフォルトでは、名前はオプションですが、すぐ右のセルに RuleSet キーワードを設定する必要があります。C 列に表示されているその他のキーワードは import と Sequential です。
  • RuleTable キーワードの後には名前が来ますが、この名前は生成されるルールの名前の接頭辞として使用されます。ルール名が必ず一意の名前となるように、行の番号が追加されます。
  • RuleTable の行は、ルールの開始する行を指します。左側の行は無視されます。
  • 行番号 14 (RuleTable 直後の行) を見てみると、キーワード CONDITION と ACTION は、下の列のデータがルールの LHS 部分または RHS 部分のいずれかに対するものであることが分かります。このようにオプションとしても設定できる他の属性もルールには存在しています。
  • 行番号 15 には、ObjectTypes の宣言が含まれます。この行の内容はオプションですが、このオプションが使用されてない場合、行は空白のままにする必要があります。この行を使用する場合、下のセル (行番号 16) の値はそのオブジェクトタイプに対する制約となります。上記の場合は、Person(age=="42")Cheese(type=="stilton") (42 と stilton は行番号 18 から来ています) が生成されます。上記の例では、"==" は暗黙的です。フィールド名だけが指定されている場合は、完全一致を生成するように、トランスレーターは推測します。
  • 行番号 16 にはルールテンプレート自体が含まれています。"$param" プレースホルダーを使用して、下のセルからのデータを補間すべき場所を指定することができます (挿入が複数ある場合は "$1"、"$2" などを使用し、下のセルのコンマ区切りの一覧からパラメーターを指定します)。行番号 17 は無視されますが、ここには列の目的に関する説明が記載されている場合があります)。
  • 行番号 18 と 19 はデータを表示します。このデータは、行番号 15 のテンプレートと統合 (補間) され、ルールを生成します。セルにデータが含まれていない場合、そのテンプレートは無視されます (つまり、条件やアクションによってはそのルールの行に該当しないものもあるということです)。ルールの行は、空白の行に到達するまで、読み込まれます。シート内に複数の RuleTables を設置することも可能です。
  • 行番号 20 には別のキーワードや値が含まれます。このように、キーワードの行の位置は関係ありません (大抵の場合は一番上に設定します)。ただし、その列に関しては、RuleTable または RuleSet のキーワードが表示される箇所と同じものでなければなりません。ここでは、列番号 C が選択されていますが、他の列を使用することもできます。

注記

ObjectType 宣言は (マージされたセルを使用して) 複数の列をつなげることができます。つまり、同じ ObjectType がマージされていないセルに含まれているものの、最終的に別種/同種のファクトと一部合致するといった別のパターンが出来上がるのではなく、マージされた範囲のすぐ下にある列はすべて、制約セットとして、毎回 1 つのファクトと合致する単一パターンに統合されます。
上記の例では、ルールは以下のようにレンダリングされます ("ObjectType" 行を使用)。
//row 18
rule "Cheese_fans_18"
when
    Person(age=="42")
    Cheese(type=="stilton")
then
    list.add("Old man stilton");
end

注記

上記のセルの該当の ObjectType に追加するには、age=="42"type=="stilton" の制約は 1 つの制約と解釈されます。上記のセルがつながっている場合、1 つの「列」に複数の制約が存在することになります。

警告

非常に大きな決定表は、メモリ要件が非常に高くなる可能性があります。

4.8. データ定義セル

DRL ファイルを生成するために使用する長方形のエリアデータ定義には 2 種類あります。1 つは RuleSet と呼ばれるセルで、ルール以外の DRL アイテムをすべて定義します。もう 1 つは反復して発生して、RuleTable で始まるコンテンツのセルの右および下にあります。これらのエリアは実際の決定表を表しており、各エリアは同様の構造を持つルールを含みます。
RuleSet のエリアにはセルのペアを含めることができ、1 つは RuleSet セルの下にあり、このセルにはもう片方のセル (同じ列で次に来るセル) に含まれる値の種類を指定するキーワードが含まれています。

4.9. ルールテーブルの列

ルールテーブルエリアの列は、ルールの左側にあるパターンと制約、ルール結果に対するアクション、個別のルール属性の値を定義します。ルールテーブルエリアには、列 1 つ以上、条件とアクション両方、ルール属性に対する任意の選択肢、それぞれに対して最大 1 つの列を含める必要があります。RuleTable というセルの行の後にくる最初の 4 つの行は、ヘッダーエリアとして割り当てられ、多くの場合ルール構築のためのコード定義に使用されます。この 4 つのヘッダー行の下に追加された行で別のルールを生成し、そのデータを使用してルールテーブルヘッダーで定義されているコードのバリエーションを提供します。

注記

キーワードはすべて大文字と小文字の区別をします。
最初のワークシートのみが決定表用に検証されます。

4.10. ルールセットのエントリ

ルールセットエリアのエントリは、DRL コンストラクト (ルール以外) を定義して、ルール属性を指定することができます。コンストラクトのエントリは繰返し使用して、各ルール属性は最大 1 回指定でき、ルールテーブルエリアで定義された同じ属性により却下されない限りすべてのルールに適用されます。
エントリは、セルのペアを縦に積み上げた順番に指定する必要があります。最初のものにキーワードが、その右側のものに値が含まれます。セルのペアのこの順番は、RuleSet でマークがついた行がキーワードを含むものである限り、空の行またはルールテーブルにより中断可能です。

4.11. ルールセットエリアのエントリ

表4.1 ルールセットエリアのエントリ

キーワード 使用法
RuleSet 生成 DRL ファイルのパッケージ名。オプション。デフォルトは rule_table 最初のエントリでなければなりません。
Sequential True または False。True の場合、Salience (優先順位) を使用してルールが上から下へ順番に発火されるようにします。 オプション。最大 1 回。省略されると、発火順が課されます。
EscapeQuotes True または False。True の場合、引用符はエスケープされ、DRL にそのまま表示されるようにします。 オプション。最大 1 回。省略すると、引用符はエスケープされます。
Import インポートする Java クラスのコンマ区切りの一覧 オプション。繰り返し可能
Variables DRL global の宣言。変数名の後にくる型のこと。複数のグローバル定義はコンマで区切る必要があります。 オプション。繰り返し可能
Functions DRL 構文に沿った 1 つまたは複数の関数定義 オプション。繰り返し可能
Queries DRL 構文に沿った 1 つまたは複数の定義 オプション。繰り返し可能
Declare DRL 構文に沿った 1 つまたは複数の宣言型 オプション。繰り返し可能

4.12. ルールセットエリアのルール属性エントリ

表4.2 ルールセットエリアのルール属性エントリ

キーワード イニシャル
PRIORITY P ルールの "salience" 値を定義する整数。"sequential" フラグでオーバーライドされます。
DURATION D ルールの "duration" の値を定義する long 整数値
TIMER T タイマーの定義。"タイマーおよびカレンダー"を参照してください。
CALENDARS E カレンダーの定義。"タイマーおよびカレンダー"を参照してください。
NO-LOOP U ブール値。"true" は、返された結果により変更が加えられたことで発生するルールのループを防止します。
LOCK-ON-ACTIVE L ブール値。"true" は同じルールフローやアジェンダグループ内にこのフラグを設定して、ルールすべてを追加でアクティブ化しないようにします。
AUTO-FOCUS F ブール値。アジェンダグループ内のルールに"true" を設定すると、ルールをアクティブ化して、自動的にこのグループにフォーカスが当たるようにします。
ACTIVATION-GROUP X アクティベーション (または XOR) グループを特定する文字列。アクティベーショングループ内のルール 1 つのみが発火されます。最初に発火したルールにより、同じグループ内の他のルールですでにアクティブ化されているものがすべてキャンセルされます。
AGENDA-GROUP G アジェンダグループを特定する文字列。このアジェンダグループはフォーカスを当てることでアクティブ化する必要があり、ルールのグループ間のフローを制御する 1 つの方法です。
RULEFLOW-GROUP R ルールフローグループを特定する文字列

4.13. RuleTable のセル

ルールテーブルはすべて、"RuleTable"が含まれているセルから開始され、オプションでその後に同じセル内の文字列が続きます。この文字列は、このルールテーブルから由来のすべてのルールに対する名前の最初の部分として使用されます (このように自動的に指定された名前は、NAME 列を使用して上書きすることができます)。このルールテーブルのルールを定義するその他のセルはすべて、このセルの下または右側に来ることになります。

4.14. 列タイプ

RuleTable セルの次の行は、列タイプを定義します。各列は、条件または結果の一部となるか、ルール属性、ルール名またはコメントを提供します。各属性列は、1 回まで使用することができます。

4.15. ルールテーブルの列ヘッダー

表4.3 ルールテーブルの列ヘッダー

キーワード イニシャル Value 使用法
NAME N その行から生成されたルールの名前を提供します。デフォルトは、RuleTable タグと行番号の後にくるテキストから構築されます。 最大 1 列
DESCRIPTION I 生成されたルール内のコメントとなるテキスト。 最大 1 列
CONDITION C 条件内のパターンにある制約を構築するためのコードスニペットおよび補間値 ルールテーブルごとに少なくとも 1 つ
ACTION A ルールの結果に対するアクションを構築するためのコードスニペットおよび補間値 ルールテーブルごとに少なくとも 1 つ
METADATA @ ルールに対するメタデータエントリを構築するためのコードスニペットおよび補間値 オプション。列いくつでも OK

4.16. 条件の要素

CONDITION とのヘッダーがついた列の場合、その後の行にあるセルは条件要素となります。
  • CONDITION の下の最初のセルにあるテキストは、次の行のスニペットが制約となり、ルール条件のパターンへと発展します。セルが周辺のセル 1 つまたは複数とマージされると、複数の制約を持つ 1 つのパターンが形成されます。すべての制約は括弧のリストとなり、このセルのテキストに追加されます。セルは空の状態でも問題ありません。空の場合、次の行のコードスニペットはそれだけで有効な条件要素になります。
    制約なしのパターンを含めるには、テキストの前にパターンを記述して別のパターンを作成します。
    パターンは、空の括弧ペアの有無に拘らず記述可能です。'from' 句はこのパターンに追加することができます。
    パターンが 'eval' で終わる場合、コードスニペットは 'eval' の後の括弧ペアに含めるようにブール値の式を生成するはずです。
  • CONDITION 配下で 2 番目にくるテキストは、2 段階の手順で処理されます。
    1. このセルのコードスニペットは、その列のさらに下にあるセルからの値を補間することで変更します。下のセルからの値と "==" を使用して比較を含む制約を作成する場合、フィールドセレクターだけで十分です。それ以外の比較演算子は、スニペット内の最後のアイテムとして指定する必要があり、下のセルからの値は追加されます。その他すべての制約フォームについては、$param のシンボルでセルの内容を含めるために位置を印付けする必要があります。$1$2 などのシンボルを使用し、下のセルで値をコンマで区切ってリストすることで、複数の挿入も可能です。
      パターン forall(delimiter){snippet} に従ったテキストは、配下のセルにあるコンマ区切りの値それぞれに対して 1 回ずつ スニペットを繰り返して (この際、$ シンボルの場所に値を挿入します)、指定の区切り文字でこれらの拡張を結合することで拡張されます。forall コンストラクトは、他のテキストで囲むことができます。
    2. 前の行にあるセルが空でない場合、完全なコードスニペットがそのセルから条件要素に追加されます。括弧のペアと、(複数の制約がマージされたセルのパターンに追加されている場合) 区切りコンマが自動的に提供されます。
      上のセルが空の場合、補間結果がそのまま使用されます。
  • CONDITION の 3 つ下のセルのテキストは、例示のみを目的として提示されています。人が解読できるように列の目的を指定するために使用されます。
  • 4 行目以降は、空でないエントリは上記の通り、補間データとして提供されます。空のセルは、このルールの制約や条件要素を省略することになります。

4.17. Action ステートメント

ACTION とのヘッダーがついた列の場合、その後の行にあるセルは Action 要素となります。
  • ACTION の 1 つ下のセルにあるテキストはオプションです。テキストがある場合、オブジェクト参照して解釈されます。
  • ACTION 配下で 2 番目にくるテキストは、2 段階の手順で処理されます。
    1. このセルのコードスニペットは、その列のさらに下にあるセルからの値を補間することで変更します。挿入が 1 つの場合、$param のシンボルでセルの内容を含めるために位置を印付けします。$1$2 などのシンボルを使用し下のセルで値をコンマで区切ってリストすることで、複数の挿入も可能です。
      マーカーシンボルのないテキストを使うことで、補間なしでメソッドを呼び出すことができます。この場合、配下の行に空でないエントリを使用してステートメントを含めます。
      Forall コンストラクトはこちらでも使用できます。
    2. 最初のセルが空でない場合、テキスト、その後に来るピリオド、2 番目のセルのテキスト、終わりを示すセミコロンが 1 列に並べられ、メソッドを呼び出して Action ステートメントとして結果に追加されます。
      上のセルが空の場合、補間結果がそのまま使用されます。
  • ACTION の 3 つ下のセルのテキストは、例示のみを目的として提示されています。人が解読できるように列の目的を指定するために使用されます。
  • 4 行目以降は、空でないエントリは上記の通り、補間データとして提供されます。空のセルは、このルールの Action ステートメントを省略することになります。

注記

置き換えテキストにコンマが含まれている場合、$param の代わりに $1 を使用できません。

4.18. Metadata ステートメント

METADATA とのヘッダーがついた列の場合、その後の行にあるセルは生成ルールに対するメタデータのアノテーションとなります。
  • METADATA の 1 つ下のセルにあるテキストは無視されます。
  • METADATA の 2 つ下にあるセルのテキストはルールの行のセルからの値を使用しますが、上記の通り補間により左右されます。メタデータマーカー文字 @ は自動的に接頭辞として追加され、このセルのテキストとして含めるべきではありません。
  • METADATA の 3 つ下のセルのテキストは、例示のみを目的として提示されています。人が解読できるかたちで列の目的を指定するために使用されます。
  • 4 行目以降は、空でないエントリは上記の通り、補間データとして提供されます。空のセルは、このルールのメタデータのアノテーションを省略することになります。

4.19. セルデータ補間の例

  • テンプレートが Foo(bar == $param) でセルが 42 の場合、結果は Foo(bar == 42) となります。
  • テンプレートが Foo(bar < $1, baz == $2) でセルに 42,43 が含まれる場合、結果は Foo(bar < 42, baz ==43) になります。
  • テンプレートが forall(&&){bar != $} でセルに 42,43 が含まれる場合、bar != 42 && bar != 43 との結果になります。

4.20. セル内での作業する際のヒント

  • 同じセル内に複数のパッケージ名がある場合、コンマで区切る必要があります。
  • 型と変数名のペアはコンマで区切る必要があります。
  • 関数は、DRL ファイルに表示されるとおりに記述して、"RuleSet" キーワードと同じ列に表示する必要があります。関数は、全ルールの行の上部、真ん中、または下部に設定することができます。
  • 複数の定義を 1 つのセルにパッキングするのではなく、Import、Variables、Functions、Queries を繰り返して使用することができます。
  • 末尾の挿入マーカーは省略可能です。
  • バインディング変数を定義することができます。
  • オブジェクト型の行に何でも設定することができます。バインディング変数の定義以外は、リテラルに挿入するための追加パターンとすることも可能です。
  • ACTION ヘッダーの下にあるセルは空のままでも結構です。このスタイルを使用すると、メソッド呼び出し 1 つだけでなく、何でも結果に使用できます (同じ手法を CONDITION 列でも適用できます)。

4.21. SpreadsheetCompiler クラス

SpreadsheetCompiler クラスは、drools-decisiontables モジュールの API スプレッドシートベースの決定表で使用する主なクラスです。このクラスは、様々な形式でスプレッドシートを使用して DRL でルールを生成します。
SpreadsheetCompiler を使用して部分的なルールファイルを生成して、ファクトの後に完全なルールパッケージをアセンブルすることができます。こうすることで、必要に応じて技術的なものとそうでないものを分けることができます。

4.22. スプレッドシートベースの決定表の使用

手順4.1 タスク

  1. ベースとして使用可能なサンプルのスプレッドシートを生成します。
  2. Rule Workbench IDE プラグインを使用する場合、ウィザードを使用してテンプレートからスプレッドシートを生成します。
  3. XSL 互換のスプレッドシートエディターを使用して XSL を変更します。

4.23. Lists

Excel では、値の lists を作成できます。これらの Lists は他のワークシートに保存して、セルに対して有効な値のリストを提供することができます。

4.24. 改訂管理

ルールに変更が加えられると、より古いバージョンはアーカイブされます。JBoss Rules のアプリケーションによっては、履歴変更を保存する機能に制限があるものありますが、別の改訂管理の手段を使用するよう推奨しています。

4.25. 表形式のデータソース

テーブル形式のデータソースは、ルールデータのソースとして使用することができ、また多数のルールを生成するためのテンプレートを作成することができます。これにより、スプレッドシートや既存のデータベースのルールなどをさらに柔軟にすることができます (テンプレートを先に開発することでルールを生成)。

4.26. ルールテンプレートの機能

ルールテンプレートを使用すると、データをルールと分離することができ、ルールのどの部分がデータ駆動型なのかといった制約がなくなります。そのため、決定表の機能をすべて利用することもできますが、以下も可能になります。
  • データベース (やその他の形式) へデータを格納すること
  • データの値を元にルールを条件付きで作成すること
  • ルールのあらゆる部分のデータを使用すること (例:条件演算子、クラス名、プロパティ名)
  • 同じデータに対して違ったテンプレートを実行すること

4.27. ルールテンプレートの例

以下は、ルールテンプレートがどのように使用されているか示しています。
1  template header
2  age
3  type
4  log
5
6  package org.drools.examples.templates;
7
8  global java.util.List list;
9
10 template "employees"
11
12 rule "Current employee_@{row.rowNumber}"
13 when
14    Name(location == @{location})
15    role(type == "@{type}")
16 then
17    list.add("@{log}");
18 end
19
20 end template
上記の例がどのようなもので構成されているか、以下に示しています。
  • 行 1: テンプレートはすべて template header で開始されます。
  • 行 2-4: ヘッダーの後は列のリストで、データに表示される順番に並んでいます。この場合、最初の列 location、2 番目の列 type、3 番目の列 log を呼び出しています。
  • 行 5: 空の行は列の定義の終わりを指定します。
  • 行 6-9: 標準のルールヘッダーテキスト。これは、標準のルール DRL で生成した DRL の一番上に表示されます。パッケージステートメント、import、global、function の定義をこの部分に挿入します。
  • 行 10: キーワード template はルールテンプレートの最初であるとのシグナルをだします。テンプレートファイルに 1 つ以上のテンプレートがある場合もありますが、各テンプレートは一意の名前を持っています。
  • 行 11-18: ルールテンプレート
  • 行 20: キーワード end template はテンプレートの最後を指定します。
以下のテンプレート例は、以下のルールを生成します。
package org.drools.examples.templates;

global java.util.List list;

rule "Current employee_1"
when
  Person(location == Melbourne)
  role(type == "receptionist")
then
  list.add("melbourne admin");
end

rule "Current employee_2"
when
  Person(location == Sydney)
  Cheese(type == "recruiter")
then
  list.add("sydney HR");
end

4.28. ルールテンプレートの実行

手順4.2 タスク

  • このコードを実行してルールテンプレートを実行します。
    DecisionTableConfiguration dtableconfiguration =
        KnowledgeBuilderFactory.newDecisionTableConfiguration();
    dtableconfiguration.setInputType( DecisionTableInputType.XLS );
    
    KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
    
    kbuilder.add( ResourceFactory.newClassPathResource( getSpreadsheetName(),
                                                        getClass() ),
                  ResourceType.DTABLE,
                  dtableconfiguration );
    

4.29. Changeset の展開例

以下の例は、http URL のロケーションからルールを、クラスパスから Excel の決定表をrロードするために展開されています。
<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 http://anonsvn.jboss.org/repos/labs/labs/jbossrules/trunk/drools-api/src/main/resources/change-set-1.0.0.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>
   
       
       
           
       
   

4.30. Changeset およびディレクトリの例

コンテンツを入れるためのディレクトリを指定することができます。ファイル名拡張から型を推測できないため、すべてのファイルは指定の型でなければなりません。
<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 http://anonsvn.jboss.org/repos/labs/labs/jbossrules/trunk/drools-api/src/main/resources/change-set-1.0.0.xsd' ><add><resource source='file://myfolder/' type='DRL' /></add></change-set>
   
       
   

4.31. ナレッジエージェント

ナレッジエージェントは自動ロード、リソースのキャッシュ、リロードを行い、ナレッジベースのプロパティファイルから設定します。エージェントは、使用するリソースに変更があると、ナレッジエージェントがナレッジベースを更新または再構築します。これの戦略は、ファクトリで指定した設定により決定されますが、通常は標準のポーリングを使用したプルベースとなっています。

4.32. ナレッジエージェントの例

以下は、ナレッジエージェントがどのように使用されているかを示しています。
KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent" );
kagent.applyChangeSet( ResourceFactory.newUrlResource( url ) );
KnowledgeBase kbase = kagent.getKnowledgeBase();

4.33. KnowledgeAgent オブジェクト

KnowledgeAgent オブジェクトは、60 秒の間隔というデフォルトのポーリングを使用してすべてのリソースを継続的にスキャンします。変更日が更新されると、新しいリソースを使用してキャッシュされたナレッジベースに変更を適用します。以前の KnowledgeBase の参照はまだ存在しており、新しく構築された KnowledgeBase にアクセスするには getKnowledgeBase() を呼び出す必要があります。ディレクトリが変更セットの一部として指定された場合、ディレクトリの全コンテンツをスキャンして変更がないか確かめます。変更の適用方法は、エージェントに渡される KnowledgeAgentConfiguration オブジェクトの中の drools.agent.newInstance プロパティにより左右されます。

4.34. ポーリングサービスの開始

手順4.3 タスク

  • ポーリングを行うにはポーリングサービスと通知サービスを開始する必要があります。以下のコードを使用します。
    ResourceFactory.getResourceChangeNotifierService().start();
    ResourceFactory.getResourceChangeScannerService().start();

4.35. KnowledgeBuilder 向けのカスタムの Classloader

手順4.4 タスク

  1. KnowledgeBuilderConfiguration を開き、カスタムのクラスローダーを指定します。
  2. カスタム設定をこれらのコンパイラーに渡す必要がある場合、 KnowledgeBuilderConfiguration オブジェクトを KnowledgeAgentFactory.newKnowledgeAgent() に送ります。

4.36. KnowledgeBase ClassLoader の再利用

多くの場合、ルールを実行できるように、リモートリソースのコンパイルプロセスで使用するクラスローダーは、ナレッジエージェントの kbase で必要なものと同じとなっています。
この手法を使用する場合、希望の ClassLoader をエージェント kbase に設定して、 KnowledgeAgentConfiguration オブジェクトの drools.agent.useKBaseClassLoaderForCompiling プロパティを使用する必要があります。
この手法では、エージェントの kbase が使用するクラスローダーを変更することで、エージェントの kbuilder classloader をランタイム時に変更することができます。これは、Changeset の漸増処理を使用しない場合も機能します。kbase が再作成されると、その設定が再利用されクラスローダーは保持されます。

4.37. KnowledgeAgentConfiguration の例

以下は、KnowledgeAgentConfiguration プロパティの例です。
KnowledgeBaseConfiguration kbaseConfig =
    KnowledgeBaseFactory.newKnowledgeBaseConfiguration(null, customClassLoader);
KnowledgeBase kbase =
    KnowledgeBaseFactory.newKnowledgeBase(kbaseConfig); //kbase with custom classloader
KnowledgeAgentConfiguration aconf =
    KnowledgeAgentFactory.newKnowledgeAgentConfiguration();
aconf.setProperty("drools.agent.newInstance", "false"); //incremental change set processing enabled
aconf.setProperty("drools.agent.useKBaseClassLoaderForCompiling", "true");
KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent(
                "test agent", kbase, aconf);

4.38. newInstance プロパティ

newInstance プロパティは、変更セットの処理をサポートします。
ナレッジエージェントは、1) 新しい変更セットが処理されるたびにナレッジベースを再作成するか、2) キャッシュされたナレッジベースを壊すことなく変更セットに適用するかの 2 種類の方法で、変更セットを処理することができます。この動作は、Agent のコンストラクターに渡される際は KnowedgeAgentConfiguration オブジェクトの newInstance プロパティで制御されます。

4.39. newInstance プロパティの使用

newInstance が true (デフォルト値) に設定されている場合、エージェントはエージェント内のキャッシュナレッジベースを破棄し、Change set の変更を含む新しいものを作成します。newInstance が false に設定されている場合、Change set は直接キャッシュされたナレッジベースに適用されます。Change set のリソースで変更されなかったルールは、ナレッジベースで置き換えられず、変更または削除されたルールがキャッシュナレッジベースから変更または削除されます。Functions、Queries、Definition 型は、変更の有無に拘らず常にキャッシュナレッジベースで置き換えられます。

4.40. newInstance の例

以下のコードスニペットは、newInstance プロパティを false に設定して、新しいナレッジエージェントを作成します。
KnowledgeAgentConfiguration aconf = KnowledgeAgentFactory.newKnowledgeAgentConfiguration();
aconf.setProperty("drools.agent.newInstance", "false");
KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent("test agent", null, aconf);

4.41. リモートの HTTP リソースキャッシュ

ナレッジエージェントは、http(s) URL からリソースをリモートから「プル」することができます。

4.42. 再起動後のリソースキャッシュのリストア

手順4.5 タスク

  • リソースがリモートですでに利用できなくなっている場合 (例:リモートのサーバーが再起動された場合) に再起動に耐えるようにするには、システムプロパティ drools.resource.urlcache を設定します (アプリケーションの書き込み権限があるディレクトリに設定するようにしてください)。ナレッジエージェントは、そのディレクトリ内にリモートリソースのコピーをキャッシュします。
    例えば、java コマンドライン -Ddrools.resource.urlcache=/users/someone/KnowledgeCache を使用すると、再起動されてもエージェントが使用できるように、リソースのローカルコピー (ルール、パッケージなど) をそのディレクトリに保存します (リモートのリソースが利用できるようになり、更新されると、自動的にローカルのキャッシュコピーを更新されます)。

第5章 処理

5.1. アジェンダ

アジェンダは Rete 機能です。WorkingMemory のアクション中に、ルールが完全合致して実行できるようになる場合があります。ワーキングメモリのアクション 1 つで、複数のルールが実行できるようになる可能性があります。ルールが完全合致すると、アクティベーションが作成され、ルールと合致ファクトを参照して、アジェンダに設定します。アジェンダは、競合解消戦略を使用して、これらのアクティベーションの実行の順番を管理します。

5.2. アジェンダの処理

エンジンは、以下の 2 つのフェーズのサイクルを繰り返します。
  1. ワーキングメモリアクション。これは、Consequence (RHS 自体) か 主な Java アプリケーションのプロセスなど、作業が実行される場所です。Consequence が終了するか、主要なアプリケーションプロセスが fireAllRules() を呼び出すと、エンジンはアジェンダ評価フェースに切り替えられます。
  2. アジェンダ評価。これは、発火するルールの選択を試みます。ルールが見つからなかった場合、そのまま存在して、見つかった場合はそのルールを発火し、このフェーズを再度ワーキングメモリアクションに切り替えます。
このプロセスは、アジェンダがなくなるまで繰り返されます。アジェンダがなくなると、呼び出し中のアプリケーションに制御が返されます。ワーキングメモリアクションが実行されている間は、ルールは発火されません。

5.3. デフォルトの競合解消戦略

Salience (優先順位)
他のルールよりも特定のルールのほうが優先度が (大きい数字を割り当てます) 高いことを指定できます。この場合は、高い salience を持つルールが優先されます。
LIFO (Last In, First Out)
LIFO 優先順位は、割り当てられたワーキングメモリアクションのカウンター値をベースにしています。この際、同じアクションが同じ値を受け取ると作成されるルールをすべて使用します。同じ優先順位を持つ発火セットの実行順は任意です。

注記

一般的なルールとして、特定の順番で発火されるルールに依存したり、フローを気にせずルールを作成したりするのはよくありません。しかし、フローが必要な場合は、アジェンダグループ、ルールフローグループ、アクティベーショングループ、コントロール/セマフォファクト (を含むがこれに限らない) などの可能性が多数存在します。この点については後のセクションで説明します。

5.4. AgendaGroup

アジェンダグループとは、アジェンダでルールを分割する方法です。「フォーカス」があるのは、1回に 1 つのグループのみになります。つまり、そのグループ内のルールに対してのみアクティベーションが有効になります。また、「自動フォーカス」のルールを持たせることも可能です。これは、ルールの条件が true の場合にそのアジェンダグループにフォーカスが当たるようになっています。
アジェンダグループは、CLIPS の用語では「モジュール」として知られています。アジェンダグループは、分類されたルールの間のフローを作成する方法を提供します。ルールエンジンや API を使用して、フォーカスが当たっているグループを切り替えることができます。ルールに、複数のフェーズや処理の順番が必要であるとはっきり分かっている場合は、この目的でアジェンダグループの使用を検討してください。

5.5. setFocus()

setFocus() が呼び出されるたびに、指定のアジェンダグループをスタックにプッシュします。フォーカスグループが空の場合スタックから取り出され、一番上にあるフォーカスグループが評価されます。アジェンダグループはスタックの複数の箇所で現れます。デフォルトのアジェンダグループは "MAIN" で、アジェンダグループが指定されていないルールはすべてこのグループに入っています。また、このグループは常にスタックの最初に来て、デフォルトで最初にフォーカスがあたっています。

5.6. setFocus() の例

以下は、setFocus()要素がどのように使用されているかを示しています。
ksession.getAgenda().getAgendaGroup( "Group A" ).setFocus();

5.7. ActivationGroup

アクティベーショングループは、同じ "activation-group" ルール属性を持つルール同士をバインドしたセットのことです。このグループ内で 1 つのルールのみが発火でき、そのルールが発火されると、それ以外のすべてのルールはアジェンダから取り消されます。発火の順番が来る前にすべてのアクティベーションを取り消す clear() メソッドは、いつでも呼び出すことができます。

5.8. ActivationGroup の例

以下は、ActivationGroup がどのように使用されているか示しています。
ksession.getAgenda().getActivationGroup( "Group B" ).clear();

5.9. RuleFlowGroup

ルールフローグループは、"ruleflow-group" ルール属性に紐付けされたルールのグループです。これらのルールは、グループがアクティブな状態でないと発火できず、グループ自体は、グループを表すノードに ruleflow の図が到達してからでなければアクティブにはなりません。ここでも、アジェンダに残っているアクティベーションをすべてキャンセルするために、clear() メソッドをいつでも呼び出すことができます。

5.10. RuleFlowGroup の例

これは、RuleFlowGroup プロパティがどのように使用されているかを示しています。
ksession.getAgenda().getRuleFlowGroup( "Group C" ).clear();

5.11. ルールとメソッドの違い

  • メソッドは直接呼び出される。
  • 特定のインスタンスを渡す。
  • 1 つの呼び出しで実行されるのは 1 つである。
  • ルールは、エンジンに挿入される限りデータを照合していき実行される。
  • ルールは直接呼び出すことができない。
  • 特定のインスタンスがルールに渡されることはない。
  • 一致内容に応じて、ルールは 1 回または複数回発火することができ、また全く発火しないこともできる。

5.12. クロスプロダクトの例

以下で、制約のない火災報知機の状態のルールについて表示しています。
rule
when
    $room : Room()
    $sprinkler : Sprinkler()
then
    System.out.println( "room:" + $room.getName() +
                        " sprinkler:" + $sprinkler.getRoom().getName() );
end
SQL の用語では、select * from Room, Sprinkler を実行しているのとよく似ており、Room テーブルのすべての行は Sprinkler テーブルのすべての行と結合され、以下のような出力となります。
room:office sprinkler: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
when
    $room : Room()
    $sprinkler : Sprinkler( room == $room )
then
    System.out.println( "room:" + $room.getName() +
                        " sprinkler:" + $sprinkler.getRoom().getName() );
end
結果、Room ごとに正しい Sprinkler を持つ 4 行分のデータだけが残ります。SQL (実際は HQL) では、該当のクエリは select * from Room, Sprinkler where Room == Sprinkler.room となります。
room:office sprinkler:office
room:kitchen sprinkler:kitchen
room:livingroom sprinkler:livingroom
room:bedroom sprinkler:bedroom

5.13. アクティベーション、アジェンダ、コンフリクトの例

この例では、キャッシュフロー算出システムについて見ていきます。以下に、実装されたクラスを 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
}
2 津のルールを使用してその四半期の貸方と借方判断して勘定残高を更新することができます。以下の 2 つのルールは、指定の期間アカウントのキャッシュフローに制約を加えます。ショートカット構文を使用してフィールド名が 2 回繰り返されないようにする "&&" に注目してください。
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.balance  += $amount;
end
rule "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.balance -= $amount; 
end
AccountPeriod が最初の四半期に設定されている場合、"increase balance for credits" のルールに制約を加えデータ 2 行に対して発火して、"decrease balance for debits" で、データ 1 行に対してアクションを行います。
データは挿入段階で称号され、fireAllRules() が呼び出されてから発火されます。その間、ルールと合致データはアジェンダに置かれ、アクティベーションとして参照されます。アジェンダは、fireAllRules() が呼び出されてすぐに発火可能でその結果を実行できるアクティベーションテーブルです。アジェンダのアクティベーションは順番に実行されます。これまでの実行の順番は任意となっている点に注目してください。
上記のアクティベーションがすべて発火されると、勘定残高は -25 になります。
AccountPeriod が第 2 四半期に更新されると、合致データの行は 1 つになるため、アジェンダのアクティベーションも 1 つだけとなります。
アクティベーションを発火すると、残高は 25 になります。

5.14. 競合解消戦略

アジェンダに 1 つ以上のアクティベーションがあることをコンフリクト状態と呼び、競合解消戦略を使用して実行順位を決定します。最も簡単なのは、デフォルトの戦略が Salience を使用してルールの優先順位を決定します。

5.15. 競合解消戦略の例

各ルールのデフォルト値は 0 で、値が高いと優先順位も高くなります。これを例示するために、勘定残高の印刷をするためのルールを 1 つ追加します。目的は、ルールが借方、貸方すべてが全勘定に適用されてからルールを実行することです。これは、マイナスの salience をこのルールに割り当て、salience がデフォルトの 0 となっている全ルールの後にこのルールが発火されるようにします。
rule "Print balance for AccountPeriod"
        salience -50
    when
        ap : AccountPeriod()
        acc : Account()        
    then
        System.out.println( acc.accountNo + " : " + acc.balance );    
end

5.16. トリガーの例

表5.1 トリガーの例

ルールビュー ビューのトリガー
select * from Account acc,
              Cashflow cf,
              AccountPeriod ap      
where acc.accountNo == cf.accountNo and 
      cf.type == CREDIT and
      cf.date >= ap.start and 
      cf.date <= ap.end
select * from Account acc, 
              Cashflow cf,
              AccountPeriod ap 
where acc.accountNo == cf.accountNo and 
      cf.type == DEBIT and
      cf.date >= ap.start and 
      cf.date <= ap.end
trigger : acc.balance += cf.amount
trigger : acc.balance -= cf.amount

5.17. ruleflow-group の例

ルールの ruleflow-group 属性の使用について以下に示しています。
rule "increase balance for credits"
  ruleflow-group "calculation"
when
  ap : AccountPeriod()
  acc : Account( $accountNo : accountNo )
  CashFlow( type == CREDIT,
            accountNo == $accountNo,
            date >= ap.start &&<= ap.end,
            $amount : amount )
then
  acc.balance  += $amount;
end
rule "Print balance for AccountPeriod"
  ruleflow-group "report"
when
  ap : AccountPeriod()
  acc : Account()
then
  System.out.println( acc.accountNo +
                      " : " + acc.balance );    
end

5.18. 推論の例

以下の例では、IsAdult プロパティを使用して、年齢を推測します。
rule "Infer Adult"
when
  $p : Person( age >= 18 )
then
  insert( new IsAdult( $p ) )
end
この推論関係は、どのルールでも利用できます。
$p : Person()
IsAdult( person == $p )
さらに、ナレッジプロセスを切り離すことで、データリークや第三者が情報を変更する機会を減らします。

5.19. 推論および TruthMaintenance の実装

手順5.1 タスク

  1. ルールセットを開きます。この例では、バス定期券発行システムを使用します。
    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
  2. insertLogical ファクトを挿入して、推論する用語を追加します。
    rule "Infer Child" when
      $p : Person( age < 16 )
    then
        insertLogical( new IsChild( $p ) )
    end
    rule "Infer Adult" when
        $p : Person( age >= 16 )
    then
        insertLogical( new IsAdult( $p ) )
    end
    ファクトが論理的に挿入されました。このファクトは、When 句が true かどうかにより左右されます。つまり、ルールが false の場合、ファクトは自動的に削除されます。2 つのルールは相互排他的であるため、これは特にうまく機能します。上記のルールでは、子供が 16 歳未満であると、IsChild ファクトが挿入されます。その人が 16 歳以上になると自動的に削除され、IsAdult ファクトが挿入されます。
  3. 定期券を発行するコードを挿入します。これらは、TMS ではカスケードされた取り消しに対して論理挿入の連鎖ができるため、論理的に挿入することもできます。
    rule "Issue Child Bus Pass" when
        $p : Person( )
        IsChild( person == $p )
    then
        insertLogical(new ChildBusPass( $p ) );
    end
     
    rule "Issue Adult Bus Pass" when
        $p : Person( age >= 16 )
        IsAdult( person =$p )
    then
        insertLogical(new AdultBusPass( $p ) );
    end
    この人が 15 歳から 16 歳になると、IsChild ファクトが自動的に取り消されるだけでなく、ChildBussPass ファクトも取り消されます。
  4. 'not' 条件要素を挿入して通知を処理します (この場合、定期券返却の依頼)。TMS が自動的に ChildBussPass オブジェクトを取り消すと、このルールがトリガーして、この人に依頼が送信されます。
    rule "Return ChildBusPass Request "when
      $p : Person( )
           not( ChildBusPass( person == $p ) )
    then
        requestChildBusPass( $p );
    end

第6章 ルール言語

6.1. KnowledgeBuilder

KnowledgeBuilder は、DRL ファイルや Excel ファイルなどのソースファイルを取得して、ナレッジベースが消費可能なルールやプロセス定義のナレッジパッケージに変換します。ResourceType クラスは、ビルダーが処理するように依頼されたリソース型を示します。

6.2. ResourceFactory

ResourceFactory には、java.io.Reader、classpath、URL、java.io.File やバイト配列などの複数のソースからリソースをロードする機能があります。決定表 (Excel の .xls ファイル) などのバイナリファイルは、Reader で渡してはいけません。これは、テキストベースのテキストのみに適しています。

6.3. 新しい KnowledgeBuilder の作成

手順6.1 タスク

  1. KnowledgeBuilderFactory を開きます。
  2. 新規のデフォルト設定を作成します。
  3. 以下を設定に入力します。
    KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
    最初のパラメーターは任意でプロパティ用です。空白のままにすると、デフォルトオプションが使用されます。オプションのパラメーターは、方言の変更や新規の accumulator 関数の登録などに使用することができます。
  4. カスタムの ClassLoader で KnowledgeBuilder を追加するには、以下のコードを使用します。
    KnowledgeBuilderConfiguration kbuilderConf = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration(null, classLoader );
    KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(kbuilderConf);
    

6.4. DRL リソースの追加

手順6.2 タスク

  1. 型のリソースは反復的に追加することができます。以下は、DRL ファイルを追加しています。ナレッジビルダーは、複数の名前空間を処理できるため、名前空間が何であってもリソースを組み合わせることができます。
    kbuilder.add( ResourceFactory.newFileResource( "/project/myrules.drl" ),
                  ResourceType.DRL);
    
  2. リソールの追加のたびに、コンパイルの結果を確認します。KnowledgeBuilder は 3 種類の重要度 (ERROR、WARNING、INFO) とのコンパイル結果のレポートを出します。
    • ERROR は、リソースのコンパイルに失敗したことを指します。エラーがある場合、リソースの追加や、ナレッジパッケージの取得をこれ以上行わないようにしてください。エラーがない場合、getKnowledgePackages() は何も返しません。
    • WARNING および INFO の結果は無視しても構いませんが、検証用に提供されています。

6.5. KnowledgeBuilder 結果の確認方法

KnowledgeBuilder API は、重要度一覧に関するビルドの結果を確認、取得するために複数のメソッドを提供しています。
    /**
     * Return the knowledge builder results for the listed severities.
     * @param severities
     * @return
     */
    KnowledgeBuilderResults getResults(ResultSeverity... severities);
    
    /**
     * Checks if the builder generated any results of the listed severities
     * @param severities
     * @return
     */
    boolean hasResults(ResultSeverity... severities ;
KnowledgeBuilder API は、hasErrors() および getErrors() のエラーのみを確認する helper メソッドが 2 つ含まれています。
if( kbuilder.hasErrors() ) {
    System.out.println( kbuilder.getErrors() );
    return;
}

6.6. KnowledgePackages の取得

リソースがすべて追加され、エラーがない場合、ナレッジパッケージのコレクションを取得できます。パッケージの名前空間ごとに KnowledgePackage が 1 つあるため、java.util.Collection となっています。これらのナレッジパッケージは直列化可能で、デプロイメントの 1 ユニットとして使用されます。
以下は、KnowledgePakcage の取得の例をです。
Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();

6.7. KnowledgeBuilder の展開例

以下は、KnowledgeBuilder のパッケージ全体例となります。
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();

6.8. バッチモードでの KnowledgeBuilder の使用

KnowledgeBuilder には、Fluent interfance (流れるようなインターフェース) を使用するバッチモードがあります。KnowledgeBuilder は以下の例のように、1 度に複数の DRL を構築することができます。
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.batch()
        .add( ResourceFactory.newFileResource( "/project/myrules1.drl" ), ResourceType.DRL )
        .add( ResourceFactory.newFileResource( "/project/myrules2.drl" ), ResourceType.DRL )
        .add( ResourceFactory.newFileResource( "/project/mytypes1.drl" ), ResourceType.DRL )
        .build();

6.9. 最後に追加した DRL ビルドの破棄

KnowledgeBuilder (バッチモードの使用の有無に拘らず) を使って、最後の DRL ビルドで追加されたものを破棄することができます。これは、KnowledgeBuilder にエラーのある DRL ビルドを追加した状態からリカバリします。以下の例を確認してください。
kbuilder.add( ResourceFactory.newFileResource( "/project/wrong.drl" ), ResourceType.DRL );
if ( kbuilder.hasErrors() ) {
    kbuilder.undo();
}

6.10. 設定および ChangeSet XML を使用した構築

ChangeSet XML 内の設定を使用して定義を作成します。単純な XML ファイルは add、remove、modify の 3 つの要素をサポートします。これらはそれぞれ、設定エンティティを定義する <resource> サブ要素の配列が含まれています。

6.11. ChangeSet XML の XML スキーマ (標準ではない)

以下は、標準ではない ChangeSet のスキーマです。
<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: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>

  

  
    
      
      
      
    
  

  
    
      
    
  

  
    
      
      
    
    
    
  

  
    
    
  

  
  
    
      
      
      
      
      
      
      
      
      
    
  

  
  
    
      
      
    
  

第7章 ChangeSet

7.1. Changeset

changeset 機能により、API を使用しないナレッジベースの構築がスムーズに行えるようになります。Changeset には、多数のリソースを含めることができ、追加の設定情報 (現在は決定表でのみ必要ですが) もサポートすることが可能です。
changeset.xml ファイルには、これに関するリソースの一覧が含まれています。このファイルから別の changeset XML ファイルに再帰的に参照することも可能です。

7.2. changeset の例

プロトコルを指定するプリフィックスを使用するリソースのアプローチが採用されています。"file" や "http"、追加の "classpath" などの java.net.URL で提供されているすべてのプロトコルに対応しています。型属性はファイル名の拡張から推測できないため,現在、リソース毎に必ず型属性を指定する必要があります。これについて、以下に例で示しています。
<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 http://anonsvn.jboss.org/repos/labs/labs/jbossrules/trunk/drools-api/src/main/resources/change-set-1.0.0.xsd' ><add><resource source='http:org/domain/myrules.drl' type='DRL' /></add></change-set>
   
       
   

上記の例は、リソース型を CHANGE_SET に変更して使用することができます。
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newClasspathResource( "myChangeSet.xml", getClass() ),
              ResourceType.CHANGE_SET );
if ( kbuilder.hasErrors() ) {
    System.err.println( builder.getErrors().toString() );
}

7.3. ChangeSet XML の例

以下は、基本的な ChangeSet 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 http://anonsvn.jboss.org/repos/labs/labs/jbossrules/trunk/drools-api/src/main/resources/change-set-1.0.0.xsd' ><add><resource source='file:/project/myrules.drl' type='DRL' /></add></change-set>
   
      
   

7.4. ChangeSet プロトコル

ChangeSet は、"file"、"http"、追加の "Classpath" など java.net.URL が提供する全プロトコルに対応しています。型属性はファイル名の拡張から推測できないため、現在、リソース毎に必ず型属性を指定する必要があります。
file: プレフィックスを使用して、リソースのプロトコルに意味をもたせます。

注記

XML スキーマを使用する場合、ChangeSet XML 自体が ClassPath リソースによりロードされない限り、ナレッジビルダーが使用するクラスローダーにデフォルト設定されます。この場合、そのリソースに対して指定されたクラスローダーを使用します。

7.5. ChangeSet XML のロード

手順7.1 タスク

  1. ChangeSet をロードするには API を使用します。
  2. 以下のコードを使用して、ChangeSet XML にアクセスします。
    kbuilder.add( ResourceFactory.newUrlResource( url ), ResourceType.CHANGE_SET );
    

7.6. リソース設定を持つ ChangeSet XML の例

以下の例は、お使いの ChangeSet にどのようにリソース設定を組み込むのかを提示しています。
 <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 http://anonsvn.jboss.org/repos/labs/labs/drools/trunk/drools-api/src/main/resources/change-set-1.0.0.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>
  
       
       
           
       
   

7.7. ChangeSet XML およびディレクトリ

以下のコードは、ディレクトリのコンテンツを ChangeSet に追加することができます。
<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 http://anonsvn.jboss.org/repos/labs/labs/drools/trunk/drools-api/src/main/resources/change-set-1.0.0.xsd' ><add><resource source='file:/projects/myproject/myrules' type='DRL' /></add></change-set>
   
      
   

注記

現在、フォルダー内のリソースはすべて同じ型である必要があります。ナレッジエージェントを使用している場合、追加、変更、削除されたリソースがないか継続的にスキャンして、キャッシュのナレッジベースを再構築します。

第8章 構築

8.1. ビルド結果の重要度

ビルドテストの型が持つデフォルトの重要度を変更することができます。例えば、既存ルールで名前が重複している新しいルールをパッケージに追加する場合などに便利です (この場合、デフォルトの動作は、古いルールを新しいルールで置き換え、INFO としてレポートします)。デプロイメントによっては、ルールの更新を避け、エラーとしてレポートしたほうがいい場合もあります。

8.2. デフォルトのビルド結果の重要度設定

手順8.1 タスク

  1. システムプロパティまたは設定ファイルを使用して重要度を設定するには、以下のプロパティを挿入します。
    // sets the severity of rule updates
    drools.kbuilder.severity.duplicateRule = <INFO|WARNING|ERROR>
    // sets the severity of function updates
    drools.kbuilder.severity.duplicateFunction = <INFO|WARNING|ERROR>
    
  2. API を使用して重要度を変更するには、以下のコードを使用します。
    KnowledgeBuilderConfiguration kbconf = ...
    
    // sets the severity of rule updates to error
    kbconf.setOption( KBuilderSeverityOption.get( "drools.kbuilder.severity.duplicateRule", ResultSeverity.ERROR ) ); 
    // sets the severity of function updates to error
    kbconf.setOption( KBuilderSeverityOption.get( "drools.kbuilder.severity.duplicateFunction", ResultSeverity.ERROR ) );
    

8.3. KnowledgePackage

ナレッジパッケージは、ルールやプロセスなどナレッジ定義の集まりです。ナレッジビルダーによりナレッジパッケージは作成されます。ナレッジパッケージは、埋め込み型で直列化可能です。現在、このパッケージにより基本的なデプロイメントユニットを形成します。

注記

ナレッジパッケージインスタンスは、ナレッジベースに追加した後は再利用できません。別のナレッジベースにインスタンスを追加する必要がある場合、直列化して「クローン作成した」結果を使用します。

8.4. 新しい KnowledgeBase の作成

手順8.2 タスク

  1. このデフォルト設定を使用して新しい KnowledgeBase を作成します。
    KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
  2. カスタムのクラスローダーが KnowledgeBuilder で使用してデフォルトのクラスローダー以外で型を解決する場合、KnowledgeBase に設定する必要があります。この手法は、KnowledgeBuilder と同じで以下のようになります。
    KnowledgeBaseConfiguration kbaseConf =
        KnowledgeBaseFactory.createKnowledgeBaseConfiguration( null, cl );
    KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase( kbaseConf );

8.5. インプロセス構築およびデプロイ

プロセス内の構築は最も単純なデプロイメントの形式です。ナレッジ定義をコンパイルして、同じ JVM 内のナレッジベースにこれらの定義を追加します。このアプローチでは、クラスパスに置くには drools-core.jar および drools-compiler.jar が必要です。

8.6. KnowledgeBase への KnowledgePackages の追加

手順8.3 タスク

  • KnowledgePackages を KnowledgeBase に追加するには、以下のコードを使用します。
    Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();
    
    KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
    kbase.addKnowledgePackages( kpkgs );
    addKnowledgePackages(kpkgs) メソッドは、繰り返し呼び出してさらにナレッジを追加することができます。

8.7. 別のプロセスでの構築およびデプロイメント

KnowledgeBase および KnowledgePackage はどちらもデプロイメントユニットで直列化可能です。つまり、必要な構築を 1 台のマシンで行い (drools-compiler.jar が必要)、他のマシンですべてをデプロイ、実行します (drools-core.jar のみが必要)。

8.8. OutputStream への KnowledgePackage の記述

これは OutputStream へ KnowledgePackage を記述するコードです。
 ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( fileName ) );
out.writeObject( kpkgs );
out.close();

8.9. InputStream から KnowledgePackage の読み取り

以下のコードを使用して、InputStream から 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 );

8.10. JBoss Rules 管理システム

JBoss Rules 管理システムは KnowledgeBases のリポジトリで、多数のルールセットを保持します。
また、システムは直列化されたナレッジパッケージをコンパイルして URL に公開し、これを使用してパッケージをロードします。

8.11. StatefulknowledgeSessions および KnowledgeBase の変更

KnowledgeBaseStatefulKnowledgeSession オブジェクトを作成して返し、オプションでこれらのオブジェクトへ参照することも可能です。
KnowledgeBase が変更されると、セッション内のデータに適用されます。この参照は弱参照で、これもオプションです。Boolean フラグにより制御されます。

8.12. 新たな knowledgeAgent

以下は、新規 KnowlegeAgent を作成するコードです。
KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent" );

8.13. OutputStream への KnowledgePackage の記述

以下は、OutputStream へ KnowledgePackage を記述する方法です。
KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent" );
kagent.applyChangeSet( ResourceFactory.newUrlResource( url ) );
KnowledgeBase kbase = kagent.getKnowledgeBase();

注記

リソースのスキャンはデフォルトでは有効ではないため、開始する必要があります。これは通知にも該当します。ResourceFactory 経由で両方とも有効にすることができます。

8.14. スキャンと通知サービスの開始

以下はスキャンと通知サービスを開始するためのコードです。
ResourceFactory.getResourceChangeNotifierService().start();
ResourceFactory.getResourceChangeScannerService().start();

8.15. ResourceChangeScanner

ResourceChangeScanner は、サービスをスキャンにするために使用します。デフォルトのリソーススキャンの期間は、ResourceChangeScannerService 経由で変更することができます。適切に更新された ResourceChangeScannerConfiguration オブジェクトは、サービスの configure() メソッドに渡され、サービスがオンデマンドで再構成できるようにします。

8.16. スキャン間隔の変更

以下は、スキャンの間隔を変更する際に使用するコードです。
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 );

8.17. ナレッジエージェントとナレッジベース間の対話

ナレッジエージェントは、空のナレッジベースや生成したナレッジベースを処理することができます。生成したナレッジベースが指定された場合、ナレッジエージェントはナレッジベースからイテレーターを実行して、見つかったリソースにサブスクライブします。ナレッジビルダーがディレクトリ内で見つかったリソースをすべて構築できるだけでなく、その情報はナレッジビルダーにより消され、これらのディレクトリが継続してスキャンされないようにします。applyChangeSet(Resource) メソッドの一部として指定されたディレクトリのみ、モニタリングされます。
スタートポイントとして KnowledgeBase を指定する利点は、KnowledgeBaseConfiguration でそれを提供できる点です。リソースの変更が検出され新しい KnowledgeBase オブジェクトがインスタンス化されると、以前の KnowledgeBaseKnowledgeBaseConfiguration を使用します。

8.18. 既存の KnowledgeBase の使用

以下は、既存の KnowledgeBase を活用するコードです。
KnowledgeBaseConfiguration kbaseConf =
    KnowledgeBaseFactory.createKnowledgeBaseConfiguration( null, cl );
KnowledgeBase kbase KnowledgeBaseFactory.newKnowledgeBase( kbaseConf );
// Populate kbase with resources here.

KnowledgeAgent kagent =
    KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent", kbase );
KnowledgeBase kbase = kagent.getKnowledgeBase();
上記の例では getKnowledgeBase() はリソースの変更が検出され新しいナレッジベースが構築されるまで指定の同じ kbase インスタンスを返します。新しいナレッジベースが構築される際は、以前の KnowledgeBase に提供された KnowledgeBaseConfiguration を使って行われます。

8.19. applyChangeSet() メソッド

ChangeSet XML と applyChangeSet() メソッドとを合わせて使用すると、スキャンプロセスにディレクトリを追加します。ディレクトリのスキャンで追加のファイルが検出されると、そのファイルはナレッジベースに追加されます。削除されたファイルも、変更されたファイルも、ナレッジベースから削除されます。

8.20. ディレクトリのコンテンツを追加するための ChangeSet XML

この XML を使用して、ディレクトリのコンテンツを ChangeSet に追加します。
<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>
   
      
   

注記

リソース型 PKG は、drools-compiler の依存関係が必要ではない点に注意してください。ナレッジエージェントは drools-core だけでこれらを処理することができます。

8.21. KnowledgeAgentConfiguration プロパティ

KnowledgeAgentConfiguration は、ナレッジエージェントのデフォルトの動作を変更するために使用します。これを使用して、ディレクトリからリソースをロードし、そのディレクトリへの変更を加え継続的なスキャンを阻止します。

8.22. スキャン動作の変更

以下のコードを使用してスキャンの動作を変更します。
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();

KnowledgeAgentConfiguration kaconf =
    KnowledgeAgentFactory.newKnowledgeAgentConfiguation();
// Do not scan directories, just files.
kaconf.setProperty( "drools.agent.scanDirectories", "false" );
KnowledgeAgent kagent =
    KnowledgeAgentFactory.newKnowledgeAgent( "test agent", kaconf );

第9章 セッション

9.1. JBoss Rules のセッション

セッションは、データを挿入することができプロセスインスタンスを開始できる KnowledgeBase から作成されます。KnowledgeBase の作成はリソースを集中的に使用する場合がありますが、セッションの作成はそうではありません。このような理由から、できる限り KnowledgeBases をキャッシュして、繰り返してセッションを作成できるようにするよう推奨されています。

9.2. KnowledgeBase からの StatefulKnowledgeSession の作成

以下は、KnowledgeBase から新しい StateefulKnowledgeSession を作成するコードです。
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();

9.3. WorkingMemoryEntryPoint メソッド

WorkingMemoryEntryPoint は、ファクトの挿入、更新、取得関連のメソッドを提供します。「エントリポイント」という用語は、ワーキングメモリ内にある複数のパーティションのどの部分に挿入するか選択できるということに関連しています。ほとんどのルールベースのアプリケーションは、デフォルトのエントリポイントだけで機能します。

9.4. KnowledgeRuntime インターフェース

KnowledgeRuntime インターフェースは、主にエンジンとの対話を行い、ルールの結論部やプロセスアクションで利用できます。KnowledgeRuntime は、WorkingMemory および ProcessRuntime 両方からのメソッドを継承して、統合された API を提供してプロセスとルールと連携します。ルールと連携すると、KnowledgeRuntimeWorkingMemoryEntryPointWorkingMemoryKnowledgeRuntime 自体の 3 つのインターフェースで形成されます。

9.5. ファクトの挿入

挿入とは、ファクトに関して WorkingMemory に通知する行為のことです。例えば、ksession.insert(yourObject) を使用して挿入を行うことができます。ファクトを挿入して、ルールに合致がないか検証します。つまり、ルールの発火を行うかどうかを決定する作業のすべてが挿入時に行われます。しかし、ファクトの挿入が完了した後に呼び出す fireAllRules() を呼び出すまで、ルールは実行されません。

9.6. FactHandle トークン

オブジェクトが挿入されると、FactHandle が返されます。この FactHandle は、WorkingMemory 内に挿入されたオブジェクトを表すために使用するトークンです。このトークンは、オブジェクトの削除や変更の際に WorkingMemory と対話をする際にも使用されます。

9.7. FactHandle の例

Job accountant = new Job("accountant");
FactHandle accountantHandle = ksession.insert( accountant );

9.8. アイデンティティと等価

これらは、ワーキングメモリにより使用される 2 つのアサーションノードです。
アイデンティティ
アイデンティティとは、ワーキングメモリが IdentityHashMap を使用してアサートされたオブジェクトをすべて保存するという意味です。新しいインスタンスアサーションは常に、新しい FactHandle を返すことになりますが、インスタンスが再度アサートされると、元のファクトハンドルを返します (つまり、同じオブジェクトに対する挿入の繰り返しを無視するということです)。
等価
等価とは、ワーキングメモリが HashMap を使用してアサートしたオブジェクトすべてを保存するという意味です。挿入されたオブジェクトが (equal メソッドによると) 既存のファクトと等価でない場合、オブジェクトインスタンスのアサーションは、新しい FactHandle のみを返します。

注記

新しいインスタンスアサーションは常に、新しい FactHandle を返すことになりますが、インスタンスが再度アサートされると、元のファクトハンドルを返します (つまり、同じオブジェクトに対する挿入の繰り返しを無視するということです)。

9.9. 取り消し

取り消しとは、ワーキングメモリからファクトを取り除くことです。つまり、これ以上ファクトのトラッキングと照合は行われず、アクティブ化されそのファクトに依存するルールはすべてキャンセルされるということです。ファクトがないことに依存するルールもありえます。ファクトを取り消すとルールがアクティブ化されるきっかけとなる場合があります。挿入コールにより返された FactHandle を使用して、取り消しを行うことができます。ルールの右側では、retract ステートメントを使用して、シンプルなオブジェクト参照と連携します。

9.10. 取り消しの例

Job accountant = new Job("accountant");
FactHandle accountantHandle = ksession.insert( accountant );
....
ksession.retract( accountantHandle );

9.11. update() メソッド

ルールエンジンは、再処理ができるようにファクトが変更されたことを通知する必要があります。update() メソッドを使用して、WorkingMemory 自体の通知ができないオブジェクトのために、オブジェクトが変更されたことを WorkingMemory に通知することができます。update() メソッドは常に、変更されたオブジェクトを 2 番目のパラメーターとして取り、イミュータブルオブジェクトに新しいインスタンスを指定することができるようになります。

注記

ルールの右側部分には、単一のステートメントでエンジンの変更と通知を行うため、modify ステートメントが推奨されます。あるいは、setter メソッドを呼び出してファクトオブジェクトのフィールド値を変更した後、すぐに update を呼び出す必要があります。すぐ呼び出しがされないと、他のファクトを変更する前でも、ルールエンジン内のインデックスに問題が発生する可能性があります。modify ステートメントはこの問題を回避することができます。

9.12. update() の例

Job accountant = new Job("accountant");
FactHandle accountantHandle = workingMemory.insert( accountant );
...
accountant.setSalary( 45000 );
workingMemory.update( accountantHandle, accountant );

9.13. クエリ

クエリは、ルールで使用されているようにパターンをベースとしてファクトセットを取得するために使用します。パターンがオプションのパラメーターを活用する場合もあります。クエリはナレッジベースで定義でき、ナレッジベースから合致した結果を戻すためにクエリを呼び出します。結果のコレクションが繰り返し行われている間、get メソッドを引数としてバイドした変数の名前で呼び出すことで、クエリでバインドされた識別子を使用して該当のファクトまたはファクトフィールドにアクセスすることができます。このバインドがファクトオブジェクトを参照している場合、パラメーターとして変数の名前を再度使用して getFactHandle を呼び出すことで FactHandle を取得できます。

9.14. クエリの例

QueryResults results =
    ksession.getQueryResults( "my query", new Object[] { "string" } );
for ( QueryResultsRow row : results ) {
    System.out.println( row.get( "varName" ) );
}

9.15. ライブクエリ

クエリを呼び出して、返されたセットを反復して結果を処理するのは、変更のモニタリングに適した方法ではありません。
これを軽減するために JBoss Rules は反復可能な結果セットを返すのではなくリスナーが接続されたライブクエリを提供しています。これらのライブクエリは、ビューを作成してこのビューのコンテンツ向けに変更イベントを公開することでオープンの状態を保ちます。アクティブ化するには、パラメーターでクエリを開始して結果ビューの変更をリッスンします。dispose メソッドはクエリを終了して、この反応型のシナリオを中断します。

9.16. 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( "cars",
                                          new Object[] { "sedan", "hatchback" },
                                          listener );
...
...
query.dispose() // calling dispose to terminate the live query

注記

ライブクエリの Glazed Lists 統合の例については、http://blog.athico.com/2010/07/glazed-lists-examples-for-drools-live.html を確認してください。

9.17. KnowledgeRuntime

KnowledgeRuntime は、グローバルの設定やチャネルの登録など、ルール、プロセス両方に適用可能なメソッドをさらに提供します ("Exit point" は "channel" と同義で以前使用されていた用語です)。

第10章 オブジェクトとインターフェース

10.1. グローバル (Global)

グローバル (Global) はルールエンジンに公開する名前付きのオブジェクトですが、ファクトと違って global をバックアップするオブジェクトに変更があってもルールの再評価はトリガーされません。Global はルールの RHS で使用されるサービスを提供するオブジェクトとして、ルールエンジンからオブジェクトを返す手段として、静的な情報を提供する際に便利です。

10.2. Global との連携

手順10.1 タスク

  1. Global をワーキングメモリに実装開始するには、ルールファイルに global を宣言して、Java オブジェクトでバックアップします。
    global java.util.List list
  2. ナレッジベースは global の識別子とその型を認識するようになったため、Global の名前とオブジェクトで ksession.setGlobal() を呼び出して、global とオブジェクトを関連付けることができます。
    List list = new ArrayList();
    ksession.setGlobal("list", list);

    重要

    Global の型と識別子を DRL コードで宣言しなかった場合は、この呼び出しから例外が送出されます。
  3. ルールの評価をする前に Global を設定するようにしてください。そうでないと、NullPointerException が送出されてしまいます。

10.3. Global の解決

Global は 3 つの方法で解決することができます。
getGlobals()
Stateless Knowledge Session メソッド getGlobals() は、セッションの global へアクセスできるようにする Global インスタンスを返します。これらは、実行呼び出しすべてに共有されます。別のスレッドで同時に実行呼び出しが実行される場合がありますので、ミュータブル global には注意してください。
Delegates
Global の解決を行う別の方法には、delegate の使用が挙げられます。global (setGlobal(String, Object) で) に値を割り当てると、識別子を値にマッピングする内部コレクション内に値が格納されます。この内部コレクションの識別子は、提供した delegate よりも優先されます。識別子がこの内部コレクションで見つからない場合、delegate global が使用されます (あれば)。
Execution
Execution scoped global は Command を使用して global を設定し、CommandExecutor に渡されます。

10.4. Session Scoped Global の例

以下は、Session Scoped Global の例です。
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
// Set a global hbnSession, 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 );

10.5. StatefulRuleSessions

StatefulRuleSession プロパティは、StatefulKnowledgeSession が継承して、エンジン外部から該当するルール関連のメソッドを提供します。

10.6. AgendaFilter オブジェクト

AgendaFilter オブジェクトは、フィルターインターフェースのオプション実装です。これを使用してアクティベーションの発火の許可または拒否を行うことが可能になります。フィルタリング対象は、実装により左右されます。

10.7. AgendaFilter の使用

手順10.2 タスク

  • フィルターを使用するには、fireAllRules() を呼び出す際に指定します。以下の例では、文字列 "Test" で終わるルールのみが許可され、それ以外はフィルタリングされ除外されてしまいます。
    ksession.fireAllRules( new RuleNameEndsWithAgendaFilter( "Test" ) );

10.8. ルールエンジンのフェーズ

エンジンは、以下の 2 つのフェーズのサイクルを繰り返します。
ワーキングメモリのアクション
これは、Consequence (RHS 自体) か 主な Java アプリケーションのプロセスなど、作業が実行される場所です。Consequence が終了するか、主要なアプリケーションプロセスが fireAllRules() を呼び出すと、エンジンはアジェンダ評価フェースに切り替えられます。
アジェンダ評価
これは、発火するルールの選択を試みます。ルールが見つからなかった場合、そのまま存在して、見つかった場合はそのルールを発火し、このフェーズを再度ワーキングメモリアクションに切り替えます。
このプロセスは、アジェンダがなくなるまで繰り返されます。アジェンダがなくなると、呼び出し中のアプリケーションに制御が返されます。ワーキングメモリアクションが実行されている間は、ルールは発火されません。

10.9. イベントモデル

イベントパッケージは、ルールの発火、アサートされたオブジェクトなど、ルールエンジンイベントを通知する手段を提供します。これにより、例えばロギングや監査のアクティビティと、アプリケーションの主要部 (およびルール) を分割することができます。

10.10. KnowlegeRuntimeEventManager

KnowlegeRuntimeEventManager インターフェースは、KnowledgeRuntime で実装され、WorkingMemoryEventManagerProcessEventManager の 2 つのインターフェースを提供します。

10.11. WorkingMemoryEventManager

WorkingMemoryEventManager により、リスナーの追加や削除が可能になり、ワーキングメモリのイベントやアジェンダをリッスンすることができます。

10.12. AgendaEventListener の追加

以下のコードスニペットは、どのようにシンプルなアジェンダーリスナーが宣言されセッションに接続されるかを示しています。発火後にアクティベーションを出力します。
ksession.addEventListener( new DefaultAgendaEventListener() {
   public void afterActivationFired(AfterActivationFiredEvent event) {
       super.afterActivationFired( event );
       System.out.println( event );
   }
});

10.13. ワーキングメモリのイベントの出力

以下のコードにより、リスナーを追加することですべてのワーキングメモリのイベントを出力することができます。
ksession.addEventListener( new DebugWorkingMemoryEventListener() );

10.14. KnowlegeRuntimeEvents

出されたイベントはすべて、KnowlegeRuntimeEvent インターフェースを実装し、このインターフェースを使用してイベントが出された実際の KnowlegeRuntime を取得することができます。

10.15. KnowledgeRuntimeEvent インターフェースに対応のイベント

現在サポートされているイベント:
  • ActivationCreatedEvent
  • ActivationCancelledEvent
  • BeforeActivationFiredEvent
  • AfterActivationFiredEvent
  • AgendaGroupPushedEvent
  • AgendaGroupPoppedEvent
  • ObjectInsertEvent
  • ObjectRetractedEvent
  • ObjectUpdatedEvent
  • ProcessCompletedEvent
  • ProcessNodeLeftEvent
  • ProcessNodeTriggeredEvent
  • ProcessStartEvent

10.16. KnowledgeRuntimeLogger

KnowledgeRuntimeLogger は、JBoss Rules の統合イベントシステムで監査ログを作成します。後に Eclipse 監査ビューワーなどのツールで検証できるように、この監査ログを使用してアプリケーションの実行ログを取ることができます。

10.17. FileLogger の有効化

FileLogger を有効にしてファイルをトラッキングするには、以下のコードを使用します。
KnowledgeRuntimeLogger logger =
  KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "logdir/mylogfile");
...
logger.close();

10.18. JBoss Rules での StatelessKnowledgeSession の使用

StatelessKnowledgeSession は拡張する代わりに StatefulKnowledgeSession をラップします。これは、主に決定サービスタイプのシナリオに焦点を当てており、dispose() を呼び出す必要がなくなります。ステートレスセッションは、反復挿入や Java コードからの fireAllRules() メソッド呼び出しはサポートしていません。execute() を呼び出す行為はワンショットメソッドで、StatefulKnowledgeSession を内部でインスタンス化し、すべてのユーザーデータを追加してユーザコマンドを実行し、fireAllRules() を呼び出した後、dispose() を呼び出します。このクラスと連携するための主な方法は、CommandExecutor でサポートされているように BatchExecution (Command のサブインターフェース) を使用することですが、単純なオブジェクトの挿入だけが必要な場合のために、便利な 2 つのメソッドが提供されています。CommandExecutor および BatchExecution は、それぞれのセクションで詳しく説明します。

10.19. コレクションを使用した StatelessKnowledgeSession の実行

以下のコードは、コレクションを使った StatelessKnowledgeSession の実行例です。
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newFileSystemResource( 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 );
}

10.20. InsertElements コマンドを使用した StatelessKnowledgeSession の実行

以下は、InsertElements コマンドを使用して StatelessKnowledgeSession を実行したコードです。
ksession.execute( CommandFactory.newInsertElements( collection ) );

注記

コレクションと個別の要素を挿入するには、CommandFactory.newInsert(collection) を使用します。

10.21. BatchExecutionHelper

CommandFactory のメソッドは、対応のコマンドを作成して、XStream とBatchExecutionHelper を使用してすべてをマーシャリングすることができます。BatchExecutionHelper は、XML 形式の詳細および BatchExecution および ExecutionResults のマーシャリングを自動化する JBoss Rules Pipuline の使用方法を提供します。

10.22. CommandExecutor インターフェース

CommandExecutor インターフェースは、"out" パラメーターを使用してデータのエクスポートができるようにします。つまり、挿入したファクト、Global、クエリの結果はすべてこのインターフェースを使用して返されることになります。

10.23. Out 識別子

以下は、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" );

第11章 モードとメソッド

11.1. シーケンシャルモード

JBoss Rules のシーケンシャルモードを使用すると、より簡単な方法でエンジンを使用することができます。ユーザーがステートレスセッションで作用してデータが初期のデータセットからアサートまたは変更されない場合、再評価の必要性なしにルールを使用できます。

11.2. シーケンシャルモードのオプション

シーケンシャルモードを使用時に選べるオプションがいくつかあります。
  1. ルールセット内の salience (優先順位) および位置でルールの順番を決める (ルールの終端ノードで sequence 属性を設定)。
  2. 配列の作成 (ルールアクティベーション候補ごとに 1 要素)。要素の位置は発火順位を示す。
  3. right-input オブジェクトメモリ以外のノードのメモリをすべて無効にする。
  4. Left Input Adapter ノードの伝播を切断して、オブジェクトとノードがコマンドオブジェクトで参照されるようにする。これは後に実行できるようにワーキングメモリのリストに追加される。
  5. すべてオブジェクトをアサート。すべてのアサートが終了して、right-input ノードのメモリが生成されると、コマンドリストを確認して順番に実行してく。
  6. 結果となるすべてのアクティベーションは、決定されたルールの順番をベースに配列内に設定される必要がある。最初と最後に生成された要素を記録して反復の幅を減らす。
  7. アクティベーションの配列を繰り返して、生成した要素を順番に実行する。
  8. ルール実行の最大回数がある場合、ネットワーク評価を早く終了して配列内でルールをすべて発火する。

11.3. シーケンシャルモードの有効化

手順11.1 タスク

  1. ステートレスセッションを開始します。
  2. シーケンシャルモードはデフォルトで無効になっています。有効にするには、RuleBaseConfiguration.setSequential(true) を呼び出すか、rulebase 設定プロパティ drools.sequential を true に設定します。
  3. シーケンシャルモードが動的アジェンダに分類できるように、 SequentialAgenda.DYNAMICsetSequentialAgenda を呼び出します。
  4. オプションで、JBossRules.sequential.agenda プロパティを sequential または dynamic に設定します。

11.4. CommandFactory

CommandFactory オブジェクトは、コマンドがステートレスセッションで実行できるようにします。完了すると、セッションを破棄する前にファクトリが fireAllRules() を実行します。

11.5. 対応の CommandFactory オプション

これらのオプションはすべて CommandFactory と互換性があります。
  • FireAllRules
  • GetGlobal
  • SetGlobal
  • InsertObject
  • InsertElements
  • Query
  • StartProcess
  • BatchExecution

11.6. 挿入コマンド

InsertObject は、オプションの "out" 識別子で単一のオブジェクトを挿入します。InsertElements は Iterable を反復して各要素を挿入します。こうすることで、ステートレスナレッジセッションがクエリをあらゆる順序で処理、実行することができます。

11.7. 挿入コマンドの例

StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
ExecutionResults bresults =
  ksession.execute( CommandFactory.newInsert( new Car( "sedan" ), "sedan_id" ) );
Sedan sedan = bresults.getValue( "sedan_id" );

11.8. 実行メソッド

実行メソッドを使用して、1 度に 1 つのコマンドを実行します。常にこのメソッドは ExecutionResults インスタンスを返して、stilton_id などの out 識別子を指定している場合、コマンドの結果にアクセスできるようになります。

11.9. 実行メソッドの例

StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
Command cmd = CommandFactory.newInsertElements( Arrays.asList( Object[] { 
                  new Car( "sedan" ),
                  new Car( "hatchback" ),
                  new Car( "convertible" ),
              });
ExecutionResults bresults = ksession.execute( cmd );

11.10. BatchExecution コマンド

BatchExecution コマンドにより、複数のコマンドを 1 度に実行することができます。これは、コマンドリストから作成された複合コマンドのことを指します。Execute はリストの項目を繰り返して順番にコマンドを実行します。つまり、 execute(...) 呼び出し 1 つで、オブジェクトを挿入、プロセスを開始、fireAllRules を呼び出し、クエリを実行することができます。

11.11. FireAllRules コマンド

FireAllRules コマンドは、最後にルールを自動的に実行します。手動でオバーライドする関数型です。

11.12. Out 識別子

コマンドは Out 識別子に対応しています。Out 識別子が設定されているコマンドは、結果を返された ExecutionResults インスタンスに追加します。

11.13. Out 識別子の例

以下の例は、BatchExecution コマンドを使用して Out 識別子がどのように機能するか示しています。
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();

List cmds = new ArrayList();        
cmds.add( CommandFactory.newInsertObject( new Car( "red", 1), "red") );
cmds.add( CommandFactory.newStartProcess( "process cars" ) );
cmds.add( CommandFactory.newQuery( "cars" ) );
ExecutionResults bresults = ksession.execute( CommandFactory.newBatchExecution( cmds ) );
Car red = ( Car ) bresults.getValue( "red" );
QueryResults qresults = ( QueryResults ) bresults.getValue( "cars" );
上記の例では、複数のコマンドが実行されます。これらの内 2 つは ExecutionResults を生成します。クエリコマンドはデフォルトで同じ識別子をクエリ名として使用しますが、別の識別子にマッピングすることもできます。

11.14. 実行 XML の例

カスタムの XStream マーシャラーは、JBoss Rules Pipelin と併用して XML スクリプトが実現できサービスに適しています。これに関して 2 つの例を以下に示しています。
BatchExecution XML:
<batch-execution><insert out-identifier='outRed'><org.drools.Car><type>red</type><price>25000</price><oldPrice>0</oldPrice></org.drools.Car></insert></batch-execution>
   
      
         
         
         
      
   

ExecutionResults XML:
<execution-results><result identifier='outBlue'><org.drools.Car><type>Blue</type><oldPrice>25</oldPrice><price>30000</price></org.drools.Car></result></execution-results>
   
      
         
                 
         
      
   

11.15. 実行マーシャリングの例

以下は XML にマーシャリングされた BatchExecution の例です。
<batch-execution><insert out-identifier="sedan"><org.drools.Car><type>sedan</type><price>1</price><oldPrice>0</oldPrice></org.drools.Car></insert><query out-identifier='cars2' name='carsWithParams'><string>hatchback</string><string>sedan</string></query></batch-execution>
  
    
      
      
      
    
  
  
    
    
  

CommandExecutorExecutionResults を返して、パイプラインコードスニペットで処理されます。上記の <batch-execution> XML サンプルと似た出力は以下のようになります。
<execution-results><result identifier="sedan"><org.drools.Car><type>sedan</type><price>2</price></org.drools.Car></result><result identifier='cars2'><query-results><identifiers><identifier>car</identifier></identifiers><row><org.drools.Car><type>hatchback</type><price>2</price><oldPrice>0</oldPrice></org.drools.Car></row><row><org.drools.Car><type>hatchback</type><price>1</price><oldPrice>0</oldPrice></org.drools.Car></row></query-results></result></execution-results>
  
    
      
      
    
          
  
    
      
        
      
      
        
          
          
          
        
      
      
        
          
          
          
        
      
    
  

11.16. Batch-execution とコマンドの例

  1. 現在、スキーマ検証をサポートする XML スキーマはありません。以下が基本的なフォーマットとなっています。Root 要素は、<batch-execution> で、0 またはそれ以上のコマンド要素を含めることができます。
    <batch-execution>
    ...
    </batch-execution>
    
  2. 挿入要素には、"out-identifier" 属性が含まれているため、挿入されたオブジェクトは結果ペイロードの一部として返されます。
    <batch-execution><insert out-identifier='userVar'>
          ...
       </insert></batch-execution>
       
    
    
  3. <insert-elements> 要素を使用してオブジェクトのコレクションを挿入することも可能です。このコマンドは、out-identifier には対応していません。org.domain.UserClass は XStream が直列化するユーザーオブジェクトの事例として使用しています。
    <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>
       
          
          
          
       
    
    
  4. <set-global> 要素は、セッションに Global を設定します。
    <batch-execution><set-global identifier='userVar'><org.domain.UserClass>
             ...
          </org.domain.UserClass></set-global></batch-execution>
       
          
       
    
    
  5. <set-global> は、他に outout-identifier の 2 つのオプション属性にも対応しています。ブール型 out の true 値は、identifier 属性かの名前を使用して Global を <batch-execution-results> ペイロードに追加します。 out-identifierout のように機能しますが、<batch-execution-results> ペイロードで使用した識別子をオーバーライドできます。
    <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>
       
          
       
       
          
       
    
    
  6. コンテンツのない <get-global> 要素があり、out-identifier 属性のみが含まれています。値の取得が <get-global> 要素の唯一の目的であるため、out 属性は必要ありません。
    <batch-execution><get-global identifier='userVar1' /><get-global identifier='userVar2' out-identifier='alternativeUserVar2'/></batch-execution>
       
       
    
    
  7. query コマンドは、パラメータークエリもパラメーターなしのクエリにも対応しています。name 属性は呼び出すクエリ名で、out-identifier<execution-results> ペイロードのクエリ結果で使用される識別子です。
    <batch-execution><query out-identifier='cars' name='cars'/><query out-identifier='cars2' name='carsWithParams'><string>red</string><string>blue</string></query></batch-execution>
       
       
          
          
       
    
    
  8. <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
    
       
          
             
                
             
          
    
    
  9. signal event コマンドでプロセスの特定が可能になります。
    <signal-event process-instance-id='1' event-type='MyEvent'><string>MyValue</string></signal-event>
       
    
    
  10. complete work item コマンドは、プロセスが完了するとユーザーに通知します。
    <complete-work-item id='" + workItem.getId() + "' ><result identifier='Result'><string>SomeOtherString</string></result></complete-work-item>
       
          
       
    
    
  11. abort work item コマンドで、実行中にプロセスを取り消すことができます。
    <abort-work-item id='21' />
    

11.17. MarshallerFactory

MarshallerFactory は、ステートフルナレッジセッションのマーシャリングとアンマーシャリングに使用します。

11.18. マーシャラーの例

以下は、実際のマーシャーラーの例です。
// ksession is the StatefulKnowledgeSession
// kbase is the KnowledgeBase
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Marshaller marshaller = MarshallerFactory.newMarshaller( kbase );
marshaller.marshall( baos, ksession );
baos.close();

11.19. マーシャリングのオプション

表11.1 マーシャリングのオプション

オプション 説明
ObjectMarshallingStrategy このインターフェースは、マーシャリングの実装用に提供され、柔軟性が増します。
SerializeMarshallingStrategy
これは、ユーザーインターフェースで Serializable または Externalizable メソッドを呼び出すデフォルトのストラテジです。
IdentityMarshallingStrategy
このストラテジは、各ユーザーオブジェクトに整数の id を作成してマップに格納し、この id はストリームに書き込まれます。
アンマーシャリングすると、IdentityMarshallingStrategy マップにアクセスしてインスタンスを取得します。つまり、IdentityMarshallingStrategy を使用する場合、Marshaller インスタンスが存続する間ステートフルで、ID を作成してマーシャルしようとするオブジェクトすべてに対する参照を保持します。

11.20. IdentityMarshallingStrategy の例

以下は、IdentityMarshallingStrategy を使用するためのコード例です。
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectMarshallingStrategy oms = MarshallerFactory.newIdentityMarshallingStrategy()
Marshaller marshaller =
  MarshallerFactory.newMarshaller( kbase, new ObjectMarshallingStrategy[]{ oms } );
marshaller.marshall( baos, ksession );
baos.close();

11.21. ObjectMarshallingStrategyAcceptor

ObjectMarshallingStrategyAcceptor は、オブジェクトのマーシャリング戦略に含まれるインターフェースです。ユーザーオブジェクトの読み取りまたは書き込みを試行すると、ObjectMarshallingStrategyAcceptor を使用してユーザーオブジェクトのマーシャリングに使用されるかどうか決定するために使用します。

11.22. ClassFilterAcceptor の実装

ClassFilterAcceptor の実装は、文字列やワイルドカードを使用してクラス名の照合ができるようにします。デフォルトは "*.*" です。

11.23. アクセプターを使用した IdentityMarshallingStrategy の例

以下は、アクセプターと IdentityMarshallingStrategy を使用した例です。承諾確認の順番は提供された配列の自然な順番で行われている点に注意してください。
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();

11.24. JBoss Rules の永続性とトランザクション

JBoss Rules では、Java Persistence API (JPA) を使用して、カスタマイズなしで長期的に永続性を保つことができます。Java Transaction API (JTA) の実装をインストールする必要があります。設定が簡単で埋め込みに対応しているため、開発目的では Bitronix Transaction Manager を使用することができます。実稼働環境では、JBoss Transactions を推奨しています。

11.25. トランザクション例

以下は、トランザクションの実行がどのように行われるか示しています。
Environment env = KnowledgeBaseFactory.newEnvironment();
env.set( EnvironmentName.ENTITY_MANAGER_FACTORY,
         Persistence.createEntityManagerFactory( "emf-name" ) );
env.set( EnvironmentName.TRANSACTION_MANAGER,
         TransactionManagerServices.getTransactionManager() );
          
// KnowledgeSessionConfiguration may be null, and a default will be used
StatefulKnowledgeSession ksession =
  JPAKnowledgeService.newStatefulKnowledgeSession( kbase, null, env );
int sessionId = ksession.getId();
 
UserTransaction ut =
  (UserTransaction) new InitialContext().lookup( "java:comp/UserTransaction" );
ut.begin();
ksession.insert( data1 );
ksession.insert( data2 );
ksession.startProcess( "process1" );
ut.commit();

11.26. JPA の使用

手順11.2 タスク

  1. 環境は EntityManagerFactoryTransactionManager の両方で設定するようにします。
  2. GUI またはコマンドから JPA を起動します。
  3. ID を使用して以前に永続化したステートフルナレッジセッションをロードします。ロールバックが発生すると ksession のステートもロールバックされるため、ロールバック後もそのまま使用を続けることができます。

11.27. JPA を持つ StatefulKnowledgeSession のロード

以下は、JPA を実装する StatefulKnowledgeSession をロードするコードです。
StatefulKnowledgeSession ksession =
  JPAKnowledgeService.loadStatefulKnowledgeSession( sessionId, kbase, null, env );

11.28. JPA の設定

永続性を有効にするには、以下の例のように複数のクラスを persistence.xml に追加する必要があります。
<persistence-unit name="org.drools.persistence.jpa" transaction-type="JTA"><provider>org.hibernate.ejb.HibernatePersistence</provider><jta-data-source>jdbc/BitronixJTADataSource</jta-data-source><class>org.drools.persistence.session.SessionInfo</class><class>org.drools.persistence.processinstance.ProcessInstanceInfo</class><class>org.drools.persistence.processinstance.ProcessInstanceEventInfo</class><class>org.drools.persistence.processinstance.WorkItemInfo</class><properties><property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/><property name="hibernate.max_fetch_depth" value="3"/><property name="hibernate.hbm2ddl.auto" value="update" /><property name="hibernate.show_sql" value="true" /><property name="hibernate.transaction.manager_lookup_class"
                      value="org.hibernate.transaction.BTMTransactionManagerLookup" /></properties></persistence-unit>
   
          
   
   
   
   
   
                     
         
         
         
         
   

11.29. JTA DataSource の設定

如何は、JTA DataSource の設定コードです。
PoolingDataSource ds = new PoolingDataSource();
ds.setUniqueName( "jdbc/BitronixJTADataSource" );
ds.setClassName( "org.h2.jdbcx.JdbcDataSource" );
ds.setMaxPoolSize( 3 );
ds.setAllowLocalTransactions( true );
ds.getDriverProperties().put( "user", "sa" );
ds.getDriverProperties().put( "password", "sasa" );
ds.getDriverProperties().put( "URL", "jdbc:h2:mem:mydb" );
ds.init();

11.30. JNDI プロパティ

Bitronix は単純な埋め込み JNDI サービスを提供しますが、これはテスト目的で理想的です。使用するには、jndi.properties ファイルを META-INF に追加して、以下の行をそのファイルに追加します。
java.naming.factory.initial=bitronix.tm.jndi.BitronixInitialContextFactory

11.31. KnowledgeBase 名前空間

以下は、構築目的で KnowledgeBase に追加できる名前空間のリストです。
  • deftemplate
  • defrule
  • deffunction
  • and/or/not/exists/test 条件要素
  • Literal、Variable、Return Value および Predicate フィールド制約

第12章 Using Spreadsheet Decision Tables

12.1. ハードキーワード

ハードキーワードは、ルールテキストで使用されるドメインオブジェクト、プロパティ、関数、その他の要素をネーミングする際に使用できないワードのことです。ハードキーワードは、truefalse などの単語ですが、コマンドとすぐに間違えられる可能性があります。

12.2. ソフトキーワード

ソフトキーワードは、ドメインプロジェクト、プロパティ、メソッド、関数、その他の要素のネーミングに使用することができます。ルールエンジンは、それらのコンテキストを認識してそれに合わせて処理します。

12.3. ソフトキーワードのリスト

  • 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
  • when
  • then
  • template
  • query
  • declare
  • function
  • global
  • eval
  • not
  • in
  • or
  • and
  • exists
  • forall
  • accumulate
  • collect
  • from
  • action
  • reverse
  • result
  • end
  • over
  • init

12.4. コメント

コメントとは、ルールエンジンが無視するテキストのセクションです。このセクションに遭遇した場合は省略されます。セマンティックコードブロック内にある場合 (ルールの RHS など) は例外です。

12.5. 1 行コメントの例

これは、1 行コメントの例です。1 行コメントを作成するには、'//' を使用します。パーサーは、コメント記号と同じ行にあり、コメント記号より後ろに来るものをすべて無視します。
rule "Testing Comments"
when
    // this is 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
end

12.6. 複数行コメントの例

これが複数行コメントの例です。この設定は、セマンティックコードブロックの内部、外部両方にあるテキストのブロックをコメントアウトします。
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

12.7. エラーメッセージ

101: No viable alternative
パーサーが決定ポイントに到達したが選択肢を特定できない場合に表示されます。
[ERR 101] Line 3:2 no viable alternative at input 'WHEN'
このメッセージは、不正な場所に設定されているトークン WHEN (ハードキーワード) にパーサーが遭遇したこと (ルール名が抜けているため) を意味します。
[ERR 101] Line 0:-1 no viable alternative at input '<eof>' in rule simple_rule in pattern [name]
引用符、アポストロフィ、括弧が正しく挿入されていない (閉じられていない) ことを表します。
102: Mismatched input
現在の入力位置で終わるはずの特定のシンボルを、パーサーが探していることを示します。
[ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule simple_rule in pattern [name]
不完全なルールステートメントが原因でこのエラーが発生しています。通常、0:-1 ポジションを取得すると、パーサーがソースの最後に到達したことを意味します。
103: Failed predicate
False と評価された意味述語をバリデートしています。通常、これらの意味述語はソフトキーワードを特定する際に使用します。
104: Trailing semi-colon not allowed
このエラーは、式がセミコロンで完了してはいけない eval 句に関連しています。
105: Early Exit
recognizer が提示のキーワードに最低でも 1 回合致する必要のある文法のサブルールに到達したにも関わらず、サブルールに合致がありませんでした。

12.8. パッケージ

パッケージとは、import や global などルールやその他関連するコンストラクトのコレクションのことです。パッケージメンバーは通常、HR ルールなどのように相互に関連しあっています。パッケージは名前空間を表し、名前空間はルールの集まりごとに一意に保つことが理想です。パッケージ名自体が名前空間で、ファイルやフォルダーには全く関係ありません。
複数のルールソースから各種ルールを組み立て、これらすべてのルールを格納する最上位のパッケージ設定を 1 つ持たせることができます (ルール組み立て時)。別の名前で宣言されているパッケージリソースは、同じものであってもマージすることはできません。しかし、単一の Rulebase には組み込みの複数パッケージを含めることができます。パッケージ宣言として同じファイル内にパッケージのルールをすべて含めるというのが一般的な構造です (これにより完全埋め込み式になります)。

12.9. Import ステートメント

Import ステートメントは Java の import ステートメントと同様に動作します。ルールで使用するオブジェクトに対して完全修飾パスとタイプ名を指定する必要があります。JBoss Rules は同じ名前の Java パッケージとパッケージ java.lang からクラスを自動的にインポートします。

12.10. Global の使用

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

12.11. From 要素

from 要素は、グローバルとして Hibernate セッションを指定することができます。また名前付きの Hibernate クエリからデータをプルすることも可能です。

12.12. e-メールサービスでの Global の使用

手順12.1 タスク

  1. ルールエンジンを呼び出す統合コードを開きます。
  2. emailService オブジェクトを取得してから、ワーキングメモリに設定します。
  3. DRL で、emailService 型のグルーバルがあり、名前を "email" と指定することを宣言します。
  4. ルールの結果で、email.sendSMS(number, message) などを使用することができます。

    警告

    グローバルはルール間でデータを共有するよう設計されておらず、このために使用すべきではありません。ルールは常に作業メモリーステータスに対して理由付けし、反応します。したがって、データをルール間で渡す場合は、データをファクトとして作業メモリーにアサートします。

    重要

    ルール内部からグローバル値を設定または変更しないでください。常にワーキングメモリーインターフェースを使用してアプリケーションから値を設定することが推奨されます。

第13章 関数

13.1. 関数

関数 は、通常の Java クラスではなく、ルールソースファイルにセマンティックコードを入れる手段です。ルールで関数を使用する主な利点は、1 つの場所ですべてのロジックを保持することができる点です。必要に応じて関数を変更することができます。
関数は、ルールの結果 (then) 部分に対してアクションを呼び出す際に最も便利です (特に、その特定のアクションを繰り返して使用する場合)。

13.2. 関数宣言の例

典型的な関数宣言は、以下のようになります。
function String hello(String name) {
    return "Hello "+name+"!";
}

注記

正確には Java の一部でないにも拘らず、function キーワードが使用されている点に注目してください。この関数へのパラメーターは、メソッドで定義されています。必要がなければパラメーターを設定する必要はありません。戻り型は通常のメソッドのように定義されます。

13.3. 静的メソッドを使用する関数宣言の例

関数宣言の以下の例は、ヘルパークラス (Foo.hello()) の静的メソッドを示しています。JBoss Rules は、関数 import の使用に対応しているため、以下を入力するために必要なコードは、以下のコードのみです。
import function my.package.Foo.hello

13.4. 関数宣言呼び出しの例

関数が定義またはインポートされる方法を問わず、結果内またはセマンティックコードブロック内で名前で呼び出して関数を使用します。これについて以下に例を示しています。
rule "using a static function"
when 
    eval( true )
then
    System.out.println( hello( "Bob" ) );
end

13.5. 型宣言

型宣言には、ルールエンジンでの目的が主に 2 つあり、それは 1) 新しい型の宣言を許可することと、2) 型のメタデータの宣言を許可することです。

13.6. 型宣言のロール

表13.1 型宣言のロール

ロール 説明
新しい型の宣言
JBoss Rules には、ファクトとしてプレーンな Java オブジェクトが同梱されています。しかし、ルールエンジンに直接モデルを定義するには、新しい型を宣言することで定義できます。また、すでにドメインモデルが構築されている場合にも使用できますが、理由付けプロセスに主に使用する追加のエンティティをこのモデルに補足する必要があります。
メタデータの宣言
ファクトにはメタ情報を関連付けることができます。メタ情報の例には、ファクト属性で表現されないデータや、そのファクト型の全インスタンスで一貫しているデータが含まれます。このメタ情報はランタイム時にエンジンからクエリを受け、理由付けのプロセスで使用されます。

13.7. 新しい型の宣言

declare のキーワード、その後にフィールドのリスト、キーワード end を使用して、新しい型を宣言します。新しいファクトにはフィールドのリストがなければならず、リストがない場合はエンジンはクラスパス内に既存のファクトがないか検索して、ファクトが見つからない場合はエラーが出されます。

13.8. 新しいファクト型の宣言例

以下の例では、Address という新しいファクト型が使用されています。このファクト型には numberstreetNamecity の 3 つの属性が含まれています。各属性には、ユーザーが作成した他のクラスや以前に宣言されたファクト型といった有効な Java 型などが含まれています。
declare Address
   number : int
   streetName : String
   city : String
end

13.9. 新しいファクト型の宣言の追加例

このファクト型の宣言では、Person の例を使用しています。dateOfBirthjava.util.Date 型 (Java API) で、address はファクト型 Address です。
declare Person
    name : String
    dateOfBirth : java.util.Date
    address : Address
end

13.10. Import の使用例

以下の例は、import 機能を使用して完全修飾クラス名を使用せずに済ませる方法を示しています。
import java.util.Date

declare Person
    name : String
    dateOfBirth : Date
    address : Address
end

13.11. 生成された Java クラス

新しいファクト型を宣言すると、JBoss Rules は、ファクト型を表す Java クラスを実装するバイトコードを生成します。生成した Java クラスは、型定義に 1 対 1 の Java Bean マッピングを行います。

13.12. 生成 Java クラスの例

以下は、Person ファクト型を使用した生成 Java クラスの例です。
public class Person implements Serializable {
    private String name;
    private java.util.Date dateOfBirth;
    private Address address;

    // empty constructor
    public Person() {...}

    // constructor with all fields 
    public Person( String name, Date dateOfBirth, Address address ) {...}

    // if keys are defined, constructor with keys
    public Person( ...keys... ) {...}

    // getters and setters
    // equals/hashCode
    // toString
}

13.13. ルールでの宣言型の使用例

生成クラスは単純な Java クラスであるため、他のファクトと同様に透過的にルールで使用できます。
rule "Using a declared Type"
when 
    $p : Person( name == "Bob" )
then// Insert Mark, who is Bob's manager.
    Person mark = new Person();
    mark.setName("Mark");
    insert( mark );
end

13.14. メタデータの宣言

メタデータは、JBoss Rules ではファクト型、ファクト属性、ルールなど複数の異なるコンストラクションに割り当てることができます。JBoss Rules は '@' マークを使用してメタデータを導入しますが、常に以下の形式を使用します。
@metadata_key( metadata_value )
括弧内の metadata_value はオプションです。

13.15. メタデータ属性との連携

JBoss Rules では、任意のメタデータ属性を宣言できますが、一部はエンジンに対して特別な意味を持ち、一部はランタイム時の問い合わせに利用できます。また、JBoss Rules では、ファクト型とファクト属性両方に対してメタデータを宣言できます。ファクト型の属性の前に宣言されたメタデータはファクト型に割り当てられ、属性の後に宣言されたメタデータはその特定の属性に割り当てられます。

13.16. ファクト型でのメタデータ属性の宣言例

以下は、ファクト型と属性のメタデータ属性を宣言する例です。このファクト型に対して宣言したメタデータが 2 つ (@author および @dateOfCreation) と名前属性に対して定義したメタデータが 2 つ (@key および @maxLength) あります。@key メタデータには必須の値がないため、括弧や値は省略されました。
import java.util.Date

declare Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )

    name : String @key @maxLength( 30 )
    dateOfBirth : Date 
    address : Address
end

13.17. @position 属性

@position 属性は、フィールドの位置を宣言するために使用し、デフォルトで宣言された順番よりも優先させることができます。これは、パターンでの位置制約に使用されます。

13.18. @position の例

以下は、@position 属性の使用例です。
declare Cheese
    name : String @position(1)
    shop : String @position(2)
    price : int @position(0)
end

13.19. 事前定義済みのクラスレベルアノテーション

表13.2 事前定義済みのクラスレベルアノテーション

アノテーション 説明
@role( <fact | event> )
この属性は、ファクトやイベントにロールを割り当てる際に使用します。
@typesafe( <boolean> )
デフォルトでは、型安全が有効な状態ですべての型宣言はコンパイルされています。@typesafe( false ) は、型安全ではない評価 (すべての制約が、MVEL 制約として生成され動的に実行される) へのフォールバックを許可してこの動作をオーバーライドする手段を提供します。これは、一般的な型コレクションや型が混合されているコレクションを処理する際に便利です。
@timestamp( <attribute name> )
タイムスタンプを作成します。
@duration( <attribute name> )
属性の実装期間を設定します。
@expires( <time interval> )
属性が失効する時間を定義することができます。
@propertyChangeSupport
Javabean 仕様に定義されているプロパティの変更のサポートを実装するファクトをアノテーションで追加できるようになり、エンジン自体がファクトプロパティでの変更をリッスンできるようになっています。
@propertyReactive 型プロパティを反応型にします。

13.20. @key 属性関数

キー属性として属性を宣言すると、主に生成型に 2 種類の影響があります。
  1. この属性はその型のキー識別子として使用されるため、生成クラスは、この型のインスタンスを比較する際にはこの属性を考慮に入れて、equals() と hashCode() メソッドを実装します。
  2. JBoss Rules は、すべてのキー属性をパラメーターとして使用してコンストラクターを生成します。

13.21. @key 宣言の例

以下は、型に対する @key 宣言の例です。JBoss Rules は、equals() と hashCode() メソッドを生成し、firstName と lastName 属性をチェックして Person の 2 つのインスタンスがそれぞれ同じであるかを判断します。ただし、age 属性はチェックしません。firstName と lastName をパラメーターとして取ることでコンストラクターも生成します。
declare Person
    firstName : String @key
    lastName : String @key
    age : int
end

13.22. キーコンストラクターでのインスタンスの作成例

以下は、キーコンストラクターを使用してインスタンスを作成する例です。
Person person = new Person( "John", "Doe" );

13.23. 位置引数

パターンは型宣言への位置引数をサポートし、@position 属性で定義されます。
位置引数は、フィールド名を指定する必要がない場合に既知の名前付きフィールドへの位置マッピングを行います (つまり、Person( name == "mark" ) は Person( "mark"; ) で書き換えることができます)。エンジンが位置引数の前のすべてのアイテムを把握するようにするため、セミコロン ';' は重要です。セミコロン ';' で区切ることで、パターンに位置引数と名前引数の両方を併用することができます。バインドされていない位置で使用する変数は、フィールドにバインドされその位置にマッピングされます。

13.24. 位置引数の例

以下の例を見てみましょう。
declare Cheese
    name : String
    shop : String
    price : int
end
デフォルトの順番は宣言の順番ですが、@position を使用してオーバーライドできます。
declare Cheese
    name : String @position(1)
    shop : String @position(2)
    price : int @position(0)
end

13.25. @Position アノテーション

@Position アノテーションは、クラスパスの元の POJO にアノテーションをつけるために使用できます。現在、クラスのフィールドのみアノテーションをつけることができます。クラスの継承に対応していますが、メソッドのインターフェースには対応していません。

13.26. パターンの例

これらのパターンの例には、2 つの制約とバインディングが含まれています。セミコロン ';' を使用して、位置のセクションと名前の引数のセクションを分けています。リテラルのみを使用する変数、リテラル、式は対応していますが、変数を使用したものには対応していません。
Cheese( "stilton", "Cheese Shop", p; )
Cheese( "stilton", "Cheese Shop"; p : price )
Cheese( "stilton"; shop == "Cheese Shop", p : price )
Cheese( name == "stilton"; shop == "Cheese Shop", p : price )

第14章 その他の宣言

14.1. 既存のタイプのメタデータの宣言

JBoss Rules では、新しいファクト型に対してメタデータ属性を宣言する場合と同様に既存の型のメタデータ属性を宣言できます。唯一の相違点は、その宣言にフィールドがないことです。

14.2. 既存型へのメタデータの宣言例

以下の例は、既存の型にメタデータを宣言する方法を示しています。
import org.drools.examples.Person

declare Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )
end

14.3. 完全修飾クラス名を使用したメタデータの宣言例

以下の例は、import アノテーションではなく、完全修飾クラス名を使用してメタデータを宣言する方法を示しています。
declare org.drools.examples.Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )
end

14.4. 宣言型に対してパラメーター化されたコンストラクトの例

宣言型:
declare Person
    firstName : String @key
    lastName : String @key
    age : int
end
コンパイラーは、暗黙的に 3 つのコンストラクターを生成します。1 つはパラメーターなし、1 つは @key フィールドのあるもの、1 つはすべてフィールドのあるものです。
Person() // parameterless constructor
Person( String firstName, String lastName )
Person( String firstName, String lastName, int age )

14.5. 型安全でないクラス

@typesafe( <boolean>) アノテーションが型宣言に追加されています。デフォルトでは、すべての型宣言は型安全が有効な状態でコンパイルされています。@typesafe( false ) は、型安全ではない評価 (すべての制約が、MVEL 制約として生成され動的に実行される) へのフォールバックを許可してこの動作をオーバーライドする手段を提供します。これは、一般的な型コレクションや型が混合されているコレクションを処理する際に便利です。

14.6. アプリケーションコードからの宣言済みタイプへのアクセス

アプリケーションは時には、宣言型からファクトにアクセスして処理する必要があります。このような場合、JBoss Rules はアプリケーションが実行する最も一般的なファクト処理に対して簡素化された API を提供します。宣言ファクトはそのファクトが宣言されたパッケージに所属します。

14.7. 型の宣言

以下は、型を宣言するプロセスについて例示しています。
package org.drools.examples

import java.util.Date

declare Person
    name : String
    dateOfBirth : Date
    address : Address
end

14.8. API を使用して宣言されたファクト型を処理する例

以下の例では、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" );

14.9. 型宣言の extends

型宣言は継承用に "extends" というキーワードをサポートしています。DRL 宣言のサブタイ部で Java で宣言された型を継承するには、フィールドなしの宣言ステートメントでスーパータイプを繰り返します。

14.10. 型宣言の extends 例

以下では、extends アノテーションの使用例を示しています。
import org.people.Person

declare Person
end

declare Student extends Person
    school : String
end

declare LongTermStudent extends Student
    years : int
    course : String
end

14.11. Trait

Traits により、クラス階層に自然に当てはまらない複数の動的な型をモデル化することができます。trait は、ランタイム時に個別のオブジェクトに適用できる (最終的に削除) インターフェースです。インターフェースから trait を作成するには、@format(trait) アノテーションを DRL の宣言に追加します。

14.12. Trait の例

declare GoldenCustomer
    @format(trait)
    // fields will map to getters/setters
    code     : String
    balance  : long
    discount : int
    maxExpense : long
end
Trait をオブジェクトに適用するには、新しい don キーワードを追加します。
when
    $c : Customer()
then
    GoldenCustomer gc = don( $c, Customer.class );
end

14.13. コアオブジェクトと trait

コアオブジェクトが trait に don すると、プロキシクラスがオンザフライで作成されます (このようなクラスは、コア/trait クラスの組み合わせごとに遅延して生成されます)。コアオブジェクトをラップして trait インターフェースを実装するプロキシインスタンスは、自動的に挿入され、他のルールをアクティブ化することもあります。インターフェースの宣言と使用や実装プロキシをエンジンから解放する利点は、複数の継承階層がルール記述時に活用できる点です。しかし、コアクラスは静的にこれらのインターフェースを実装する必要がなく、コアとしてレガシークラスの使用がスムーズに行えるようにします。オブジェクトは trait を don することができます。ただし、効率の問題で、@Traitable アノテーションを宣言 bean クラスに追加して、コンパイラーが生成する必要のあるグルーコードの量を減らすことができます。これはオプションで、エンジンの動作を変更するわけではありません。

14.14. @Traitable の例

以下では、@Traitable アノテーションの使用例を示しています。
declare Customer
    @Traitable
    code    : String
    balance : long
end

14.15. Trait でのルールの記述

コアクラスと trait インターフェースは唯一プロキシレベルでつながっています (つまり、trait はコアクラスに特別に紐付けられているわけではありません)。つまり、同じ trait を完全に違うオブジェクトに適用することができます。こういった理由から、trait はコアオブジェクトのフォールドを透過的に公開しないのです。Trait インターフェースを使用してルールを記述すると、通常通りこのインターフェースのフィールドのみが利用できます。しかし、コアオブジェクトフィールドに該当するインターフェースのフィールドはプロキシクラスによりマッピングされます。

14.16. Trait とルールの例

以下の例は、Trait インターフェースがフィールドにマッピングされる例です。
when
    $o: OrderItem( $p : price, $code : custCode )
    $c: GoldenCustomer( code == $code, $a : balance, $d: discount )
then
    $c.setBalance( $a - $p*$d );
end

14.17. 隠しフィールド

隠しフィールドとは、インターフェースに公開されていないコアクラスのフィールドです。

14.18. 二部プロキシ (two-part proxy)

二部プロキシは、直感的に処理されないソフトフィールドや隠しフィールドを処理するために開発されました。内部ではプロキシは、適切なプロキシとラッパーで構成されています。プロキシはインターフェースを実装して、ラッパーはコアオブジェクトフィールドを管理し、名前/値マップをソフトフィールドにマッピングします。プロキシは必要に応じ、コアオブジェクトとマップラッパーの両方を使用してインターフェースを実装します。

14.19. ラッパー

ラッパーは、ルール記述の際により疎性な型指定ができるようになります。しかし、他にも用途があります。ラッパーは、オブジェクトに追加された trait の数に拘らず、ラップするオブジェクトに固有のものとなります。同じオブジェクトのプロキシはすべて、同じラッパーを共有します。さらに、ラッパーには、ラップされたオブジェクトに添付されたプロキシすべてに対する後方参照が含まれており、trait がお互いを効果的に参照できるようにしています。

14.20. ラッパーの例

以下は、ラッパーの使用例です。
when
    $sc : GoldenCustomer( $c : code, // hard getter
                          $maxExpense : maxExpense > 1000 // soft getter
    )
then
    $sc.setDiscount( ... ); // soft setter
end

14.21. isA アノテーションを使用したラッパー例

以下は、isA アノテーションを使ったラッパーの例です。
$sc : GoldenCustomer( $maxExpense : maxExpense > 1000,
                      this isA "SeniorCustomer"
)

14.22. Trait の削除

ビジネスロジックでは、ラップされたオブジェクトから trait を削除する必要がある場合もあります。削除の方法は 2 種類あります。
Logical don
Trait オペレーションを行うことでプロキシを論理的に挿入します。
then
    don( $x, // core object
         Customer.class, // trait class
         true // optional flag for logical insertion
    )
shed キーワード
shed キーワードは、指定の引数型に該当するプロキシを削除します。
then
    Thing t = shed( $x, GoldenCustomer.class )
このオペレーションは、org.drools.factmodel.traits.Thing インターフェースを実装する別のプロキシを返します。ここでは、getFields() と getCore() メソッドが定義されます。内部では、宣言された trait がすべて生成されこのインターフェースを継承します (指定された他のインターフェースだけでなく)。こうすることで、通常はなくなってしまうソフトフィールドを持ったラッパーを保存することができます。

14.23. ルール構文の例

以下は、ルール作成時に使用すべき構文の例を示しています。
rule "<name>"
    <attribute>*
when
    <conditional element>*
then
    <action>*
end

14.24. タイマー属性の例

以下は、timer 属性の例です。
timer ( int: <initial delay><repeat interval>? )
timer ( int: 30s )
timer ( int: 30s 5m )

timer ( cron: <cron expression> )
timer ( cron:* 0/15 * * * ? )

14.25. タイマー

以下は JBoss Rules で使用可能なタイマーです。
Interval
Interval ("int:" で指定) タイマーは、java.util.Timer オブジェクトのセマンティクスに従いますが、このタイマーは最初の遅延とオプションでその間隔を繰り返します。
Cron
Cron ("cron:" で指定) タイマーは標準の Unix cron 式に従います。

14.26. Cron タイマーの例

以下は 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

14.27. カレンダー

カレンダーは、ルールが発火できるタイミングを制御する際に使用します。JBoss Rules は Quartz カレンダーを使用します。

14.28. Quartz カレンダーの例

以下は、Quartz カレンダーの例です。
Calendar weekDayCal = QuartzHelper.quartzCalendarAdapter(org.quartz.Calendar quartzCal)

14.29. カレンダーの登録

手順14.1 タスク

  1. StatefulKnowledgeSession を開始します。
  2. 以下のコードを使用してカレンダーを登録します。
    ksession.getCalendars().set( "weekday", weekDayCal );
  3. カレンダーとタイマーを合わせて使用する場合、以下のコードを使用します。
    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” );
    end

14.30. 左辺部 (LHS)

左辺部 (LHS: Left Hand Side) はルールの条件部分に使用される一般的な名前です。LHS は 0 個またそれ以上の条件要素で構成されます。LHS が空の場合、常に True となる条件要素として考慮され、新しい WorkingMemory セッションが作成されると 1 度だけアクティブ化されます。

14.31. 条件の要素

条件要素は、1 つまたは複数のパターンで動作します。最も一般的な条件要素は and で、何も接続されていないルールの LHS に複数のパターンがある場合、この条件要素は暗黙的に示されます。

14.32. 条件要素なしのルールの例

以下は、条件要素がないルールの例です。
rule "no CEs"
when
    // empty
then
    ... // actions (executed once)
end

// The above rule is internally rewritten as:

rule "eval(true)"
when
    eval( true )
then
    ... // actions (executed once)
end

第15章 パターン

15.1. パターン

パターン要素は、最も重要な条件要素です。ワーキングメモリに挿入された各ファクトにマッチする可能性があります。パターンには制約が含まれており、オプションでパターンのバインディングがあります。

15.2. パターンの例

以下は、パターンの例です。
rule "2 unconnected patterns"
when
    Pattern1()
    Pattern2()
then
    ... // actions
end

// The above rule is internally rewritten as:

rule "2 and connected patterns"
when
    Pattern1()
    and Pattern2()
then
    ... // actions
end

注記

and は、主導宣言バインディングを持つことができません。これは、宣言は 1 回に 1 つのファクトしか参照できないためで、and が満たされると両方のファクトと合致します。

15.3. パターン一致

パターンは、指定の型のファクトと照合されます。この型は、ファクトオブジェクトの実際のクラスである必要はありません。パターンは、スーパークラスかインターフェースを参照でき、そこで違った種類のクラス多数からファクトが合致する可能性があります。制約は括弧内で定義されます。

15.4. パターンのバインディング

パターンは、合致するオブジェクトにバインドすることができます。これは、$p などのパターンバインディング変数を使用することで可能です。

15.5. 変数でバインドしたパターンの例

以下は、変数を使用したパターンバインディングの例です。
rule ...
when
    $p : Person()
then
    System.out.println( "Person " + $p );
end

注記

接頭辞のドル記号 ($) は必須ではありません。

15.6. 制約

制約は、true または false を返す式です。たとえば、5 は 6 よりも小さいという制約を指定することができます。

第16章 要素と変数

16.1. Java Bean でのプロパティアクセス (POJO)

bean プロパティは直接使用することができます。bean プロパティは、引数を取らずに何かを返す標準の Java bean ゲッター (getMyProperty() メソッド (またはプリミティブブール型 isMyProperty()) を使用して公開します。
JBoss Rules は標準の JDK Introspector クラスを使用して、このマッピングを行いますので、標準の Java bean 使用に準拠します。

警告

プロパティアクセサーは、ルールに影響を与える可能性のある形でオブジェクトのステータスを変更してはいけません。ルールエンジンは、呼び出しと呼び出しの間で合致した結果を効率的にキャッシュして処理を早めます。

16.2. POJO の例

以下は、bean プロパティの例です。
Person( age == 50 )

// this is the same as:
Person( getAge() == 50 )
age プロパティ
age プロパティは getAge() ゲッターの代わりに、DRL の age として記述します。
プロパティアクセサー
フィールドのインデックス化にによりパフォーマンスを向上するため、明示的にゲッター (getAge()) ではなくプロパティアクセス (age) を使用することができます。

16.3. POJO との連携

手順16.1 タスク

  1. 以下の例を参照してください。
    public int getAge() {
        Date now = DateUtil.now(); // Do NOT do this
        return DateUtil.differenceInYears(now, birthday);
    }
  2. これを解決するには、現在の日付をラップするファクトをワーキングメモリに挿入して、必要に応じて fireAllRules 間のファクトを更新します。

16.4. POJO フォールバック

POJO と連携する場合 fallback メソッドが適用されます。プロパティのゲッターが見つからない場合、コンパイラーは、メソッド名にプロパティ名を引数なしで使用して再度ソートします。ネスト化したプロパティもインデックス化されます。

16.5. フォールバックの例

以下は、フォールバック実装時にどのようになるかを示しています。
Person( age == 50 )

// If Person.getAge() does not exists, this falls back to:
Person( age() == 50 )
以下は、ネスト化したプロパティの場合どのようになるかを示しています。
Person( address.houseNumber == 50 )

// this is the same as:
Person( getAddress().getHouseNumber() == 50 )

警告

ステートフルセッションでは、ワーキングメモリはネスト化された値を認識せず、変化があっても分からないので、ネスト化されたアクセサーの使用時には注意が必要です。親参照がワーキングメモリに挿入される場合は、これらは不変であると考えてください。ネスト化された値を変更する場合、すべての外部ファクトは更新済みであるとマークする必要があります。上記の例では、houseNumber が変わると、その Address を持つ Person は更新済とマークする必要があります。

16.6. Java 表現

表16.1 Java 表現

機能
パターンの括弧内にある制約として boolean を返す Java 表現を使用できます。Java 表現は、プロパティアクセスなど他の表現と合わせて利用することができます。
Person( age == 50 )
ロジックや数式のように括弧を使用して、評価優先度を変更することができます。
Person( age > 100 && ( age % 10 == 0 ) )
Java メソッドを再利用できます。
Person( Math.round( weight / ( height * height ) ) < 25.0 )
フィールドや値が違う型の場合、型強制が常に試行されます。不正に型の強制が行われると例外が送出されます。
Person( age == "10" ) // "10" is coerced to 10

警告

メソッドはルールに影響を与える可能性のある形式でオブジェクトのステータスを変更してはいけません。LHS のファクトで実行するメソッドは 読み取り専用のメソッドでなければなりません。

警告

(これらのファクトが変更があるたびにワーキングメモリで更新済とマークされない限り) ファクトのステータスは、ルールの呼び出しと呼び出しの間で変更してはいけません。
Person( System.currentTimeMillis() % 1000 == 0 ) // Do NOT do this

重要

==!= 以外のすべての演算子は、通常の Java セマンティクスを使用します。
== 演算子は null セーフの equals() セマンティクスを持ちます。
// Similar to: java.util.Objects.equals(person.getFirstName(), "John")
// so (because "John" is not null) similar to:
// "John".equals(person.getFirstName())
Person( firstName == "John" )
!= 演算子は null セーフの !equals() セマンティクスを持ちます。
// Similar to: !java.util.Objects.equals(person.getFirstName(), "John")
Person( firstName != "John" )

16.7. コンマ区切りの演算子

コンマ文字 (',') は制約グループを区切るために使用し、この文字には暗黙的で接続的なセマンティクスがあります。
コンマ演算子は、読み取りがより簡単でエンジンが最適化できるため、上位の制約で使用されます。

16.8. コンマ区切りの演算子例

以下は、暗黙的かつ接続的なセマンティクスを持つコンマ区切りのシナリオです。
// Person is at least 50 and weighs at least 80 kg
Person( age > 50, weight > 80 )
// Person is at least 50, weighs at least 80 kg and is taller than 2 meter.
Person( age > 50, weight > 80, height > 2 )

注記

コンマ (,) 演算子は、括弧などの複合制約の表現に組み込むことはできません。

16.9. バインド変数

JBoss Rules では変数にプロパティをバインドすることができます。こうすることで実行速度が早まりパフォーマンスを向上することができます。

16.10. バインド変数の例

以下は、変数にバインドされたプロパティの例です。
// 2 persons of the same age
Person( $firstAge : age ) // binding
Person( age == $firstAge ) // constraint expression

注記

後方互換性の理由で、制約バインディングと制約表現を以下のように混合することが可能です (推奨はされていません)。
// Not recommended
Person( $age : age * 2 < 100 )
// Recommended (separates bindings and constraint expressions)
Person( age * 2 < 100, $age : age )

16.11. ユニフィケーション

複数のプロパティの間で引数を統一化することができます。位置引数は常にユニフィケーションで処理され、名前付きの引数にはユニフィケーションの記号':=' が存在します。

16.12. ユニフィケーションの例

以下は、2 つの引数を統一化した例です。
Person( $age := age ) 
Person( $age := age)

16.13. JBoss Rules のオプションと演算子

表16.2 JBoss Rules のオプションと演算子

オプション 説明
日付リテラル
dd-mmm-yyyy の日付形式はデフォルトでサポートされています。drools.dateformat という名前の別の日付形式マスクを指定してカスタマイズすることができます。さらなる制御が必要な場合は制限を使用します。
Cheese( bestBefore < "27-Oct-2009" )
List および Map アクセス
インデックスにより List の値に直接アクセスできます。
// Same as childList(0).getAge() == 18
Person( childList[0].age == 18 )
値キー
キーにより Map の値に直接アクセスできます。
// Same as credentialMap.get("jsmith").isValid()
Person( credentialMap["jsmith"].valid )
略記組合せ比較条件
これは、&& または || を使用することでフィールドの制限を複数設置できるようになります。括弧を使ったグループ化も可能で、結果的に再帰的構文パターンになります。
// Simple abbreviated combined relation condition using a single &&
Person( age > 30 &&< 40 )
// Complex abbreviated combined relation using groupings
Person( age ( (> 30 &&< 40) ||
              (> 20 &&< 25) ) )
// Mixing abbreviated combined relation with constraint connectives
Person( age > 30 &&< 40 || location == "london" )
演算子
演算子は、自然な順序でプロパティに対して使用することができます。例えば、日付フィールド <String フィールドの に来ます。アルファベット順でこちらのほうが早いためです。
Person( firstName < $otherFirstName )
Person( birthDate < $otherBirthDate )
演算子 matches
有効な Java regular expression とフィールドを照合します。通常、正規表現は文字列リテラルですが、有効な正規表現を解決する変数も使用可能です。String プロパティに対してのみ適用されます。null 値に matches を使用すると、常に false と評価されます。
Cheese( type matches "(Buffalo)?\\S*Mozarella" )
演算子 not matches
この演算子は、文字列が正規表現に一致しない場合に true を返します。同じルールは matches 演算子に適用されます。String プロパティにのみ適用されます。
Cheese( type not matches "(Buffulo)?\\S*Mozarella" )
演算子 contains
contains の演算子は、 コレクションまたは配列に指定の値が含まれているかどうか確認するために使用します。Collection プロパティにのみ適用されます。
CheeseCounter( cheeses contains "stilton" ) // contains with a String literal
CheeseCounter( cheeses contains $var ) // contains with a variable
演算子 not contains
not contains の演算子は、コレクションまたは配列に指定の値が含まれていないかどうか確認するために使用します。Collection プロパティにのみ適用されます。
CheeseCounter( cheeses not contains "cheddar" ) // not contains with a String literal
CheeseCounter( cheeses not contains $var ) // not contains with a variable
演算子 memberOf
演算子 memberOf は、フィールドがコレクションまたは配列のメンバーかどうかを確認するために使用します。コレクションは変数でなければなりません。
CheeseCounter( cheese memberOf $matureCheeses )
演算子 not memberOf
演算子 not memberOf は、フィールドがコレクションまたは配列のメンバーでないかどうかを確認するために使用します。コレクションは変数でなければなりません。
CheeseCounter( cheese not memberOf $matureCheeses )
演算子 soundslike
この演算子は matches に似ていますが、指定の値とよく似た音 (英語の発音) であるかどうかを確認します。
// match cheese "fubar" or "foobar"
Cheese( name soundslike 'foobar' )
演算子 str
演算子 str は、String のフィールドが特定の値で開始、終了するかどうかを確認するために使用します。文字列の長さを確認するためにも使用できます。
Message( routingValue str[startsWith] "R1" )
Message( routingValue str[endsWith] "R2" )
Message( routingValue str[length] 17 )
複合値制約
複合値制約は、合致する値が複数ある可能性がある場合に使用します。現在 innot in のエバリュエーターのみがこれに対応しています。この演算子の 2 つ目のオペランドは、括弧でくくりコンマで区切った値のリストでなければなりません。値は変数、リテラル、戻り値、修飾 ID として指定できます。いずれのエバリュエーターも実は syntactic sugar で、!= および == の演算子を使用して複数の制限一覧を内部的に再度記述します。
Person( $cheese : favouriteCheese )
Cheese( type in ( "stilton", "cheddar", $cheese ) )

16.14. 演算子の優先順位

表16.3 演算子の優先順位

演算子の型 演算子 備考
(ネスト化された) プロパティアクセス . 通常の Java セマンティクスではない
List/Map アクセス [ ] 通常の Java セマンティクスではない
制約バインディング : 通常の Java セマンティクスではない
乗算 */%
加算 +-
変換 <<>>>>>
リレーショナル <><=>=instanceof
同等 ==!= 通常の Java (not) 同等 セマンティクスを使用せず、(not) 等価セマンティクスを使用します。
非短絡演算子 AND &
非短絡演算子の排他的論理和 OR ^
非短絡演算子の包含的論理和 OR |
論理積 AND &&
論理和 OR ||
三項演算子 ? :
コンマ区切りの AND , 通常の Java セマンティクスではない

16.15. 粒度の高いプロパティ変更リスナー

これは、指定のパターン内に実際に制約またはバインドされたプロパティへの変更に対応するためだけのパターン合致機能です。これにより、擬似的なオブジェクトの分割を避け、パフォーマンスの向上と再帰をしやすくします。

注記

デフォルトでは、ルールエンジンの動作が以前のリリースと後方互換を保つために、この機能はオフになっています。特定の bean でこの機能をアクティブ化するには、 @propertyReactive のアノテーションをつける必要があります。

16.16. 粒度の高いプロパティ変更リスナーの例

DRL の例
declare Person
          @propertyReactive
          firstName : String
          lastName : String
          end
Java クラスの例
@PropertyReactive
          public static class Person {
          private String firstName;
          private String lastName;
          }

16.17. 粒度の高いプロパティ変更リスナーとの連携

このリスナーを使用すると、無限の反復を避けるために no-loop 属性を実装する必要がなくなります。エンジンは、ルールの RHS が他のプロパティを変更しつつ、プロパティ上でパターン合致が行われていることを認識します。Java クラスでは、呼び出しが実際に他のプロパティを変更したことを通知するために、メソッドのアノテーションも可能です。

16.18. @watch でのパターンの使用

@watch のアノテーションをパターンにつけることで、プロパティの推論セットを変更してそのパターンを削除することができます。@watch アノテーションで名前が付けられたプロパティは、自動的に推論されたものに追加されます。! で名前を開始することで、1 つまたは複数のパターンを明示的に除外することができます。また、パターンの中で使用されているタイプのプロパティすべてをリッスンするまたはしないというパターンを指定するには、それぞれワイルドカード * および !* を使用します。

16.19. @watch の例

以下は、ルールの LHS の @watch アノテーションです。
// listens for changes on both firstName (inferred) and lastName
          Person( firstName == $expectedFirstName ) @watch( lastName )

          // listens for all the properties of the Person bean
          Person( firstName == $expectedFirstName ) @watch( * )

          // listens for changes on lastName and explicitly exclude firstName
          Person( firstName == $expectedFirstName ) @watch( lastName, !firstName )

          // listens for changes on all the properties except the age one
          Person( firstName == $expectedFirstName ) @watch( *, !age )

注記

@PropertyReactive のアノテーションがついた型を使用して、あるパターンにこのアノテーションを使用するのは意味がないため、ルールコンパイラーはコンパイルエラーを出します。また、@watch (例:@watch( firstName, ! firstName ) など) で同じプロパティを重複して使用するとコンパイルエラーが発生します。

16.20. @PropertySpecificOption の使用

デフォルトで @watch を有効にするか、KnowledgeBuilderConfiguration の on オプションを使用してこのアノテーションを利用できなくします。新しい PropertySpecificOption は、以下の 3 つの値を取ることができます。
- DISABLED => the feature is turned off and all the other related annotations are just ignored
          - ALLOWED => this is the default behavior: types are not property reactive unless they are not annotated with @PropertySpecific
          - ALWAYS => all types are property reactive by default

16.21. 基本的な条件要素

表16.4 基本的な条件要素

名前 説明 追加のオプション
and
条件要素 and は、他の条件要素を組み合わせて論理的に連携します。JBoss Rules は接頭辞 and、接中辞 and の両方に対応しています。また、括弧で明示的にグループ化することもできます。また。従来の接中辞、接頭辞 and も使用できます。
//infixAnd
Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType )
//infixAnd with grouping
( Cheese( cheeseType : type ) and
  ( Person( favouriteCheese == cheeseType ) or 
    Person( favouriteCheese == cheeseType ) )
接頭辞 and にも対応しています。
(and Cheese( cheeseType : type )
     Person( favouriteCheese == cheeseType ) )
LHS の root 要素は暗黙的な接頭辞 and で、指定する必要はありません。
when
    Cheese( cheeseType : type )
    Person( favouriteCheese == cheeseType )
then
    ...
or
これは、2 つ以上のよく似たルールを生成するショートカットです。JBoss Rules は接頭辞 or と接中辞 or の両方に対応しています。従来の接頭辞、接中辞、括弧の明示的グループ化を使用することができます。
//infixOr
Cheese( cheeseType : type ) or Person( favouriteCheese == cheeseType )
//infixOr with grouping
( Cheese( cheeseType : type ) or
  ( Person( favouriteCheese == cheeseType ) and
    Person( favouriteCheese == cheeseType ) )
(or Person( sex == "f", age > 60 )
    Person( sex == "m", age > 65 )
オプションのパターンバインディングを可能にします。各パターンは、eponymous 変数を使用して、個別にバインドする必要があります。
pensioner : ( Person( sex == "f", age > 60 ) or Person( sex == "m", age > 65 ) )
(or pensioner : Person( sex == "f", age > 60 ) 
    pensioner : Person( sex == "m", age > 65 ) )
not
これは、absent と指定されたオブジェクトがワーキングメモリに含まれていないことを確認します。適用される条件要素を囲む括弧が後にくる場合もあります (単一のパターンの場合は括弧を省略することができます)。
// 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") )
exists
これは、指定のアイテムが存在するかどうか、ワーキングメモリをチェックします。適用される CE を囲む括弧がキーワード exists の後に来る必要があります (単一のパターンの場合は括弧を省略することができます)。
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") )

注記

条件要素 or の動作は、フィールド制約の制約や制限に対する接続演算子 || とは違います。エンジンは、条件要素 or を解釈することができません。代わりに、or を持つルールは多くのサブルールで再記述されます。このプロセスは最終的に root ノードに or を 1 つと CE ごとにサブルール 1 つ持つルールとなります。サブルールはそれぞれ通常のルールのようにアクティブ化して発火することができます。これらのサブルールの間での特別な動作や対話はありません。

16.22. 条件要素 Forall

この要素は、最初のパターンと合致したすべてのファクトが残りのパターンすべてと一致する場合、True と評価されます。これはスコープデリミターです。そのため、以前にバインドされた変数を使用できますが、条件要素内にバインドされた変数は、要素の外側で使用することができません。
Forall は、他の条件要素内にネスト化することができます。例えば、forallnot 条件要素内で使用できます。単一パターンにはオプションで括弧が使用されますが、ネスト化された forall では括弧を必ず使用してください。

16.23. 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
単一パターン forall
rule "All Buses are Red"
when
    forall( Bus( color == 'red' ) )
then
    // all Bus facts are red
end
複数パターン forall
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
ネスト化された forall
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

16.24. 条件要素 From

条件要素 from は、LHS パターンと照合させる任意のデータのソースを指定できます。これにより、エンジンがワーキングメモリではなくデータから推論することができます。このデータソースは、バインド変数にあるサブフィールドか、メソッド呼び出しの結果のいずれかが可能です。他のアプリケーションのコンポーネントやフレームワークとカスタマイズなしに統合できる、力強い構造となっています。一般的な例として、hibernate の名前がついたクエリを使用して、データベースからオンデマンドででリトリーブしたデータと統合ができます。
オブジェクトソースを定義するために使用した表現は、通常の MVEL 構文に準拠しています。そのため、オブジェクトのプロパティナビゲーションを簡単に使用し、メソッドの呼び出しを行い、マップやコレクション要素へアクセスすることができます。

重要

lock-on-activefrom を使用すると、ルールが発火されない可能性があります。
この問題に対処するには、複数の方法があります。
  • 前ファクトをワーキングメモリーにアサートできる場合は from の使用をさけるか、制約表現でネスト化されたオブジェクト参照を使用します (以下参照)。
  • 条件内 (LHS) の最後の文章として modify ブロックで使用するために割り当てられた変数を配置します。
  • 同じ rule-flow グループでルールのアクティベーションをどのように行うか明示的に管理できる場合、lock-on-active を使用しないようにします。

16.25. From の例

パターンでの推論およびバインディング
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
すべてのオブジェクトで反復
rule "apply 10% discount to all items over US$ 100,00 in an order"
when
    $order : Order()
    $item  : OrderItem( value > 100 ) from $order.items
then
    // apply discount to $item
end
lock-on-active と併用
rule "Assign people in North Carolina (NC) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
    $p : Person(address.state == "NC" )  
then
    modify ($p) {} // Assign person to sales region 1 in a modify block
end

rule "Apply a discount to people in the city of Raleigh"
ruleflow-group "test"
lock-on-active true
when
    $p : Person(address.city == "Raleigh" )  
then
    modify ($p) {} //Apply discount to person in a modify block
end

16.26. 条件要素 collect

条件要素 collect は、ルールが指定のソースかワーキングメモリーから取得したオブジェクトのコレクションから推測できるようにします。一階述語論理の用語では、これがカーディナリティ (cardinality) 数量子です。
java.util.Collection インターフェースを実装してデフォルトの no-arg パブリックコンストラクタを提供する具体的なクラスが、collect の結果パターンとなりえます。java.util.Collection を実装してデフォルトの no-arg パブリックコンストラクタを提供する限り、ArrayList、LinkedList、HashSet などの Java コレクションや独自のクラスを使用することができます。
collect 条件要素の前にバインドされた変数は、ソースおよび結果パターンの両方のスコープ内であるため、これらを使用してお使いのソースや結果パターンを制約することができます。collect 内でバインドされた場合 collect 条件要素外では使用できません。

16.27. 条件要素 Accumulate

条件要素 accumulatecollect の機能だけでなく collect で達成できない結果を達成できるため、collect がより柔軟で力強くなったものであるといえます。オブジェクトのコレクションで反復して、各要素にカスタムのアクションを実行することができます。最終的に、結果オブジェクトを返します。
Accumulate は、事前定義済みの accumulate 関数もインラインのカスタムコードの使用にも対応しています。ただし、ルール作成者によるコードの管理が困難でコードの重複が頻繁に起こるため、インラインのカスタムコードは避けるべきです。Accumulate 関数のほうがテストや再利用も簡単です。
Accumulate 条件要素は、複数の違った構文にも対応しています。推奨されている構文は以下に記載のトップレベルの accumulate ですが、その他の構文にも後方互換を確保するため対応しています。

16.28. 条件要素 accumulate の構文

トップレベルの accumulate 構文
accumulate( <source pattern>; <functions> [;<constraints>] )
構文の例
rule "Raise alarm"
when
    $s : Sensor()
    accumulate( Reading( sensor == $s, $temp : temperature );
                $min : min( $temp ),
                $max : max( $temp ),
                $avg : average( $temp );
                $min < 20, $avg > 70 )
then
    // raise the alarm
end
上記の例では、min、max、average が Accumulate 関数で、センサーが読み取ったすべての気温の値について最小値、最大値、平均値を算出します。

16.29. 条件要素 accumulate の関数

  • average
  • min
  • max
  • count
  • sum
  • collectList
  • collectSet
これらの一般的な関数は入力値として任意の式を受け取ります。たとえば、注文した全アイテムの平均利益を計算する場合、以下のように average 関数を使用してルールを記述することができます。
rule "Average profit"
when
    $order : Order()
    accumulate( OrderItem( order == $order, $cost : cost, $price : price );
                $avgProfit : average( 1 - $cost / $price ) )
then
    // average profit for $order is $avgProfit
end

16.30. 条件要素 accumulate およびプラガビリティ

Accumulate 関数はすべてプラグインができます。つまり、必要であれば、カスタムのドメイン固有の関数を簡単にエンジンに追加でき、ルールは制限なしにすぐにそれらの関数を使用することができます。新しい Accumulate 関数を実装するには、org.drools.runtime.rule.TypedAccumulateFunction インターフェースを実装する Java クラスを作成して設定ファイルに 1 行追加するか、エンジンに新しい関数を通知するためのシステムプロパティを設定します。

16.31. 条件要素 accumulate とプラガビリティの例

Accumulate 関数の実装例として、以下に average 関数の実装を示しています。
/**
 * An implementation of an accumulator capable of calculating average values
 */
public class AverageAccumulateFunction implements org.drools.runtime.rule.TypedAccumulateFunction {

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    }

    public void writeExternal(ObjectOutput out) throws IOException {

    }

    public static class AverageData implements Externalizable {
        public int    count = 0;
        public double total = 0;
        
        public AverageData() {}

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            count   = in.readInt();
            total   = in.readDouble();
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeInt(count);
            out.writeDouble(total);
        }

    }

    /* (non-Javadoc)
     * @see org.drools.base.accumulators.AccumulateFunction#createContext()
     */
    public Serializable createContext() {
        return new AverageData();
    }

    /* (non-Javadoc)
     * @see org.drools.base.accumulators.AccumulateFunction#init(java.lang.Object)
     */
    public void init(Serializable 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(Serializable 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(Serializable 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(Serializable 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;
    }

    /**
     * {@inheritDoc}
     */
    public Class< ? > getResultType() {
        return Number.class;
    }

}

16.32. 条件要素 accumulate 関数のコード

プラグイン関数のコード (設定ファイルに入力)
jbossrules.accumulate.function.average =
   org.jbossrules.base.accumulators.AverageAccumulateFunction
その他の構文:戻り型を持つ単一関数
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

16.33. インライン式のカスタムコードを使用した accumulate

警告

インライン式のカスタムコードと accumulate を使用するのは、これを使うルールの保守とテストが困難でコードの再利用ができないなど、複数の理由により適切とは言えません。独自の accumulate 関数を実装することで、テストが簡素化されます。この形式の accumulate は後方互換用にサポートされています。
インライン式のカスタムコードを使用した accumulate 条件要素の一般的な構文は以下のとおりです。
<result pattern>from accumulate(<source pattern>,init(<init code>),action(<action code>),reverse(<reverse code>),result(<result expression>) )  
                                    
                                    
                                    

以下に、各要素の意味を説明をしています。
  • <source pattern>: このソースパターンは、エンジンがソースオブジェクトと照合する際の通常のパターンのことです。
  • <init code>: これは、選択したダイアレクトのコードのセマンティックブロックであり、ソースオブジェクトに対する繰り返し処理が行われる前に各 Tuple に対して一度実行されます。
  • <action code>: これは、選択したダイアレクトのコードのセマンティックブロックであり、各ソースオブジェクトに対して実行されます。
  • <reverse code>: これは選択したダイアレクトのコードのセマンティックブロック (任意) で、これが指定されている場合はソースパターンに合致しなくなった各ソースオブジェクトに対して実行されます。このコードブロックの目的は、ソースオブジェクトが変更または削除された際にエンジンがデクリメント演算を実行できるように <action code> ブロックで行われた演算を元に戻すことでこれらの演算を大幅に改善することです。
  • <result expression>: これは選択したダイアレクトのセマンティック式で、すべてのソースオブジェクトに対して繰り返し処理が実行された後に実行されます。
  • <result pattern>: <result expression> から返されたオブジェクトに対してエンジンが照合を行う通常のパターンです。合致があると、accumulate 条件要素は true と評価され、エンジンはルール内の次の条件要素を評価に進みます。合致がない場合は、accumulate 条件要素が false と評価され、エンジンはそのルールの条件要素の評価を終了します。

16.34. インライン式のカスタムコードを使用した 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 ),
                              init( double total = 0; ),
                              action( total += $value; ),
                              reverse( total -= $value; ),
                              result( total ) )
then
    # apply discount to $order
end
上記の例では、ワーキングメモリーの各 Order に対してエンジンは init code を実行してすべての変数をゼロに初期化します。次に、その順番で OrderItem オブジェクトすべてで反復を行い、それぞれに対してアクションを実行します (この例では、全アイテムの値を合計して total 変数にします)。全 OrderItem オブジェクトで反復した後、result expression に該当する値を返します (上記の例では変数 total の値)。最後に、エンジンはその結果と Number パターンを照合して、double value が 100 以上であればルールが発火されます。
カスタムオブジェクトのインスタンス化と生成
rule "Accumulate using custom objects"
when
    $person   : Person( $likes : likes )
    $cheesery : Cheesery( totalAmount > 100 )
                from accumulate( $cheese : Cheese( type == $likes ),
                                 init( Cheesery cheesery = new Cheesery(); ),
                                 action( cheesery.addCheese( $cheese ); ),
                                 reverse( cheesery.removeCheese( $cheese ); ),
                                 result( cheesery ) );
then
    // do something
end

16.35. 条件要素 eval

条件要素 eval は基本的にキャッチオールの条件要素で、どのセマンティックコードでも実行できます (プリミティブブール型を返します)。このコードは、ルールの LHS でバインドされた変数とルールパッケージの関数を参照できます。eval を使用しすぎると、ルールの宣言性が減少しエンジンのパフォーマンスが下がる可能性があります。eval はパターンのどこでも使用できますが、ベストプラクティスはルールの LHS 内で最後の条件要素として追加することです。
eval はインデックス化ができないため、フィールド制約ほど効率的ではありません。しかし、関数が時間をかけて変化する値を返す場合などフィールド制約では許容できないため、このような場合は eval の使用が理想的です。

16.36. 条件要素 eval の例

以下は eval の使用例です。
p1 : Parameter()
p2 : Parameter()
eval( p1.getList().containsKey( p2.getItem() ) )
p1 : Parameter()
p2 : Parameter()
// call function isValid in the LHS
eval( isValid( p1, p2 ) )

16.37. 右辺部 (RHS)

右辺部 (RHS: Right Hand Side) はルールの結果またはアクション部を指す一般的な名称でうs。RHS の主な目的は、ワーキングメモリーのデータを挿入、削除、変更することです。この部分には、実行するアクションのリストを含める必要があります。ルールの RHS 部は小さく保つことで、宣言性を確保して読み取りやすくします。

注記

RHS に命令および条件コードが必要な場合、複数のルールに分割します。

16.38. RHS コンビニエンスメソッド

表16.5 RHS コンビニエンスメソッド

名前 説明
update(object, handle);
エンジンにオブジェクトが変更し (LHS で何かにバインドされているたもの)、ルールを再検討する必要があると通知します。
update(object);
update() を使用して、ナレッジヘルパーは渡されたオブジェクトのアイデンティティチェックをして facthandle を検索します (エンジンに挿入する Java bean にプロパティ変更リスナーを渡した場合、オブジェクトの変更があっても update() を呼び出す必要はありません)。ファクトのフィールド値が変更すると、他のファクトを変更する前に update を呼び出す必要があります。呼び出さないと、ルールエンジン内のインデックス化に問題が出てくる可能性があります。modify キーワードを使用することでこの問題を回避できます。
insert(newobject());
独自に作成した新しいオブジェクトをワーキングメモリーに設定します。
insertLogical(newobject());
挿入に似ていますが、現在ルールを発火している真理をサポートするファクトがこれ以上ない場合、オブジェクトは自動的に削除されます
retract(handle);
ワーキングメモリーからオブジェクトを削除します。

16.39. Drools 変数を使用したコンビニエンスメソッド

  • drools.halt() の呼び出しはルール実行をすぐに中断します。現在のセッションが fireUntilHalt() と連携していた時点に制御を戻すために必要です。
  • insert(Object o)update(Object o)retract(Object o) メソッドは drools でも呼び出されますが、頻繁に使用されるためオブジェクト参照なしで呼び出すことができます。
  • drools.getWorkingMemory()WorkingMemory オブジェクトを返します。
  • drools.setFocus( String s) は指定のアジェンダグループにフォーカスを設定します。
  • ルールの RHS から呼び出された drools.getRule().getName() はルールの名前を返します。
  • drools.getTuple() は現在実行中のルールと合致する Tuple を返し、drools.getActivation() は該当するアクティベーションを行います (これらの呼び出しはロギングやデバッグ目的に便利です)。

16.40. kcontext 変数を使用したコンビニエンスメソッド

  • kcontext.getKnowledgeRuntime().halt() の呼び出しはルールの実行を即座に終了します。
  • getAgenda() アクセサーは、セッションの Agenda への参照を返し、順番に様々なルールグループ (アクティベーショングループ、アジェンダグループ、ルールフローグループ) へアクセスできるようにします。一般的な例はアジェンダグループの一部をアクティブ化することで、これは長めの呼び出しで可能になります。
    // give focus to the agenda group CleanUp
    kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus();
    (drools.setFocus( "CleanUp" ) を使用しても同じことが行えます)
  • クエリを実行するには getQueryResults(String query) を呼び出し、そこで結果を処理することができます。
  • イベント管理を処理するメソッドは、ワーキングメモリーやアジェンダに対してイベントリスナーの追加や削除を可能にします。
  • getKnowledgeBase() メソッドは KnowledgeBase オブジェクト、システム内の全ナレッジのバックボーン、現在のセッションの開始元を返します。
  • setGlobal(...)getGlobal(...)getGlobals() で global を管理できます。
  • getEnvironment() メソッドはランタイムの Environment を返します。

16.41. modify ステートメント

表16.6 modify ステートメント

名前 説明 構文
modify
これにより、fact を更新する際に構造化したアプローチを行うことができます。更新オペレーションと多くのセッター呼び出しを組み合わせてオブジェクトのフィールドを変更します。
modify ( <fact-expression> ) {<expression> [ ,<expression> ]*
}
括弧内の <fact-expression> は、ファクトのオブジェクト参照を生成する必要があります。ブロック内の表現リストには、通常のオブジェクト参照 (自動的にコンパイラーにより先頭に追加) なしに記述されるように指定のオブジェクトに対するセッター呼び出しを含めなければなりません。
rule "modify stilton"
when
    $stilton : Cheese(type == "stilton")
then
    modify( $stilton ){
        setPrice( 20 ),
        setAge( "overripe" )
    }
end

16.42. クエリーの例

注記

結果を返すには、ksession.getQueryResults("name") を使用します。"name" にはクエリーの名前が入ります。これは、クエリー結果のリストを返すため、クエリーに該当したオブジェクトを取得できます。
年齢 30 を超える人に関するクエリー
query "people over the age of 30" 
    person : Person( age > 30 )
end
年齢が x を超え、y に住んでいる人に関するクエリー
query "people over the age of x"  (int x, String y)
    person : Person( age > x, location == y )
end

16.43. QueryResults の例

標準の 'for' loop を使用して QueryResults を反復します。各要素は QueryResultsRow で、tuple の各列にアクセスする際に使用できます。これらの列は、バインドされた宣言名またはインデックスの位置によりアクセスできます。
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() + "\n" );
}

16.44. 他のクエリを呼び出すクエリ

クエリは他のクエリを呼び出すことができます。オプションのクエリ引数と組み合わせて、抽出クエリスタイルの後ろ向き連鎖を提供します。また、位置と名前付きを混ぜることも可能ですが、位置が先に来る必要があります (セミコロンで区切る)。リテラル表現はクエリ引数として渡すことが可能ですが、表現と変数を混合することはできません。

注記

このプロセスで '?' 記号を使用すると、クエリのみがプルされ、結果が返されると基盤のデータが変更してもこれ以上結果を受け取ることはありません。

16.45. 他のクエリを呼び出すクエリ例

別のクエリを呼び出すクエリ
declare Location
    thing : String 
    location : String 
end

query isContainedIn( String x, String y ) 
    Location(x, y;)
    or 
    ( Location(z, y;) and ?isContainedIn(x, z;) )
end
ライブクエリを使用してクエリの結果から変更を徐々に反応的に受け取る例
query isContainedIn( String x, String y ) 
    Location(x, y;)
    or 
    ( Location(z, y;) and isContainedIn(x, z;) )
end

rule look when 
    Person( $l : likes ) 
    isContainedIn( $l, 'office'; )
then
   insertLogical( $l 'is in the office' );
end

16.46. 派生クエリのユニフィケーション

JBoss Rules は、派生クエリのユニフィケーションに対応しています。これは引数はオプションであるという意味です。静的なフィールド org.drools.runtime.rule.Variable.v. を使用して引数を指定せずに Java からクエリを呼び出すことができます (別の変数インスタンスではなく、'v' を使用する必要があります)。これらは 'out' 引数と呼ばれます。

注記

クエリ自体は、コンパイル時には引数が in または out のいずれであるかを宣言しません。これは、使うたびにランタイム時にだけ定義することができます。

第17章 ドメイン固有言語 (DSL)

17.1. ドメイン固有言語

ドメイン固有言語 (または DSL) は、問題ドメイン専用のルール言語を作成する手段です。DSL 定義のセットには、DSL 「センテンス」から DRL コンストラクトへの変換が含まれており、基礎となるすべてのルール言語とエンジン機能の使用を可能にします。ルールを DSL ルール (DSLR) ファイルで記述可能で、それが DRL ファイルに変換されます。
DSL and DSLR ファイルはプレーンテキストファイルで、これらのファイルの作成や変更にはどんなテキストエディターでもご利用いただけます。また、DSL の機能が完全に提供されないかもしれませんが、IDE や Web ベースの BRMS で使用できる DSL と DSLR エディターもあります。

17.2. DSL の使用

DSL は、ドメインオブジェクトやルールエンジンのネイティブ言語やメソッドのモデリングによる技術的な複雑性からルール作成 (およびルール作成者) を切り離す層として機能します。DSL は実装詳細を見せず、ルールロジックの真意にフォーカスします。DSL のセンテンスは、条件要素やルールで繰り返し使用される結果アクションの「テンプレート」としての役割も果たすことができます (バリエーションも少しあり)。このようなバリエーションに対応する手段として提供されるパラメーターを使用して、これらの反復フレーズにマッピングされるように DSL センテンスを定義することができます。

17.3. DSL の例

表17.1 DSL の例

説明
[when]Something is {colour}=Something(colour=="{colour}")
[when] は、式のスコープを示します (ルールの LHS または RHS に有効であるかどうかなど)。
括弧内のキーワードの後にくる部分は、ルールで使用する式です。
等号 = の右部分で、式がルール言語にマッピングされています。この文字列の形式はそのマッピング先にによって異なります (RHS または LHS)。LHS の場合は、通常の LHS 構文に基づいた用語でなければならず、RHS の場合は、Java ステートメントの場合があります。

17.4. DSL パーサーの仕組み

DSL パーサーが DSL で記述されたルールファイルの行と DSL 定義の式を照合するときは、必ず文字列操作の 3 つの手順が実行されます。
  • DSL は、式に括弧で囲まれた変数名が含まれる場所に表示される文字列の値を抽出します。
  • このようなキャプチャーで取得した値は、その名前がマッピングの右辺部 (RHS) に出てくる箇所で常に補間されます。
  • 補間された文字列は、DSL ルールファイルの行の式全体と照合して合致したものを置き換えます。

注記

例えば、前にくる文字を任意とする'?' を使用することができます。これを使用するリユは、DSL の自然言語の句にあるバリエーションに対応するためです。しかし、これらの式が正規表現のパターンだとすると、Java のパターン構文のワイルドカード文字はすべて、バックスラッシュ ('\') が前にきた場合エスケープされなければなりません。

17.5. DSL コンパイラー

DSL コンパイラーは DSL ルールファイルを行ごとに変換します。変換する必要がない場合は、キャプチャーが文字テキスト (単語または単一文字) で囲まれてることを確認してください。結果、パーサーが行う照合オペレーションは、行の中からサブストリングを抽出します。以下の例は、引用句が特有な文字として使用されています (キャプチャーを囲む文字は補間の際には含まれず内容だけが含まれます)。

17.6. DSL 構文の例

表17.2 DSL 構文の例

名前 説明
引用符 ルールエディターにより入力される可能性のあるテキストデータに対して引用符を使用します。テキストが正しく照合されるように取得された値を単語で囲むこともできます。
[when]something is "{color}"=Something(color=="{color}")
[when]another {state} thing=OtherThing(state=="{state}"
括弧 DSL マッピングでは、括弧 "{" と "}" 変数の定義または参照を囲むためだけに使用する必要があります。括弧で囲むとキャプチャーとなります。表現または右辺部で置き換えたテキストがリテラルで処理される必要がある場合、これらを、表現または右辺部の置換テキスト内でリテラルに使用する場合は、バックスラッシュ ("\") を前に付けてエスケープする必要があります。
[then]do something= if (foo) \{ doSomething(); \}
正しい構文でマッピングする例 なし
# 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
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 "Utah"
   ==> Person(name="Bob") and Person(age >= 30, location="Utah")

注記

DSL ルール行からプレーンテキストをキャプチャーし、式で文字列リテラルとして使用する場合は、マッピングの右辺部で引用符を提供する必要があります。

17.7. DSL 表現の連鎖

DSL 表現は、一度に使用できるように一つに連鎖させることができます。終了箇所と開始箇所、パラメーターを表すテキストの終了箇所を明確にする必要があります (パラメーター値として行末まですべてのテキストが取得されてしまう危険性があります)。DSL 定義ファイルの順番に従い、DSL 表現が次々に試行されます。合致があると、残りの DSL 表現も検証されます。

17.8. ファクトへの制約の追加

表17.3 ファクトへの制約の追加

名前 説明
LHS 条件の表現
DSL の機能は、単純な規定により制約をパターンに追加することができます。DSL 表現がハイフン ("-") で開始されている 場合、フィールド制約と考えられるため、その前にくる最後のパターンの行に追加されます。
この例では、クラス Cheese には type、price、age、country のフィールドがあります。通常の DRL で LHS の条件を表現することができるものもあります。
Cheese(age < 5, price == 20, type=="stilton", country=="ch")
DSL の定義
この例にある DSL 定義は、3 つの DSL 句となり、これらのフィールドに関する制約の組み合わせを作成する際に使用することができます。
[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}'
"-"
パーサーは、"-" で始まる行を取得し、先行のパターンに制約として追加します (必要に応じてコンマを挿入します)。
There is a Cheese with
        - age is less than 42
        - type is 'stilton'
Cheese(age<42, type=='stilton')
DSL 句の定義
フィールド制約を処理する様々な演算子や一般的な表現に対して DSL 句を定義すると、DSL のエントリ数を減らします。
[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()
[when][]- {field:\w*} {operator} {value:\d*}={field} {operator} {value}
DSL 定義ルール なし
There is a Cheese with
   - age is less than 42
   - rating is greater than 50
   - type equals 'stilton'
この特定の場合では、"is less than" の句は < で置き換えられ、その行は最終の DSL エントリと合致します。これによりハイフンは削除されますが、最終結果は先行のパターンに制約として追加されます。すべての行を処理した後の結果 DRL テキストは以下のようになります。
Cheese(age<42, rating > 50, type=='stilton')

注記

個別の DSL 式が同じ行を順番に照合する場合は、DSL のエントリの順序が重要です。

17.9. DSL 開発のヒント

  • お使いのアプリケーションが必要とするルールの表現サンプルを記述して、開発しながらテストも行います。
  • DRL と DSLR のルールは、データモデルにしたがってエンティティを参照します。このデータモデルは、ルール内で定義される推測プロセスにより変化するアプリケーションデータを表現します。
  • データモデルの方が多くがファクトの場合、ルールの記述はより簡単です。
  • 変数部分をパラメーターとしてマークします。こうすることで、便利な DSL エントリに関する信頼できる糸口が提供されます。
  • 特定の条件要素やアクションを大なり記号 (">") を行頭につけ DRL 形式で残すことで、この初期設計段階で条件やアクション関連の実装決定を延期することができます。
  • 新しいルールは、既存の DSL 定義を再利用したり、パラメータを既存の条件または結果エントリに追加したりすることにより記述できます。
  • DSL エントリの数は少なくして抑えてください。パラメーターを使用すると、類似のルールパターンまたは制約に対して、同じ DSL センテンスを適用できます。

17.10. DSL および DSLR 参照

DSL ファイルは行指向形式のテキストファイルです。このエントリは、DRL 構文にしたがい、DSLR ファイルをファイルに変換するために使用されます。
  • "#" または "//" (前にスペースの有無に拘らず) で始まる行は、コメントとして扱われます。"#/" で始まるコメント行は、デバッグオプションを要求する単語がないかスキャンされます。以下を参照してください。
  • 開始括弧 ("[") で始まる行は、DSL エントリ定義の最初の行とみなされます。
  • 他の任意の行は先行する DSL エントリ定義に追加され、行の最後はスペースで置換されます。

17.11. DSL エントリの構成

DSL エントリは、以下の 4 つの部分から構成されます。
  • スコープの定義。括弧 ("[" および "]") で囲まれた "when" または"condition"、"then" または "consequence"、"*"、"keyword" の 1 つで記述されています。これは、DSL エントリがルールの条件や結果 (あるいは条件と結果両方) に対して有効であるかを指定します。"keyword" のスコープは、エントリがグローバルかどうか (DSLR ファイル内のどこでも認識されるか) を指定します。
  • 型の定義。Java クラス名として記述し括弧で括ります。この部分は、次の部分が開始括弧で始まらない限りオプションです。括弧内が空白のペアは無効です。
  • 正規表現 (Java) で構成され、任意の数の組み込み変数定義を持ち等号 ("=") で終わる DSL 式。変数定義は波括弧 ("{" と "}") で囲まれ、変数名と 2 つのオプションの添付 (コロン (":") で区切られます) から構成されます。添付が 1 つの場合は、一致するテキストの正規表現が変数に割り当てられます。添付が 2 つの場合は、最初の添付が GUI エディターのヒントとなり、2 つ目の添付が正規表現になります。
    表現内で文字をリテラルで使用する必要がある場合、正規表現で "magic" 状態文字はすべて、その文字の前にバックスラッシュ ("\") を付けてエスケープする必要があることに注意してください。
  • 行の区切り等号後の部分は、正規表現に一致する DSLR テキストの置換テキストです。これには変数参照 (波括弧で囲まれた変数名) を含めることができます。オプションで、変数名の後に感嘆符 ("!") と変換機能を指定できます。以下を参照してください。
    置き換え文字列でリテラルに括弧 ("{" および "}") を使用する必要がある場合は、これらの括弧の前にバックスラッシュ ("\") を挿入してエスケープする必要があります。

17.12. DSL 拡張のデバッグオプション

表17.4 DSL 拡張のデバッグオプション

単語 説明
結果 結果となる DRL テキストを行番号とともに出力します。
steps 条件および結果行の各拡張ステップを出力します。
keyword スコープ "keyword" を持つすべての DSL エントリの内部表現をダンプします。
when スコープ "when" または "*" を持つすべての DSL エントリの内部表現をダンプします。
then スコープ "when" または "*" を持つすべての DSL エントリの内部表現をダンプします。
usage すべての DSL エントリの使用統計を表示します。

17.13. DSL 定義の例

以下は、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} )\{ \}

17.14. DSLR ファイルの変換

DSLR ファイルの変換は以下のように行われます。
  1. テキストがメモリに読み込まれます。
  2. 各 keyword エントリがテキスト全体に適用されます。キーワード定義からの正規表現を変更するには、ホワイトスペースのシーケンスを任意の数のホワイトスペースに一致するパターンで置き換え、変数定義を定義またはデフォルトの (".*?) で提供された正規表現からのキャプチャーで置き換えます。次に、変更された正規表現に一致する文字列がDSLR テキストにないか徹底的に検索されます。さらに、変数キャプチャーに対応する一致文字列のサブ文字列が抽出され、対応する置換テキストの変数参照が置換され、このテキストによって DSLR テキストの一致文字列が置換されます。
  3. 以下で説明されているように、"when" と "then" 間、および "then" と "end" 間の DSLR テキストのセクションは、一律した方法で行ごとに特定され処理されます。
    行に対して、行のセクションに関係する各 DSL エントリが、DSL ファイルに現れる順序で取得されます。この正規表現部分は変更されます。ホワイトスペースは任意の数のホワイトスペースと合致するパターンで置換されます。正規表現の変数定義は、この正規表現のキャプチャーにより置換されます (デフォルト値は ".*?")。結果となる正規表現が行のすべてまたは部分に一致する場合、一致箇所が適切に変更された置換テキストで置換されます。
    置換テキストの変更は、正規表現キャプチャに対応するテキストで変数参照を置換することによって行われます。このテキストは、変数参照で提供される文字列変換機能に従って変更できます。詳細については以下を参照してください。
    同じエントリで定義されない変数を指定する変数参照が存在する場合は、エキスパンダーがその名前の変数にバインドされた値を置換します (これが現在のルールの先行する行のいずれかで定義された場合)。
  4. 条件の DSLR 行がハイフンで始まる場合、展開された結果は最後の行に挿入されます。これには (型名の後に括弧のペアがくる) パターンの条件要素を含める必要があります。このペアが空白の場合、展開された行 (有効な制約を含む) が単に挿入されます。それ以外の場合は、コンマ (",") が前に挿入されます。
    結果の DSLR 行がハイフンで始まる場合、展開された結果が最後の行に挿入されます。これには、"modify" ステートメントが含まれ、括弧のペア ("{" と "}") で終わります。このペアが空白の場合は、展開された行 (有効なメソッドコールを含む) が単に挿入されます。それ以外の場合は、コンマ (",") が前に挿入されます。

注記

現時点では、ハイフンで始まる行を使用して、他の条件要素形式に ("accumulate" など) にテキストを挿入することはできません。または、最初の挿入に対してのみ可能です ("eval" など)。

17.15. 文字列変換関数

表17.5 文字列変換関数

名前 説明
uc すべての文字を大文字に変換します。
lc すべての文字を小文字に変換します。
ucfirst 最初の文字を大文字、他のすべての文字を小文字に変換します。
num 文字列からすべての数字と "-" を抽出します。元の文字列の下二桁の前に "." または "," がある場合、小数点が該当の位置に挿入されます。
a?b/c 文字列を文字列 a と比較し、同じである場合は、b と置換し、同じでない場合は、c と置換します。ただし、c は別の triplet abc である場合があるため、構造全体が実際には変換テーブルになります。

17.16. 文字列 DSL 変換関数

表17.6 文字列 DSL 変換関数

名前 説明
.dsl
DSL 定義を含むファイルには、通常拡張子 .dsl が与えられます。これは Knowledge Builder に ResourceType.DSL で渡されます。DSL 定義を使用するファイルに対して、拡張子 .dslr を使用する必要があります。Knowledge Builder では ResourceType.DSLR が期待されます。ただし、IDE は、ルールファイルを適切に認識し、使用するためにファイルの拡張子に依存します。
# 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 の指定
DSL は、DSL を使用するルールファイルよりも前に Knowledge Builder に渡す必要があります。
DSLR ファイルを解析および展開する場合、DSL 設定が読み込まれ、パーサーに提供されます。パーサーは DSL 式を「認識」し、ネイティブのルール言語式に変換します。
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 );

第18章 XML

18.1. XML 形式

警告

XML ルール言語は、Drools 5.x で導入された機能に対応するため更新されていませんが、この機能は廃止される可能性があります。
オプションとして、JBoss Rules は、DRL の代わりに "ネイティブ" ルール言語をサポートします。これにより、ユーザーはルールを XML データとして取得および管理できます。非 XML DRL の形式のように、XML 形式はできるだけ早く内部の "AST" 表現に解析されます (SAX パーサーの使用)。外部の変換ステップは必要ありません。

18.2. XML ルールの例

以下は、XML でルールはどのように記述されるかを示しています。
<?xml version="1.0" encoding="UTF-8"?><package name="com.sample"
         xmlns="http://drools.org/drools-5.0"
         xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
         xs:schemaLocation="http://drools.org/drools-5.0 drools-5.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>










    
    

    









    
            
                
                    
                        
                            
                                
                                
                            
                            
                                
                                
                            
                        
                    

                    
                        
                    
                

                
                    
                        
                    

                    
                        
                    
                

                
                    
                        
                    

                    
                        
                            
                            
                        
                    
                
            
        

        
            
                
                    
                
            

            
                
                    
                        
                    
                                
            
        

        
            
                
                    
                        
                        
                    
                
                
                    
                
                
                    
                
            

            
                
                
                    
                    
                
            
        

        
            
                
                    
                    
                    
                
                        
        

        
            
            
                
            
        
  
 




18.3. XML 要素

表18.1 XML 要素

名前 説明
global
ルールで参照できる Global オブジェクトを定義します。
function
関数をルールで使用できるようにするための関数宣言が含まれます。戻り型、一意の名前、パラメーターをコードスニペットのボディに指定する必要があります。
import
ルールで使用する型をインポートします。

18.4. ルール要素の詳細

このルールの例には LHS および RHS (条件と結果) のセクションがあります。RHS はルールがアクティブ化される際に実行されるセマンティックコードのブロックです。LHS は、条件要素、制約、制限のネスト化された要素が含まれるため、RHS よりも若干複雑です。
<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>






    
        
            
                
                
                
                
            
        
    

    
        
            
                
                
            
        
    



18.5. XML ルールの要素

表18.2 XML ルールの要素

要素 説明
パターン
型 (クラス) を指定し、おそらくそのクラスのインスタンスに変数をバインドできるようになります。必要がある制約と制限がパターンオブジェクトの下にネストされます。Predicate および Return Value 制約を使用すると、Java 表現を組み込むことができます。
条件要素 (not、exists、and、or)
DRL のように動作します。以下にネストされた要素と "and" 要素は論理的に "and" によって結合されます。"or" (さらにネストできます) と同様に、"Exists" と "Not" はパターンと連携してパターンの制約を満たすファクトが存在するかしないかをチェックします。
Eval
ブール値 (単なる断片なのでセミコロンで終了しないでください) と評価される限り、有効な Java コードのスニペットを実行できます。これには、関数の呼び出しを含めることができます。ルールエンジンが毎回評価する必要があるため、Eval は列よりも非効率です。ただし、これは列制約で何をする必要があるか表現できる場合は "catch all" 機能です。

18.6. XML と DRL 間の自動変換

JBoss Rules には形式間を変換するいくつかのユーティリティクラスが含まれます。これは、ソース形式からのルールを AST に解析し、適切なターゲット形式に「ダンプ」することで動作します。これにより、たとえば、ルールを DRL で記述して XML にエクスポートすることができます。

18.7. XML と DRL 間の自動変換用のクラス

XML と DRL ファイル感で変換を行う際に使用するクラスを以下に示しています。これらの組み合わせを使用することで、形式間の変換が可能になります (変換がまたに戻すことも可能)。
  • DrlDumper - DRL のエクスポート
  • DrlParser - DRL の読み込み
  • XmlPackageReader - XML の読み込み

注記

DSL は (DSL を使用する DRL から) 保護されませんが、変換は可能です。

第19章 Java ルールエンジンアプリケーションプログラミングインターフェース

19.1. JSR94

JSR94 は、JBoss Rules で使用されている API です。単一の API から複数のルールエンジンに対応することができます。JSR94 は、ルール言語自体の処理は行いません。
JSR94 規格は、ルールエンジンの機能に関する「最小限の共通点」を表します。つまり、標準の Knowledge API よりも JSR94 には機能が少なくなっています。JSR94 を使用すると、JBoss Rules の機能をすべて使用することができません。global を使用して、JSR94 と連携するプロパティマップで DRL、DSL、XML に対応する必要があります。これにより、移植性のない機能を導入します。

注記

JSR94 にはルール言語がないため、ルールエンジンを切り替える際の複雑性は少ししか解決できないに拘らず、利点はほとんどありません。Red Hat は、Knowledge (JBoss Rules および jBPM) API 以外でプログラミングを行うように推奨しています。

19.2. Javax.rules インターフェース

  • Handle
    Handle は、StatefulRuleSession に追加された WorkingMemory からオブジェクトから取得しなおすために使用されます。HandleWorkingMemory から Object を変更または削除することができます。オブジェクトを変更するには、 StatefulRuleSession から updateObject() を呼び出します。オブジェクトを削除するには、HandleremoveObject() をパラメーターとして呼び出します。Java Rule Engine API の実装の内部では、カプセル化された Knowledge (Drools および jBPM) の modifyObject() および retractObject() メソッドを呼び出します。
  • ObjectFilter
    このインターフェースは RuleSession のオブジェクトをフィルタリングするために使用します。
  • RuleExecutionSetMetadata
    RuleExecutionSetMetadata は RuleExecutionSet の名前、説明、URI を格納するために使用します。
  • RuleRuntime
    RuleRuntime は RuleSession へのキーで、RuleRuntime は RuleServiceProvider から取得します。
    RuleRuntime を取得する場合、RuleSession を開くために createRuleSession() を呼び出します。
    RuleRuntime で RuleAdministrator で登録したすべての RuleExecutionSets の URI 一覧を取得できます。RuleSession をルールエンジンに対して開くためには、文字列としての URI が必要です。ルールエンジンは、RuleSession 内の RuleExecutionSet ルールを使用します。
    Map は、Global に使用します。Global は (Drools 2.x では) 以前 ApplicationData と呼ばれていました。キーは Global の識別子である必要があり、値は Global として使用するオブジェクトである必要があります。
  • RuleSession
    RuleSession は、ルールエンジンへ問い合わせする場合に使用するオブジェクトです。
    RuleRuntime から RuleSession を取得する場合、StatefulRuleSession か StatelessRuleSession のいずれかです。
    すべてのリソースが解放されるように、release()-メソッドを呼び出します。
  • StatefulRuleSession
    1 回以上ルールエンジンを実行する必要がある場合、StatefulRuleSession を実行します。オブジェクトのアサート、ルールの実行などができます。
    Rule Session にアサートするすべてのオブジェクトに関する Handle を取得します。ワーキングメモリのオブジェクトを削除または変更する際に必要になりますので、なくさないようにしてください。実装内で使用される Drools のワーキングメモリには直接問い合わせできません。これには RuleSession を使用します。
  • StatelessRuleSession
    StatelessRuleSession は、ルールエンジンへのコンタクト 1 つのみとなります。ルールエンジンへオブジェクトの一覧を渡して、ルールエンジンはオブジェクトすべてをアサートし直後に実行を開始します。結果はオブジェクトの一覧となります。結果一覧の内容はルールにより左右されmさう。ルールがワーキングメモリーからのオブジェクトを変更または削除しない場合、再度追加したオブジェクトをすべて取得するはずです。
    取得前にオブジェクトの結果一覧をフィルタリングする ObjectFilter を使用することができます。

19.3. Javax.rules クラス

  • RuleServiceProvider
    RuleServiceProvider により、新しいルールセッションを開くために必要な RuleAdministrator や RuleRuntime へアクセスできるようにします。RuleServiceProvider を取得するには、RuleServiceProviderManager.getRuleServiceProvider() を呼び出します。
    J2EE 環境では、RuleServiceProvider を JNDI にバインドして、ルックアップを作成してアプリケーションすべてに設置することができます。
  • RuleServiceProviderManager
    RuleServiceProvider は頻繁に DriverManager と比較して JDBC で使用します。これは、データベースのドライバーを設定するように機能します。

19.4. Javax.rules の例外

  • ConfigurationException
    この例外は、ユーザー設定にエラーがあった場合に送出されます。
  • InvalidHandleException
    この例外は、クライアントが無効な Handle をルールエンジンに渡した場合に送出されます。
  • InvalidRuleSessionException
    InvalidRuleSessionException はメソッドが RuleSession で呼び出され、RuleSession の内部のステータスが無効の場合に送出されるはずです。これは、StatefulRuleSession が直列化され外部リソースにアクセスできなうなってしまった場合に発生する場合があります。この例外は、RuleSession は無効のステータスであるとのシグナルを送信する際にも使用されます (release メソッドが呼び出された後に使用しようとし場合など)。 (JCP API ドキュメンテーションより抜粋)
  • RuleException
    javax.rules パッケージの例外クラスすべてに対するベースクラス
  • RuleExecutionException
    Drools 3 JSR 94 実装ではこの例外は送出されません。
  • RuleExecutionSetNotFoundException
    この例外は、クライアントが RuleExecutionSet を RuleRuntime から依頼して、URI または RuleExecutionSet が見つからなかった場合に送出されます (JCP API ドキュメンテーションから抜粋)。
  • RuleSessionCreateException
    この例外は、クライアントが RuleRuntime から RuleSession を依頼して、RuleSession を返すことができないといったエラーが発生した場合に送出されます (JCP API ドキュメンテーションから抜粋)。
  • RuleSessionTypeUnsupportedException
    この例外は、クライアントが RuleSession を依頼してベンダーが指定の型の対応していないか、RuleExecutionSet 自体が依頼したモードに対応していない場合に送出されます (JCP API ドキュメンテーションから抜粋)。

19.5. ルールサービスプロバイダーの使用

手順19.1 タスク

  1. 以下のコードを使用して JBoss Rules ルールサービスプロバイダーを読み込みます。
    Class ruleServiceProviderClass = Class.forName("org.drools.jsr94.rules.RuleServiceProviderImpl");
  2. 以下のコードを使用して登録します。
    RuleServiceProviderManager.registerRuleServiceProvider( "http://jboss.com/products/rules", ruleServiceProviderClass);
  3. 以下のコードを使用して RuleServiceProvider への呼び出しを行います。
    RuleServiceProviderManager.getRuleServiceProvider("http://jboss.com/products/rules");
  4. ルールサービスを停止するには、このコードで登録を解除します。
    RuleServiceProviderManager.deregisterRuleServiceProvider( "http://jboss.com/products/rules");

19.6. Javax.rules.admin インターフェース

  • LocalRuleExecutionSetProvider
  • Rule
  • RuleAdministrator
  • RuleExecutionSet
  • RuleExecutionSetProvider

19.7. Javax.rules.admin の例外

  • RuleAdministrationException
    javax.rules.admin パッケージの administration RuleException クラスすべてに対するベースクラス (JCP API ドキュメンテーションから抜粋)
  • RuleExecutionSetCreateException
    ルール実行セットを作成する際にエラーが発生すると、この例外が起こります。
  • RuleExecutionSetDeregistrationException
    URI からルール実行セットの登録解除を試行するときにエラーが発生すると、この例外が起こります。
  • RuleExecutionSetRegisterException
    URI からルール実行セットに登録を試行するときにエラーが発生すると、この例外が起こります。

19.8. RuleServiceProvider

RuleServiceProvider により、RuleRuntime や RuleAdministrator API にアクセスできるようになります。RuleAdministrator は、RuleExecutionSet オブジェクトの管理に管理 API を提供することで、RuleExecutionSet の登録が可能でき、RuleRuntime 経由で取得することができるようになります。

19.9. RuleServiceProviderManager

RuleServiceProviderManager は、RuleServiceProviders の登録や取得を管理します。JBossRules RuleServiceProvider 実装は、クラスが Class.forName を使用して読み込まれる際に、JDBC ドライバーとよく似た方法で、自動的に静的ブロックで登録されます。

19.10. RuleServiceProvider 自動登録の例

以下は、自動 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/");

19.11. LocalRuleExecutionSet を RuleAdministrator API で登録

手順19.2 タスク

  1. RuleExecutionSet を作成します。空の LocalRuleExecutionSetProvider または RuleExecutionSetProvider を返すファクトリメソッドを提供する RuleAdministrator を使用することで、この作成が可能です。
  2. RuleExecutionSet の名前を指定します。
  3. 以下のように RuleExecutionSet を登録します。
    // Register the RuleExecutionSet with the RuleAdministrator
    String uri = ruleExecutionSet.getName();
    ruleAdministrator.registerRuleExecutionSet(uri, ruleExecutionSet, null);
  4. LocalRuleExecutionSetProvider を使用して、ストリームなどのような、直列化できないローカルソースから RuleExecutionSets を読み込みます。
  5. RuleExecutionSetProvider を使用して、DOM 要素やパッケージなどのような、直列化できるソースから RuleExecutionSets を読み込みます。"ruleAdministrator.getLocalRuleExecutionSetProvider( null );" と the "ruleAdministrator.getRuleExecutionSetProvider( null );" (null をパラメーターとして使用)
  6. 以下の例は、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 );
    
  7. "ruleExecutionSetProvider.createRuleExecutionSet( reader, null )" プロパティを使用して、受信ソースの設定を提供します。null が指定されると、デフォルトを使用して drl として入力を読み込みます。マップで利用可能なキーは "source" と "dsl" です。"source" キーは、値として "drl" か "xml" を取ります。
  8. "source" を "drl" に設定して DRL を読み込み、"xml" を設定して XML ソースを読み込みます。"xml" は "dsl" key/value 設定を無視します。"dsl" キーは、Reader または String (dsl のコンテンツ) を値として取ることができます。以下の dsl の例を参照してください。
    // 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 );
    

19.12. ステートフルおよびステートレス RuleSessions

手順19.3 タスク

  1. 以下のように RuleServiceProvider へアクセスしてランタイムを取得します。
    RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime();
  2. ルールセッションを作成するには、RuleRuntime パブリック定数 2 つの内 1 つを使用します。パブリック定数は、"RuleRuntime.STATEFUL_SESSION_TYPE" と "RuleRuntime.STATELESS_SESSION_TYPE" で RuleSession をインスタンス化する RuleExecutionSet に URI を追加します。
  3. オプションで、Global を指定するプロパティにアクセスします。
  4. createRuleSession(...) メソッドは、RuleSession インスタンスを返します。StatefulRuleSession または StatelessRuleSession にキャストする必要があります。
    (StatefulRuleSession) session =
      ruleRuntime.createRuleSession( uri,
                                     null,
                                     RuleRuntime.STATEFUL_SESSION_TYPE );
    session.addObject( new PurchaseOrder( "cheese" ) );
    session.executeRules();
  5. StatelessRuleSession を使用すると、オプションのフィルター、オブジェクト一覧を渡す executeRules(List list) を呼び出し、結果のオブジェクトが返されます。
    (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 );

19.13. JSR94 での Global の使用

JSR94 は、RuleSession ファクトリメソッドに渡されるプロパティマップを使用することで global に対応しています (移植性がない形で)。Global は DRL または XML ファイルで先に定義する必要があります。そうでないと、例外が送出されます。キーは、DRL または XML で宣言された識別子を表現し、値は実行時に使用するインスタンスとなります。

19.14. JSR94 で Global を使用した例

以下は、JSR94 での global の実装例です。
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 で global "一覧"を宣言するのを忘れないでください。
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

19.15. JSR94 に関する参考資料

JSR94 に関する詳細情報は、以下のリンクを参照してください。
  1. Java Rule Engine API (JSR 94) の公式な JCP 仕様
  2. Java Rule Engine API ドキュメンテーション
  3. The Logic From The Bottom Line: An Introduction to The Drools Project. N. Alex Rupp 著 (2004 年に TheServiceSide.com で公開)
  4. Getting Started With the Java Rule Engine API (JSR 94): Toward Rule-Based Applications. Dr. Qusay H. Mahmoud 著 (2005 年 Sun Developer Network で公開)
  5. Jess and the javax.rules API. Ernest Friedman-Hill 著 (2003 年に TheServerSide.com で公開)

第20章 JBoss Developer Studio

20.1. ルールの統合開発環境 (IDE)

統合開発環境 (IDE) は、様々な形式でルールを編集、テストする環境を提供してアプリケーションと統合します。
JBoss Rules IDE は、Eclpse プラグインとして提供され、Eclipse 内からルールを作成、管理して、お使いのアプリケーションとルールを統合することができます。これはオプションのツールです。JBoss Rules IDE は Red Hat Developer Studio (以前の JBoss IDE) の一部でもあります。

20.2. Rules IDE 機能

Rules IDE には以下の機能が含まれています。
  1. テキスト/グラフィカルルールエディター
    1. DRL 構文を認識して、コンテンツアシストを提供するエディター (アウトラインビューを含む)
    2. DSL (ドメイン固有言語) の拡張を認識して、コンテンツアシストを提供するエディター
  2. RuleFlow グラフィカルエディター
    プロセス (ルールフロー) を表す画像グラフを編集できます。その後、RuleFlow がお使いのルールパッケージに適用され命令形制御を行います。
  3. 以下を素早く作成するウィザード
    1. "rules" プロジェクト
    2. ルールのリソース (DRL ファイル)
    3. ドメイン固有言語
    4. 決定表
    5. ruleflow
  4. ドメイン固有言語エディター
    1. ユーザーの言語からルール言語へのマッピングを作成して管理します。
  5. ルールのバリデーション
    1. ルールが入力されると、ルールは背景に構築され可能な場合は問題ビューでエラーが報告されます。

20.3. JBoss Rules ランタイム

JBoss Rules ランタイムは、JBoss Rules プロジェクト jars の固有リリース 1 つを表現する jar ファイルの集まりです。ランタイムを作成するには、希望のリリースに IDE をポイントする必要があります。また、プラグイン自体に含まれる最新の JBoss Rules プロジェクト jar に基づき新しいランタイムを作成することもできます。Eclipse ワークスペースに対してデフォルトの JBoss Rules ランタイムを指定する必要がありますが、個々のプロジェクトはデフォルトを上書きし、そのプロジェクトに対してのみ適切なランタイムを選択できます。

20.4. JBoss Rules ランタイムの定義

手順20.1 タスク

  1. JBoss Rules で新しいセッションを作成します。
  2. WindowPreferences を選択します。"Preferences" ダイアログが表示されます。
  3. このダイアログの左側の JBoss Rules カテゴリーで、"Installed JBoss Rules runtimes" を選択します。右側のパネルに、現在定義されている JBoss Rules ランタイムが表示されるはずです。
  4. add ボタンをクリックします。ダイアログが表示され、ランタイムの名前とランタイムがあるファイルシステムの場所を入力するように求められます。
  5. JBoss Rules Eclipse プラグインからデフォルトの jar ファイルを使用するには、Create a new Drools 5 runtime ... ボタンをクリックすると新しい JBoss Rules ランタイムが自動的に作成されます。ファイルブラウザーが表示され、このランタイムを作成するファイルシステムの場所の入力が求められます。その後、プラグインが自動的に必要とされる依存関係を指定のフォルダーにコピーします。
  6. JBoss Rules プロジェクトの固有のリリースを 1 つ使用するには、必要とされる JBoss Rules ライブラリと依存パッケージが含まれているファイルシステム上にフォルダーを作成します。ランタイムに名前を指定して、必要な jar がすべて含まれているこのフォルダーの場所を選択します。OK をクリックします。
  7. ランタイムは、インストールした JBoss Rules ランタイムのテーブルに表示されます。新しく作成されたランタイムのチェックボックスをクリックして、デフォルトの JBoss Rules ランタイムに設定します。デフォルトの JBoss Rules ランタイムは、プロジェクト固有のランタイムが選択されていない JBoss Rules プロジェクトすべてのランタイムとして使用されます。
  8. デフォルトのランタイムを変更し、デフォルトのランタイムを使用しているすべてのプロジェクトがクラスパスを適切に更新するには、Eclipse を再起動します。

20.5. JBoss Rules プロジェクトに対するランタイムの選択

手順20.2 タスク

  1. New JBoss Rules Project ウィザードを使用して、新しい JBoss Rules プロジェクトを作成します。
  2. または、Java オブジェクトを右クリックして Convert to JBoss Rules Project ダイアログをクリックすることで、アクションを使用して既存の Java プロジェクトを JBoss Rules プロジェクトに変換します。プラグインは、必要な jar すべてをプロジェクトのクラスパスに自動的に追加します。
  3. オプションで、New JBoss Rules Project ウィザードの最後のページで、プロジェクト固有のランタイムを選択することができます。Use default JBoss Rules runtime チェックボックスのチェックを外して、ドロップダウンボックスで適切なランタイムを選択します。
  4. preferences にアクセスして、ランタイムを追加するには、workspace preferences へ移動して、Configure workspace settings ... をクリックします。
  5. プロジェクトプロパティを開いて JBoss Rules カテゴリを選択することで、いつでも JBoss Rules プロジェクトのランタイムを変更することができます。Enable project specific settings チェックボックスにチェックをつけて、ドロップダウンボックスから適切なランタイムを選択します。
  6. Configure workspace settings ... リンクをクリックします。すると、現在インストールされているランタイムが表示されている workspace preferences が表示されます。メニューを使用してこのスペースで新しいランタイムを追加します。Enable project specific settings チェックボックスの選択を解除すると、global preferences で定義されているデフォルトのランタイムを使用します。

20.6. Rule ファイルの例

新しく作成されたプロジェクトには、src/rules ディレクトリの中にあるサンプルのルールファイル (Sample.drl)、JBoss Rules エンジンでルールを実行するために使用できるサンプルの Java ファイル (DroolsTest.java) が含まれています。これは、com.sample パッケージの src/java フォルダーに入っています。実行時に必要なその他の jar は、JBoss Rules Library と呼ばれる、カスタムのクラスパスコンテナーのクラスパスに追加されます。

20.7. JBoss Rules Builder

JBoss Rules プラグインは JBoss Rules Builder 機能を Eclipse インスタンスに追加します。つまり、リソースが変更されるとルールを構築してバリデーションを行うプロジェクトでビルダーを有効にすることができます。これは、Rule Project ウィザードで自動的に有効になりますが、プロジェクト上で手動で有効にすることも可能です。ルールを完全にバリデーションするには、当然ながら単体テストでルールを実行する必要があります。

注記

ファイルに 500 以上のルールが含まれるルールファイルの場合、パフォーマンスが低下する可能性があります。対処するには、ビルダーをオフにするか、ルールエディターをまだ使用できる .rule ファイルに大きいルールを設定してください。ただし、バックグラウンドでルールの構築は行われません。

20.8. 新規ルールの作成

手順20.3 タスク

  1. 空の .drl テキストファイルを作成します。
  2. このファイルにルールをコピーして貼り付けます。
  3. 保存して終了します。
  4. または、Rules ウィザードを使用してルールを作成します。この際、Ctrl+N を渡すか、ツールバーから選択します。
  5. このウィザードは、ルールリソースを生成するための基本オプションに関する入力を求めます。ルールファイルを保存するには、ディレクトリ src/rules を通常作成して、適切な名前のサブディレクトリを作成します。パッケージ名は必須で、Java のパッケージ名とよく似ています (つまり、関連のルールをグループ化するための名前空間を設定します)。
  6. 適切なオプションを選択して、Finish をクリックします。
結果

展開可能なルールスケルトンが完成しました。

20.9. ルールエディター

ルールエディターは、ルールマネージャーおよび開発者が変更を加えるところです。ルールエディターは、Eclipse の通常のテキストエディターのパターンに従います。このルールエディターは、ポップアップコンテンツアシストがあり、Ctrl+Space を押すとポップアップコンテンツアシストが起動します。
ルールエディターは、.drl (または .rule) の拡張子を持つファイル上で動作します。通常、これらには関連のルールが含まれていますが、同じパッケージの名前空間に入れることでグループ化されている個別ファイルにルールを入れることも可能です。これらの DRL ファイルはプレーンテキストファイルです。

20.10. JBoss ルールビュー

ルールの変更時は、以下のビューを切り替えることができます。
ワーキングメモリのビュー
JBoss Rules ワーキングメモリの全要素を表示します。
アジェンダのビュー
アジェンダにあるすべての要素を表示します。アジェンダにあるルールごとに、ルール名およびバインド変数が表示されます。
グローバルデータのビュー
JBoss Rules ワーキングメモリで現在定義されているすべてのグローバルデータを表示します。
監査ビュー
ルールエンジンの実行時にロギングされたイベントを含む監査ログをツリー形式で表示する際に利用できます。
Rete ビュー
DRL ファイルの現在の Rete Network を表示します。DRL エディターウィンドウの下にある "Rete Tree" タブをクリックすると表示されます。Rete Network が表示されると、個別のノードでドラッグアンドドロップを使用して、最適なネットワーク全体図をアレンジすることができます。また、長方形をドラッグして複数のノードを選択することもでき、全グループを移動することが可能です。

注記

Rete ビューは、JBoss Rules Builder がプロジェクトのプロパティに設定されているプロジェクトでのみ機能します。他のプロジェクトは、回避策をお使いください。現在のプロジェクトの横に JBoss Rules プロジェクトを設定し、Rete ビューで確認するライブラリと DRL を変換します。DRL エディターの右下のタブをクリックしてから、"Generate Rete View" をクリックします。

20.11. JBoss Rules ビューの使用

手順20.4 タスク

  1. JBoss Rules ビューを使用するには、ワーキングメモリを呼び出してコードのブレークポイントを作成します。例えば、workingMemory.fireAllRules() を呼び出す行は、ブレークを設定するには理想的な場所です。
  2. デバッガーがジョイント部分で停止した場合、デバッグ変数のビューでワーキングメモリの変数を選択します。次に、利用可能なビューを使用して、選択したワーキングメモリの詳細を表示します。

20.12. Show Logical Structure

Show Logical Structure は、JBoss Rules ビューで使用します。ワーキングメモリやアジェンダアイテムにある要素の論理構造の表示を切り替えることができます。例えば、論理構造により、要素のセットがより明確にわかるように表示できます。

20.13. 監査ログの作成

手順20.5 タスク

  1. 監査ログを作成するには、ルールエンジンを実行します。新しい監査ログを作成するかの選択を求められます。
  2. 以下のコードを入力します。
    StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
    // Create a new Knowledge Runtime Logger, that logs to file.
    // An event.log file is created in the subdirectory log dir (which must exist) of the working directory
    KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger( ksession, "log/event");
    
    ksession.insert(...);
    ksession.fireAllRules();
    
    // stop logging
    logger.close();
  3. 監査ビューの最初のアイコン Open Log アクションをクリックしてログを開き、ファイルを選択します。監査ビューには、ルール実行時にロギングされたイベントすべてが表示されます。

20.14. 監査ビューのイベントアイコン

表20.1 監査ビューのイベントアイコン

アイコン 説明
緑色の四角 オブジェクトが挿入されました。
黄色の四角 オブジェクトが更新されました。
赤色の四角 オブジェクトが削除されました。
右向きの矢印 アクティベーションが作成されました。
左向きの矢印 アクティベーションがキャンセルされました。
青色の菱形 アクティベーションが実行されました。
プロセスのアイコン ルールフローが開始または終了しました。
アクティビティアイコン Ruleflow-group のアクティベーションおよびアクティベーション解除
JBoss Rules アイコン ルールまたはルールパッケージが追加または削除されました。

20.15. 原因取得のメソッド

アクティベーションの実行時にイベントが発生した場合、そのアクティベーションの実行イベントの子として表示されます。以下のイベントが発生すると、原因を取得することができます。
  1. イベントを変更または削除するオブジェクトの原因がそのオブジェクトの最後のオブジェクトイベントの場合。オブジェクトがアサートしたイベントまたはそのオブジェクトで最後に変更されたオブジェクトのイベントのいずれかです。
  2. アクティベーションがキャンセルまたは実行されたイベントの原因が該当のアクティベーションの作成イベントの場合。

注記

イベントを選択する場合、原因が監査ビューに緑色で表示されます。アクションを右クリックして、"Show Cause" のメニューアイテムを選択します。すると、選択したイベントの原因までスクロールされます。

20.16. DSL エディター

DSL エディターは、Language (言語) の Rule Expression (ルール表現) へのマッピングをテーブル形式で確認できます。Language Expression (言語表現) は、DSL 設定からの Language Expression を提案できるように、ルールエディターのコンテンツアシストをフィードします (ルールリソースを編集するために読み込むと、ルールエディターは DSL 設定を読み込みます)。

20.17. ルール言語マッピング

ルール言語のマッピングは、言語表現がルールエンジンコンパイラーによってどのようにコンパイルされるかを定義します。このルールの言語表現の形式は、ルールの条件部分またはアクション部分になるかにより左右されます (例えば、RHS では Java のスニペットの可能性があります)。scope アイテムは、表現が所属するところを指定して、when アイテムは LHS その後に RHS を指定します。さらに * アイテムはどこでも対応できます。また、キーワードのエイリアスを作成することも可能です。

20.18. ルール言語マッピングでの作業

手順20.6 タスク

  1. DSL エディターを開きマッピングタブを選択します。
  2. マッピングアイテム (表の中の 1 行) を選択して、表の下のテキストフィールドの表現とマッピングを確認します。
  3. 編集ボタンをクリックまたは押して編集ダイアログを開きます。
  4. その他のボタンでマッピングの削除や追加が可能です。使用中にマッピングを削除しないようにしてください。

20.19. DSL 変換コンポーネント

表20.2 DSL 変換コンポーネント

名前 説明
パーサー パーサーは、DSL のルールテキストを行毎に読み込み、言語表現の一部と照合します。照合の後、波括弧 (例: {age}) に囲まれるプレースホルダーに該当する値がルールソースから抽出されます。
プレースホルダー 該当のルール表現のプレースホルダーは、該当する値で置き換えられます。例えば、自然言語表現は、フィールド age および location を元に型 person のファクトに対して 2 つの制約をマッピングします。そして、{age} と {location} の値は、元のルールテキストから抽出されます。

注記

drl で特定のルールで言語マッピングを使用しない場合、表現の前に > を付けると、コンパイラーは言語定義にしたがった変換は実施しません。また、ドメイン固有言語はオプションである点にも注意してください。ルールがコンパイルされると、.dsl ファイルも使用できるようにする必要があります。

20.20. 大きな DRL ファイルで作業する際のヒント

  1. 使用する JDK により、Permanent Generation (Perm Gen) の最大サイズを増やすことができます。この方法は、-XX:MaxPermSize=###m で Eclipse を起動します。
  2. 4000 以上のルールセットは、Permanent Generation を 128Mb 以上に設定する必要があります。
  3. .rule 拡張子を持つファイルにルールを設定します。バックグラウンドのビルダーは、IDE がより早く実行できるように変更が加えられても毎回コンパイルを試行するわけではありません。

20.21. ブレークポイントの作成

手順20.7 タスク

  1. ブレークポイントを作成してルールのデバッグをより簡単に行うには、DRL エディターを開き、使用する DRL ファイルを読み込みます。
  2. ブレークポイントを追加する行で DRL エディターのルーラーをダブルクリックします。ルールのブレークポイントは、ルールの結果でのみ作成される点に注目してください。ブレークポイントが作成できない行でダブルクリックしても何もおこりません。ブレークポイントは、ルーラーをもう一度ダブルクリックして削除することができます。
  3. ルーラーを右クリックします。Toggle breakpoint アクションを含むポップアップメニューが表示されます。ルールのブレークポイントは、ルールの結果でのみ作成することができる点に注意してください。ルールのブレークポイントがその行で作成できない場合は、このアクションは自動的に無効になります。
  4. アクションをクリックして、選択した行にブレークポイントを追加します (ブレークポイントがすでにある場合は削除されます)。

    注記

    Debug Perspective にはブレークポイントのビューが含まれており、定義されたすべてのブレークポイントの参照、そのプロパティの取得、ブレークポイントの有効化/無効化または削除などを行うために、このビューを使用します。

20.22. JBoss Rules アプリケーションとしてのデバッグ

手順20.8 タスク

  1. DRL エディターを開きます。
  2. アプリケーションの主要クラスを選択します。
  3. 主要クラスの右クリックして、Debug As > サブメニューを選択後、JBoss Rules Application を選びます。
    または、Debug ... メニューアイテムを選択して、デバッグ設定の作成、管理、実行を行う新しいダイアログを開きます。
  4. 左側のツリーの Drools Application アイテムを選択して、New launch configuration ボタン (ツリーの上にあるツールバーの一番左端のアイコン) をクリックします。これにより新しい設定が作成されますが、最初に選択した主要クラスをもとにすでに入力されているプロパティもあります。
  5. デバッグ設定の名前を意味があるものに変更します。他のプロパティについてはすべてデフォルトで確定します。
  6. 下側の Debug ボタンをクリックしてアプリケーションのデバッグを開始します。デバッグ設定の定義は一度だけで結構です。JBoss Rules アプリケーションを次に実行する際は、JBoss Rules ツリーノードのサブ要素としてツリーの内にある以前に定義された設定を選択して JBoss Rules ボタンをクリックします。Eclipse ツールバーにはショートカットボタンが含まれており、以前の設定の 1 つをすばやく再実行できるようになっています。
結果

Debug ボタンをクリックすると、アプリケーションは実行を開始して、ブレークポイントに到達すると停止します。JBoss Rules のブレークポイントに到達すると、毎回該当の DRL ファイルが開きアクティブな行がハイライトされます。また、Variables ビューには、全ルールのパラメーターとその値が含まれています。デフォルトの Java デバッグアクションを使用して、次に何をするか (再開、終了、中断など) 決定することができます。デバッグビューを使用して、その時点のワーキングメモリやアジェンダのコンテンツも確認することができます。現在実行中のワーキングメモリが表示されるため、ワーキングメモリを選択する必要はありません。

20.23. Rules IDE の設定

Rules IDE には、カスタマイズ可能な設定が含まれており、以下のオプションを設定することができます。
Java リソースが変更されると、すべてのルールが再度、自動的に解析される
Java クラスが変更されると、すべてのルールが再度構築されるようにトリガーされます。
DRL ファイルの相互参照を可能にする
DRL ファイルのリソースが別のファイルで定義した別のリソースを参照できるようにします。例えば、別のファイルで宣言した型を使用して、ファイルにルールを設定することができます。このオプションを有効にすると、2 つの違った DRL ファイルで同じリソース (同じパッケージ内で同じ名前を持つ 2 つのルール) を宣言することができなくなります。
内部の Drools クラスの使用
パブリック API に公開されていない JBoss Rules クラスの使用を許可、却下、警告 (警告の生成) します。

第21章 Hello World の例

21.1. HelloWorld の例: KnowledgeBase とセッションの作成

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();
  • KnowledgeBuilder は DRL ソースファイルを、ナレッジベースが消費可能な Package オブジェクトに変換するために使用します。
  • add メソッドは Resource インターフェースとリソース型をパラメーターとして取得します。Resource は、様々な場所から DRL ソースファイルを取得するために使用できます。この場合、ResourceFactory を使用してクラスパスから DRL ファイルを取得しましたが、ディスクファイルや URL から取得数rことも可能です。
  • 様々な名前空間の複数パッケージは、同じナレッジベースに追加することができます。
  • ナレッジベースはパッケージのバリデーションを行いますが、エラー情報へのアクセスは文字列としてでしか行えません。そのため、エラー情報のデバッグをするには、KnowledgeBuilder インスタンスで実行する必要があります。
  • ビルダーにエラーがなくなると、Package コレクションを取得して、KnowledgeBaseFactory からの KnowledgeBase をインスタンス化してパッケージコレクションに追加します。

21.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");
  • デフォルトのデバッグリスナーは、DebugAgendaEventListenerDebugWorkingMemoryEventListener の 2 つが提供されています。これらのリスナーは、コンソールウィンドウに表示される System.err ストリームにデバッグイベント情報を出力します。
  • KnowledgeRuntimeLogger は、グラフィカルビューワーで表示可能な実行監査を提供します。このロガーは、アジェンダとワーキングメモリのリスナー上に構築された特別な実装です。
  • エンジンの実行が完了すると logger.close() を呼び出す必要があります。

21.3. HelloWorld の例: Message クラス

public static class Message {
    public static final int HELLO   = 0;
    public static final int GOODBYE = 1;

    private String          message;
    private int             status; 
    ...
}
  • この例で使用されている単一のクラスには、メッセージ (文字列) とステータス (HELLO または GOODBYE の integer の 1 つ) のフィールド 2 つがあります。

21.4. HelloWorld の例: 実行

final Message message = new Message();
message.setMessage("Hello World");
message.setStatus(Message.HELLO);
ksession.insert(message);

ksession.fireAllRules();

logger.close();

ksession.dispose();
  • 単一の Message オブジェクトは、メッセージテキスト "Hello World" とステータス HELLO で作成され、エンジンに挿入されます。この時点で fireAllRules() が実行されます。
  • ネットワークの評価は、挿入時に行われます。プログラム実行が fireAllRules() メソッド呼び出しに到達するまでには、エンジンはどのルールが完全に一致して発火可能かすでに把握しています。

注記

Java アプリケーションとしてこの例を実行する方法:
  1. Eclipse IDE で org.drools.examples.helloworld.HelloWorldExample クラスを開きます。
  2. クラスを右クリックして Run as... を選び Java application を選択します。

21.5. HelloWorld の例: コンソールウィンドウでの System.out

Hello 
Goodbye
==>[ActivationCreated(0): rule=Hello World; 
                   tuple=[fid:1:1:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]]
[ObjectInserted: handle=[fid:1:1:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96];
                 object=org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]
[BeforeActivationFired: rule=Hello World; 
                   tuple=[fid:1:1:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]]
==>[ActivationCreated(4): rule=Good Bye; 
                   tuple=[fid:1:2:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]]
[ObjectUpdated: handle=[fid:1:2:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96];
                old_object=org.drools.examples.helloworld.HelloWorldExample$Message@17cec96;
                new_object=org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]
[AfterActivationFired(0): rule=Hello World]
[BeforeActivationFired: rule=Good Bye; 
                   tuple=[fid:1:2:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]]
[AfterActivationFired(4): rule=Good Bye]
  • fireAllRules() メソッドでブレークポイントを設置して ksession 変数を選択することで、"Hello World" ルールははすでにアクティブ化され、アジェンダでパターンに一致する作業は挿入時に実行されることが分かります。
  • アプリケーションは、System.out へ移動するよう出力し、デバッグリスナーは System.err へ移動するよう出力します。

21.6. HelloWorld の例: ルール "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
  • ルールの LHS (when の後ろ) 部では、ステータスが Message.HELLO のワーキングメモリに挿入された Message オブジェクトごとにルールがアクティベートされると記述されています。
  • また、変数バインドが 2 つ作成されています。message 変数は message 属性に、m 変数は合致した Message オブジェクト自体にバインドされています。
  • ルールの RHS (then の後ろ) または結果部では、ルールの属性 dialect が宣言しているように、MVEL 式言語を使用して記述されています。
  • System.outmessage バインド変数の内容を出力した後、ルールは message の値と m にバインドされている Message オブジェクトの status 属性を変更します。
  • MVEL の modify ステートメントは、ブロックの最後で自動的に変更を通知するエンジンを使用して、1 つのステートメントに代入ブロックを適用することができます。

21.7. HelloWorld の例: "Debug as..." オプションの使用

手順21.1 タスク

  1. このデバッグオプションを使用するにはお使いの Eclipse IDE の org.drools.examples.HelloWorld クラスを開きます。
  2. このクラスを右クリックして「Debug as...」を選択し、「Drools application」を選びます。そのルールとルールの場所の情報が表示されます。

21.8. HelloWorld の例: ルール "Good Bye"

rule "Good Bye"
      dialect "java"
  when
      Message( status == Message.GOODBYE, message : message )
  then
      System.out.println( message ); 
end
  • "java" 方言を指定する "Good Bye" ルールは、ステータスが Message.GOODBYEMessage オブジェクトと照合すること以外は、"Hello World" ルールとよく似ています。

第22章 Salience ステータスの例

22.1. Salience ステータスの例: State クラスの例

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...
}
  • State クラスには、名前と現在のステータスのフィールドがあります (org.drools.examples.state.State クラス参照)。各オブジェクトで考えられる 2 つのステータスは NOTRUNFINISHED です。

22.2. 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, Drools will use JavaBean
// PropertyChangeListeners so you don't 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
  • 各インスタンスは順番にセッションにアサートされ、その後に fireAllRules() が呼び出されます。

22.3. Salience ステータスの例: アプリケーションの実行

手順22.1 タスク

  1. Eclipse IDE の org.drools.examples.state.StateExampleUsingSalience クラスを開きます。
  2. このクラスの上で右クリックして Run as... を選び Java application を選択します。以下の出力が表示されます。
    A finished
    B finished
    C finished
    D finished
    

22.4. Salience ステータスの例: オペレーションのある監査ロギングを使用

手順22.2 タスク

  1. オペレーションが生成した監査ログを表示するには、IDE を開き Window をクリックしてから Show View を選び、その後、Other...DroolsAudit View の順に選択します。
  2. "Audit View" で Open Log ボタンをクリックして <drools-examples-dir>/log/state.log ファイルを選択します。

22.5. Salience ステータスの例: ルール "Bootstrap"

rule Bootstrap
    when
        a : State(name == "A", state == State.NOTRUN )
    then
        System.out.println(a.getName() + " finished" );
        a.setState( State.FINISHED );
end
結果
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
  • 全アクションと該当の変更がワーキングメモリに表示されます。
  • ステータス NOTRUN のステータスオブジェクト A のアサーションにより、 Bootstrap ルールがアクティベートされますが、その他の State オブジェクトのアサーションからの影響はすぐにはありません。
  • ルール Bootstrap を実行すると、ステータスが A から FINISHED に変更され、ルール "A to B" をアクティベートします。

22.6. Salience ステータスの例: ルール "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 C" の salience 値が高いため (10 とデフォルトの salience 値 0)、先に発火されオブジェクト C をステータス FINISHED に変更します。
  • アジェンダビューは、ルール内にデバッグポイントを設置しアジェンダビューを開いた状態で、ジェンダーのステータスを調査するために使用することもできます。

22.7. Salience ステータスの例: ルール "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
  • ルール "B to D" は最後に発火され、オブジェクト D をステータス FINISHED に変更します。
  • 実行するルールがないためエンジンが停止します。

22.8. Salience ステータスの例: 動的ファクトの挿入

// By setting dynamic to TRUE, JBoss Rules will use JavaBean
// PropertyChangeListeners so you don't have to call modify or update().
final boolean dynamic = true;

session.insert( fact, dynamic );
  • エンジンがファクトのプロパティを参照して反応するには、アプリケーションがエンジンに対して変更があったことを通知する必要があります。これは、modify ステートメントを使用してルールで明示するか、エンジンにファクトが JavaBeans specification で定義されているように PropertyChangeSupport を実装していると暗黙的に通知することで実行できます。
  • 上記の例では、ルールに modify ステートメントを明示しないでもいいように、PropertyChangeSupport の使用方法を紹介しています。
  • org.drools.example.State クラスと同じように、ファクトが PropertyChangeSupport を実装するようにします。

22.9. Salience ステータスの例: PropertyChangeSupport を持つセッター

public void setState(final int newState) {
    int oldState = this.state;
    this.state = newState;
    this.changes.firePropertyChange( "state",
                                     oldState,
                                     newState );
}
  • org.drools.examples クラス内の state のセッター
  • PropertyChangeListener オブジェクトを使用する場合、各セッターは通知用に追加でコードを実装する必要があります。

22.10. Salience ステータスの例: グループルール "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
結果
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
  • デフォルトでは、アジェンダグループ "MAIN" にすべてのルールが入っています。
  • "agenda-group" 属性により、ルールに対して別のアジェンダグループを指定することができます。最初は、ワーキングメモリのフォーカスは "MAIN" に置かれています。
  • グループのルールは、グループにフォーカスが当たっているときのみ発火されます。setFocus() メソッドか、ルール属性 auto-focus を使用することで可能になります。
  • auto-focus は、ルールが合致してアクティベートされた場合に、ルールが自動的にアジェンダグループにフォーカスを設定するという意味です。"B to D" の前に "B to C" を発火できるようにするのは、この "auto-focus" なのです。
  • ルール "B to C" がアジェンダグループ "B to D" で setFocus() を呼び出して、アクティブなルールを発火するため、ルール "B to D" を発火させることができます。

22.11. Salience ステータスの例: グループルール "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

22.12. Salience ステータスの例: グループルール "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
この例では、以下のような出力が表示されるはずです。
A finished
B finished
C finished
D finished
E finished
  • StateExampleWithDynamicRules は、別のルールをルールベース (fireAllRules() の後ろ) に追加します。

第23章 フィボナッチの例

23.1. フィボナッチの例: クラス

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...
}
  • Sequence フィールドは、フィボナッチ数の順番におけるオブジェクトの位置を指定するために使用します。
  • Value フィールドは、その順位のフィボナッチオブジェクトの値を表示します。算出の必要な値を指定するために -1 を使用しています。

23.2. フィボナッチの例: 実行

手順23.1 タスク

  1. Eclipse IED を起動します。
  2. org.drools.examples.fibonacci.FibonacciExample クラスを開きます。
  3. クラスを右クリックして Run as... を選び Java application を選択します。
結果

Eclipse は、コンソールウィンドウに以下の出力を表示します ("...snip..." はスペースを節約するために省略された行のことです)。

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

23.3. フィボナッチの例: 実行の詳細

ksession.insert( new Fibonacci( 50 ) );
ksession.fireAllRules();
  • Java でこれを使用するには、Sequnce フィールドが 50 個ある Fibonacci オブジェクトを 1 つ挿入します。
  • 再帰的なルールは、他の 49 Fibonacci オブジェクトを挿入するために使用します。
  • 上記の例は、MVEL 方言を使用しています。つまり、modify キーワードを使用でき、エンジンに変更を通知するブロックセッターのアクションが可能となります。