Red Hat Training

A Red Hat training course is available for JBoss Enterprise SOA Platform

第4章 ルール言語

4.1. 概要

JBoss Rules は「ネイティブ」ルール言語を持ちます。この形式は句読点の扱いが非常に簡単で、問題ドメインへ言語を適合できるようにする「エクスパンダー」を用いて自然言語およびドメイン固有の言語をサポートします。本章ではこのネイティブルール形式について説明します。
構文を示すために使用される図は railroad 図と呼ばれ、言語的な用語のフローチャートに似ています。興味がある方は DRL.g 内のルール言語に対する Antlr3 グラマーを参照することもできますが、必須ではありません。Rule Workbench を使用する場合、コンテンツアシスタントはルール構造の大部分に対応します。例えば、「ru」を入力し、ctrl キーとスペースキーを同時に押すと、ルール構造が構築されます。

4.1.1. ルールファイル

ルールファイルとは通常、.drl 拡張子を持つファイルです。DRL ファイルには複数のルール、クエリ、および関数を含めることができ、またルールやクエリによって割り当てられ使用されるインポート、グローバルおよび属性などの一部のリソース宣言も含めることができます。 複数のルールファイルにまたがってルールを分散することもできます。この場合、拡張子 .rule が推奨されますが必須ではありません。ファイルにまたがってルールを分散すると、大量のルールを管理する場合に役立ちます。DRL ファイルは簡単なテキストファイルです。
ルールファイルの全体的な構造は以下のようになります。
package package-name

imports

globals

functions

queries

rules
要素が宣言される順番は重要ではありません。ただし、パッケージ名は例外で、宣言される場合はルールファイルの最初の要素でなければなりません。すべての要素は任意であるため、必要な要素のみを使用できます。以降の項でこれらの要素について 1 つずつ説明します。

4.1.2. ルールの構造

ルールの大まかな構造は次のようになります。
rule "name"
    attributes
when
    LHS
then
    RHS
end
ほとんどの部分で句読点は必要ありません。"name" の二重引用符や改行も任意です。属性は簡単で (常に任意です)、ルールが取るべき挙動について示します。LHS はルールの条件的な部分で、特定の構文に従います (説明は以下を参照)。RHS は基本的にダイアレクト固有のセマンティックコードを実行できるようにするブロックです。
空白文字は重要ではありませんが、ドメイン固有の言語の場合のみ重要となることに注意してください。ドメイン固有の言語を使用する場合、各行は後続の行の前に処理され、空白文字はドメイン言語では重要であることがあります。

4.2. キーワード

本項では ハードキーワードソフトキーワード の概念について説明します。
ハードキーワードは予約されたキーワードであるため、ルールテキストで使用されるドメインオブジェクト、プロパティー、メソッド、関数またはその他の要素を命名する時に使用できません。
ハードキーワードの一覧は次の通りです。ルールを書くときにこれらのキーワードを識別子として使用しないでください。
truefalseaccumulate
collectfromnull
overthenwhen
ソフトキーワードは直接のコンテキストでのみ認識されるため、これらの言葉は他の場所でも使用できます。
ソフトキーワードの一覧は次の通りです。
lock-on-activedate-effectivedate-expires
no-loopauto-focusactivation-group
agenda-groupruleflow-groupentry-point
durationpackageimport
dialectsalienceenabled
attributesruleextend
templatequerydeclare
functionglobaleval
notinor
andexistsforall
actionreverseresult
endinit 
ハードキーワードおよびソフトキーワードは、notSomething()accumulateSomething() のようにメソッド名に使用することが可能です。
DRL 言語では、ルールテキスト上でハードキーワードをエスケープすることも可能です。この機能により、キーワードの「衝突」を懸念せずに既存のドメインオブジェクトを使用することができます。言葉をエスケープするには、次のようにアクサングラーブで文字を囲みます。
Holiday( `when` == "july" )
エスケープは、左側と右側のコードブロックにあるコード式内以外の場所であればどこでも使用できます。適切な使用方法の例は次の通りです。
rule "validate holiday by eval" 
dialect "mvel"
when
    h1 : Holiday( )
    eval( h1.when == "july" )
then
    System.out.println(h1.name + ":" + h1.when);
end
rule "validate holiday" 
dialect "mvel"
when
    h1 : Holiday( `when` == "july" )
then
    System.out.println(h1.name + ":" + h1.when);
end

4.3. コメント

コメントは rule engine によって無視されるテキストの部分のことです。セマンティックコードブロック 内にコメントがある場合以外は、コメントが検出されると、ルールの右側のように無視されます。コメントには 単一行コメント複数行コメント の 2 種類があります。
単一行コメント

図4.1 単一行コメント

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

図4.2 複数行コメント

複数行コメントは、セマンティックコードブロックの内外部両方のテキストブロックを示すために使用されます。
例は次の通りです。
rule "Test Multi-line Comments"
when
    /* this is a multi-line comment
       in the left hand side of a rule */
    eval( true )
then
    /* and this is a multi-line comment
       in the right hand side of a rule */
end 

4.4. エラーメッセージ

JBoss Rules には標準化されたエラーメッセージの機能があります。この機能は、問題の検索や解決を迅速および容易にします。本項を読んで、エラーメッセージの識別および解釈方法を学び、報告される問題の一部を解決する方法について確認しましょう。
エラーメッセージの形式は次のようになります。
エラーメッセージの形式

図4.3 エラーメッセージの形式

最初のブロック
この領域はエラーコードを特定します。
2 番目のブロック
この領域には行および列の情報が表示されます。
3 番目のブロック
この行行には問題を説明するテキストが表示されます。
4 番目のブロック
最初のコンテキストです。通常、エラーが発生したルール、関数、テンプレート、またはクエリを示します (このブロックは必須ではありません)。
5 番目のブロック
エラーが発生したパターンを特定します (このブロックは必須ではありません)。
以下にすべてのエラーメッセージについて説明します。

4.4.1. 101: No viable alternative

最も一般的なエラーに対して表示されるメッセージです。parser が決定ポイントに到達し、代替を特定できない場合に表示されます。例は次の通りです。
rule one
when
    exists Foo()
    exits Bar()
then
end
この例は次のメッセージを生成します。
[ERR 101] Line 4:4 no viable alternative at input 'exits' in rule one
一見、有効な構文に見えるかもしれませんが、exits != exists であるため、実際は有効ではありません。
別の例を見てみましょう。
package org.drools;
rule
when
    Object()
then
    System.out.println("A RHS");
end
上記のコードは次のエラーメッセージを生成します。
[ERR 101] Line 3:2 no viable alternative at input 'WHEN'
このメッセージは、 parserWHEN トークンを検出し(実際、これはハードキーワードです)、ルール名がないためこれが誤った場所にあることを意味します。
単純に語彙を間違えた時も同じエラーメッセージが表示されます。この問題の例は次の通りです。
rule simple_rule
when
    Student( name == "Andy )
then
end
閉じ引用符がないため、parser が次のようなエラーメッセージを生成します。
[ERR 101] Line 0:-1 no viable alternative at input
'<eof>' in rule simple_rule in pattern Student

注記

通常、行と列の情報は正確ですが、parser0:-1 の位置を生成する場合があります。この場合、引用符、アポストロフィ、および括弧がすべて閉じられているか確認してください。

4.4.2. 102: Mismatched input

このエラーは、parser が特定の記号を探していたものの、現在の入力場所になかったことを示しています。例は次の通りです。
rule simple_rule
when
    foo3 : Bar(
このコードは次のメッセージを生成します。
[ERR 102] Line 0:-1 mismatched input '<eof>' expecting
')' in rule simple_rule in pattern Bar
この問題を修正するには、ルールステートメントを完成させます。

注記

0:-1 位置は parser がソースの最後に到達したことを意味します。
次のコードは複数のエラーメッセージを生成します。
package org.drools;

rule "Avoid NPE on wrong syntax"
when
    not(Cheese((type=="stilton",price==10)||(type=="brie",price==15))
        from $cheeseList)
then
    System.out.println("OK");
end
以下はこのソースに関連するエラーです。
[ERR 102] Line 5:36 mismatched input ',' expecting ')' in rule
"Avoid NPE on wrong syntax" in pattern Cheese

[ERR 101] Line 5:57 no viable alternative at input 'type' in
rule "Avoid NPE on wrong syntax"

[ERR 102] Line 5:106 mismatched input ')' expecting 'then' in
          rule "Avoid NPE on wrong syntax"
2 つ目の問題が最初の問題に関連していることに注意してください。この問題を修正するには、コンマ (,) を && 演算子に置き換えます。

注記

エラーメッセージが複数ある場合、1 つずつ修正するのがよいでしょう。一部のメッセージは他のメッセージの結果としてのみ生成されます。

4.4.3. 103: Failed predicate

これは、検証するセマンティック述語false として評価されたことを意味します。通常、これらのセマンティック述語は「ソフト」キーワードを識別するために使用されます。次の例でも同様です。
package nesting;
dialect "mvel"

import org.drools.Person
import org.drools.Address

fdsfdsfds

rule "test something"
when
    p: Person( name=="Michael" )
then
    p.name = "other";
    System.out.println(p.name);
end
この例によって生成されるメッセージは次の通りです。
[ERR 103] Line 7:0 rule 'rule_key' failed predicate:
{(validateIdentifierKey(DroolsSoftKeywords.RULE))}? in rule
fdsfdsfds は無効なテキストで、parser はそれをソフトキーワードの rule として識別できません。

注記

このエラーは 102: Mismatched input と大変よく似ていますが、通常はソフトキーワードが関係します。

4.4.4. 104: Trailing semi-colon not allowed

このエラーは eval 節と関係しています。式が誤ってセミコロンで終了すると発生します。例は次の通りです。
rule simple_rule
when
    eval(abc();)
then
end
このエラーは eval 節内の末尾にあるセミコロンによって発生します。
[ERR 104] Line 3:4 trailing semi-colon not allowed in rule
simple_rule
この問題を修正するのは簡単です。セミコロンを削除します。

4.4.5. 105: Early Exit

これは、recognizer がどの代替とも一致しない文法 (grammar) のサブルールを検出すると発生します。これは、parser が出口のないブランチに入ったことを意味します。この状態を表す例は次のようになります。
template test_error
    aa s  11;
end
結果となるエラーメッセージ全体は次の通りです。
[ERR 105] Line 2:2 required (...)+ loop did not match anything
at input 'aa' in template test_error
この問題を修正するには、数値を削除します (新しいテンプレートスロットを開始する可能性がある有効なデータタイプではなく、他のルールファイルコンストラクトの可能な開始でもないため)。
本項の内容を読んだ後、エラーメッセージの意味とエラーメッセージが示す問題の修正方法について理解していただけたと思います。

4.5. パッケージ

パッケージはインポートやグローバルなど、ルールやその他関連するコンストラクトのコレクションです。通常、パッケージメンバーは相互に関連しています。その例が HR ルールなどになります。パッケージは名前空間を表し、名前空間はルールのグループ化に対して一意であることが理想的です。パッケージ名自体が名前空間ですが、ファイルやフォルダーには関連しません。
複数のルールソースからルールをアセンブルすることが可能で、すべてのルールが対象となる (ルールがアセンブルされた時)、トップレベルのパッケージ設定を持つことが可能です。しかし、異なる名前で宣言された同じパッケージリソースへマージすることは不可能です。単一の Rulebase には含まれる複数のナレッジパッケージを構築することが可能です。パッケージのルールをすべてパッケージ宣言と同じファイルに格納し、自己充足にすることが一般的です。
次の syntax diagram は、パッケージを構成するすべてのコンポーネントを表しています。パッケージには名前空間がある必要があり、パッケージ名の標準的な Java の慣例を使用して宣言する必要があることに注意してください (空白文字を使用できるルール名とは異なり、名前空間には空白文字を使用できません)。ルールの順序に関しては、ファイルの上にある必要がある package ステートメント以外は、任意の順序でルールファイルに記載されます。すべての場合で、セミコロンの使用は任意となります。
package

図4.4 package

注記

ルール属性もすべてパッケージレベルで書き込みされ、属性のデフォルト値が置き換えられることに注意してください (ルール属性の項を参照)。変更されたデフォルトもルール内の属性設定によって置き換えられることがあります。

4.5.1. import

import

図4.5 import

import ステートメントは Java の import ステートメントと同様に挙動します。ルールで使用したいオブジェクトの完全修飾パスとタイプ名を指定する必要があります。JBoss Rules は、同名の Java パッケージおよび java.lang パッケージより自動的にクラスをインポートします。

4.5.2. global

global

図4.6 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 );
    
これらは、アプリケーションからワーキングメモリーへ渡すオブジェクトの名前付きインスタンスにすぎないことに注意してください。そのため、サービスロケーターやサービス自体など、好きなオブジェクトを渡すことができます。from 要素が新たに導入されたため、Hibernate セッションをグローバルとして渡し、from によって名前付きの Hibernate クエリよりデータをプルすることが一般的になりました。
電子メールサービスのインスタンスがこの一例となります。ルールエンジンを呼び出す統合コードで、emailService オブジェクトを取得し、ワーキングメモリーに設定します。タイプが EmailService のグローバルがあることを DRLで宣言し、名前を「email」にします。その後、ルール結果にて email.sendSMS(number, message) などを使用できます。
グローバルはルール間でデータを共有するためのものではないため、このような目的で使用すべきではありません。ルールは常にワーキングメモリーの状態を理由付けし、反応するため、ルール間でデータを渡したい場合はデータをファクトとしてワーキングメモリーにアサートします。
ルール内よりグローバルの値を設定または変更することは推奨されません。常にワーキングメモリーインターフェースを使用してアプリケーションより値を設定することが推奨されます。

4.6. 関数

関数

図4.7 関数

関数 を使用して、セマンティックコードをルールソースファイル (普通の Java クラスではなく) に格納します。helper クラス以上のことは実行できませんが (実際にコンパイラーが「背後」で helper クラスを生成します)、ロジックをすべて 1 つの場所に集められることが主な利点となります。また、必要に応じて関数を変更することもできます (これには良い面と悪い面の両方があります)。
関数はルールの結果でアクションを呼び出す場合に最も便利です。特定のアクションが繰り返し使用される場合 (電子メールのメッセージの内容など、各ルールの異なるパラメーターのみ) 、特に有用です。
以下は標準的な関数の宣言になります。
function String hello(String name) {
    return "Hello "+name+"!";
}

注記

実際には Java の一部ではなくても、function キーワードが使用されます。関数にとってパラメーターは通常のメソッドと同様です (また、必要でない場合はパラメーターを使用する必要はありません)。戻り値 は通常のメソッドと同様です。
代わりに、Foo.hello() のように helper クラスで静的メソッドを使用することもできます。JBoss Rules では 関数インポート を使用できます。次のコード例は使用方法を表しています。
import function my.package.Foo.hello
上記両方の場合、関数を使用するには、結果またはセマンティックコードブロック内のいずれかで関数の名前を呼び出します。以下は最終的な例になります。
rule "using a static function"
when 
    eval( true )
then
    System.out.println( hello( "Bob" ) );
end

4.7. タイプ宣言

meta_data

図4.8 meta_data

type_declaration

図4.9 type_declaration

タイプ宣言のルールエンジンに関する主な 2 つの目的は、新しいタイプの宣言を許可することと、タイプのメタデータの宣言を許可することです。
  • 新しいタイプの宣言: JBoss Rules はそのままの状態でプレーン POJO をファクトとして動作します。Java などの下位言語でモデルを作成することを懸念せずに直接モデルをルールエンジンへ定義したい時があります。また、ドメインモデルが既に構築されている状態で、主に理由付け処理中に使用される追加エンティティーを用いてこのモデルを補完したい場合や補完する必要がある場合もあります。
  • メタデータの宣言: ファクトに、関連するメタ情報があることがあります。メタ情報の例には、ファクト属性によって表されず、そのファクトタイプのすべてのインスタンス間で一貫するあらゆる種類のデータが含まれます。メタ情報はエンジンによってランタイム時にクエリされ、理由付け処理で使用されることがあります。

4.7.1. 新しいタイプの宣言

新しいタイプを宣言するには、declare をキーワードとして使用し、その後にフィールドのリストとキーワード end を指定します。

例4.1 新しいファクトタイプの宣言: Address

declare Address
   number : int
   streetName : String
   city : String
end
上記の例は Address という新しいファクトタイプを宣言します。このファクトタイプには、 numberstreetName、および city の 3 つの属性があります。各属性は有効な Java タイプであるタイプを持ちます。これには、ユーザーや以前宣言された他のファクトタイプによって作成される他のクラスが含まれます。
たとえば、別のファクトタイプである Person を宣言できます。

例4.2 新しいファクトタイプ Person の宣言

declare Person
    name : String
    dateOfBirth : java.util.Date
    address : Address
end
上記の例の通り、dateOfBirth は Java API からの java.util.Date タイプで、address は以前に定義されたファクトタイプ Address です。
以前に説明した import を使用すると、書き込みごとにクラスの完全修飾名を記述する必要がなくなります。

例4.3 import を使用して完全修飾クラス名の使用を省略する

import java.util.Date

declare Person
    name : String
    dateOfBirth : Date
    address : Address
end
新しいファクトタイプを宣言する場合、JBoss Rules はコンパイル時にファクトタイプを表す POJO を実装するバイトコードを生成します。生成された Java クラスは、タイプ定義の 1 対 1 の Java Bean マッピングになります。そのため、前の例の場合、生成される Java クラスは次のようになります。

例4.4 以前の Person ファクトタイプ宣言に対して生成された Java クラス

public class Person implements Serializable {
    private String name;
    private java.util.Date dateOfBirth;
    private Address address;

    // getters and setters
    // equals/hashCode
    // toString
}
これは単純な POJO であるため、生成されたクラスは他のファクト同様にルールで透過的に使用できます。

例4.5 ルールで宣言されたタイプの使用

rule "Using a declared Type"
when 
    $p : Person( name == "Bob" )
then
    System.out.println( "The name of the person is "+ )
    // lets insert Mark, that is Bob's mate
    Person mark = new Person();
    mark.setName("Mark");
    insert( mark );
end

4.7.2. メタデータの宣言

JBoss Rules では、メタデータはファクトタイプ、ファクト属性、およびルールなど、さまざまなコンストラクションに割り当てることができます。JBoss Rules は @ 記号を使用してメタデータを導入し、常に次の形式を使用します。
@matadata_key( metadata_value )
括弧と metadata_value は任意です。
たとえば、値が Bob である author のようなメタデータ属性を宣言したい場合、次のように記述します。

例4.6 任意のメタデータ属性に宣言

@author( Bob )
JBoss Rules では、任意のメタデータ属性を宣言できますが、一部はエンジンに対して特別な意味を持っており、その他のメタデータ属性は実行時のクエリに対して使用できます。また、JBoss Rules ではファクトタイプとファクト属性の両方に対してメタデータを宣言できます。ファクトタイプのフィールドの前に宣言されたメタデータはファクトタイプへ割り当てられ、属性の後に宣言されたメタデータは特定の属性に割り当てられます。

例4.7 ファクトタイプと属性に対するメタデータ属性の宣言

import java.util.Date

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

    name : String @key @maxLength( 30 )
    dateOfBirth : Date 
    address : Address
end
上記の例では、ファクトタイプに対して 2 つのメタデータが宣言され (@author@dateOfCreation)、さらに 2 つのメタデータが 2 つの名前属性 (@key@maxLength) に対して定義されています。@key メタデータは値を持たないため、括弧と値が省略されていることに注意してください。

4.7.3. 既存タイプに対するメタデータの宣言

JBoss Rules では、新しいファクトタイプにメタデータ属性を宣言する場合と同様に既存のタイプのメタデータ属性を宣言することができます。唯一の違いは宣言にフィールドがないことです。
たとえば、クラス org.drools.examples.Person があり、このクラスに対してメタデータを宣言したい場合、次のコードを作成します。

例4.8 既存のタイプに対するメタデータの宣言

import org.drools.examples.Person

declare Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )
end
インポートを使用する代わりに完全修飾名でクラスを参照することも可能ですが、クラスはルールでも参照されるため、通常はインポートを追加し、短いクラス名を使用した方が効率的です。

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

declare org.drools.examples.Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )
end

4.7.4. アプリケーションコードからの宣言されたタイプへのアクセス

宣言されたタイプは通常ルールファイルの内部で使用され、Java モデルはルールとアプリケーション間でモデルを共有する時に使用されます。しかし、アプリケーションがルールエンジンをラッピングし、ルール管理に対して高レベルでドメイン固有のユーザーインターフェースを提供する場合、アプリケーションが宣言されたタイプよりファクトにアクセスし、処理する必要があることがあります。
このような場合、生成されたクラスは Java Reflection API を用いて通常通り処理できますが、通常は小さな結果のために大量の作業が必要になります。JBoss Rules はアプリケーションが行う最も一般的なファクトの処理に対して簡易 API を提供します。
宣言されたファクトは宣言が行われたパッケージに属することが最初の重要点になります。たとえば、下記の例では Personorg.drools.examples パッケージに属すため、生成されるクラスの完全修飾名は org.drools.examples.Person になります。

例4.10 org.drools.examples パッケージでのタイプの宣言

package org.drools.examples

import java.util.Date

declare Person
    name : String
    dateOfBirth : Date
    address : Address
end
前述の通り、宣言されたタイプはナレッジベースのコンパイル時に生成されます。アプリケーションはアプリケーションの実行時のみアクセスできます。そのため、アプリケーションからの直接参照にこれらのクラスは使用できません。
JBoss Rules は、アプリケーションコードから宣言されたタイプを処理できるインターフェースを提供します (org.drools.definition.type.FactType)。このインターフェースより、宣言されたファクトタイプのフィールドをインスタンス化および読み書きすることができます。

例4.11 API を介した宣言されたファクトタイプの処理

// get a reference to a knowledge base with a declared type:
KnowledgeBase kbase = ...

// get the declared FactType
FactType personType = kbase.getFactType( "org.drools.examples",
                                         "Person" );

// handle the type as necessary:
// create instances:
Object bob = personType.newInstance();

// set attributes values
personType.set( bob,
                "name",
                "Bob" );
personType.set( bob,
                "age",
                42 );

// insert fact into a session
StatefulKnowledgeSession ksession = ...
ksession.insert( bob );
ksession.fireAllRules();

// read attributes
String name = personType.get( bob, "name" );
int age = personType.get( bob, "age" );

API には他にも役に立つメソッドが含まれています。このようなメソッドには一度にすべての属性を設定するメソッド、マップから値を読み取るメソッド、すべての属性を一度に読み取るメソッド、マップにデータを投入するメソッドなどがあります。
API は Java リフレクションと似ていますが、リフレクションは使用しません。その代わりに、バイトコードによって生成されたより高速なアクセッサーに依存します。

4.8. ルール

ルール

図4.10 ルール

ルールでは、特定の条件セットが発生した時に、左側に指定され、右側のアクションのリストとして指定されたものを実行するよう指定します。ユーザーからの一般的な疑問としては、「なぜ when を if の代わりに使用するのか」などがあります。when が if の代わりに選択される理由は、if は通常、特定時に条件がチェックされるプロシージャー実行フローの一部であるためです。反対に、when は条件評価が特定の評価結果または時点に関係しませんが、エンジンが生存する間はいつでも連続して実行されます。条件が満たされると、アクションが実行されます。
ルールには、ルールパッケージ内で一意となる名前を付ける必要があります。ルールが単一の DRL で 2 回定義された場合は、ロード時にエラーが発生します。パッケージ内に既存するルール名が含まれる DRL が追加された場合は、以前のルールが置き換えられます。ルール名に空白文字が存在する場合、ルール名を二重引用符で囲む必要があります (常に二重引用符を使用してください)。
属性は任意です。必ず 1 行に 1 つずつ記述してください。
ルールの左側は when キーワードの後に指定し (新しい行にするのが理想的です)、同様に右側は when キーワードの後に指定します (この場合も新しい行にするのが理想的です)。ルールはキーワード end で終了します。ルールをネストすることはできません。

例4.12 ルール構文の概要

rule "<name>"
    <attribute>*
when
    <conditional element>*
then
    <action>*
end

例4.13 単純なルール

rule "Approve if not rejected"
    salience -100 
    agenda-group "approval"
when
    not Rejection() 
    p : Policy(approved == false, policyState:status)
    exists Driver(age > 25)
    Process(status == policyState)
then
    log("APPROVED: due to no objections."); 
    p.setApproved(true);
end

注記

JBoss Rules は、プリミティブまたはオブジェクトラッパー形式で番号を保持しようとするため、int プリミティブにバインドされた変数がコードブロックまたは式で使用されると、手作業でボックスを解除する必要がなくなります。この点が、すべてのプリミティブが「自動的にボックス化」 され、手作業による「ボックス化の解除」が必要な JBoss Rules 3.0 とは異なります。オブジェクトラッパーにバインドされた変数はオブジェクトのままになります。この場合は、自動ボックス化およびボックス化の解除を処理する既存の JDK 1.5 および JDK 5 のルールが適用されます。フィールド制約を評価する場合は、システムが値の 1 つを同等の形式に強制変換します。そのため、プリミティブはオブジェクトラッパーに相当します。

4.8.1. ルール属性

ルール属性は、宣言によってルールの動作に影響を与えることができます。単純な属性もありますが、Ruleflow など複雑なサブシステムの一部であるものもあります。JBoss Rules を活用するには、本項を読んで各属性をよく理解するとよいでしょう。
ルール属性

図4.11 ルール属性

表4.1 ルール属性

属性 デフォルト値 タイプ コメント
no-loop
false
ブール値
ルールの結果がファクトを変更すると、ルールが再度アクティベートされることがあり、再帰が行われる原因となります。no-loop を true に設定して、現在のデータセットに対する activation の作成が無視されるようにします。
ruleflow-group
N/A
文字列
ルールフロー 機能を使用して、ルールの実行を制御します (同じ ruleflow-group 識別子によってアセンブルされたルールはグループがアクティブな時のみ実行されます)。
lock-on-active
false
ブール値
uleflow-group がアクティブになるか、agenda-group がフォーカスを受けると、lock-on-active が true に設定されたそのグループ内のルールがアクティブでなくなります。更新元に関係なく、一致するルールのアクティベーションは破棄されます。これは、変更がルール自体のみによって引き起こされるわけではないため、no-loop の強いバージョンになります。これは、ファクトを変更する複数のルールが存在し、ルールの再照合や再実行を希望しない場合の計算ルールに最適です。ruleflow-group がアクティブでなくなるか、agenda-group がフォーカスを失った時のみ、lock-on-active が true に設定されたルールがアジェンダに配置されるアクティベーションに対して再び有効になります。
salience
0
整数
各ルールは整数を割り当てることができる salience 属性を持ちます。デフォルトはゼロです。salience は優先度の形式で、activation queue で順序付けされる時に大きい値を持つルールに高い優先度が割り当てられます。
agenda-group
MAIN
文字列
アジェンダグループを用いるとアジェンダを分割できるため、実行制御を強化できます。フォーカスを取得したアジェンダグループのルールのみ実行が許可されます。
auto-focus
false
ブール値
auto-focus 値が true のルールがアクティベートされた場合や、ルールの agenda グループがフォーカスを取得していない場合、フォーカスがグループに与えられ、ルールを実行できるようになります。
activation-group
N/A
文字列
この文字列は同じ activation グループに属するルールを識別します。このようなグループのルールはお互いを排他的に実行します。つまり、実行する activation グループの最初のルールは他のルールのアクティベーションをキャンセルし、実行を阻止します。

注記

これは以前 Xor グループと呼ばれていましたが、厳密には Xor の定義を満たしていません。
dialect
パッケージの指定通り
文字列で、可能な値は javamvel
この属性を使用して、左側または右側のいずれかにあるコード式に使用される言語を指定します。現在、Java と MVFLEX 式言語 の 2 つのダイアレクトを使用できます (ダイアレクトはパッケージレベルで指定できますが、この属性を使用すると、ルールのパッケージ定義を上書きできます)。
date-effective
N/A
日付と時間の定義が含まれる文字列
この属性で設定されたタイムスタンプよりも現在の日時が後であることを示す場合にのみルールをアクティベートできます。
date-expires
N/A
日付と時間の定義が含まれる文字列
この属性で設定されたタイムスタンプよりも現在の日時が後であるとルールをアクティベートできません。
duration
デフォルト値なし
long
この属性を使用すると、指定された期間後に属性が true のままであった場合にルールが実行されます。
次のコード例は、一般的な属性の一部を使用する方法について表しています。
rule "my rule"
salience 42
agenda-group "number 1"
when ...

4.8.2. タイマーとカレンダー

JBoss Rules は、間隔および cron ベース両方のタイマーをサポートするようになりました。これらのタイマーは、廃止された duration 属性の代わりとなります。

例4.14 timer 属性の使用例

timer ( int: <initial delay> <repeat interval>? )
timer ( int: 30s )
timer ( int: 30s 5m )
 
timer ( cron: <cron expression> )
timer ( cron:* 0/15 * * * ? )
間隔「int:」タイマーは、初期遅延の JDK セマンティックに従い、任意で繰り返し間隔が続きます。Cron 「cron:」タイマーは標準的な cron 式に従います。

例4.15 cron の例

rule "Send SMS every 15 minutes"
    timer (cron:* 0/15 * * * ?)
when
    $a : Alarm( on == true )
then
    channels[ "sms" ].insert( new Sms( $a.mobileNumber, "The alarm is still on" ));
end
ルールの実行時、カレンダーが制御するようになりました。カレンダー API は http://www.quartz-scheduler.org/ にモデル化されています。

例4.16 クオーツ式カレンダーの適応

Calendar weekDayCal = QuartzHelper.quartzCalendarAdapter(org.quartz.Calendar quartzCal)
カレンダーは StatefulKnowledgeSession で登録されます。

例4.17 カレンダーの登録

ksession.getCalendars().set( "week day", weekDayCal );
これらは、通常のルールやタイマーが含まれるルールと共に使用できます。ルールカレンダー属性は 1 つまたは複数のコンマカレンダー名を持つことが可能です。

例4.18 カレンダーとタイマーを一緒に使用

rule "weekdays are high priority"
   calendars "weekday"
   timer (int:0 1h)
when 
    Alarm()
then
    send( "priority high - we have an alarm” );
end 

rule "weekend are low priority"
   calendars "weekend"
   timer (int:0 4h)
when 
    Alarm()
then
    send( "priority low - we have an alarm” );
end

4.8.3. 左側条件要素

左側 (Left-Hand Side, LHS) は、ルールの 条件 (when) 部分の共通名で、条件要素 で構成されます (何も指定しなくても問題ありません。左側が空白である場合、eval(true) と書き換えられます。そのため、ルールの条件は常に true のままになります)。
左側は、新しい working memory セッションが作成された時に一度だけアクティベートされます。
左側

図4.12 左側

以下は conditional element のないルールになります。
rule "no CEs"
when
then
        <action>*
end
これは内部で次のように書き換えられます。
rule "no CEs"
when
    eval( true )
then
    <action>*
end
条件要素は 1 つまたは複数の パターン で動作します (詳細は次に説明します)。最も一般的な条件要素は and で、ルールの左側に複数の完全に無関係なパターンがある場合に暗示されます。

注記

or パターンとは異なり、and主導宣言バインディング を持つことができません。これは、宣言は 1 つのファクトへのみ参照でき、 and が満たされると複数のファクトを照合するため、どのファクトへバインドするのか分からなくなるためです。

4.8.3.1. パターン

パターンconditional element の最も重要な型です。以下のエンティティー関係図はパターンの制約を構成するさまざまな部分の概要と、これらの部分がどのように連携するかを表しています。各部分の詳細は、図やコード例と共に本項の後半で取り上げます。
パターンエンティティー関係図

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

エンティティー関係図の上部を見ると、パターンはゼロ以上の制約で構成され、パターンバインディングは任意であることが分かります。次の図は構文の形式を表しています。
パターン

図4.14 パターン

最も簡単な形式 (制約のない状態) では、パターンは該当する型のファクトに対して照合されます。以下の場合、型は Cheese であるため、パターンは working memory の各 Cheese オブジェクトに対して照合されます。

注記

「型」はファクトオブジェクトの実際のクラスである必要はありません。パターンは スーパークラス やインターフェースも参照する場合があるため、多数のクラスからのファクトと一致する可能性があります。
Cheese( )
$c などのパターンバインディング変数を使用して一致するオブジェクトを参照します。$ プレフィックスの使用は任意ですが、このプレフィックスを使用すると変数をフィールドを区別しやすくなるため、複雑なルールに対処するする場合に便利です。
$c : Cheese( )
構文の主な要素は括弧です。括弧内に制約を置きます。主な型は フィールド制約インライン評価、および 制約グループ になります。
,&&|| のいずれかを使用して制約を区切ります。ただし、これらの記号は若干ことなる機能を持つことに注意してください。
制約

図4.15 制約

制約

図4.16 制約

constraintGroup

図4.17 constraintGroup

コンマ (,) は制約グループを区切るために使用されます。これは暗黙的で接続的な意味論を持ちます。
# Cheese type is stilton and price < 10 and age is mature.
Cheese( type == "stilton", price < 10, age == "mature" )
この例では、3 つの制約グループがあり、各制約グループは単一の制約を持っています。
  1. type == "stilton" の通り、型は stilton です。
  2. price < 10 の通り、価格 (price) は 10 未満です。
  3. age == "mature" の通り、熟成度 (age) は mature です。
&&|| を用いると、グループは複数の制約を持つことができます。例は次の通りです。
// Cheese type is "stilton" and price < 10, and age is mature
Cheese( type == "stilton" && price < 10, age == "mature" )
// Cheese type is "stilton" or price < 10, and age is mature
Cheese( type == "stilton" || price < 10, age == "mature" )
この場合、 2 つの制約グループが存在します。最初の制約グループは 2 つの制約を持ち、2 つ目の制約グループは制約を 1 つ持ちます。
結合記号は次の順で最初から最後まで評価されます。
  1. &&
  2. ||
  3. ,
評価の優先度を変更するには、論理や数式のように括弧を使用します。例は次の通りです。
# Cheese type is stilton and ( price is less than 20 or age is mature ).
Cheese( type == "stilton" && ( price < 20 || age == "mature" ) ) 
この場合、括弧を使用すると ||&& の前に評価されるようになります。
&&, は同じ意味論を持ちますが、異なる優先度で解決されることに注意してください。そのため、複合制約式に , を組み込むことはできません。
// invalid as ',' cannot be embedded in an expression:
Cheese( ( type == "stilton", price < 10 ) || age == "mature" )
// valid as '&&' can be embedded in an expression:
Cheese( ( type == "stilton" && price < 10 ) || age == "mature")
4.8.3.1.1. フィールド制約
フィールド制約 を使用して名前付きフィールドを制限します。任意でフィールド名に変数バンディングを使用することができます。
fieldConstraint

図4.18 fieldConstraint

制限には次の 3 つの形式があります。
  • 単一値制限
  • 複合値制限
  • 複数制限
制限

図4.19 制限

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

図4.20 リテラル

qualifiedIdentifier

図4.21 qualifiedIdentifier

変数

図4.22 変数

returnValue

図4.23 returnValue

null であるフィールドに対してチェックを行うには、通常通り ==!= を使用します。リテラルの null キーワード (Cheese(type != null) のように使用) は、evaluator が例外をスローしない場合、値が null であると true を返します。
フィールドと値が異なる型である場合、システムは常に 型強制 を実行しようとします。「悪質な」強制を行おうとすると、例外が発生します。たとえば、数値 evaluatorten を文字列として提供すると、例外が発生しますが、10 を提供すると数値の 10 に強制します (強制は常に値型ではなくフィールド型を優先します)。
4.8.3.1.4. 単一値制限
singleValueRestriction

図4.24 singleValueRestriction

4.8.3.1.5. 演算子
演算子

図4.25 演算子

== および != 演算子はすべての型で有効です。型値が順序付けされている時は他のリレーショナル演算子を使用することもできます。データフィールドでは < は 「前」を意味します。 matchesnot matches のペアは文字列フィールドへのみ適用し、containsnot contains には Collection 型のフィールドが必要になります (evaluator とフィールドに対して正しい値を強制しようとします)。
matches 演算子
これは有効な Java 正規表現があるフィールドと一致します。正規表現は通常は文字列リテラルですが、有効な正規表現を解決する変数を使用することもできます。

重要

Java とは異なり、文字列リテラルとして書かれる正規表現内にエスケープは必要ありません。
Cheese( type matches "(Buffalo)?\S*Mozarella" )
not matches 演算子
この演算子は文字列が正規表現と一致しない時に true を返します。matches 演算子でも同じルールが適用されます。使用例は次の通りです。
Cheese( type not matches "(Buffulo)?\S*Mozerella" )
contains 演算子
この演算子を使用して、 collection または array フィールドに指定された値が含まれていることを確認します。
CheeseCounter( cheeses contains "stilton" ) // contains with a String literal
CheeseCounter( cheeses contains $var ) // contains with a variable
not contains 演算子
この演算子を使用して、collection または array フィールドに指定された値がないことを確認します。
CheeseCounter( cheeses not contains "cheddar" ) // not contains with a String literal
CheeseCounter( cheeses not contains $var ) // not contains with a variable

注記

後方互換性を維持するため、excludes 演算子もサポートされています。この演算子は not contains と同等です。
memberOf 演算子
この演算子を使用してフィールドが collection または array のメンバーであるかどうかを確認します。コレクションは変数である必要があります。
CheeseCounter( cheese memberOf $matureCheeses )
not memberOf 演算子
この演算子を使用して、フィールドが collection または array のメンバーでないかどうかを確認します。コレクションは変数である必要があります。
CheeseCounter( cheese not memberOf $matureCheeses )
soundslike 演算子
この演算子は matches と似ていますが、言葉が指定値と似た発音であるかどうかを確認します。Soundex アルゴリズムを使用し、英語の発音を基にして言葉の発音を確認します。
// match cheese "fubar" or "foobar"
Cheese( name soundslike 'foobar' )
4.8.3.1.6. リテラル制限
最も単純な形式の制限です。フィールドを指定されたリテラルに対して評価します。リテラルは、数値、日付、文字列、またはブール値になります。
literalRestriction

図4.26 literalRestriction

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

図4.27 variableRestriction

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

図4.28 returnValueRestriction

戻り値制限 は、リテラル、有効な Java プリミティブまたはオブジェクト、以前バインドされた変数、関数呼び出し、および演算子によって構成される括弧で囲まれている式です。使用される関数が時間に依存する結果を返すことは禁止されています。
Person( girlAge : age, sex == "F" )
Person( age == ( girlAge + 2) ), sex == 'M' )
4.8.3.1.9. 複合値制限
一致する可能性がある値が複数ある時に 複合値制限 を使用します (現在、in および not in evaluators のみがサポートしています)。
2 つ目のオペランドは、括弧で囲まれている値のコンマ区切りリストである必要があります。値は変数、リテラル、戻り値、または修飾識別子として渡すことができます。両方の evaluators は実際は「シンタクティックシュガー」で、!= および == 演算子を使用して複数制限のリストとして内部で書き直されます。
compoundValueRestriction

図4.29 compoundValueRestriction

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

図4.30 multiRestriction

restrictionGroup

図4.31 restrictionGroup

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

図4.32 インライン評価式

インライン評価 (inline eval) 制約はプリミティブなブール値へ解決する有効なダイアレクト式を使用できます。この式は時間が経過しても一定である必要があります。現在または以前のパターンより以前バインドされた変数を使用できます。フィールドバインディング変数を自動的に作成するため、auto-vivification (自動有効化) も使用されます。

注記

現在の変数でない識別子が発見された場合、識別子が現在のオブジェクト型のフィールドであるかを判断するためにビルダーによってチェックされます。識別子がこれに該当する場合、フィールドバインディングが同じ名前の変数として自動的に作成されます。これは、フィールド変数の auto-vivification (自動有効化) と呼ばれます。
この例では、男性が女性よりも 2 歳年上となる可能な男女のペアをすべて探します。age 変数は auto-vivification によって 2 つ目のパターンで自動作成されます。
Person( girlAge : age, sex = "F" )
Person( eval( age == girlAge + 2 ), sex = 'M' )
4.8.3.1.12. ネストされたアクセサー
JBoss Rules では、フィールド制約で ネストされたアクセサー を使用できます。これには、MVFLEX 式言語のアクセサーグラフ表記法を使用します (ネストされたアクセサーを持つフィールド制約は、実際には MVFLEX 式言語の inline-eval 制約として書き換えられます)。

警告

working memory はネストされた値を認識せず、ネストされた値がいつ変更するかも認識しないため、nested accessors を使用する時は十分注意してください。親参照が working memory に存在する間は常に不変の値として見なしてください。
ネストされた値を変更するには、最初に親オブジェクトを削除し、その後再度アサートします。グラフのルートに親が 1 つのみ存在する場合は、MVEL ダイアレクトの modify とその ブロックセッター を使用し、ネストされたアクセサーの割り当てを書くことができます。この場合、ルートの親オブジェクトを必要に応じて取り消したり挿入したりすることができます (Nested accessors は演算子記号のどちらの側でも使用できます)。
// Find a pet older than its owners first-born child
$p : Person( ) 
Pet( owner == $p, age > $p.children[0].age )
これは内部で MVEL inline eval として書き換えられます。
// Find a pet older than its owners first-born child
$p : Person( ) 
Pet( owner == $p, eval( age > $p.children[0].age ) ) 

警告

nested accessorsdirect field accesses よりもパフォーマンスへの影響がかなり大きいため、注意して使用してください。

4.8.3.2. and 条件要素

and conditional element を使用して他の conditional elements を論理積にグループ化します。
左側の root element は暗黙的な and プレフィックスです。これは指定する必要はありません。JBoss Rulesand をプレフィックスとインフィックスの両方としてサポートしますが、暗黙的なグループ化により混乱を避けることができるため、プレフィックスの使用が推奨されます。
prefixAnd

図4.33 prefixAnd

(and Cheese( cheeseType : type )
Person( favouriteCheese == cheeseType ) )
when
Cheese( cheeseType : type )
Person( favouriteCheese == cheeseType )
必要な場合は、and インフィックスと括弧による明示的なグループ化がサポートされます。

注記

and の代わりに && 記号を使用できます。この記号は廃止されましたが、レガシーサポートの理由で現在でも使用可能です。
infixAnd

図4.34 infixAnd

//infixAnd
Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType )
//infixAnd with grouping
( Cheese( cheeseType : type ) and
( Person( favouriteCheese == cheeseType ) or 
Person( favouriteCheese == cheeseType ) )

4.8.3.3. or 条件要素

or conditional element を使用して他の conditional element を論理和にグループ化します。

注記

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

図4.35 prefixOr

(or Person( sex == "f", age > 60 )
Person( sex == "m", age > 65 )
必要な場合、or インフィックスと括弧による暗示的なグループ化がサポートされます。

注記

or の代わりに || 記号を使用できます。この記号は廃止されましたが、レガシーサポートの理由で現在でも使用可能です。
infixOr

図4.36 infixOr

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

4.8.3.4. eval 条件要素

eval

図4.37 eval

eval conditional element はプリミティブなブール値を返す意味論コードを実行できるようにする「catch-all」です。このコードはルールの左側にバインドされた変数か、ルールパッケージの関数を参照できます。

警告

eval を使用しすぎないようにしてください。ルールの宣言性が低下し、engine のパフォーマンスが低下することがあります。eval はパターンのどこにでも使用できますが、ルールの左側で最後の条件要素として追加することが推奨されます。
Evals に索引を付けることはできません。そのため、フィールド制約 ほど効率的ではありません。しかし、時間とともに変化する値を返す関数として使用することに適しています (フィールド制約にはこの機能はありません)。
p1 : Parameter() 
p2 : Parameter()
eval( p1.getList().containsKey(p2.getItem()) )
// call function isValid in the LHS
eval( isValid(p1, p2) )

4.8.3.5. not 条件要素

not

図4.38 not

not conditional element は、1 階論理の 非存在数量詞 (non-existential quantifier) です。この目的は、working memory に存在しないものを確認することです。
not キーワードは、括弧で囲まれた conditional element の前に指定する必要があります (最も単純なユースケースでは、括弧を省略することが可能です)。
not Bus()
// Brackets are optional:
not Bus(color == "red")

// Brackets are optional:
not ( Bus(color == "red", number == 42) ) 

// "not" with nested infix and - two patterns,
// brackets are requires:
not ( Bus(color == "red") and Bus(color == "blue") )

4.8.3.6. exists 条件要素

exists

図4.39 exists

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

4.8.3.7. conditional elements

forall

図4.40 forall

forall conditional elementJBoss Rules の 1 階論理のサポートを完全にします。最初のパターンに一致するすべてのファクトが、残りの各パターンにも一致する場合、foralltrue として評価します。例は次の通りです。
rule "All English buses are red"
when
    forall( $bus : Bus( type == 'english') 
    Bus( this == $bus, color = 'red' ) )
then
    # all english buses are red
end
このルールは、タイプが english の各 Bus オブジェクトを選択します。そして、このパターンに一致する各ファクトに対し、後続のパターンが評価されます。後続のパターンにも一致する場合、forall conditional elementtrue として評価します。
該当するタイプの各ファクトが制約セットと一致するよう指定するには、以下のような単純な単一パターンを記述します。
rule "All Buses are Red"
when
    forall( Bus( color == 'red' ) )
then
    # all asserted Bus facts are red
end
反対に、複数パターンの場合は次のようになります。
rule "all employees have health and dental care programs"
when
    forall( $emp : Employee()
        HealthCare( employee == $emp )
        DentalCare( employee == $emp )
    )
then
    # all employees have health and dental care
end
forall は not などの他の conditional elements 内にネストできます。

重要

単一パターンの場合のみ括弧の使用が任意になります。そのため、ネストされる場合は括弧を使用する必要があります。
rule "not all employees have health and dental care"
when 
    not ( forall( $emp : Employee()
        HealthCare( employee == $emp )
        DentalCare( employee == $emp ) ) 
    )
then
    # not all employees have health and dental care
end

注記

not( forall( p1 p2 p3...)) は次のコードと同等です。
not(p1 and not(and p2 p3...))

重要

forall範囲の区切り文字 であることに注意してください。そのため、以前にバインドされた変数を使用できますが、内部でバインドされた変数は外部で使用することはできません。

4.8.3.8. from 条件要素

from

図4.41 from

from conditional element を使用して、左側のパターンによって一致されるデータの任意のソースを指定します。これを行うことにより、 engineworking memory で発見されなかったデータを推論できます。データソースはバインドされた変数上のサブフィールドや、メソッド呼び出しの結果であることがあります。
これは、他のアプリケーションコンポーネントやフレームワークとそのまま統合できるようにする強力なコンストラクトです。一般的な例の 1 つは、Hibernate の名前が付けられたクエリを使用してデータベースから要求に応じて読み出されるデータとの統合です。
通常の MVFLEX 式言語の構文に従う式を使用してオブジェクトソースを定義します。これにより、メソッドコールの実行、マップや collections 要素へのアクセス、オブジェクトプロパティーナビゲーション の使用を簡単に行うことができます。
別のパターンサブフィールドへの推論とバインディングを表す簡単な例は次の通りです。
rule "validate zipcode"
when
    Person( $personAddress : address ) 
    Address( zipcode == "23920W") from $personAddress 
then
    # zip code is ok
end
グラフ表記法を使用して同じことを行う方法は次の通りです。
rule "validate zipcode"
when
    $p : Person( ) 
    $a : Address( zipcode == "23920W") from $p.address 
then
    # zip code is ok
end
前述の例は単一パターンの評価でしたが、オブジェクトソース上で from を使用してオブジェクトのコレクションを返すこともできます。この場合、from がコレクションの各オブジェクトを繰り返し、個別に照合しようとします。以下は、注文の各商品を 10 % 割引するためのルールが含まれる例になります。
rule "apply 10% discount to all items over $ 100,00 in an order"
when
    $order : Order()
    $item  : OrderItem( value > 100 ) from $order.items
then
    # apply discount to $item
end
各注文の値が 100 を越える各商品に対して、このルールが一度実行されます。
fromlock-on-active ルール属性と共に使用する場合、予期しない結果がもたらされることがあるため特に注意してください。次のように若干変更が加えられている前述の例を考慮してください。
rule "Assign people in Queensland (QLD) to sales region 1"
    ruleflow-group "test"
    lock-on-active true
when
    $p : Person( ) 
    $a : Address( state == "QLD") from $p.address 
then
    modify ($p) {} #Assign person to sales region 1 in a modify block
end

rule "Apply a discount to people in the city of Brisbane"
    ruleflow-group "test"
    lock-on-active true
when
    $p : Person( ) 
    $a : Address( city == "Brisbane") from $p.address 
then
    modify ($p) {} #Apply discount to person in a modify block
end
この例では、QLD 州の Brisbane に在住する人は Sales Region 1 に割り当てられ、割引が適用されます (つまり、両方のルールがアクティベートおよび実行されることを想定しますが、2 つ目のルールのみが実行されます)。
監査ログを有効にすると、2 つ目のルールが実行された時に最初のルールを非アクティベートします。lock-on-active ルール属性により、ファクトのセットが変更された時にルールによって新しいアクティベーションが作成されないようにするため、最初のルールは再アクティベートできません (ファクトのセットは変更されませんが、 from を使用すると目的に関係なく、評価される度に新しいファクトが返されます)。
次の手順に従います。
  1. 前述のパターンを使用する必要性について検討します。
    これは異なるルールフローグループ全体で多くのルールが存在するためです。ルールが working memory および該当するルールフローのダウンストリームにある他のルールを変更し、必要性を再評価しなければならない場合、modify を使用することが重要となります。しかし、同じルールフローグループの他のルールがお互いにアクティベーションを再帰的に配置しないようにしてください。
    この場合、ルールがルール自体を再帰的にアクティベートできないようになるため、no-loop 属性では効果がありません。したがって、lock-on-active を使用してください。
  2. この問題に対応する方法はいくつかあります。
    • すべてのファクトを working memory にアサートできる場合に from を使用しないようにするか、制約式でネストされたオブジェクト参照を使用します (下記を参照)。
    • modify block で使用するために割り当てられた変数を、左側の条件の最後にある文として配置します。
    • 同じルールフローグループ内のルールがお互いにアクティベーションを配置する方法を明示的に管理できる場合、lock-on-active を使用しないようにします。
    この中で推奨される解決法は、working memory へすべてのファクトを直接アサートできる場合に from の使用を最小限に抑える方法になります。
    上記の例では、Person および Address インスタンスの両方を working memory にアサートすることが可能です。グラフは比較的単純であるため、次のようにルールを変更するとさらに簡単です。
    rule "Assign people in Queensland (QLD) to sales region 1"
        ruleflow-group "test"
        lock-on-active true
    when
        $p : Person(address.state == "QLD" )  
    then
        modify ($p) {} #Assign person to sales region 1 in a modify block
    end
    
    rule "Apply a discount to people in the city of Brisbane"
        ruleflow-group "test"
        lock-on-active true
    when
        $p : Person(address.city == "Brisbane" )  
    then
        modify ($p) {} #Apply discount to person in a modify block
    end
    
  3. 両方のルールは想定通り実行されるようになりますが、この方法で常にネストされたファクトへアクセスできるわけではありません。Person が 1 つまたは複数の Addresses を保持し、存在数量詞 を使用して特定条件を満たす住所を最低でも 1 つ person に一致させたい例を考えてみましょう。この場合、コレクションを推論するため from を使用する必要があります。
    これを実現する方法は複数ありますが、すべてで lock-on-active 使用時の問題が発生するわけではありません。たとえば、次のように from を使用すると、両方のルールが想定通りに実行されます。
    rule "Assign people in Queensland (QLD) to sales region 1"
        ruleflow-group "test"
        lock-on-active true
    when
        $p : Person($addresses : addresses)
        exists (Address(state == "QLD") from $addresses)  
    then
        modify ($p) {} #Assign person to sales region 1 in a modify block
    end
    
    rule "Apply a discount to people in the city of Brisbane"
        ruleflow-group "test"
        lock-on-active true
    when
        $p : Person($addresses : addresses)
        exists (Address(city == "Brisbane") from $addresses)  
    then
        modify ($p) {} #Apply discount to person in a modify block
    end
    
    しかし、若干異なる方法では問題が発生します。
    rule "Assign people in Queensland (QLD) to sales region 1"
        ruleflow-group "test"
        lock-on-active true
    when
        $assessment : Assessment()
        $p : Person()
        $addresses : List() from $p.addresses
        exists (Address( state == "QLD") from $addresses) 
    then
        modify ($assessment) {} #Modify assessment in a modify block
    end
    
    rule "Apply a discount to people in the city of Brisbane"
        ruleflow-group "test"
        lock-on-active true
    when
        $assessment : Assessment()
        $p : Person()
        $addresses : List() from $p.addresses 
        exists (Address( city == "Brisbane") from $addresses)
    then
        modify ($assessment) {} #Modify assessment in a modify block
    end
    
    この場合、from を使用すると $addresses が返されます。またこの例では、可能な解決法を示す新しいオブジェクト assessment が導入されます。$addresses 変数が移動され、各ルールの最後の条件となると、両方のルールは想定通り実行されます。
    上記の例は、ルールのアクティベーションを損失せずに fromlock-on-active と組み合わせて使用する方法を表していますが、左側の条件の配置順序に依存するという欠点があります。またこれにより、問題を起こす可能性のある条件を追跡する必要があるため、ルールの作成が極めて複雑になります。
  4. 代わりに、ファクトをさらに working memory へアサートする方がよいでしょう。この場合、人の住所が working memory へアサートされると、from を使用する必要がなくなります。

注記

すべてのデータを working memory にアサートするのは現実的ではなく、他の方法を使用する必要がある場合があります。
lock-on-active の必要性を再評価することが 1 つの方法です。working memory が変更された時にお互いを再帰的にアクティベートしないようにする条件が各ルールに含まれるようにして、同じルールフローグループ内のルールがお互いをアクティベートするよう、直接管理する方法もあります。たとえば、前述の例の場合、割引が既に適用されているか確認し、適用されている場合はルールがアクティベートしないようにする条件をルールに追加できます。

4.8.3.9. collect 条件要素

collect

図4.42 collect

collect conditional element を使用して、指定のソースまたは working memory のいずれかより取得されたオブジェクトのコレクションをルールが推定するようにします。

注記

1 階論理 では 基数数量詞 と呼ばれます。
import java.util.ArrayList
rule "Raise priority if system has more than 3 pending alarms"
when
    $system : System()
    $alarms : ArrayList( size >= 3 )
    from collect( Alarm( system == $system, status == 'pending' ) )
then
    # Raise priority, because system $system has
    # 3 or more alarms pending. The pending alarms
    # are $alarms.
end
この場合、ルールは working memory で該当する各システムの保留のアラームを探し、ArrayLists でグループ化します。1 つのシステムに対して 3 つ以上のアラームが見つかった場合にルールが実行されます。
collect の結果パターンは、 java.util.Collection インターフェースを実装し、引数のないデフォルトの公開コンストラクターを提供する「具体的」なクラスになります。これは java.util.Collection を実装し、これらの条件を満たす限り、ArrayListLinkedListHashSet、またはカスタムクラスなどの Java 使用できることを意味します。

注記

ソースと結果パターンの両方を他のパターンとして制約できます。
collect conditional element の前にバインドされた変数は、ソースと結果パターン両方のスコープ内にあります。このような変数を使用してこれらのパターンを制約します。しかし、collect はバインディングの scope delimiter であるため、内部のバインディングは外部では使用できません。
collect はネストされた from conditional elements を許可します。そのため、以下は collect の有効な使用例になります。
import java.util.LinkedList;
rule "Send a message to all mothers"
when
    $town : Town( name == 'Paris' )
    $mothers : LinkedList() 
    from collect( 
        Person( gender == 'F', children > 0 ) 
        from $town.getPeople() 
        )
then
    # send a message to all mothers
end

4.8.3.10. accumulate 条件要素

accumulate

図4.43 accumulate

accumulate conditional elementcollect よりも柔軟で強力な条件要素です。ルールはオブジェクトのコレクションにまたがって繰り返しでき、各要素に対してカスタムのアクションを実行できます。終了すると結果オブジェクトを返します。
accumulate conditional element の一般的な構文は次の通りです。
<result pattern> from accumulate(<source pattern>,
    init( <init code> ),
    action( <action code> ),
    reverse( <reverse code> ),
    result( <result expression> ) )
各要素の意味は次のようになります。
  • <source pattern>: engine が各ソースオブジェクトとの一致を試みる通常のパターンです。
  • <init code>: 選択されたダイアレクトのコードにある意味論ブロックです。ソースオブジェクトで繰り返しする前に各タプルに対して一度実行されます。
  • <action code>: 各ソースオブジェクトに対して実行される選択されたオブジェクトにあるコードの意味論ブロックです。
  • <reverse code>: 選択されたダイアレクトにあるコードの任意の意味論ブロックです。存在する場合、ソースパターンと一致しなくなった各ソースオブジェクトに対して実行されます。ソースオブジェクトが変更または取り消された時に engine が減分計算を行えるよう、<action code> ブロックで実行された計算を元に戻すことが目的です。このような操作のパフォーマンスを大幅に向上します。
  • <result expression>: すべてのソースオブジェクトが繰り返し処理された後に実行される選択されたダイアレクトの意味論の式です。
  • <result pattern>: engine<result expression> から返されたオブジェクトと一致しようとする通常のパターンです。一致する場合、accumulate 条件要素が true として評価し、engine はルールにある次の条件要素を評価します。
    一致しない場合、 accumulate 条件要素は false として評価し、ルールの条件要素を評価することを停止します。
例は次の通りです。
rule "Apply 10% discount to orders over US$ 100,00"
when
    $order : Order()
    $total : Number( doubleValue > 100 ) 
    from accumulate( OrderItem( order == $order, $value : value ),
        init( double total = 0; ),
        action( total += $value; ),
        reverse( total -= $value; ),
        result( total ) )
then
    # apply discount to $order
end
この場合、以下が発生します。
  1. engineworking memory の各 order に対する init のコード を実行します。これは、 total 変数をゼロに初期化します。
  2. 次に、該当する注文に対する OrderItem オブジェクトすべてで繰り返し処理を行い、各オブジェクトに対してアクションを実行します (この場合、全商品の合計値が計算され、total 変数に置かれます)。
  3. result expression に対応する値が返されます (total 変数の値)。
  4. エンジンは結果を Number パターンに一致しようとします。double の値が 100 よりも大きい場合にルールが実行されます。

重要

この例では Java が意味論のダイアレクトとして使用されています。そのため、initaction、および reverse コードブロックではセミコロンをステートメントの区切り文字として使用しなければならないことに注意してください。結果は式であり、; は許可されません。他のダイアレクトを使用する場合は、常に特定の構文に従ってください。

重要

reverse code は任意ですが、update および retract の使用時にパフォーマンスが向上するため、Red Hat は reverse code の使用を強く推奨します。
accumulate conditional element を使用してソースオブジェクト上のアクションを実行します。次項の例は、カスタムオブジェクトをインスタンス化し、値を投入します。
4.8.3.10.1. Accumulate 関数
accumulate conditional element は非常に強力な CE ですが、累積関数 と呼ばれる事前定義された関数を使用する場合、特に簡単に使用することができます。累積関数は accumulate とほぼ同様に動作しますが、accumulate conditional element すべてにカスタムコードを明示的に記述する代わりに、一般的な操作に対して事前定義されたコードを使用できることが異なります。
例は次の通りです。これは、accumulate 関数を用いて、次のように注文に割引を適用するルールをプログラミングできることを表しています。
rule "Apply 10% discount to orders over US$ 100,00"
when
$order : Order()
$total : Number( doubleValue > 100 ) 
from accumulate( OrderItem( order == $order, $value : value ),
sum( $value ) )
then
# apply discount to $order
end
この場合、sumaccumulate 関数です。名前の通り、各 OrderItem$value の合計を計算し、結果を返します。
JBoss Rules には以下の accumulate 関数が内蔵されています。
  • average
  • min
  • max
  • count
  • sum
このような一般的な関数は、式を入力として受け入れます。たとえば、注文の全商品の平均利益を計算するには、次のように average 関数を使用してルールを記述します。
rule "Average profit"
when
    $order : Order()
    $profit : Number() 
    from accumulate( OrderItem( order == $order, $cost : cost, $price : price )
        average( 1 - $cost / $price ) )
then
    # average profit for $order is $profit
end
accumulate 関数はすべて プラグ可能 です。そのため、必要な場合はカスタマイズされたドメイン固有の関数を比較的簡単に engine へ追加することが可能です。追加後、ルールは制限なしでこれらの関数を使用できます。新しい accumulate 関数を実装するには、以下の手順に従います。
  1. org.drools.base.acumulators.AccumulateFunction インターフェースを実装する Java クラスを作成します。
  2. 設定ファイルに行を追加するか、システムプロパティーを設定し、engine に新しい関数について知らせます。
    次の例は、average 関数の実装を示しています。
    /*
    * Copyright 2007 JBoss Inc
    * 
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    * 
    *      http://www.apache.org/licenses/LICENSE-2.0
    * 
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    *
    * Created on Jun 21, 2007
    */
    package org.drools.base.accumulators;
    
    
    /**
    * An implementation of an accumulator capable of calculating average values
    * 
    * @author etirelli
    *
    */
    public class AverageAccumulateFunction implements AccumulateFunction {
    
    protected static class AverageData {
    public int    count = 0;
    public double total = 0;
    }
    
    /* (non-Javadoc)
    * @see org.drools.base.accumulators.AccumulateFunction#createContext()
    */
    public Object createContext() {
    return new AverageData();
    }
    
    /* (non-Javadoc)
    * @see org.drools.base.accumulators.AccumulateFunction#init(java.lang.Object)
    */
    public void init(Object context) throws Exception {
    AverageData data = (AverageData) context;
    data.count = 0;
    data.total = 0;
    }
    
    /* (non-Javadoc)
    * @see org.drools.base.accumulators.AccumulateFunction#accumulate(java.lang.Object,
    * java.lang.Object)
    */
    public void accumulate(Object context,
                   Object value) {
    AverageData data = (AverageData) context;
    data.count++;
    data.total += ((Number) value).doubleValue();
    }
    
    /* (non-Javadoc)
    * @see org.drools.base.accumulators.AccumulateFunction#reverse(java.lang.Object,
    * java.lang.Object)
    */
    public void reverse(Object context,
                Object value) throws Exception {
    AverageData data = (AverageData) context;
    data.count--;
    data.total -= ((Number) value).doubleValue();
    }
    
    /* (non-Javadoc)
    * @see org.drools.base.accumulators.AccumulateFunction#getResult(java.lang.Object)
    */
    public Object getResult(Object context) throws Exception {
    AverageData data = (AverageData) context;
    return new Double( data.count == 0 ? 0 : data.total / data.count );
    }
    
    /* (non-Javadoc)
    * @see org.drools.base.accumulators.AccumulateFunction#supportsReverse()
    */
    public boolean supportsReverse() {
    return true;
    }
    
    }
    
    統合の作業はすべて engine によって実行されるため、コードは非常に簡単です。
  3. 機能を engine へプラグするには、configuration ファイルへ追加します。
    drools.accumulate.function.average =
        org.drools.base.accumulators.AverageAccumulateFunction
    

    重要

    必ず drools.accumulate.function. プレフィックスを使用してください。
    average は関数がルールファイルでどのように使用されるかを制御します。org.drools.base.accumulators.AverageAccumulateFunction は関数の動作を実装するクラスの完全修飾名です。

4.8.4. 右側

4.8.4.1. 使用法

右側結果 またはルールのアクション部分の共通名です。ここに、実行するアクションのリストを置くことができます。

重要

ルールは本質的に アトミック である必要があるため、右側で命令または条件コードを使用するのは適切ではありません (「これである場合にこれをやるかもしれない」ではなく、「これである場合にこれをやる」)。
宣言的で解読できるようにするため、ルールの右側は小さくする必要もあります。右側で命令または条件コードが必要になりそうな場合、1 つのルールを小さい複数のルールに分割することを考慮してください。
右側を使用して working memory のデータを挿入、取り消し、または変更します。これを行うには、最初に working memory を参照せずに working memory を変更する次の 便宜的なメソッド (convenience mehtod) を利用します。
  • update(object, handle) を使用して、オブジェクト (左側にあるものにバインドされている) が変更されたため、ルールの「再検討」が必要になる可能性があることを engine に伝えます。
  • update(object) を使用して、ナレッジヘルパー が必要な facthandle をルックアップするようにします。これは、渡されたオブジェクトの ID チェックを使用して行われます (Java bean に property change listeners を提供する場合、engine に挿入するため、オブジェクト変更時に update() を呼び出す必要はありません)。
  • insert(new Something ()) を使用して、新しく作成されたオブジェクトを working memory に置きます。
  • insertLogical(new Something())insert と似ていますが、現在実行されているルールの真実をサポートするファクトがこれ以上存在しない場合に、オブジェクトが自動的に取り消される点が異なります。
  • retract(handle) を使用して working memory からオブジェクトを削除します。
convenience methods は、実際には Knowledge Helper インスタンスへショートカットを提供するマクロにすぎません。これを行うことにより、rules ファイルから working memory へのアクセスが可能になります。
事前定義された KnowledgeHelper 変数によって、便利なメソッドを複数呼び出すことが可能です。
  • drools.halt() を使用して、ルールの実行を即座に終了します。これは、現在のセッションが fireUntilHalt() で開始された時点へ制御を戻すために行います。
  • insert(Object o)update(Object o)、および retract(Object o) メソッドを呼び出すこともできます (頻繁に使用されるため、オブジェクト参照なしで呼び出すことが可能です)。
  • drools.getWorkingMemory() を使用して working memory オブジェクトを返します。
  • drools.setFocus( String s) を使用して、指定された agenda group にフォーカスを設定します。
  • drools.getRule().getName() を使用してルール名を返します。
  • drools.getTuple() を使用して現在実行しているルールに一致する タプル を返します。drools.getActivation() は対応する activation を返します (これらの呼び出しはデバッグの処理に便利です)。
完全な Knowlege Runtime アプリケーションプログラミングインターフェースは、KnowledgeContext 型の事前定義された変数 kcontext を介して公開されます。この getKnowledgeRuntime() メソッドが KnowledgeRuntime 型のオブジェクトを送信し、多数のメソッドへのアクセスを提供します。これらのメソッドは、右側の論理をコーディングする時に便利です。
  • kcontext.getKnowledgeRuntime().halt() 呼び出しを使用して、ルールの実行を即座に終了します。
  • getAgenda() アクセサーを使用して、このセッションの agenda への参照を返します。これは、さまざまなアクティベーション、アジェンダ、およびルールフローグループへのアクセスを提供します。以下ようなアジェンダグループのアクティベーションは比較的一般的な使用例になります。
    // give focus to the agenda group CleanUp
    kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus();
    

    注記

    drools.setFocus( "CleanUp" ) を使用して上記を実現することも可能です。
  • クエリを実行するには、getQueryResults(String query) を呼び出します。その後、「クエリ」 の通り、結果を処理することが可能です。
  • working memoryagenda event listeners を追加および削除できる event management に対応するためのメソッドがあります。
  • getKnowledgeBase() メソッドを使用して KnowledgeBase オブジェクトを返します。このオブジェクトはシステムのバックボーンで、現在のセッションの発信元です。
  • setGlobal(...)getGlobal(...)、および getGlobals() を用いてグローバルを管理します。
  • getEnvironment() を使用して run-time環境 を返します (これはオペレーティングシステムの環境と同様です)。

4.8.4.2. modify ステートメント

これは、fact の更新を実行する構造化された方法を提供する言語拡張です。更新操作と、オブジェクトのフィールドを変更する複数の setter 呼び出しを組み合わせます。構文スキーマは次の通りです。
modify ( <fact-expression> ) {
    <expression> [ , <expression> ]*
}

重要

括弧で囲まれた <fact-expression>ファクトオブジェクト参照 を生成しなければなりません。必ず、ブロックの式リストが該当するオブジェクトの setter 呼び出しで構成されるようにしてください (これらは、コンパイラーによって自動的に事前終了される通常のオブジェクト参照を用いずに記述されます)。
以下は、ファクトの変更の簡単な例になります。
rule "modify stilton"
when
    $stilton : Cheese(type == "stilton")
then
    modify( $stilton ){
        setPrice( 20 ),
        setAge( "overripe" )
    }
end

4.9. クエリ

query

図4.44 query

クエリは、示された条件に一致するファクトをワーキングメモリーで検索する最も単純な方法です。そのため、ルールの LHS 構造のみが含まれ、「when」や「then」は指定しません。クエリには任意のパラメーターセットがあり、各パラメーターを任意に型付けすることも可能です。型が指定されていない場合、Object 型と見なされます。エンジンは必要に応じて値を強制しようとします。クエリ名は KnowledgeBase に対してグローバルであるため、同じ名前のクエリを同じ RuleBase の異なるナレッジパッケージに追加しないでください。
結果を返すには ksession.getQueryResults("name") を使用し、"name" はクエリ名に置き換えます。これは、クエリと一致したオブジェクトを読み出せるクエリ結果のリストを返します。
最初の例は、年齢が 30 を超える人を問い合わせする単純なクエリです。2 つ目の例はパラメーターを使用して年齢制限と地域を組み合わせます。

例4.19 年齢が 30 を超える人を問い合わせるクエリー

query "people over the age of 30" 
    person : Person( age > 30 )
end

例4.20 年齢が x を超え、y に住む人を問い合わせるクエリー

query "people over the age of x"  (int x, String y)
    person : Person( age > x, location == y )
end
標準的な for ループを使用して返された QueryResults を繰り返し処理します。各要素は QueryResultsRow で、タプルの各列へアクセスするために使用します。これらの列は、バインドされた宣言名または索引の位置よりアクセスできます。

例4.21 年齢が 30 を超える人を問い合わせるクエリー

QueryResults results = ksession.getQueryResults( "people over the age of 30" );
System.out.println( "we have " + results.size() + " people over the age  of 30" );

System.out.println( "These people are are over 30:" );

for ( QueryResultsRow row : results ) {
    Person person = ( Person ) row.get( "person" );
    System.out.println( person.getName() + "
" );
}

4.10. ドメイン固有言語

ドメイン固有言語 (DSL) は、問題があるドメイン専用のルール言語を作成する方法です。DSL 定義のセットは、DSL 「センテンス」から DRL コンストラクトへの変換で構成され、基礎となるすべてのルール言語とエンジン機能の使用を可能にします。DSL では、ルールを DSL ルール (DSLR) ファイルに記述し、これらのファイルは DRL ファイルに変換されます。
DSL ファイルと DSLR ファイルはプレーンテキストファイルで、テキストエディターで作成または変更できます。DSL と DSLR は、統合開発環境と Business Rules Management System の Web ユーザーインターフェースの両方で使用できます。

4.10.1. ドメイン固有言語の使用

ドメイン固有言語には以下の利点があります。
  • ルールの作成と、engine が操作するドメインオブジェクトを区別するレイヤーとして機能します。これは、ドメインの専門家 (ビジネスアナリストなど) によってルールが読まれ、検証される必要がある場合に便利です。DSL は実装の詳細を隠し、ルール論理に集中します。
  • DSL センテンスは、ルールで繰り返し使用される条件要素と結果アクションのテンプレートとしても機能します。
  • DSL の実行時、ルールエンジンは影響を受けません。DSL はコンパイル時の機能であり、特別なパーサーとトランスフォーマーを必要とします。

4.10.2. ドメイン固有言語の作成

ドメイン固有言語の開発を開始する時に、次の点を考慮してください。
  • ドメイン固有言語の作成には、技術者とドメインの専門家の協力が必要となります。
  • 最初に、大きさと複雑さを把握するために、アプリケーションが必要なルールの例を記述します。
  • ルールにおける類似のステートメントと繰り返しのステートメントを特定し、変数の部分をパラメーターとしてマーク付けします。
  • 言語が開発されたら、ルールをテストします。
  • アプリケーションのデータモデルがデータ型をファクトとして表す場合、ルールの記述は通常容易になります。
  • 行の最初に大なり記号 (">") を付けて条件要素とアクションを DRL 形式のままにすると、初期設計段階で条件とアクションに関する実装の決定を延期することができます (これはデバッグのステートメントを挿入する際にも便利です)。
  • 既存の DSL 定義を再使用したり、既存の条件または結果エントリーにパラメーターを追加したりしてルールを記述することが可能です。
  • DSL エントリーの数をできるだけ少なくするようにしてください。パラメーターを使用すると、似たようなルールパターンや制約に同じ DSL センテンスを適用することができます。

4.10.3. ドメイン固有言語の管理

ドメイン固有言語の設定はプレーンテキストファイルに保存されます。
DSL のメカニズムにより、条件式と結果アクションをカスタマイズできます。グローバル置換メカニズムの キーワード も使用できます。
[when]Something is {color}=Something(color=="{color}")
上記の例には以下が適用されます。
  • [when] キーワードは、式のスコープを示します (ルールの LHS (左側) または RHS (右側) に有効であるかどうかなど)。
  • 括弧で囲まれたキーワードの後の部分は、ルールで使用する式になります。通常は自然言語の式ですが、自然言語以外の式でも可能です。
  • 最初の等号 = の右部分は、式のルール言語へのマッピングになります。この文字列の形式は、宛先、RHS、または LHS によって異なります。LHS の場合、通常の LHS 構文に基づいた用語となり、RHS の場合は Java ステートメントとなることがあります。
DSL パーサーが DSL で記述されたルールファイルの行と DSL 定義の式を照合する場合、文字列操作の 3 つの手順が実行されます。最初に、式の括弧で囲まれた変数名が含まれる場所に表示される文字列値が抽出されます (たとえば {color} など)。次に、括弧で囲まれた名前がマッピングの右側にある場合に、これらのキャプチャーから取得された値が補間されます。最後に、DSL ルールファイルの行にある式全体と一致する値を、補間された文字列が置き換えます。
式 (等号の左側にある文字列など) は、DSL ルールファイルの行に対するパターンマッチング操作の正規表現として使用され、行のすべてまたは一部と一致します。つまり、? を使用して先行する文字が任意であることを示すことができます。これにより、DSL の自然言語のフレーズの変異を解消できます。ただし、これらの式は正規表現のパターンであるため、Java のパターン構文の マジック 文字はすべてバックスラッシュ (\) でエスケープする必要があります。

注記

コンパイラーは DSL ルールファイルを行ごとに変換することに注意してください。上記の例では、「something is」の後から行の最後までのテキストがすべて {colour} の置換値としてキャプチャーされ、ターゲット文字列を補間するために使用されます。
異なる DSL 式をマージし、複合の DRL パターンを生成するには、複数の独立した操作で DSLR 行を変換する必要があります。キャプチャーされた値が必ず文字テキスト (単語や単一の文字) で囲まれるようにしてこれを行います。パーサーによって実行されたマッチング操作は、行からサブ文字列を抽出します。次の例では、引用符が示差的な文字として使用されています。キャプチャーされた値を囲むために使用する文字は補間中には含まれず、文字間の内容のみが含まれます。
ルールエディターが入力するテキストデータに対して引用符を使用します。キャプチャーされた値を単語で囲んで、テキストが適切に一致するようにすることも可能です。
例は次の通りです。
[when]This is "{something}" and "{another}"=Something(something=="{something}", another=="{another}")
[when]This is {also} valid=Another(something=="{also}")
ドメイン固有言語の式で句読点 (引用符以外) を使用しないようにしてください。DSL を使用するルール作成者は句読点を簡単に忘れてしまうことがあり、一部の句読点 (括弧、ピリオド、および疑問符) は DSL 定義をエスケープするために必要になります。
DSL マッピングでは、キャプチャーの結果となる変数定義や参照を囲むためのみに波括弧 ( { および }) を使用する必要があります。波括弧をそのまま式や右側の置換テキスト内で使用したい場合は、前にバックスラッシュ (\) を付けてエスケープする必要があります。
[then]do something= if (foo) \{ doSomething(); \}

注記

波括弧 ({ および }) を DSL 定義の置換文字列で表示する場合は、バックスラッシュ (\) でエスケープする必要があります。
DSL ルールの行からプレーンテキストをキャプチャーし、拡張で文字列リテラルとして使用したい場合は、マッピングの右側で引用符を使用する必要があります。

例4.22 DSL マッピングエントリー

#This is a comment to be ignored.
[when]There is a Person with name of "{name}"=Person(name=="{name}")
[when]Person is at least {age} years old and lives in "{location}"=Person(age >= {age}, location=="{location}")
[then]Log "{message}"=System.out.println("{message}");
[when]And = and
例4.22「DSL マッピングエントリー」 を前提とすると、次の例はさまざまな DSLR スニペットの拡張を表しています。

例4.23 DSL マッピングエントリーの拡張

There is a Person with name of "kitty" 
  ==> Person(name="kitty")
Person is at least 42 years old and lives in "Atlanta" 
  ==> Person(age > 42, location="Atlanta")
Log "boo" 
  ==> System.out.println("boo");
There is a Person with name of "Bob" and Person is at least 30 years old and lives in "Atlanta" 
  ==> Person(name="kitty") and Person(age > 30, location="Atlanta")

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

ルール条件を記述する場合、制約の任意の組み合わせをパターンへ追加できることが一般的な要件になります。ファクト型に多くのフィールドが存在する場合があるため、組み合わせごとに個別の DSL ステートメントを提供することは非常に困難になります。
DSL 機能を使用すると、ハイフン (-) を DSL 式の先頭に追加することでパターンに制約を追加することができます。式がハイフンで始まる場合、式はフィールド制約と見なされ、その前にある最後のパターン行に追加されます。
たとえば、Cheese クラスに typepriceage、および country のフィールドがある場合、次のように左側の条件の一部を通常の DRL ファイルに表現することが可能です。

例4.24 DRL の LHS 条件

Cheese(age < 5, price == 20, type=="stilton", country=="ch")
例4.25「制約の追加」 の DSL 定義により 3 つの DSL フェーズが生成され、これらのフィールドに関係する制約の組み合わせを作成するために使用することができます。

例4.25 制約の追加

[when]There is a Cheese with=Cheese()
[when]- age is less than {age}=age<{age}
[when]- type is '{type}'=type=='{type}'
[when]- country equal to '{country}'=country=='{country}'
次のように条件を持つルールを記述することができます。

例4.26 制約の記述

There is a Cheese with
        - age is less than 42
        - type is 'stilton'
parser- で始まる行を見つけ、制約として先行のパターンに追加し、必要な場合にコンマを挿入します。 例4.26「制約の記述」 の例では、結果となる DRL は次のようになります。
Cheese(age<42, type=='stilton')
すべての数値フィールドをすべての関係演算子と組み合わせると (前の例の DSL「age is less than」に従う)、多くの 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()
これらの定義は条件をテキストで記述することが可能であること意味します (is less than など)。

注記

個別の DSL 式が同じ行を順に照合する場合、DSL のエントリーの順序が重要になります。

注記

フィールド制約のフィルタリングされたリストを Context Assistant で表示させるには、- を押した後に Ctrl+Space を押してこのリストから項目を選択します。
最初の項目のドメイン固有コードを [when][org.drools.Cheese]- age is less than {age} のように変更します。同じことを、上記の例の全項目に対して行います。
追加の [org.drools.Cheese] コードは、文が直上の主要な制約にのみ適用されることを表しています (この場合は There is a Cheese with)。
たとえば、Cheese というクラスが存在し、Content Assistance を用いた方法で追加される場合、com.yourcompany.Something オブジェクトスコープでマーク付けされた項目のみが有効となり、リストに表示されます。これは完全に任意となります。

4.10.5. DSL および DSLR 参照

DSL ファイルは行試行形式のテキストファイルです。エントリーは、DSLR ファイルを DRL 構文のファイルへ変換するために使用されます。
  • # または // 始まる行はコメントとして扱われます (先頭に空白文字がある場合とない場合の両方)。#/ で始まるコメント行は、デバッグオプションを要求する単語に対してスキャンされます。
  • 左角括弧 [ で始まる行は DSL エントリー定義の最初の行と見なされます。
  • 他の行はすべて先行する DSL エントリー定義に付加され、行端が空白文字に置き換えられます。
DSL エントリーは次の 4 つの部分で構成されます。
  • スコープの定義
    • [condition] または [when]
    • [consequence] または [then]
    • [keyword] (rule または end など)。
      キーワードは、グローバルであるかどうか (DSLR ファイル全体で認識されるなど)、エントリーのスコープを示します。
  • Java クラス名として記述されたタイプ定義は括弧で囲まれます。次の部分が左括弧で始まる場合を除き、この部分は任意となります。括弧の中が空の場合は無効になります。
  • DSL 式は (Java) 正規表現で構成され、任意数の組み込み 点数定義 を持ち、等号 = で終了します。変数定義は波括弧 { および } で囲まれます。変数定義は変数名と 2 つの任意のアタッチメントで構成され、コロン : で区切られます。アタッチメントが 1 つある場合、変数に割り当てられるテキストを一致する正規表現となります。2 つのアタッチメントがある場合、最初のアタッチメントは GUI エディターのヒントで、2 つ目が正規表現になります。
    正規表現で magic である文字を式の中で文字通り使用する場合、その文字の前にバックスラッシュ \ を付けてエスケープする必要があることに注意してください。
  • 区切り等号の後にある行の残りの部分は、正規表現に一致するすべての DSLR テキストの置換テキストになります。これには、波括弧で囲まれた変数名など、変数参照が含まれるようにすることもできます。任意で、変数名の後に感嘆符 (!) と変換関数を使用することもできます。
    置換文字列内で波括弧 { および } を文字通り使用する場合、括弧の前にバックスラッシュ \ を付けてエスケープする必要があることに注意してください。
DSL 拡張のデバッグは #/ で始まるコメント行を使用して選択的に有効にすることができます。このコメント行には下表の単語が 1 つ以上含まれることがあります。結果となる出力は標準出力に書き込まれます。

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

単語 説明
result 結果となる DRL テキストを行番号と共に出力します。
steps 条件および結果行の各拡張ステップを出力します。
keyword スコープ keyword を持つすべての DSL エントリーの内部表現をダンプします。
when スコープ when または * を持つすべての DSL エントリーの内部表現をダンプします。
then スコープ then または * を持つすべての DSL エントリーの内部表現をダンプします。
usage すべての 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} )\{ \}

4.10.6. DSLR ファイルのトランスフォーメーション

DSLR ファイルのトランスフォーメーションの順序は次の通りです。
  1. テキストがメモリーへ読み込まれます。
  2. keyword エントリーがテキスト全体に適用されます。最初に、空白文字シーケンスを任意数の空白文字と一致するパターンに置き換え、変数定義をデフォルトの ".*? または定義で提供される正規表現より作成されたキャプチャーに置き換え、キーワード定義からの正規表現が変更されます。次に、DSLR テキストにて、変更された正規表現と一致する文字列が徹底的に検索されます。さらに、変数キャプチャーに対応する一致する文字列のサブ文字列が抽出され、対応する置換テキストの変数参照を置き換えます。DSLR テキストの一致する文字列はこのテキストによって置き換えられます。
  3. whenthen の間にある DSLR テキストは、均一に行ごとに検索されます。 thenend の間にある DSLR テキストは、均一に行ごとに処理されます。
    行では、行のセクションに関係する各 DSL エントリーが、DSL ファイルに記述されている順番通りに取得されます。正規表現の部分は変更されます。空白文字は任意数の空白文字と一致するパターンによって置き換えられ、正規表現を持つ変数定義はこの正規表現を持つキャプチャーによって置き換えられます (デフォルトは .*? になります)。結果となる正規表現が行のすべてまたは一部と一致すると、一致した部分が適切に変更された置換テキストによって置き換えられます。
    変数参照を正規表現のキャプチャーに対応するテキストに置き換えることで置換テキストが変更されます。このテキストは、変数参照に提供される文字列変換関数に従って変更することができます。
    同じエントリーで定義されない変数を命名する変数参照がある場合、その名前の変数にバインドされた値をエキスパンダーが置換します (現在のルールの前にある行の 1 つに定義されている場合)。
  4. 条件の DSLR 行がハイフンで始まる場合、拡張された結果は最後の行に挿入されます。最後の行にはパターン CE (例: 型名の後に左右の括弧が続く) が含まれる必要があります。括弧内が空白である場合、拡張された行 (有効な制約が含まれている) が挿入されます。これ以外の場合、コンマ (,) がその前に挿入されます。
    結果の DSLR 行がハイフンで始まる場合、拡張された結果は最後の行に挿入されます。最後の行には modify ステートメントが含まれるはずで、左右の波括弧 { および } で終わります。括弧内が空白である場合、拡張された行 (有効なメソッド呼び出しが含まれる) が挿入されます。これ以外の場合、コンマ (,) がその前に挿入されます。

注記

現在、ハイフンで始まる行を使用して他の条件要素形式 (accumulate など) にテキストを挿入することは できません。最初の挿入のみ実行されることはあります (eval など)。

4.10.7. 文字列トランスフォメーション関数

表4.3「文字列トランスフォーメーション関数」 にすべての文字列トランスフォーメーション関数が記載されています。

表4.3 文字列トランスフォーメーション関数

名前 説明
uc すべての文字を大文字に変換します。
lc すべての文字を小文字に変換します。
ucfirst 最初の文字を大文字に変換し、他の文字はすべて小文字に変換します。
num すべての数字と - を文字列から抽出します。元の文字列の最後にある 2 つの数字の前に . または , がある場合、少数点が対応する場所に挿入されます。
a?b/c 文字列と文字列 a を比較し、同等であれば b と置き換え、同等でなければ c と置き換えます。ただし、c は別の abc の 3 つ組である可能性もあるため、構造全体が実際にはトランスレーションテーブルになります。
次の DSL の例は、文字列トランスフォーメーション関数の使用方法を表しています。

例4.27 DSL 文字列トランスフォーメーション関数

# definitions for conditions
[when][]There is an? {entity}=${entity!lc}: {entity!ucfirst}()
[when][]- with an? {attr} greater than {amount}={attr} <= {amount!num}
[when][]- with a {what} {attr}={attr} {what!positive?>0/negative?%lt;0/zero?==0/ERROR}
DSL 定義が含まれるファイルには、慣例的に .dsl が拡張子して使用されます。これは、ResourceType.DSL で Knowledge Builder へ渡されます。DSL 定義を使用するファイルでは、.dslr を拡張子として使用すべきです。Knowledge Builder は ResourceType.DSLR を想定します。しかし、 IDE はファイル拡張子に依存してルールファイルを正しく認識し、ルールファイルと動作します。
DSL は、DSL を使用するルールファイルよりも前に Knowledge Builder へ渡す必要があります。
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 );
DSLR ファイルの解析および拡張では、DSL 設定が読み取られ、パーサーへ提供されます。パーサーは DSL 式を認識し、ネイティブのルール言語式に変換します。

4.10.8. BRMS および IDE におけるドメイン固有言語

Guided Editor を使用してルールを開発する場合でも、ドメイン固有言語を使用することができます。

重要

Guided Editor は複雑な式を処理できないことがあるため、できるだけ簡単な式を使用するようにしてください。
Guided Editor を使用すると、小さなデータキャプチャーのテキストフィールド「形式」を定義できます (ドメイン固有言語式を選択すると、GUI に項目が追加され、{token} へのみデータの入力が許可されます)。
パッケージが BRMS でビルドされる時にドメイン固有言語は自動的に追加されます。
等号開発環境にドメイン固有言語を追加するには、drools-ant タスクを使用するか、「XML ルール言語」 のようにコードを組み込みます。

4.11. XML ルール言語

JBoss Rules は任意で「ネイティブ」XML ルール言語を DRL の代替としてサポートします。これにより、ルールを XML データとしてキャプチャーおよび管理することができます。非 XML の DRL 形式と同様に、XML 形式は内部「AST」表現へできるだけ早く (SAX パーサーを使用) 解析されます。外部のトランスフォーメーション手順は必要ありません。すべての機能は DRL が使用できる XML で使用可能です。

4.11.1. XML を使用する場合

XML の使用が推奨されるケースは複数あります。ただし、XML は人間が判読することが難しく、視覚的に肥大化したルールを作成できるため、デフォルトとして選択しないことが推奨されます。
他にも、入力 (プログラミングで生成されたルール) よりルールを生成するツールがある場合や、他のルール言語または XML を出力する他のツールから交換するツールがある場合に XML を使用したいことがあります (XSLT を使用すると XML 形式同士を簡単に変換できます)。通常の DRL は常に生成することができます。
また、すでに設定で XML を使用する製品に JBoss Rules を組み込むこともできるため、ルールを XML 形式にしたいこともあります。XML で独自のルール言語を作成することができます。独自のルール言語を作成するため、常に AST オブジェクトを直接使用できることに注意してください (オープンアーキテクチャーであるため多数のオプションがあります)。

4.11.2. XML 形式

XML 言語を定義する完全な W3C 標準 (XML スキーマ) に準拠する XSD が提供されます (ここでは繰り返し説明しません)。言語の概要は次の通りです。
<?xml version="1.0" encoding="UTF-8"?>

<package name="com.sample"
  xmlns="http://drools.org/drools-4.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
  xs:schemaLocation="http://drools.org/drools-4.0 drools-4.0.xsd">

  <import name="java.util.HashMap" />
  <import name="org.drools.*" />

  <global identifier="x" type="com.sample.X" />
  <global identifier="yada" type="com.sample.Yada" />

  <function return-type="void" name="myFunc">
    <parameter identifier="foo" type="Bar" />
    <parameter identifier="bada" type="Bing" />
    <body>System.out.println("hello world");</body>
  </function>

  <rule name="simple_rule">
    <rule-attribute name="salience" value="10" />
    <rule-attribute name="no-loop" value="true" />
    <rule-attribute name="agenda-group" value="agenda-group" />
    <rule-attribute name="activation-group" value="activation-group" />

    <lhs>
      <pattern identifier="foo2" object-type="Bar" >
        <or-constraint-connective>
          <and-constraint-connective>
            <field-constraint field-name="a">
              <or-restriction-connective>
                <and-restriction-connective>
                  <literal-restriction evaluator=">" value="60" />
                  <literal-restriction evaluator="<" value="70" />
                </and-restriction-connective>
                <and-restriction-connective>
                  <literal-restriction evaluator="<" value="50" />
                  <literal-restriction evaluator=">" value="55" />
                </and-restriction-connective>
              </or-restriction-connective>
            </field-constraint>

            <field-constraint field-name="a3">
              <literal-restriction evaluator="==" value="black" />
            </field-constraint>
          </and-constraint-connective>

          <and-constraint-connective>
            <field-constraint field-name="a">
              <literal-restriction evaluator="==" value="40" />
            </field-constraint>

            <field-constraint field-name="a3">
              <literal-restriction evaluator="==" value="pink" />
            </field-constraint>
          </and-constraint-connective>

          <and-constraint-connective>
            <field-constraint field-name="a">
              <literal-restriction evaluator="==" value="12"/>
            </field-constraint>

            <field-constraint field-name="a3">
              <or-restriction-connective>
                <literal-restriction evaluator="==" value="yellow"/>
                <literal-restriction evaluator="==" value="blue" />
              </or-restriction-connective>
            </field-constraint>
          </and-constraint-connective>
        </or-constraint-connective>
      </pattern>

      <not>
        <pattern object-type="Person">
          <field-constraint field-name="likes">
            <variable-restriction evaluator="==" identifier="type"/>
          </field-constraint>
        </pattern>

        <exists>
          <pattern object-type="Person">
            <field-constraint field-name="likes">
              <variable-restriction evaluator="==" identifier="type"/>
            </field-constraint>
          </pattern>        
        </exists>
      </not>

      <or-conditional-element>
        <pattern identifier="foo3" object-type="Bar" >
          <field-constraint field-name="a">
            <or-restriction-connective>
              <literal-restriction evaluator="==" value="3" />
              <literal-restriction evaluator="==" value="4" />
            </or-restriction-connective>
          </field-constraint>
          <field-constraint field-name="a3">
            <literal-restriction evaluator="==" value="hello" />
          </field-constraint>
          <field-constraint field-name="a4">
            <literal-restriction evaluator="==" value="null" />
          </field-constraint>
        </pattern>

        <pattern identifier="foo4" object-type="Bar" >
          <field-binding field-name="a" identifier="a4" />
          <field-constraint field-name="a">
            <literal-restriction evaluator="!=" value="4" />
            <literal-restriction evaluator="!=" value="5" />
          </field-constraint>
        </pattern>
      </or-conditional-element>

      <pattern identifier="foo5" object-type="Bar" >
        <field-constraint field-name="b">
          <or-restriction-connective>
            <return-value-restriction evaluator="==" >
              a4 + 1
            </return-value-restriction>
            <variable-restriction evaluator=">" identifier="a4" />
            <qualified-identifier-restriction evaluator="==">
              org.drools.Bar.BAR_ENUM_VALUE
            </qualified-identifier-restriction>
          </or-restriction-connective>
        </field-constraint>      
      </pattern>

      <pattern identifier="foo6" object-type="Bar" >
        <field-binding field-name="a" identifier="a4" />
        <field-constraint field-name="b">
          <literal-restriction evaluator="==" value="6" />
        </field-constraint>
      </pattern>
    </lhs>
    <rhs>
      if ( a == b ) {
      assert( foo3 );
      } else {
      retract( foo4 );
      }
      System.out.println( a4 );
    </rhs>
  </rule>

</package>
最初の XML テキストには一般的な XML 要素、パッケージ宣言、インポート、グローバル、関数、およびルール自体があります。JBoss Rules の機能についてある程度理解している場合は、説明がなくてもほとんどの要素について理解できるはずです。
import 要素はルールで使用したい型をインポートします。
global 要素はルールで参照できるグローバルオブジェクトを定義します。
function にはルールで使用される関数のための関数宣言が含まれます。戻り型、一意の名前、およびパラメーターをコードのスニペットのボディーに指定する必要があります。
ルールは以下で説明します。

例4.28 rule 要素の詳細

<rule name="simple_rule">
<rule-attribute name="salience" value="10" />
<rule-attribute name="no-loop" value="true" />
<rule-attribute name="agenda-group" value="agenda-group" />
<rule-attribute name="activation-group" value="activation-group" />

<lhs>
    <pattern identifier="cheese" object-type="Cheese">
        <from>
            <accumulate>
                <pattern object-type="Person"></pattern>
                <init>
                    int total = 0;
                </init>
                <action>
                    total += $cheese.getPrice();
                </action>
                <result>
                    new Integer( total ) );
                </result>
            </accumulate>
        </from>
    </pattern>

    <pattern identifier="max" object-type="Number">
        <from>
            <accumulate>
               <pattern identifier="cheese" object-type="Cheese"></pattern>
               <external-function evaluator="max" expression="$price"/>
            </accumulate>
        </from>
    </pattern>
</lhs>
<rhs>
    list1.add( $cheese );
</rhs>
</rule>
上記のルールの詳細では、ルールには LHS と RHS (条件と結果) のセクションがあります。RHS は単純で、ルールがアクティベートされた時に実行される意味論コードのブロックになります。LHS は若干複雑で、条件要素、制約、および制限のネストされた要素が含まれます。
LHS の主要な要素は Pattern 要素です。これにより、型 (クラス) を指定したり、そのクラスのインスタンスへ変数をバインドすることができます。満たされなければならない制約や制限はパターンオブジェクト下でネストされます。述部や戻り値の制約により、Java 式を組み込むことができます。
これにより、not、exists、and などの条件要素が残ります。これらは DRL のように挙動します。以下にネストされた要素と「and」要素は論理的に「and」によって結合されます。「or」も同様です (さらにネストすることができます)。「Exists」と「Not」はパターンを回避して、パターンの制約を満たすファクトが存在するかどうかをチェックします。
Eval 要素を使用すると、ブール値を評価する限り、Java コードの有効なスニペットを実行することが可能です (断片であるため、ブール値をセミコロンで終了しないでください)。これに、関数の呼び出しが含まれるようにすることも可能です。ルールエンジンが毎回評価を行うため、Eval は列よりも非効率的ですが、列の制約で何を行う必要があるかを表現できる時の「キャッチオール」機能です。

4.11.3. 形式 (XML と DRL) 間の自動転換

JBoss Rules には、ある形式から他の形式へ変換するためのユーティリティークラスがいくつか含まれています。これは、ルールをソース形式から AST へ解析し、適切なターゲット形式へ「ダンピング」することによって機能します。これにより、ルールを DRL で記述し、必要に応じて将来的に XML にエクスポートすることが可能です。
この作業を行う必要がある時に注意すべきクラスは次の通りです。
XmlDumper - XML のエキスポート。
DrlDumper - DRL のエキスポート
DrlParser - DRL の読み取り。
XmlPackageReader - XML の読み取り。
上記のクラスを組み合わせると、任意の形式間で変換を行うことが可能です (ラウンドトリップを含む)。DSL は保持されませんが (DSL を使用する DRL から)、変換できることに注意してください。
XSLT を自由に使用して、さまざまな XML の可能性を提供してください。XSLT やその種類は XML を強化します。