第2章 DRL (Drools Rule Language) ルール言語
DRL (Drools Rule Language) ルールは、.drl テキストファイルに直接定義するビジネスルールです。これらの DRL ファイルは、Business Central 内の他のすべてのルールアセットが最終的にレンダリングされるソースとなります。Business Central インターフェースで DRL ファイルを作成して管理するか、または Red Hat CodeReady Studio や別の統合開発環境 (IDE) を使用して Maven または Java プロジェクトの一部として外部で作成することができます。DRL ファイルには、最低でもルールの条件 (when) およびアクション (then) を定義するルールを 1 つ以上追加できます。Business Central の DRL デザイナーは、Java、DRL、および XML の構文を強調表示します。
DRL ファイルは、以下のコンポーネントで構成されます。
DRL ファイル内のコンポーネント
package
import
function // Optional
query // Optional
declare // Optional
global // Optional
rule "rule name"
// Attributes
when
// Conditions
then
// Actions
end
rule "rule2 name"
...
以下の DRL ルールの例では、ローン申し込みのデシジョンサービスで年齢制限を指定します。
申込者の年齢制限に関するルールの例
rule "Underage"
salience 15
agenda-group "applicationGroup"
when
$application : LoanApplication()
Applicant( age < 21 )
then
$application.setApproved( false );
$application.setExplanation( "Underage" );
end
DRL ファイルには、ルール、クエリー、関数が 1 つまたは複数含まれており、このファイルで、ルールやクエリーで割り当て、使用するインポート、グローバル、属性などのリソース宣言を定義できます。DRL パッケージは、DRL ファイルの一番上に表示し、ルールは通常最後に表示されます。他の DRLコンポーネントはどのような順番でも構いません。
ルールごとに、ルールパッケージ内で一意の名前を指定する必要があります。パッケージ内の DRL ファイルで、同じルール名を複数回使用すると、ルールのコンパイルに失敗します。特にルール名にスペースを使用する場合など、ルール名には必ず二重引用符 (rule "rule name") を使用して、コンパイルエラーが発生しないようにしてください。
DRL ルールに関連するデータオブジェクトはすべて、DRL ファイルと同じ Business Central のプロジェクトパッケージに置く必要があります。同じパッケージのアセットはデフォルトでインポートされます。その他のパッケージの既存アセットは、DRL ルールを使用してインポートできます。
2.1. DRL のパッケージ
パッケージは、データオブジェクト、DRL ファイル、デシジョンテーブル、他のアセットタイプなど、Red Hat Process Automation Manager に含まれる関連アセットをまとめたフォルダーです。また、パッケージは、各ルールグループに固有の namespace としても機能します。1 つのルールベースには、複数のパッケージを含めることができます。通常、パッケージだけで自己完結できるように、パッケージのすべてのルールはパッケージ宣言と同じファイルに保存します。ただし、対象のルールで使用するのに、他のパッケージからオブジェクトをインポートできます。
以下は、ローン申請デシジョンサービスの DRL ファイルのパッケージ名と namespace の例です。
DRL ファイルのパッケージ定義例
package org.mortgages;
2.2. DRL のインポートステートメント
Java のインポートステートメントと同様に、DRL ファイルのインポートで、ルールで使用するオブジェクトの完全修飾パスとタイプ名を識別します。packageName.objectName の形式でパッケージとデータオブジェクトを指定し、複数のインポートを指定する場合には別の行に指定します。デシジョンエンジンは自動的に、DRL パッケージと同じ名前の Java パッケージおよび、java.lang パッケージからクラスを自動的にインポートします。
以下の例は、住宅ローン申請デシジョンテーブルに含まれるローン申請オブジェクトのインポートステートメントです。
DRL ファイルのインポートステートメント例
import org.mortgages.LoanApplication;
2.3. DRL の機能
DRL ファイルの関数は、Java クラスではなく、ルールのソースファイルにセマンティックコードを追加します。関数は、特に、ルールのアクション (then) 部分が繰り返し使用され、パラメーターだけがルールごとに異なる場合に便利です。DRL ファイルのルールで、関数を宣言したり、ヘルパークラスから静的メソッドを関数としてインポートしたりすることで、ルールのアクション (then) 部分で、名前を指定して関数を使用できます。
以下は、DRL ファイルで宣言またはインポートされる関数の例です。
ルールを含む関数宣言の例 (オプション 1)
function String hello(String applicantName) {
return "Hello " + applicantName + "!";
}
rule "Using a function"
when
// Empty
then
System.out.println( hello( "James" ) );
end
ルールを含む関数インポートの例 (オプション 2)
import function my.package.applicant.hello;
rule "Using a function"
when
// Empty
then
System.out.println( hello( "James" ) );
end
2.4. DRL のクエリー
DRL ファイルのクエリーは、デシジョンエンジンのワーキングメモリーで DRL ファイル内のルールに関連するファクトを検索します。DRL ファイルにクエリー定義を追加してから、アプリケーションコードで一致する結果を取得します。クエリーは、定義された条件セットを検索するため、when または then を指定する必要はありません。クエリー名は KIE ベースでグローバルとなるため、プロジェクト内の他のすべてのルールクエリーと重複しないように固有の名前にする必要があります。クエリーの結果を返すには、ksession.getQueryResults("name") を使用して QueryResults 定義を構成します ("name" はクエリー名)。これにより、クエリーの結果の一覧が返され、クエリーに一致したオブジェクトを取得できるようになります。DRL ファイルのルールに、クエリーとクエリー結果パラメーターを定義します。
以下は、ローン申請デシジョンサービスの未成年の申請者に関する DRL ファイルのクエリー定義と、付属のアプリケーションコードの例です。
DRL ファイルにおけるクエリー定義の例
query "people under the age of 21"
$person : Person( age < 21 )
end
クエリー結果を取得するためのアプリケーションコードの例
QueryResults results = ksession.getQueryResults( "people under the age of 21" ); System.out.println( "we have " + results.size() + " people under the age of 21" );
標準的な for ループを使用して、返される QueryResults を反復処理できます。各要素は QueryResultsRow で、これを使用してタプルの各列にアクセスできます。
クエリー結果を取得し、反復処理するためのアプリケーションのコード例
QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age of 21" );
System.out.println( "These people are under the age of 21:" );
for ( QueryResultsRow row : results ) {
Person person = ( Person ) row.get( "person" );
System.out.println( person.getName() + "\n" );
}
2.5. DRL でのタイプ宣言とメタデータ
DRL ファイルの宣言は、DRL ファイルのルールで使用するファクトタイプまたはメタデータを新たに定義します。
-
新規ファクトタイプ: Red Hat Process Automation Manager に含まれる
java.langパッケージのデフォルトのファクトタイプはObjectですが、必要に応じて DRL ファイルで他のタイプを宣言できます。DRL ファイルでファクトタイプを宣言すると、Java などの低級言語でモデルを作成せず、デシジョンエンジンで新しいファクトモデルを直接定義することができます。また、ドメインモデルがすでにビルドされていて、推論 (reasoning) のプロセスで主に使用する追加のエンティティーでこのモデルを補完する場合に、新規タイプを宣言することもできます。 -
ファクトタイプのメタデータ:
@key(value)形式のメタデータを新規または既存のファクトを関連付けることができます。メタデータには、ファクト属性で表現されない、該当のファクトタイプのすべてのインスタンス間で一貫性のあるすべての種類のデータを使用できます。メタデータはランタイム時に、デシジョンエンジンでクエリーでき、推論 (reasoning) のプロセスで使用できます。
2.5.1. DRL のメタデータを使用しないタイプ宣言
新規ファクトの宣言にメタデータは必要ありませんが、属性またはフィールドの一覧を含める必要があります。タイプ宣言に指定属性が含まれていない場合には、デシジョンエンジンはクラスパス内で既存のファクトクラスを検索し、クラスが見つからない場合はエラーを出します。
以下の例は、DRL ファイルにメタデータがない新規ファクトタイプ Person の宣言です。
ルールが 1 つ含まれる新規ファクトタイプの宣言の例
declare Person
name : String
dateOfBirth : java.util.Date
address : Address
end
rule "Using a declared type"
when
$p : Person( name == "James" )
then // Insert Mark, who is a customer of James.
Person mark = new Person();
mark.setName( "Mark" );
insert( mark );
end
この例では、新規ファクトタイプ Person には name、dateOfBirth、および address の 3 つの属性が含まれます。属性ごとにタイプがあり、このタイプには作成する別のクラスや以前に宣言したファクトタイプなどの、有効な Java タイプを指定できます。 dateOfBirth 属性には、Java API からの java.util.Date タイプがあり、address 属性には、以前に定義したファクトタイプ Address が含まれます。
宣言するたびにクラスの完全修飾名が記述されないように、完全なクラス名を import 句の一部として定義できます。
インポートの完全修飾クラス名でのタイプ宣言例
import java.util.Date
declare Person
name : String
dateOfBirth : Date
address : Address
end
新規のファクトタイプを宣言すると、デシジョンエンジンはコンパイル時にファクトタイプを表す Java クラスを生成します。生成される Java クラスはタイプ定義の一対一の JavaBeans マッピングとなります。
たとえば、以下の Java クラスはPerson タイプ宣言例から生成されます。
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` and `hashCode`
// `toString`
}
Person タイプ宣言が含まれる以前のルールの例が示すように、他のファクトと同様にルールで生成したクラスを使用できます。
宣言された Person ファクトタイプを使用するルールの例
rule "Using a declared type"
when
$p : Person( name == "James" )
then // Insert Mark, who is a customer of James.
Person mark = new Person();
mark.setName( "Mark" );
insert( mark );
end
2.5.2. DRL の列挙タイプの宣言
DRL は、declare enum <factType> 形式の列挙タイプの宣言をサポートします。列挙タイプの後にはコンマ区切りの値の一覧が続き、最後はセミコロンで終了します。これにより、DRL ファイルのルールで列挙リストを使用できます。
たとえば、以下の列挙タイプの宣言では、従業員スケジュールルールの曜日を定義します。
スケジュールルールを使用した列挙タイプ宣言の例
declare enum DaysOfWeek
SUN("Sunday"),MON("Monday"),TUE("Tuesday"),WED("Wednesday"),THU("Thursday"),FRI("Friday"),SAT("Saturday");
fullName : String
end
rule "Using a declared Enum"
when
$emp : Employee( dayOff == DaysOfWeek.MONDAY )
then
...
end
2.5.3. DRL の拡張タイプ宣言
DRL は、declare <factType1> extends <factType2> 形式のタイプ宣言の継承をサポートします。DRL で宣言したサブタイプで Java で宣言したタイプを拡張するには、フィールドなしの宣言ステートメントで親タイプを繰り返し使用します。
たとえば、以下のタイプ宣言はトップレベルの Person タイプから Student タイプを拡張し、さらに Student サブタイプから LongTermStudent タイプを拡張します。
拡張タイプ宣言の例
import org.people.Person
declare Person end
declare Student extends Person
school : String
end
declare LongTermStudent extends Student
years : int
course : String
end
2.5.4. DRL のメタデータが含まれるタイプ宣言
@key(value) (値は任意) 形式のメタデータをファクトタイプまたはファクト属性に関連付けることができます。メタデータには、ファクト属性で表現されていない、該当のファクトタイプのすべてのインスタンス間で一貫性のあるすべての種類のデータを使用できます。メタデータはランタイム時に、デシジョンエンジンでクエリーでき、推論 (reasoning) のプロセスで使用できます。ファクトタイプの属性の前に宣言するメタデータはファクトタイプに割り当てられ、属性の後に宣言するメタデータはこの特定の属性に割り当てられます。
以下の例では、@author と @dateOfCreation のメタデータ属性 2 つが Person ファクトタイプに、@key と @maxLength のメタデータアイテム 2 つが name 属性に割り当てられています。@key メタデータ属性には必須の値がないので、括弧と値は省略されます。
ファクトタイプおよび属性のメタデータ宣言例
import java.util.Date
declare Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
name : String @key @maxLength( 30 )
dateOfBirth : Date
address : Address
end
既存タイプのメタデータ属性を宣言する場合には、すべての宣言の import 句の一部として、または個別の declare 句の一部として完全修飾クラス名を特定できます。
インポートタイプのメタデータ宣言の例
import org.drools.examples.Person
declare Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
end
宣言タイプのメタデータ宣言例
declare org.drools.examples.Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
end
2.5.5. DRL でのファクトタイプと属性宣言のメタデータタグ
DRL 宣言でカスタムメタデータ属性を定義することはできますが、デシジョンエンジンはファクトタイプまたはファクトタイプ属性の宣言についての、以下の事前定義メタデータタグもサポートします。
VoiceCall クラスを参照する本セクションの例では、サンプルアプリケーションドメインモデルに以下のクラスの詳細が含まれていることを前提としています。
Telecom ドメインモデルの例における VoiceCall ファクトクラス
public class VoiceCall {
private String originNumber;
private String destinationNumber;
private Date callDateTime;
private long callDuration; // in milliseconds
// Constructors, getters, and setters
}
- @role
このタグは、指定のファクトタイプが複雑なイベントの処理時にデシジョンエンジンにて通常のファクトまたはイベントとして処理されるかどうかを決定します。
デフォルトパラメーター:
factサポート対象のパラメーター:
fact、event@role( fact | event )
例: イベントタイプとして VoiceCall の宣言
declare VoiceCall @role( event ) end
- @timestamp
このタグは、デシジョンエンジンのすべてのイベントに自動的に割り当てられます。デフォルトでは、タイムはセッションクロックにより提供され、デシジョンエンジンのワーキングメモリーへの挿入時にイベントに割り当てられます。セッションクロックが追加するデフォルトのタイムスタンプの代わりに、カスタムのタイムスタンプ属性を指定することができます。
デフォルトパラメーター: デシジョンエンジンのセッションクロックが追加する時間
サポート対象のパラメーター: セッションクロックタイムまたはカスタムのタイムスタンプ属性
@timestamp( <attributeName> )
例: VoiceCall のタイムスタンプ属性の宣言
declare VoiceCall @role( event ) @timestamp( callDateTime ) end
- @duration
このタグは、デシジョンエンジンのイベントの持続期間を決定します。イベントは、interval-based イベントまたは point-in-time イベントのいずれかになります。interval-based イベントには持続期間があり、この持続期間が経過するまでデシジョンエンジンのワーキングメモリーで持続します。point-in-time イベントには持続期間はなく、基本的には期間の時間単位が 0 の interval-based イベントと同じです。デフォルトでは、デシジョンエンジンのすべてのイベントの持続期間は 0 です。デフォルトの代わりに、カスタムの持続期間属性を指定することができます。
デフォルトパラメーター: null (ゼロ)
サポート対象のパラメーター: カスタムの持続期間属性
@duration( <attributeName> )
例: VoiceCall の持続期間属性の宣言
declare VoiceCall @role( event ) @timestamp( callDateTime ) @duration( callDuration ) end
- @expires
このタグは、デシジョンエンジンのワーキングメモリーでイベントの有効期限が切れるまでの時間を決定します。デフォルトでは、イベントは現在のルールのいずれにも一致せず、それらのいずれもアクティベートできなくなった時点で失効します。イベント失効後の期間を定義できます。また、このタグの定義は、KIE ベースの一時的な制約やスライディングウィンドウから算出した暗黙的な有効期限のオフセットもオーバーライドします。デシジョンエンジンがストリームモードで実行中の場合にのみ、このタグを使用できます。
デフォルトパラメーター: null (イベントがルールに一致せず、ルールをアクティブにできなくなるとイベントの有効期限が切れる)
サポート対象のパラメーター:
[#d][#h][#m][#s][[ms]]形式のカスタムのtimeOffset属性@expires( <timeOffset> )
例: VoiceCall イベントに対する有効期限のオフセットの宣言
declare VoiceCall @role( event ) @timestamp( callDateTime ) @duration( callDuration ) @expires( 1h35m ) end
- @typesafe
このタグは、型安全性を有効して/有効にせずに指定のファクトタイプをコンパイルするかどうかを決定します。デフォルトでは、すべてのタイプ宣言は型安全性が有効な状態でコンパイルされます。この動作を type-unsafe の評価にオーバーライドすることもできます。type-unsafe の評価の場合、すべての制約は MVEL 制約として生成され、動的に実行されます。これは、一般的なコレクションではない場合や、タイプが混同されているコレクションを処理する場合に便利です。
デフォルトパラメーター:
trueサポート対象のパラメーター:
true、false@typesafe( <boolean> )
例: type-unsafe 評価の VoiceCall の宣言
declare VoiceCall @role( fact ) @typesafe( false ) end
- @serialVersionUID
このタグは、ファクト宣言でシリアル化可能なクラスの
serialVersionUIDの指定値を定義します。シリアル化可能なクラスで明示的にserialVersionUIDが宣言されていない場合には、Java Object Serialization Specification に記載されているように、シリアル化のランタイムが、各種のクラスのさまざまな側面に基づいてそのクラスのデフォルトのserialVersionUID値を計算します。ただし、デシリアル化の結果を最適化し、シリアル化した KIE セッションの互換性を向上するには、関連のクラスや DRL 宣言の要件に応じてserialVersionUIDを設定します。デフォルトパラメーター: Null
サポート対象のパラメーター: カスタムの
serialVersionUID整数@serialVersionUID( <integer> )
例: VoiceCall クラスの serialVersionUID の宣言
declare VoiceCall @serialVersionUID( 42 ) end
- @key
このタグを指定すると、ファクトタイプ属性をファクトタイプのキー識別子として使用できるようになります。生成されたクラスは
equals()とhashCode()メソッドを実装できるようになり、該当タイプの 2 つのインスタンスが同等であるかを判別できるようになります。デシジョンエンジンは、すべてのキー属性をパラメーターとして使用してコンストラクターを生成することもできます。デフォルトパラメーター: なし
サポート対象のパラメーター: なし
<attributeDefinition> @key
例: Person タイプ属性をキーとして宣言する
declare Person firstName : String @key lastName : String @key age : int endこの例では、デシジョンエンジンは
firstNameとlastName属性をチェックして、Personの 2 つのインスタンスが同等であるかどうかを判断しますが、age属性はチェックしません。また、このデシジョンエンジンは暗黙的に 3 つのコンストラクターを生成します。1 つはパラメーターなし、もう 1 つ目は@keyフィールドのあるコンストラクター、3 つ目はすべてのフィールドのあるコンストラクターです。キー宣言に基づくコンストラクターの例
Person() // Empty constructor Person( String firstName, String lastName ) Person( String firstName, String lastName, int age )
以下の例のように、キーコンストラクターに基づいてタイプのインスタンスを作成できます。
キーコンストラクターを使用したインスタンスの例
Person person = new Person( "John", "Doe" );
- @position
このタグは、位置引数で宣言されたファクトタイプ属性またはフィールドの位置を決定し、デフォルトで宣言した属性の順番をオーバーライドします。このタグを使用して、タイプ宣言または位置引数で制約の形式を維持しつつ、パターンの位置制約を変更できます。このタグは、クラスパスのクラスにあるフィールドに対してのみ使用できます。1 つのクラスのフィールドでこのタグを使用する場合も、使用しない場合もありますが、このタグのない属性は、宣言の順番では最後になります。クラスの継承はサポートされますが、メソッドのインターフェースはサポートされません。
デフォルトパラメーター: なし
サポート対象のパラメーター: 整数
<attributeDefinition> @position ( <integer> )
例: ファクトタイプを宣言し、宣言した順番をオーバライドする
declare Person firstName : String @position( 1 ) lastName : String @position( 0 ) age : int @position( 2 ) occupation: String endこの例では、位置引数に含まれる属性の優先順位は以下のとおりです。
-
lastName -
firstName -
age -
occupation
位置引数では、位置が既知の名前付きフィールドにマッピングされるので、フィールド名を指定する必要はありません。たとえば、
Person( lastName == "Doe" )の引数はPerson( "Doe"; )と同じです。ここでは、lastNameフィールドには、DRL 宣言の最上位の位置アノテーションが含まれます。セミコロン;は、その前にあるものはすべて位置引数であることを示します。セミコロンを使用して引数を区切ることで、パターンで位置引数と名前付き引数を混ぜて使用できます。バインドされていない位置引数の変数は、その位置にマッピングされているフィールドにバインドされます。以下のパターン例では、位置引数と名前付き引数をさまざまな方法で構築する方法を示します。このパターンには、制約が 2 つ、バインディングが 1 つ含まれており、セミコロンで位置引数のセクションと名前付き引数のセクションを分けています。位置引数では、変数とリテラル、およびリテラルのみを使用する式がサポートされていますが、変数だけの使用はサポートされていません。
位置引数および名前付き引数を含むパターン例
Person( "Doe", "John", $a; ) Person( "Doe", "John"; $a : age ) Person( "Doe"; firstName == "John", $a : age ) Person( lastName == "Doe"; firstName == "John", $a : age )
位置引数は、入力引数 または 出力引数 とに分類できます。入力引数は、以前に宣言したバインディングと、ユニフィケーション (unification) を使用したそのバインディングに対する制約が含まれます。出力引数は、この宣言を生成して、バインディングがまだ存在しない場合には、これを位置引数で表現するフィールドにバインディングします。
拡張タイプの宣言で
@positionアノテーションを定義する場合は、属性の位置がサブタイプに継承されるので注意が必要です。このように継承されることで、属性の順番が混同し、混乱を生じさせる可能性があります。2 つのフィールドに同じ@positionの値を指定でき、連続する値は宣言する必要がありません。位置が繰り返し使用される場合には、継承を使用することで競合が発生しないように解決されます。この場合、親タイプの位置の値の優先順位が高く、その後は最初から最後の順番で宣言を使用します。たとえば、以下の拡張タイプ宣言では、位置の優先順位が混合します。
位置アノテーションが混合した拡張ファクトタイプの例
declare Person firstName : String @position( 1 ) lastName : String @position( 0 ) age : int @position( 2 ) occupation: String end declare Student extends Person degree : String @position( 1 ) school : String @position( 0 ) graduationDate : Date endこの例では、位置引数に含まれる属性の優先順位は以下のとおりです。
-
lastName(親タイプでの位置 0) -
school(サブタイプでの位置 0) -
firstName(親タイプでの位置 1) -
degree(サブタイプでの位置 1) -
age(親タイプでの位置 2 ) -
occupation(位置アノテーションがない最初のフィールド) -
graduationDate(位置アノテーションがない 2 番目のフィールド)
-
2.5.6. ファクトタイプに対するプロパティー変更の設定およびリスナー
デフォルトでは、デシジョンエンジンは、ルールがトリガーされるたびに、ファクトタイプに対するすべてのファクトパターンを再評価しません。代わりに、指定のパターン内に制約またはバインドされている変更されたプロパティーのみに対応します。たとえば、ルールが、ルールアクションの一環として modify() を呼び出すものの、アクションが KIE ベースで新しいデータを生成しない場合、データが変更されないため、デシジョンエンジンはすべてのファクトパターンを自動的に再評価しません。このプロパティーのリアクティビティー動作は、KIE ベースでの不要な再帰を阻止し、より効率的なルール評価をもたらします。また、この動作は無限再帰を回避するために no-loop ルール属性を必ずしも使用する必要がないことを意味します。
以下の KnowledgeBuilderConfiguration オプションを使用して、このプロパティーリアクティビティー動作を変更または無効にできます。次に、Java クラスまたは DRL ファイルでプロパティー変更設定を使用し、必要に応じてプロパティーリアクティビティーを調整します。
-
ALWAYS: (デフォルト) すべてのタイプはプロパティーリアクティブです。ただし、@classReactiveプロパティー変更設定を使用して、特定タイプのプロパティーリアクティビティーを無効にできます。 -
ALLOWED: すべてのタイプはプロパティーリアクティブではありません。ただし、@propertyReactiveプロパティー変更設定を使用して、特定タイプのプロパティーリアクティビティーを有効にできます。 -
DISABLED: すべてのタイプはプロパティーリアクティブではありません。すべてのプロパティー変更リスナーは無視されます。
KnowledgeBuilderConfiguration におけるプロパティーリアクティビティー設定の例
KnowledgeBuilderConfiguration config = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration(); config.setOption(PropertySpecificOption.ALLOWED); KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(config);
または、Red Hat Process Automation Manager ディストリビューションにおける standalone.xml ファイルの drools.propertySpecific システムプロパティーを更新できます。
システムプロパティーにおけるプロパティーリアクティビティー設定の例
<system-properties> ... <property name="drools.propertySpecific" value="ALLOWED"/> ... </system-properties>
デシジョンエンジンは、ファクトクラスまたは宣言された DRL ファクトタイプに対して、以下のプロパティー変更の設定およびリスナーをサポートします。
- @classReactive
デシジョンエンジンでプロパティーリアクティビティーが
ALWAYSに設定されている場合 (すべてのタイプはプロパティーリアクティブ)、このタグは特定の Java クラスまたは宣言された DRL ファクトタイプに対してデフォルトのプロパティーリアクティビティー動作を無効にします。このタグは、特定パターン内に制約またはバインドされる変更されたプロパティーのみに対応するのではなく、ルールがトリガーされるたびに指定されたファクトタイプのすべてのファクトパターンをデシジョンエンジンが再評価する必要がある場合に使用できます。例: DRL タイプの宣言におけるデフォルトのプロパティーリアクティビティーの無効化
declare Person @classReactive firstName : String lastName : String end例: Java クラスにおけるデフォルトのプロパティーリアクティビティーの無効化
@classReactive public static class Person { private String firstName; private String lastName; }- @propertyReactive
プロパティーリアクティビティーがデシジョンエンジンで
ALLOWEDに設定されている場合 (指定されていない場合、すべてのタイプはプロパティーリアクティブではない)、このタグは特定の Java クラスまたは宣言された DRL ファクトタイプに対してプロパティーリアクティビティーを有効にします。デシジョンエンジンが指定されたファクトタイプに対して指定のパターン内に制約またはバインドされている変更されたプロパティーのみに対応するようにする場合に、このタグを使用できます。例: DRL タイプの宣言におけるプロパティーリアクティビティーの有効化 (リアクティビティーがグローバルに無効にされる場合)
declare Person @propertyReactive firstName : String lastName : String end例: Java クラスでのプロパティーのリアクティビティーの有効化 (リアクティビティーがグローバルに無効にされる場合)
@propertyReactive public static class Person { private String firstName; private String lastName; }- @watch
このタグは、DRL ルールのファクトパターンにインラインで指定する追加のプロパティーに対するプロパティーリアクティビティーを有効にします。このタグがサポートされるのは、デシジョンエンジンでプロパティーリアクティビティーが
ALWAYSに設定されている場合か、またはプロパティーリアクティビティーがALLOWEDに設定され、関連するファクトタイプが@propertyReactiveタグを使用する場合に限られます。DRL ルールでこのタグを使用して、ファクトプロパティーリアクティビティー論理で指定されたプロパティーを追加または除外できます。デフォルトパラメーター: なし
サポート対象のパラメーター: プロパティー名、
*(all)、!(not)、!*(no properties)<factPattern> @watch ( <property> )
例: ファクトパターンにおけるプロパティーリアクティビティーの有効化または無効化
// Listens for changes in both `firstName` (inferred) and `lastName`: Person(firstName == $expectedFirstName) @watch( lastName ) // Listens for changes in all properties of the `Person` fact: Person(firstName == $expectedFirstName) @watch( * ) // Listens for changes in `lastName` and explicitly excludes changes in `firstName`: Person(firstName == $expectedFirstName) @watch( lastName, !firstName ) // Listens for changes in all properties of the `Person` fact except `age`: Person(firstName == $expectedFirstName) @watch( *, !age ) // Excludes changes in all properties of the `Person` fact (equivalent to using `@classReactivity` tag): Person(firstName == $expectedFirstName) @watch( !* )
デシジョンエンジンは、
@classReactiveタグ (プロパティーリアクティビティーを無効にする) を使用するファクトタイプのプロパティーに対して@watchタグを使用する場合に、またはデシジョンエンジンでプロパティーリアクティビティーがALLOWEDに設定され、関連するファクトタイプが@propertyReactiveタグを使用しない場合に、コンパイルエラーを生成します。また、@watch( firstName, ! firstName )などのリスナーアノテーションでプロパティーを複製する場合でも、コンパイルエラーが生じます。- @propertyChangeSupport
JavaBeans Specification で定義されたプロパティー変更のサポートを実装するファクトの場合、このタグによりデシジョンエンジンがファクトプロパティーの変更を監視できるようになります。
例: JavaBeans オブジェクトでのプロパティー変更のサポートの宣言
declare Person @propertyChangeSupport end
2.5.7. アプリケーションコード内の DRL で宣言されたタイプへのアクセス
DRL の宣言タイプは通常 DRL ファイル内で使用され、 Java モデルは通常モデルをルールとアプリケーション間で共有する場合に使用されます。宣言タイプは KIE ベースのコンパイル時に生成されるため、アプリケーションはアプリケーションのランタイムまでこの宣言タイプにアクセスできません。状況によっては、アプリケーションが宣言タイプからファクトに直接アクセスし、処理する必要があります (とくにアプリケーションがデシジョンエンジンをラップして、ルール管理用によりレベルの高い、ドメイン固有のユーザーインターフェースを提供する場合)。
アプリケーションコードから宣言タイプを直接処理するには、Red Hat Process Automation Manager で org.drools.definition.type.FactType API を使用します。この API を使用して、宣言ファクトタイプでフィールドのインスタンス化、読み取り、書き込みを行います。
以下のコード例では、アプリケーションから Person ファクトタイプを直接変更します。
FactType API を使用した宣言されたファクトタイプを処理するアプリケーションコード例
import java.util.Date;
import org.kie.api.definition.type.FactType;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;
...
// Get a reference to a KIE base with the declared type:
KieBase kbase = ...
// Get the declared fact type:
FactType personType = kbase.getFactType("org.drools.examples", "Person");
// Create instances:
Object bob = personType.newInstance();
// Set attribute values:
personType.set(bob, "name", "Bob" );
personType.set(bob, "dateOfBirth", new Date());
personType.set(bob, "address", new Address("King's Road","London","404"));
// Insert the fact into a KIE session:
KieSession ksession = ...
ksession.insert(bob);
ksession.fireAllRules();
// Read attributes:
String name = (String) personType.get(bob, "name");
Date date = (Date) personType.get(bob, "dateOfBirth");
API には、一度にすべての属性を設定したり、Map コレクションから値を読み取ったり、すべての属性を一度に Map コレクションに読み込んだりするなどの、他の便利なメソッドも含まれます。
API の動作は Java リフレクションと似ていますが、API はリフレクションを使用せず、生成されるバイトコードで実装されるさらに高性能のアクセサーに依存します。
2.6. DRL のグローバル変数
DRL ファイルのグローバル変数は通常、ルールの結果で使用するアプリケーションサービスなど、ルールのデータやサービスを提供し、ルールの結果で追加されるログや値など、ルールからのデータを返します。KIE セッション設定や REST 操作を使用したデシジョンエンジンのワーキングメモリーにグローバル値を設定し、DRL ファイルのルールの上にグローバル変数を宣言してから、ルールのアクション部分 (then) でこれを使用します。グローバル変数が複数ある場合には、DRL ファイルで行を分けて使用してください。
以下は、DRL ファイルにおけるデシジョンエンジンのグローバル変数一覧の設定と、対応するグローバル変数定義の例です。
デシジョンエンジンに対するグローバルリストの設定例
List<String> list = new ArrayList<>(); KieSession kieSession = kiebase.newKieSession(); kieSession.setGlobal( "myGlobalList", list );
ルールを使用したグローバル変数定義の例
global java.util.List myGlobalList;
rule "Using a global"
when
// Empty
then
myGlobalList.add( "My global list" );
end
グローバル変数に定数イミュータブル値がない場合には、ルールの条件設定にグローバル変数を使用しないでください。グローバル変数はデシジョンエンジンのワーキングメモリーに挿入されないため、デシジョンエンジンでは変数の値の変更を追跡できません。
グローバル変数を使用してルール間でデータを共有しないでください。ルールは常に、ワーキングメモリーの状態に関して推論し、これに対応するので、ルールからルールにデータを渡す必要がある場合には、データをファクトとしてデシジョンエンジンのワーキングメモリーにアサートしてください。
グローバル変数のユースケースとして、メールサービスの例があります。デシジョンエンジンを呼び出す統合コードで、emailService オブジェクトを取得してから、デシジョンエンジンのワーキングメモリーでそのオブジェクトを設定します。DRL ファイルで、emailService のグローバルタイプがあり、名前が "email" であることを宣言すると、ルールの結果で email.sendSMS(number, message) などのアクションを使用できるようになります。
複数のパッケージで同じ ID のグローバル変数を宣言した場合には、すべてのパッケージを同じタイプで設定し、すべてが同じグローバル値を参照するようにする必要があります。
2.7. DRL でのルール属性
ルール属性は、ルールの動作を変更するためにビジネスルールに追加できる追加の仕様です。DRL ファイルでは、ルール属性は、以下の形式で、通常ルールの条件とアクションの上に定義します。複数の属性がある場合には、別々の行に指定します。
rule "rule_name"
// Attribute
// Attribute
when
// Conditions
then
// Actions
end以下の表には、ルールに割り当て可能な属性のサポートされる値と名前が一覧でまとめられています。
表2.1 ルールの属性
| 属性 | 値 |
|---|---|
|
|
ルールの優先順位を定義する整数。ルールの salience 値を高くすると、アクティベーションキューに追加したときの優先順位が高くなります。
例: |
|
|
ブール値。このオプションを選択すると、ルールが有効になります。このオプションを選択しないと、ルールは無効になります。
例: |
|
|
日付定義および時間定義を含む文字列。現在の日時が
例: |
|
|
日時定義を含む文字列。現在の日時が
例: |
|
|
ブール値。このオプションを選択すると、以前一致した条件がこのルールにより再トリガーとなる場合に、このルールを再度アクティブにする (ループする) ことができません。条件を選択しないと、この状況でルールがループされます。
例: |
|
|
ルールを割り当てるアジェンダグループを指定する文字列。アジェンダグループを使用すると、アジェンダをパーティションで区切り、ルールのグループに対する実行をさらに制御できます。フォーカスを取得したアジェンダグループのルールだけがアクティブになります。
例: |
|
|
ルールを割り当てるアクティベーション (または XOR) グループを指定する文字列。アクティベーショングループでアクティブにできるルールは 1 つだけです。最初のルールが実行されると、アクティベーショングループのすべてのルールの保留中のアクティべーションはすべてキャンセルされます。
例: |
|
|
ルールの条件が一致している場合に、ルールがアクティブになってからの時間をミリ秒で定義する長整数値。
例: |
|
|
ルールのスケジュールに対する
例: |
|
|
ルールをスケジュールするための Quartz カレンダー定義。
例: |
|
|
アジェンダグループ内のルールにのみ適用可能なブール値。このオプションが選択されている場合は、次にルールがアクティブになると、そのルールが割り当てられたアジェンダグループに自動的にフォーカスが移ります。
例: |
|
|
ルールフローグループまたはアジェンダグループ内のルールにのみ適用可能なブール値。このオプションを選択すると、次回ルールのルールフローグループがアクティブになるか、ルールのアジェンダグループがフォーカスを受け取ると、(ルールフローグループがアクティブでなくなるか、アジェンダグループがフォーカスを失うまで) ルールをアクティブにすることができません。これは、
例: |
|
|
ルールフローグループを指定する文字列。ルールフローグループで、グループが関連するルールフローによってアクティブにされる場合に限りルールを発行できます。
例: |
|
|
ルールのコード表記に使用される言語を指定する文字列 (
例: |
2.7.1. DRL でのタイマーおよびカレンダールール属性
タイマーおよびカレンダーは、DRL ルール属性であり、これを使用してスケジュールと時間の制約を DRL ルールに適用できます。これらの属性を使用するには、ユースケースによって追加の設定が必要になります。
DRL ルールの timer 属性は、ルールのスケジュール設定を行うための int (間隔) または cron タイマー定義を指定する文字列で、以下の形式をサポートします。
タイマー属性の形式
timer ( int: <initial delay> <repeat interval> ) timer ( cron: <cron expression> )
間隔タイマー属性の例
// Run after a 30-second delay timer ( int: 30s ) // Run every 5 minutes after a 30-second delay each time timer ( int: 30s 5m )
cron タイマー属性の例
// Run every 15 minutes timer ( cron:* 0/15 * * * ? )
間隔タイマーは、最初に遅延があり、オプションで間隔が繰り返されるという java.util.Timer オブジェクトのセマンティクスに従います。Cron タイマーは標準の Unix cron 式に準拠します。
以下の DRL ルール例では、cron タイマーを使用して 15 分ごとに SMS テキストメッセージを送信します。
cron タイマーを使用した DRL ルールの例
rule "Send SMS message 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
一般的に、タイマーが制御するルールは、ルールがトリガーされた時点でアクティブになり、タイマーの設定に合わせてルールの結果が繰り返し実行されます。実行は、ルールの条件が受信ファクトに一致しなくなる時点で停止します。ただし、デシジョンエンジンがタイマー付きのルールを処理する方法は、デシジョンエンジンが アクティブモード か、パッシブモード かによって異なります。
デフォルトでは、ユーザーまたはアプリケーションが明示的に fireAllRules() を呼び出した場合に、デシジョンエンジンは パッシブモード で実行され、定義されたタイマー設定によりルールが評価されます。一方、ユーザーまたはアプリケーションが fireUntilHalt() を呼び出す場合には、 デシジョンエンジンは アクティブモードで で実行され、ユーザーまたはアプリケーションが halt() を明示的に呼び出すまでルールを継続的に評価します。
デシジョンエンジンがアクティブモードの場合には、fireUntilHalt() への呼び出しからコントロールが戻った後でもルールの結果が実行され、デシジョンエンジンは、ワーキングメモリーに加えられた変更に対して リアクティブ のままになります。たとえば、タイマールールの実行のトリガーに関連したファクトを削除すると、反復実行が停止になり、ファクトが挿入されるので、ルールが一致するとそのルールが実行されます。ただし、デシジョンエンジンは継続して アクティブ な訳ではなく、ルールの実行後にだけアクティブになります。そのため、タイマーで制御されるルールが次回に実行されるまで、デシジョンエンジンは非同期のファクト挿入には反応しません。KIE セッションを破棄するとタイマーアクティビティーはすべて中断されます。
デシジョンエンジンがパッシブモードの場合は、タイマー付きのルールの結果は、 fireAllRules() が再度呼び出される場合いのみ評価されます。ただし、以下の例にあるように、パッシブモードでは TimedRuleExecutionOption オプションで KIE セッションを設定することで、デフォルトのタイマー実行動作を変更できます。
パッシブモードでタイマー付きルールを自動的に実行するための KIE セッションの設定
KieSessionConfiguration ksconf = KieServices.Factory.get().newKieSessionConfiguration(); ksconf.setOption( TimedRuleExecutionOption.YES ); KSession ksession = kbase.newKieSession(ksconf, null);
以下の例のように、TimedRuleExecutionOption オプションに追加で FILTERED 仕様を設定することで、対象のルールをフィルターするコールバックを定義できるようになります。
どのタイマー付きのルールを自動的に実行するかをフィルターするための KIE セッション
KieSessionConfiguration ksconf = KieServices.Factory.get().newKieSessionConfiguration();
conf.setOption( new TimedRuleExecutionOption.FILTERED(new TimedRuleExecutionFilter() {
public boolean accept(Rule[] rules) {
return rules[0].getName().equals("MyRule");
}
}) );
間隔タイマーの場合は、int ではなく、expr を指定した式タイマーを使用して、固定値の代わりに、式として遅延と間隔の両方を定義できます。
以下の DRL ファイルの例では、遅延と期間を含むファクトタイプを宣言し、その後に後続のルールの式タイマーでこの遅延と期間を使用します。
式タイマーが含まれるルールの例
declare Bean
delay : String = "30s"
period : long = 60000
end
rule "Expression timer"
timer ( expr: $d, $p )
when
Bean( $d : delay, $p : period )
then
// Actions
end
この例の $d と $p などの式は、ルールのパターンが一致する部分に定義した変数を使用できます。この変数には String の値を使用でき、これは期間や (ミリ秒単位の) 期間の long 値に内部で変換される数値に解析できます。
間隔と式のタイマーはいずれも、以下のオプションのパラメーターを使用できます。
-
startおよびend:Date、またはDateまたはlong値を表すString。この値には、new Date( ((Number) n).longValue() )の形式の Java のDateに変換されるNumberを指定することも可能です。 -
repeat-limit: タイマーが許可する最大反復回数を定義する整数。endおよびrepeat-limitの両パラメーターが設定されている場合には、2 つのどちらかに先に到達した時点でタイマーが停止します。
オプションの start、end、および repeat-limit パラメーターが使用されるタイマー属性の例
timer (int: 30s 1h; start=3-JAN-2020, end=4-JAN-2020, repeat-limit=50)
この例では、ルールは 1 時間ごとに 30 秒の遅延の後に実行されるようにスケジュールされ、2020 年 1 月 3 日に開始し、2020 年 1 月 4 日またはサイクルが 50 回繰り返された後に終了するようにスケジュールされています。
システムが一時停止すると (セッションがシリアライズされた後にデシリアライズされる場合など)、ルールは、一時停止中に実施されなかったアクティベーションの数にかかわらず、実施されなかったアクティベーションからの回復を 1 回のみ実施するようにスケジュールされており、ルールはその後にタイマー設定と同期して再度スケジュールされます。
DRL ルールの calendar 属性は、ルールのスケジュールのための Quartz カレンダー定義であり、以下の形式をサポートします。
カレンダー属性の形式
calendars "<definition or registered name>"
カレンダー属性の例
// Exclude non-business hours calendars "* * 0-7,18-23 ? * *" // Weekdays only, as registered in the KIE session calendars "weekday"
以下の例ように、Quartz カレンダー API に基づいて Quartz カレンダーを適用し、そのカレンダーを KIE セッションに登録することができます。
Quartz カレンダーの適用
Calendar weekDayCal = QuartzHelper.quartzCalendarAdapter(org.quartz.Calendar quartzCal)
KIE セッションでのカレンダーの登録
ksession.getCalendars().set( "weekday", weekDayCal );
カレンダーは、標準のルールや、タイマーを使用するルールと共に使用できます。カレンダー属性には、String リテラルとして記述された 1 つ以上のコンマ区切りのカレンダー名を含めることができます。
以下のルールの例では、カレンダーとタイマーの両方を使用してルールをスケジュールします。
カレンダーとタイマーを使用したルールの例
rule "Weekdays are high priority"
calendars "weekday"
timer ( int:0 1h )
when
Alarm()
then
send( "priority high - we have an alarm" );
end
rule "Weekends are low priority"
calendars "weekend"
timer ( int:0 4h )
when
Alarm()
then
send( "priority low - we have an alarm" );
end
2.8. DRL のルール条件 (WHEN)
DRL ルールの when 部分 (ルールの左辺 (LHS)とも言う) には、アクションを実行するのに満たされなければならない条件が含まれます。条件は、パッケージ内で使用可能なデータオブジェクトに基づいて、指定された一連の パターン および制約、オプションの バインディング およびサポートされるルール条件要素 (キーワード) で構成されます。たとえば、銀行のローンの申し込みに年齢制限 (21 歳以上) が必要な場合、Underage ルールの when 条件は Applicant( age < 21 ) となります。
DRL は if ではなく when を使用します。これは、if が一般に手続き型の実行フローの一部であり、条件が特定の時点でチェックされるためです。一方、when は、条件の評価が特定の評価シーケンスや時点に限定されず、いつでも継続的に行われることを示しています。条件が満たされるたびに、これらのアクションが実施されます。
when セクションを空にすると、条件は true であると見なされ、デシジョンエンジンで fireAllRules() 呼び出しが最初に実施された場合に、then セクションのアクションが実行されます。これは、デシジョンエンジンの状態を設定するルールを使用する場合に便利です。
以下のルール例では、空の条件を使用して、ルールが実行されるたびにファクトを挿入します。
条件のないルール例
rule "Always insert applicant"
when
// Empty
then // Actions to be executed once
insert( new Applicant() );
end
// The rule is internally rewritten in the following way:
rule "Always insert applicant"
when
eval( true )
then
insert( new Applicant() );
end
ルールの条件が、定義されたキーワード接続詞 (and、or、または not など) なしで複数のパターンを使用する場合、デフォルトの接続詞は and になります。
キーワード接続詞なしのルールの例
rule "Underage"
when
application : LoanApplication()
Applicant( age < 21 )
then
// Actions
end
// The rule is internally rewritten in the following way:
rule "Underage"
when
application : LoanApplication()
and Applicant( age < 21 )
then
// Actions
end
2.8.1. パターンと制約
DRL ルール条件の パターン は、デシジョンエンジンによって照合されるセグメントです。パターンは、デシジョンエンジンのワーキングメモリーに挿入される各ファクトと一致する可能性があります。パターンには 制約 を含めることもでき、これにより一致するファクトをより詳細に定義できます。
制約のない最も単純なフォームでは、パターンは指定されたタイプのファクトに一致します。以下の例ではタイプが Person であるため、このパターンはデシジョンエンジンのワーキングメモリーのすべての Person オブジェクトに一致します。
ファクトタイプが 1 つの場合のパターン例
Person()
このタイプは、ファクトオブジェクトの実際のクラスである必要はありません。パターンは、複数の異なるクラスのファクトと一致する可能性のあるスーパーユーザーやインターフェースも参照できます。たとえば、以下のパターンは、デシジョンエンジンのワーキングメモリーにあるすべてのオブジェクトと一致します。
すべてのオブジェクトの場合のパターン例
Object() // Matches all objects in the working memory
パターンの括弧は制約を囲みます (以下のユーザーの年齢に関する制約など)。
制約のあるパターンの例
Person( age == 50 )
制約は、true または false を返す式です。DRL 内のパターンの制約は、基本的にはプロパティーのアクセスなどの拡張が設定された Java の式ですが、== および != に対する equals() および !equals() セマンティクスなど、若干の違いがあります (通常の same および not same セマンティクスではありません)。
JavaBean プロパティーはパターンの制約から直接アクセスできます。Bean プロパティーは、引数を使用せずに何かを返す標準の JavaBeans の getter を使用して内部的に公開されます。たとえば、age プロパティーは、DRL で getter の getAge() ではなく、age として記述されます。
JavaBeans プロパティーを使用した DRL 制約構文
Person( age == 50 ) // This is the same as the following getter format: Person( getAge() == 50 )
Red Hat Process Automation Manager は標準の JDK leavingspector クラスを使用してこのマッピングを行うため、標準の JavaBeans 仕様に従います。デシジョンエンジンのパフォーマンスの最適化には、getAge() のように getter を明示的に使用するのではなく、age のようなプロパティーアクセスの形式を使用します。
デシジョンエンジンは効率化のために呼び出し間で一致した結果をキャッシュするため、プロパティーアクセサーを使用してルールに影響を与える可能性がある仕方でオブジェクトの状態を変更しないでください。
たとえば、プロパティーアクセサーを以下のように使用しないでください。
public int getAge() {
age++; // Do not do this.
return age;
}public int getAge() {
Date now = DateUtil.now(); // Do not do this.
return DateUtil.differenceInYears(now, birthday);
}
2 番目の例に従う代わりに、ワーキングメモリーに現在の日付をラップするファクトを挿入し、必要に応じてそのファクトを fireAllRules() の間で更新します。
ただし、プロパティーの getter が見つからなかった場合、コンパイラーは、以下のようにこのプロパティー名をフォールバックメソッド名として引数なしで使用します。
オブジェクトが見つからない場合のフォールバックメソッド
Person( age == 50 ) // If `Person.getAge()` does not exist, the compiler uses the following syntax: Person( age() == 50 )
以下の例のように、パターンでアクセスプロパティーをネストすることもできます。ネストされたプロパティーにはデシジョンエンジンでインデックス化されます。
ネストされたプロパティーアクセスを使用するパターンの例
Person( address.houseNumber == 50 ) // This is the same as the following format: Person( getAddress().getHouseNumber() == 50 )
ステートフルな KIE セッションでは、ネストされたアクセサーの使用に注意が必要です。デシジョンエンジンのワーキングメモリーではネストされた値は認識されず、これらの値の変更は検出されません。ネストされた値の親参照がワーキングメモリーに挿入されている場合はこれらの値を不変と見なすか、ネストされた値を変更する必要がある場合は、すべての外部ファクトを更新済みとしてマークします。前の例では、houseNumber プロパティーが変更された場合は、この Address が指定された Person は更新済みとしてマークされる必要があります。
パターンの括弧内では boolean 値を制約として返す任意の Java 式を使用できます。Java 式は、プロパティーアクセスなどの他の式の拡張機能と組み合わせることができます。
プロパティーアクセスと Java 式を使用する制約が設定されたパターンの例
Person( age == 50 )
評価の優先度は、論理式や数式のように括弧を使用して変更できます。
制約の評価順序の例
Person( age > 100 && ( age % 10 == 0 ) )
以下の例のように、制約で Java メソッドを再利用することもできます。
再利用される Java メソッドによる制約の例
Person( Math.round( weight / ( height * height ) ) < 25.0 )
デシジョンエンジンは効率化を図るために呼び出し間で一致の結果をキャッシュするため、ルールに影響を与える可能性のある方法でオブジェクトの状態を変更するために制約を使用しないでください。ルール条件のファクトで実行されるメソッドは、読み取り専用のメソッドである必要があります。また、ファクトの状態は、ファクトがワーキングメモリーで更新済みとしてマークされているのでない限り、毎回変更されるたびにルールの呼び出し間で変更されません。
たとえば、以下のような方法でパターンの制約を使用しないでください。
Person( incrementAndGetAge() == 10 ) // Do not do this.
Person( System.currentTimeMillis() % 1000 == 0 ) // Do not do this.
DRL 内の制約演算子には、標準の Java 演算子の優先順位が適用されます。DRL 演算子は、== および != 演算子を除き、標準の Java セマンティクスに従います。
== 演算子は、通常の same セマンティクスではなく、null 安全な equals() セマンティクスを使用します。たとえば、Person( firstName == "John" ) というパターンは java.util.Objects.equals(person.getFirstName(), "John") と同様であり、"John" は null でないため、このパターンは "John".equals(person.getFirstName()) にも似ています。
!= 演算子は、通常の not same セマンティクスではなく null 安全な !equals() セマンティクスを使用します。たとえば、Person( firstName != "John" ) というパターンは、!java.util.Objects.equals(person.getFirstName(), "John") に似ています。
フィールドと制約の値が異なるタイプの場合、デシジョンエンジンは型強制 (type coercion) を使用して競合を解決し、コンパイルエラーを減らします。たとえば、"ten" が数値エバリュエーターで文字列として指定される場合、コンパイルエラーが発生しますが、"10" は数値 10 に型強制されます。型強制では、フィールドのタイプは常に値のタイプより優先されます。
型強制された値を使用する制約の例
Person( age == "10" ) // "10" is coerced to 10
制約のグループの場合は、コンマ区切り (,) を使って、暗黙的な and の接続的なセマンティクスを使用することができます。
複数の制約があるパターンの例
// Person is at least 50 years old and weighs at least 80 kilograms: Person( age > 50, weight > 80 ) // Person is at least 50 years old, weighs at least 80 kilograms, and is taller than 2 meters: Person( age > 50, weight > 80, height > 2 )
&& および , 演算子のセマンティクスは同じですが、これらは異なる優先順位で解決されます。&& 演算子は || 演算子より優先され、&& および || 演算子はどちらも , 演算子より優先されます。デシジョンエンジンのパフォーマンスと人による可読性を最適化するために、コンマ演算子は最上位レベルの制約で使用してください。
括弧内など、複合制約式にコンマ演算子を埋め込むことはできません。
複合制約式での不適切なコンマの例
// Do not use the following format: Person( ( age > 50, weight > 80 ) || height > 2 ) // Use the following format instead: Person( ( age > 50 && weight > 80 ) || height > 2 )
2.8.2. パターンと制約でバインドされた変数
パターンおよび制約に変数をバインドして、ルールの他の部分の一致するオブジェクトを参照することができます。バインドされた変数は、ルールをより効率的に、かつデータモデルでのファクトへのアノテーションの付け方と一貫した方法で定義するのに役立ちます。(とくに複雑なルールの場合に) ルール内で変数とフィールドを簡単に区別するには、変数に対して標準の形式である $variable を使用します。この規則は便利ですが、DRL で必須ではありません。
たとえば、以下の DRL ルールでは、Person ファクトが指定されたパターンに対して変数 $p が使用されています。
バインドされた変数が使用されているパターン
rule "simple rule"
when
$p : Person()
then
System.out.println( "Person " + $p );
end
同様に、以下の例のように、パターンの制約で変数をプロパティーにバインドすることもできます。
// Two persons of the same age: Person( $firstAge : age ) // Binding Person( age == $firstAge ) // Constraint expression
より明確で効率的なルールを定義するには、制約のバインディングと制約式を必ず分離します。バインディングと式の組み合わせはサポートされますが、パターンが複雑になり、評価の効率に影響が及ぶ可能性があります。
// Do not use the following format: Person( $age : age * 2 < 100 ) // Use the following format instead: Person( age * 2 < 100, $age : age )
デシジョンエンジンは同じ宣言に対するバインディングをサポートしませんが、複数のプロパティー間での引数の ユニフィケーション をサポートします。位置引数は、常にユニフィケーションで常に処理され、名前付き引数の場合はユニフィケーション記号 := が使用されます。
以下のパターンの例では、2 つの Person ファクト間で age プロパティーを統合します。
ユニフィケーションが使用されるパターンの例
Person( $age := age ) Person( $age := age )
ユニフィケーションは、シーケンスオカレンスのバインドされたフィールドの同じ値に対して、最初のオカレンスと制約のバインディングを宣言します。
2.8.3. ネストされた制約とインラインキャスト
以下の例のように、ネストされたオブジェクトの複数のプロパティーにアクセスしなければならない場合があります。
複数のプロパティーにアクセスするパターンの例
Person( name == "mark", address.city == "london", address.country == "uk" )
以下の例のように、これらのプロパティーのアクセサーを、.( <constraints> ) という構文を使用してネストされたオブジェクトに対してグループ化することで、ルールを読みやすくすることができます。
グループ化された制約を使用したパターンの例
Person( name == "mark", address.( city == "london", country == "uk") )
ピリオドの接頭辞 . は、メソッド呼び出しとネストされたオブジェクト制約とを区別します。
パターンでネストされたオブジェクトを使用する場合は、構文 <type>#<subtype> を使用してサブタイプにキャストし、親タイプの getter をサブタイプに対して利用可能にします。以下の例のように、オブジェクト名または完全修飾クラス名のいずれかを使用して、1 つまたは複数のサブタイプにキャストできます。
サブタイプへのインラインキャストを使用したパターンの例
// Inline casting with subtype name: Person( name == "mark", address#LongAddress.country == "uk" ) // Inline casting with fully qualified class name: Person( name == "mark", address#org.domain.LongAddress.country == "uk" ) // Multiple inline casts: Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )
これらのパターン例では、Address を LongAddress に、さらに最後の例にある DetailedCountry にキャストし、各ケースのサブタイプで親の getter を利用可能にします。
以下の例のように、instanceof 演算子を使用して、パターンを使用した後続のフィールドで指定されたタイプの結果を推測できます。
Person( name == "mark", address instanceof LongAddress, address.country == "uk" )
インラインキャストが使用できない場合 (たとえば instanceof が false を返す場合)、評価は false と見なされます。
2.8.4. 制約内の日付リテラル
デフォルトで、デシジョンエンジンは dd-mmm-yyyy という日付形式をサポートします。この日付形式 (必要に応じて時間形式マスクを含む) は、システムプロパティー drools.dateformat="dd-mmm-yyyy hh:mm" を使用して、別の形式マスクを指定することによってカスタマイズすることができます。日付形式は、drools.defaultlanguage および drools.defaultcountry システムプロパティーを使用し、言語ロケールを変更することによってカスタマイズすることもできます (たとえば、タイのロケールは drools.defaultlanguage=th および drools.defaultcountry=TH と設定します)。
日付のリテラル制限を使用したパターンの例
Person( bornBefore < "27-Oct-2009" )
2.8.5. DRL のパターン制約でサポートされている演算子
DRL では、パターン制約の演算子で標準の Java セマンティクスがサポートされていますが、いくつかの例外があり、追加となる DRL 固有の演算子もいくつかあります。以下の一覧は、標準の Java セマンティクスとは異なる方法で処理される DRL の制約の演算子や DRL の制約に固有の演算子をまとめています。
.(),#.()演算子を使用すると、プロパティーのアクセサーをネストされたオブジェクトにグループ化でき、#演算子を使用すると、ネストされたオブジェクトのサブタイプにキャストできます。サブタイプにキャストすることで、親タイプの getter をサブタイプに対して使用可能にできます。オブジェクト名または完全修飾クラス名のいずれかを使用することができ、1 つまたは複数のサブタイプにキャストすることができます。ネストされたオブジェクトが使用されるパターンの例
// Ungrouped property accessors: Person( name == "mark", address.city == "london", address.country == "uk" ) // Grouped property accessors: Person( name == "mark", address.( city == "london", country == "uk") )
注記ピリオドの接頭辞
.は、メソッド呼び出しとネストされたオブジェクト制約とを区別します。サブタイプへのインラインキャストを使用したパターンの例
// Inline casting with subtype name: Person( name == "mark", address#LongAddress.country == "uk" ) // Inline casting with fully qualified class name: Person( name == "mark", address#org.domain.LongAddress.country == "uk" ) // Multiple inline casts: Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )
!.この演算子を使用すると、null 安全な方法でプロパティーを逆参照します。パターンマッチングの適切な結果を得るには、
!.演算子の左側の値を null にすることはできません (!= nullと解釈される)。null 安全な逆参照を使用した制約の例
Person( $streetName : address!.street ) // This is internally rewritten in the following way: Person( address != null, $streetName : address.street )
[]この演算子を使用して、インデックスで
List値にアクセスするか、またはキーでMap値にアクセスします。ListおよびMapアクセスを使用する制約の例// The following format is the same as `childList(0).getAge() == 18`: Person(childList[0].age == 18) // The following format is the same as `credentialMap.get("jdoe").isValid()`: Person(credentialMap["jdoe"].valid)<,<=,>,>=これらの演算子は、自然順序付けのあるプロパティーに使用されます。たとえば、
<演算子は、Dateフィールドでは 前 を意味し、Stringフィールドでは アルファベット順で前 であることを意味します。これらのプロパティーは、比較可能なプロパティーにのみ適用されます。before演算子を使用した制約の例Person( birthDate < $otherBirthDate ) Person( firstName < $otherFirstName )
==,!=制約では、これらの演算子を、通常の
sameおよびnot sameセマンティクスではなく、equals()および!equals()メソッドとして使用します。null 安全な等価性を使用する制約の例
Person( firstName == "John" ) // This is similar to the following formats: java.util.Objects.equals(person.getFirstName(), "John") "John".equals(person.getFirstName())
null 安全な非等価性を使用する制約の例
Person( firstName != "John" ) // This is similar to the following format: !java.util.Objects.equals(person.getFirstName(), "John")
&&,||これらの演算子を使用して、フィールドに複数の制約を追加する略記組合せ比較条件を作成します。再帰的な構文パターンを作成するには、括弧
()を使用して制約をグループ化します。略記組合せ比較を使用する制約の例
// 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")
matches,not matchesこれらの演算子を使用して、指定された Java 正規表現にフィールドが一致するかしないかを示します。一般に、正規表現は
Stringリテラルですが、有効な正規表現に解決される変数もサポートされます。これらの演算子はStringプロパティーのみに適用されます。null値に対してmatchesを使用する場合、結果の評価は常にfalseになります。null値に対してnot matchesを使用する場合、結果の評価は常にtrueになります。Java の場合のように、Stringリテラルとして記述された正規表現は二重のバックスラッシュ\\を使用してエスケープする必要があります。正規表現と一致する制約または一致しない制約の例
Person( country matches "(USA)?\\S*UK" ) Person( country not matches "(USA)?\\S*UK" )
contains,not containsこれらの演算子を使用して、フィールドの
ArrayまたはCollectionが指定された値を含むか、または含まないかを検証します。これらの演算子はArrayまたはCollectionプロパティーに適用されますが、これらの演算子をString.contains()および!String.contains()の制約チェックの代わりとして使用することもできます。コレクションに対して
containsおよびnot containsが使用された制約の例// Collection with a specified field: FamilyTree( countries contains "UK" ) FamilyTree( countries not contains "UK" ) // Collection with a variable: FamilyTree( countries contains $var ) FamilyTree( countries not contains $var )
String リテラルに対して
containsおよびnot containsが使用された制約の例// Sting literal with a specified field: Person( fullName contains "Jr" ) Person( fullName not contains "Jr" ) // String literal with a variable: Person( fullName contains $var ) Person( fullName not contains $var )
注記下位互換性を確保するため、
excludes演算子はnot containsの同義語としてサポートされます。memberOf,not memberOfこれらの演算子を使用して、フィールドが変数として定義されている
ArrayまたはCollectionのメンバーであるかどうかを検証します。ArrayまたはCollectionは変数でなければなりません。コレクションと
memberOfおよびnot memberOfを使用する制約の例FamilyTree( person memberOf $europeanDescendants ) FamilyTree( person not memberOf $europeanDescendants )
soundslikeこの演算子を使用して、ある単語を英語で発音した場合に、指定された値と発音がほぼ同じであるかどうかを検証します (
matches演算子に類似)。この演算子は Soundex アルゴリズムを使用します。soundslikeを使用した制約の例// Match firstName "Jon" or "John": Person( firstName soundslike "John" )
strこの演算子を使用して、
Stringであるフィールドが指定された値で開始されているか、または終了されているかを検証します。この演算子を使用して、Stringの長さを検証することもできます。strを使用する制約の例// Verify what the String starts with: Message( routingValue str[startsWith] "R1" ) // Verify what the String ends with: Message( routingValue str[endsWith] "R2" ) // Verify the length of the String: Message( routingValue str[length] 17 )
in,notinこれらの演算子を使用して、制約の中で一致する可能性がある複数の値を指定します (複合値の制約)。複合値の制約についての機能をサポートするのは
in演算子およびnot in演算子のみです。これらの演算子の 2 番目のオペランドは括弧で囲まれたコンマ区切りの値のリストでなければいけません。値は変数、リテラル、戻り値、または修飾された識別子として指定できます。これらの演算子は、==または!=演算子を使用し、複数の制約のリストとして内部に再書き込みされます。inおよびnotinを使用した制約の例Person( $color : favoriteColor ) Color( type in ( "red", "blue", $color ) ) Person( $color : favoriteColor ) Color( type notin ( "red", "blue", $color ) )
2.8.6. DRL のパターン制約における演算子の優先順位
DRL では、適用可能な制約演算子の場合は標準的な Java 演算子の優先順位をサポートしていますが、一部の例外と、DRL に固有の追加の演算子がいくつかあります。以下の表には、適用可能な DRL 演算子を優先順位の高いものから低いものの順で記載しています。
表2.2 DRL のパターン制約における演算子の優先順位
| 演算子のタイプ | 演算子 | 注記 |
|---|---|---|
|
ネストされているか、null 安全なプロパティーアクセス |
|
標準の Java セマンティクスではない |
|
|
|
標準の Java セマンティクスではない |
|
制約のバインディング |
|
標準の Java セマンティクスではない |
|
乗法 |
| |
|
加法 |
| |
|
シフト |
| |
|
リレーショナル |
| |
|
等価性 |
|
標準の Java の |
|
非短絡 (Non-short-circuiting) |
| |
|
非短絡の排他的 |
| |
|
非短絡の包含的 |
| |
|
論理 |
| |
|
論理 |
| |
|
三項 |
| |
|
コンマ区切り |
|
標準の Java セマンティクスではない |
2.8.7. DRL でサポートされるルール条件要素 (キーワード)
DRL では、DRL のルール条件で定義するパターンで使用できる以下のルール条件要素 (キーワード) がサポートされます。
and条件コンポーネントを論理積に分類します。接中辞および接頭辞の
andがサポートされます。括弧()を使用することにより、パターンを明示的にグループ化できます。デフォルトでは、結合演算子を指定しない場合、リストされているパターンはすべてandで結合されます。andを使用したパターンの例//Infix `and`: Color( colorType : type ) and Person( favoriteColor == colorType ) //Infix `and` with grouping: (Color( colorType : type ) and (Person( favoriteColor == colorType ) or Person( favoriteColor == colorType )) // Prefix `and`: (and Color( colorType : type ) Person( favoriteColor == colorType )) // Default implicit `and`: Color( colorType : type ) Person( favoriteColor == colorType )
注記先頭の宣言のバインディングには
andキーワードを使用しないでください (orなどは使用できます)。宣言が参照できるのは一度に 1 つのファクトのみであり、andと宣言のバインディングを使用すると、andが満たされた場合にこの要素が両方のファクトと一致してしまうため、エラーが発生します。andの誤った使用例// Causes compile error: $person : (Person( name == "Romeo" ) and Person( name == "Juliet"))
or条件コンポーネントを論理和にグループ化します。接中辞および接頭辞の
orがサポートされます。括弧()を使用することにより、パターンを明示的にグループ化できます。orと共にパターンバインディングを使用することもできますが、各パターンは個別にバインディングする必要があります。orを使用したパターンの例//Infix `or`: Color( colorType : type ) or Person( favoriteColor == colorType ) //Infix `or` with grouping: (Color( colorType : type ) or (Person( favoriteColor == colorType ) and Person( favoriteColor == colorType )) // Prefix `or`: (or Color( colorType : type ) Person( favoriteColor == colorType ))
orとパターンのバインディングを使用したパターンの例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 ))or条件要素の動作は、フィールド制約の制約および制限を対象とした接続的な||演算子とは異なります。デシジョンエンジンはor要素を直接解釈しませんが、論理変換を使用して、orが使用されているルールを複数のサブルールに書き換えます。このプロセスにより、最終的には、ルートノードおよび各条件要素のサブルールとして、1 つのorを使用するルールが生成されます。各サブルールは、通常のルールと同様に有効にされ、実行されます。その場合、サブルール間で動作や対話はとくに行われません。したがって、
or条件要素については複数の類似したルールを生成するための近道であり、複数の論理和が true である場合には、複数のアクティベーションが作成される可能性があることに留意してください。exists存在している必要のあるファクトおよび制約を指定します。このオプションは、初回の一致についてのみトリガーされ、後続の一致については無視されます。この要素を複数のパターンで使用する場合は、これらのパターンを括弧
()で囲みます。existsを使用したパターンの例exists Person( firstName == "John") exists (Person( firstName == "John", age == 42 )) exists (Person( firstName == "John" ) and Person( lastName == "Doe" ))not存在していてはならないファクトと制約を指定します。この要素を複数のパターンで使用する場合は、これらのパターンを括弧
()で囲みます。notを使用したパターンの例not Person( firstName == "John") not (Person( firstName == "John", age == 42 )) not (Person( firstName == "John" ) and Person( lastName == "Doe" ))forall最初のパターンと一致するすべてのファクトが残りのパターンのすべてと一致するかどうかを検証します。
forall構成が満たされると、ルールはtrueと評価されます。この要素は範囲の区切りであるため、以前にバインドされたすべての変数を使用できますが、内部でバインドされた変数を外部で使用することはできません。forallを使用したルールの例rule "All full-time employees have red ID badges" when forall( $emp : Employee( type == "fulltime" ) Employee( this == $emp, badgeColor = "red" ) ) then // True, all full-time employees have red ID badges. endこの例では、ルールによってタイプが
"fulltime"であるすべてのEmployeeオブジェクトが選択されます。このパターンに一致するそれぞれのファクトに対して、ルールは、従うパターン (バッジの色) を評価し、一致すると、ルールはtrueと評価されます。デシジョンエンジンのワーキングメモリー内の指定されたタイプのファクトすべてが制約セットに一致するように指定するには、
forallを単一パターンで使用し、単純化を図ることができます。forallと 1 つのパターンを使用するルールの例rule "All full-time employees have red ID badges" when forall( Employee( badgeColor = "red" ) ) then // True, all full-time employees have red ID badges. end複数のパターンと
forall構成を使用するか、またはnot要素構成内など、他の条件要素でネスト化することができます。forallと複数のパターンを使用するルールの例rule "All employees have health and dental care programs" when forall( $emp : Employee() HealthCare( employee == $emp ) DentalCare( employee == $emp ) ) then // True, all employees have health and dental care. endforallとnotを使用するルールの例rule "Not all employees have health and dental care" when not ( forall( $emp : Employee() HealthCare( employee == $emp ) DentalCare( employee == $emp ) ) ) then // True, not all employees have health and dental care. end注記forall( p1 p2 p3 …)の形式はnot( p1 and not( and p2 p3 … ) )と等価です。fromこれを使用してパターンのデータソースを指定します。これにより、デシジョンエンジンがワーキングメモリーにないデータに対して推論できるようになります。データソースには、バインドされた変数のサブフィールド、またはメソッド呼び出しの結果を指定できます。オブジェクトソースの定義に使用される式として、通常の MVEL 構文に準拠する任意の式を使用できます。このため、
from要素により、オブジェクトプロパティーのナビゲーションを使用して、メソッド呼び出しを実行し、マップとコレクション要素にアクセスすることが簡単にできます。fromおよびパターンのバインディングを使用するルールの例rule "Validate zipcode" when Person( $personAddress : address ) Address( zipcode == "23920W" ) from $personAddress then // Zip code is okay. endfromとグラフ表記を使用するルールの例rule "Validate zipcode" when $p : Person() $a : Address( zipcode == "23920W" ) from $p.address then // Zip code is okay. endすべてのオブジェクトに対して反復処理される
fromのルールの例rule "Apply 10% discount to all items over US$ 100 in an order" when $order : Order() $item : OrderItem( value > 100 ) from $order.items then // Apply discount to `$item`. end注記オブジェクトコレクションの規模が大きい場合には、以下の例のように、サイズの大きいグラフが含まれるオブジェクトを追加して、デシジョンエンジンが頻繁に反復作業を行うのではなく、コレクションを KIE セッションに直接追加して、条件内でコレクションを結合します。
when $order : Order() OrderItem( value > 100, order == $order )
fromおよびlock-on-activeルール属性を使用するルールの例rule "Assign people in North Carolina (NC) to sales region 1" ruleflow-group "test" lock-on-active true when $p : Person() $a : Address( state == "NC" ) from $p.address then modify ($p) {} // Assign the person to sales region 1. end rule "Apply a discount to people in the city of Raleigh" ruleflow-group "test" lock-on-active true when $p : Person() $a : Address( city == "Raleigh" ) from $p.address then modify ($p) {} // Apply discount to the person. end重要fromとlock-on-activeのルール属性を同時に使用すると、ルールが実行されなくなります。この問題に対しては、以下のいずれかの方法で対処できます。-
すべてのファクトをデシジョンエンジンのワーキングメモリーに挿入したり、制約式でネストされたオブジェクト参照を使用したりする場合は、
from要素は使用しないでください。 -
ルール条件の最後の文として、
modify()ブロックで使用される変数を配置します。 -
同じルールフローグループ内のルールがアクティベーションを相互に組み込む方法を明示的に管理できる場合、
lock-on-activeルール属性は使用しないでください。
from句を含むパターンの後に、括弧から始まる別のパターンを使用することはできません。この制限がある理由は、DRL パーサーがfrom式を"from $l (String() or Number())"として読み取り、この式を関数呼び出しと区別できないためです。この最も単純な回避策は、以下の例に示すように、from句を括弧でラップする方法です。fromが適切に使用されていないルールと適切に使用されているルールの例// Do not use `from` in this way: rule R when $l : List() String() from $l (String() or Number()) then // Actions end // Use `from` in this way instead: rule R when $l : List() (String() from $l) (String() or Number()) then // Actions end-
すべてのファクトをデシジョンエンジンのワーキングメモリーに挿入したり、制約式でネストされたオブジェクト参照を使用したりする場合は、
entry-pointエントリーポイントまたはパターンのデータソースに対応した イベントストリーム を定義します。この要素は通常、
from条件要素と共に使用します。イベントのエントリーポイントを宣言し、デシジョンエンジンがそのエントリーポイントからのデータのみを使用してルールを評価することが可能です。エントリーポイントは、DRL ルールで参照することによって暗黙的に宣言することも、Java アプリケーションで明示的に宣言することもできます。from entry-pointを使用したルールの例rule "Authorize withdrawal" when WithdrawRequest( $ai : accountId, $am : amount ) from entry-point "ATM Stream" CheckingAccount( accountId == $ai, balance > $am ) then // Authorize withdrawal. endEntryPoint オブジェクトが使用され、ファクトが挿入された Java アプリケーションコードの例
import org.kie.api.runtime.KieSession; import org.kie.api.runtime.rule.EntryPoint; // Create your KIE base and KIE session as usual: KieSession session = ... // Create a reference to the entry point: EntryPoint atmStream = session.getEntryPoint("ATM Stream"); // Start inserting your facts into the entry point: atmStream.insert(aWithdrawRequest);collectルールで条件の一部として使用できるオブジェクトのコレクションを定義します。このルールは、指定されたソースまたはデシジョンエンジンのワーキングメモリーのいずれかよりコレクションを取得します。
collect要素の結果パターンには、java.util.Collectionインターフェースを実装し、デフォルトの引数を持たないパブリックコンストラクターを指定する任意の具象クラスを使用できます。List、LinkedList、およびHashSetのような Java コレクションを使用することも、独自のクラスを使用することもできます。条件内でcollect要素の前に変数がバインドされている場合は、その変数を使用してソースおよび結果パターンの両方を制限することができます。ただし、collect要素内で作成されるバインディングをその外部で使用することはできません。collectを使用するルールの例import java.util.List rule "Raise priority when system has more than three pending alarms" when $system : System() $alarms : List( size >= 3 ) from collect( Alarm( system == $system, status == 'pending' ) ) then // Raise priority because `$system` has three or more `$alarms` pending. endこの例では、ルールは指定された各システムのデシジョンエンジンのワーキングメモリーの保留中のすべてのアラームを評価し、それらを
Listにグループ化します。指定されたシステムについての 3 つ以上のアラームが見つかった場合には、ルールが実行されます。以下の例のように、ネストされた
from要素と共にcollect要素を使用することもできます。collectとネストされたfromを使用するルールの例import java.util.LinkedList; rule "Send a message to all parents" when $town : Town( name == 'Paris' ) $mothers : LinkedList() from collect( Person( children > 0 ) from $town.getPeople() ) then // Send a message to all parents. endaccumulateオブジェクトのコレクションを反復処理し、各要素に対してカスタムアクションを実行し、結果オブジェクトを返します (制約が
trueに評価される場合)。この要素は、collect条件要素のより柔軟性が高い、強化された形式です。accumulate条件で事前に定義した関数を使用するか、必要に応じてカスタム関数を実装できます。また、ルール条件でaccumulateの短縮形であるaccを使用することもできます。以下の形式を使用して、ルールに
accumulate条件を定義します。accumulateの推奨形式accumulate( <source pattern>; <functions> [;<constraints>] )
注記デシジョンエンジンは下位互換性を確保するために
accumulate要素の代替形式をサポートしますが、この形式は、ルールとアプリケーションの最適なパフォーマンスという点ではより適しています。デシジョンエンジンは、以下の事前に定義された
accumulate関数をサポートします。これらの関数は、任意の式を入力として受け入れます。-
average -
min -
max -
count -
sum -
collectList -
collectSet
以下のルールの例では、
min、max、およびaverageはaccumulate関数であり、各センサーの読み取り値での最低、最高、および平均の温度値を算出します。温度値を算出する
accumulateを使用したルールの例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以下のルールの例では、
accumulateを指定したaverage関数を使用して、ある注文のすべてのアイテムの平均収益を計算します。平均収益を計算する
accumulateを使用したルールの例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`. endaccumulateの条件でカスタムかつドメイン固有の関数を使用するには、org.kie.api.runtime.rule.AccumulateFunctionインターフェースを実装する Java クラスを作成します。たとえば、以下の Java クラスはAverageData関数のカスタム実装を定義します。average関数がカスタムで実装された Java クラスの例// An implementation of an accumulator capable of calculating average values public class AverageAccumulateFunction implements org.kie.api.runtime.rule.AccumulateFunction<AverageAccumulateFunction.AverageData> { 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.kie.api.runtime.rule.AccumulateFunction#createContext() */ public AverageData createContext() { return new AverageData(); } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#init(java.io.Serializable) */ public void init(AverageData context) { context.count = 0; context.total = 0; } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#accumulate(java.io.Serializable, java.lang.Object) */ public void accumulate(AverageData context, Object value) { context.count++; context.total += ((Number) value).doubleValue(); } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#reverse(java.io.Serializable, java.lang.Object) */ public void reverse(AverageData context, Object value) { context.count--; context.total -= ((Number) value).doubleValue(); } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#getResult(java.io.Serializable) */ public Object getResult(AverageData context) { return new Double( context.count == 0 ? 0 : context.total / context.count ); } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#supportsReverse() */ public boolean supportsReverse() { return true; } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#getResultType() */ public Class< ? > getResultType() { return Number.class; } }DRL ルールでカスタム関数を使用するため、
import accumulateステートメントを使用してその関数をインポートします。カスタム関数をインポートするための形式
import accumulate <class_name> <function_name>
インポートされた
average関数を使用するルールの例import accumulate AverageAccumulateFunction.AverageData 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-
2.8.8. DRL ルール条件内でオブジェクトのグラフが使用される OOPath 構文
OOPath は、DRL ルールの条件の制約でオブジェクトのグラフを参照するために設計された XPath のオブジェクト指向構文の拡張です。OOPath は、コレクションおよびフィルター制約を処理する間に XPath からのコンパクト表記を使用して関連要素を移動します。また、OOPath はとくにオブジェクトグラフの場合に役に立ちます。
ファクトのフィールドがコレクションである場合、from 条件要素 (キーワード) を使用してバインドし、そのコレクションのすべての項目を 1 つずつ判断することができます。ルール条件制約でオブジェクトのグラフを参照する必要がある場合、from 条件要素を過度に使用すると、以下の例のように冗長かつ繰り返しの多い構文になります。
from を使用してグラフオブジェクトを参照するルールの例
rule "Find all grades for Big Data exam"
when
$student: Student( $plan: plan )
$exam: Exam( course == "Big Data" ) from $plan.exams
$grade: Grade() from $exam.grades
then
// Actions
end
この例では、ドメインモデルには Student オブジェクトと学習の Plan が含まれています。Plan には、ゼロ以上の Exam インスタンスを指定でき、Exam にはゼロ以上の Grade インスタンスを指定できます。このルール設定が機能するためにデシジョンエンジンのワーキングメモリーに存在する必要があるのは、グラフのルートオブジェクト (この例では Student) のみです。
from ステートメントを使用するより効率的な別の方法として、以下の例のように短縮された OOPath 構文を使用できます。
OOPath 構文でオブジェクトのグラフを参照するルールの例
rule "Find all grades for Big Data exam"
when
Student( $grade: /plan/exams[course == "Big Data"]/grades )
then
// Actions
end
通常、OOPath 式の中核となる文法は、以下のように拡張された Backus-Naur form (EBNF) 表記で定義されます。
OOPath 式の EBNF 表記
OOPExpr = [ID ( ":" | ":=" )] ( "/" | "?/" ) OOPSegment { ( "/" | "?/" | "." ) OOPSegment } ;
OOPSegment = ID ["#" ID] ["[" ( Number | Constraints ) "]"]
OOPath 式の実際の特性および機能は以下のとおりです。
-
非リアクティブな OOPath 式の場合は、スラッシュ
/または疑問符とスラッシュ?/で始まります (このセクションで後述します)。 -
オブジェクトの単一プロパティーを、ピリオド
.演算子を使用して逆参照できます。 -
オブジェクトの複数のプロパティーをスラッシュ
/演算子を使用して逆参照できます。コレクションが返される場合、この式はコレクションの値を反復処理します。 1 つまたは複数の制約を満たさない、走査されたオブジェクトをフィルターで除外できます。この制約は、以下の例のように、角括弧内の述語式として記述されます。
述語式としての制約
Student( $grade: /plan/exams[ course == "Big Data" ]/grades )
汎用コレクションで宣言されたクラスのサブクラスに走査されたオブジェクトをダウンキャストできます。以下の例に示すように、後続の制約も、このサブクラスで宣言されたプロパティーにのみ安全にアクセスすることができます。このインラインキャストで指定されたクラスのインスタンスではないオブジェクトは、自動的にフィルターで除外されます。
ダウンキャストオブジェクトが使用される制約
Student( $grade: /plan/exams#AdvancedExam[ course == "Big Data", level > 3 ]/grades )
現在の反復処理されたグラフの前に走査されたグラフのオブジェクトを前方参照できます。たとえば、以下の OOPath 式は、合格した試験の平均を上回るグレードにのみ一致します。
前方参照オブジェクトを使用する制約
Student( $grade: /plan/exams/grades[ result > ../averageResult ] )
以下の例のように、別の OOPath 式を再帰的に使用できます。
再帰的な制約式
Student( $exam: /plan/exams[ /grades[ result > 20 ] ] )
以下の例のように、角括弧
[]内のオブジェクトのインデックスを使用することにより、そのオブジェクトにアクセスすることができます。Java の規則に従うために、OOPath のインデックスは 0 をベースとし、XPath のインデックスは 1 をベースとします。インデックスによるオブジェクトへのアクセスが設定された制約
Student( $grade: /plan/exams[0]/grades )
OOPath 式は、リアクティブまたは非リアクティブにできます。デシジョンエンジンは、OOPath 式の評価中に走査された深くネストされたオブジェクトを含む更新には反応しません。
これらのオブジェクトが変更に反応するようにするには、org.drools.core.phreak.ReactiveObject クラスを拡張するようにオブジェクトを変更します。オブジェクトを変更して ReactiveObject クラスを拡張すると、ドメインオブジェクトはいずれかのフィールドが更新されている場合に、以下のように継承されたメソッド notifyModification を呼び出して、デシジョンエンジンに通知します。
試験が別のコースに移動したことをデシジョンエンジンに通知するオブジェクトメソッドの例
public void setCourse(String course) {
this.course = course;
notifyModification(this);
}
次の対応する OOPath 式を使すると、試験が別のコースに移動された場合にルールが再実行され、そのルールに一致するグレードのリストが再計算されます。
「Big Data」ルールの OOPath 式の例
Student( $grade: /plan/exams[ course == "Big Data" ]/grades )
以下の例に示すように、/ セパレーターの代わりに ?/ セパレーターを使用して、OOPath 式の一部のみについてリアクティビティーを無効にすることもできます。
部分的に非リアクティブである OOPath 式の例
Student( $grade: /plan/exams[ course == "Big Data" ]?/grades )
この例では、デシジョンエンジンは試験に対して実施された変更や、計画に試験が追加された場合に反応しますが、既存の試験に新しいグレードが追加された場合には反応しません。
OOPath の一部が非リアクティブである場合は、OOPath 式の残りの部分も非リアクティブになります。たとえば、以下の OOPath 式は完全に非リアクティブです。
完全に非リアクティブである OOPath 式の例
Student( $grade: ?/plan/exams[ course == "Big Data" ]/grades )
こうした理由から、同じ OOPath 式内で ?/ セパレーターを複数回使用することはできません。たとえば、以下の式はコンパイルエラーの原因となります。
重複した非リアクティビティーマーカーを使用する OOPath 式の例
Student( $grade: /plan?/exams[ course == "Big Data" ]?/grades )
OOPath 式のリアクティビティーを有効にするもう 1 つの方法は、Red Hat Process Automation Manager で List および Set インターフェースの専用の実装を使用することです。これらの実装は、ReactiveList および ReactiveSet クラスです。また、ReactiveCollection クラスも使用できます。これらの実装により、Iterator および ListIterator クラスを使用した可変操作の実行もリアクティブにサポートされます。
以下のクラスの例では、これらのクラスを使用して OOPath 式のリアクティビティーを設定します。
OOPath 式のリアクティビティーを設定する Java クラスの例
public class School extends AbstractReactiveObject {
private String name;
private final List<Child> children = new ReactiveList<Child>(); 1
public void setName(String name) {
this.name = name;
notifyModification(); 2
}
public void addChild(Child child) {
children.add(child); 3
// No need to call `notifyModification()` here
}
}
2.9. DRL におけるルールアクション (THEN)
ルールの then 部分 (または、ルールの 右辺 (RHS)) には、ルールの条件部分が満たされる場合に実行されるアクションが含まれます。アクションは、1 つ以上の メソッド で構成されます。これらのメソッドは、ルール条件とパッケージ内で使用できる利用可能なデータオブジェクトに応じて結果を実行します。たとえば、銀行がローン申請者が 21 歳を超えていることを要件としているが (ルール条件が Applicant( age < 21 ))、ローン申請者が 21 歳未満の場合、"Underage" ルールの then アクションは setApproved( false ) となり、年齢が基準に達していないためローンの申し込みは承認されません。
ルールアクションの主な目的は、デシジョンエンジンのワーキングメモリー内でデータの挿入、削除、または変更を行うことです。有効なルールアクションは小規模かつ宣言的で、可読性があるものです。ルールアクションで必須または条件付きコードを使用する必要がある場合は、ルールを小規模かつより宣言的な複数のルールに分割します。
申込者の年齢制限に関するルールの例
rule "Underage"
when
application : LoanApplication()
Applicant( age < 21 )
then
application.setApproved( false );
application.setExplanation( "Underage" );
end
2.9.1. DRL でサポートされるルールアクションメソッド
DRL では DRL ルールアクションで使用できる以下のルールアクションメソッドがサポートされてます。これらのメソッドを使用することで、最初にワーキングメモリーインスタンスを参照せずに、デシジョンエンジンのワーキングメモリーを変更できます。これらのメソッドは Red Hat Process Automation Manager ディストリビューションの KnowledgeHelper クラスで提供されるメソッドへのショートカットとして機能します。
すべてのルールアクションメソッドについては、Red Hat カスタマーポータルから Red Hat Process Automation Manager 7.7.0 Source Distribution ZIP ファイルをダウンロードし、~/rhpam-7.7.0-sources/src/drools-$VERSION/drools-core/src/main/java/org/drools/core/spi/KnowledgeHelper.java に移動してください。
setこれを使用してフィールドの値を設定します。
set<field> ( <value> )
ローン申し込みの承認の値を設定するルールアクションの例
$application.setApproved ( false ); $application.setExplanation( "has been bankrupt" );
modifyファクトの変更するフィールドを指定し、デシジョンエンジンに変更を通知します。このメソッドは、ファクトの更新に対する構造化されたアプローチを提供します。このメソッドは、
update操作とオブジェクトフィールドを変更する setter 呼び出しを組み合わせたものです。modify ( <fact-expression> ) { <expression>, <expression>, ... }ローン申し込み件数および承認を変更するルールアクションの例
modify( LoanApplication ) { setAmount( 100 ), setApproved ( true ) }updateフィールドと更新される関連ファクト全体を指定して、その変更をデシジョンエンジンに通知します。ファクトが変更されたら、更新された値の影響を受ける可能性がある別のファクトを変更する前に、
updateを呼び出す必要があります。この追加設定を回避するには、modifyメソッドを代わりに使用します。update ( <object, <handle> ) // Informs the decision engine that an object has changed update ( <object> ) // Causes `KieSession` to search for a fact handle of the object
ローンの申し込み件数および承認を更新するルールアクションの例
LoanApplication.setAmount( 100 ); update( LoanApplication );
注記property-change リスナーを指定する場合は、オブジェクトの変更時にこのメソッドを呼び出す必要はありません。property-change リスナーの詳細については、「Red Hat Process Automation Manager のデシジョンエンジン」を参照してください。
insertnewファクトをデシジョンエンジンのワーキングメモリーに挿入し、ファクトに必要な結果として生成されるフィールドと値を定義します。insert( new <object> );
新しいローン申請者オブジェクトを挿入するルールアクションの例
insert( new Applicant() );
insertLogicalデシジョンエンジンに
newファクトを論理挿入する場合に使用します。デシジョンエンジンはファクトの挿入、取り消しに対して論理的な決定を行う役割を担います。通常の挿入または記述による挿入の後には、ファクトは明示的に取り消される必要があります。論理挿入後にファクトを挿入したルールの条件が true ではなくなった場合、挿入されたファクトは自動的に取り消されます。insertLogical( new <object> );
新しいローン申請者オブジェクトを論理的に挿入するルールアクションの例
insertLogical( new Applicant() );
deleteデシジョンエンジンからオブジェクトを削除します。キーワード
retractも DRL でサポートされており、同じアクションを実行しますが、DRL のコードでは、キーワードinsertとの整合性を考慮してdeleteが通常推奨されます。delete( <object> );
ローン申請者オブジェクトを削除するルールアクションの例
delete( Applicant );
2.9.2. drools および kcontext 変数のその他のルールアクションメソッド
標準のルールアクションメソッドに加えて、デシジョンエンジンでは、ルールアクションで使用できる事前定義された drools および kcontext 変数と組み合わせて使用できるメソッドもサポートしています。
drools 変数を使用して、Red Hat Process Automation Manager ディストリビューションの KnowledgeHelper クラスからメソッドを呼び出すことができます。これは、標準のアクションメソッドのベースとなるクラスでもあります。すべての drools ルールアクションのオプションについては、Red Hat カスタマーポータルから Red Hat Process Automation Manager 7.7.0 Source Distribution の ZIP ファイルをダウンロードし、~/rhpam-7.7.0-sources/src/drools-$VERSION/drools-core/src/main/java/org/drools/core/spi/KnowledgeHelper.java に移動してください。
以下の例は、drools 変数と共に使用できる一般的なメソッドです。
-
drools.halt(): ユーザーまたはアプリケーションで以前にfireUntilHalt()が呼び出されている場合は、ルールの実行を終了します。ユーザーまたはアプリケーションがfireUntilHalt()を呼び出す場合、デシジョンエンジンは アクティブモード で開始され、ユーザーまたはアプリケーションがhalt()を明示的に呼び出すまでルールの評価を継続します。それ以外の場合、デシジョンエンジンはデフォルトで パッシブモード で実行され、ユーザーまたはアプリケーションが明示的にfireAllRules()を呼び出す場合にのみルールを評価します。 -
drools.getWorkingMemory():WorkingMemoryオブジェクトを返します。 -
drools.setFocus( "<agenda_group>" ): ルールが属する指定されたアジェンダグループにフォーカスを設定します。 -
drools.getRule().getName(): ルールの名前を返します。 -
drools.getTuple()、drools.getActivation(): 現在実行されているルールに一致するTupleを返し、対応するActivationを提供します。これらの呼び出しは、ロギングやデバッグのを行う場合に役立ちます。
kcontext 変数を getKieRuntime() メソッドと共に使用して、KieContext クラスや、拡張によりご使用の Red Hat Process Automation Manager ディストリビューションの RuleContext クラスから別のメソッドを呼び出すことができます。完全な Knowledge Runtime API は kcontext 変数を通じて公開され、詳細なルールアクションメソッドを提供します。kcontext ルールアクションのすべてのオプションについては、Red Hat カスタマーポータルから Red Hat Process Automation Manager 7.7.0 Source Distribution の ZIP ファイルをダウンロードし、~/rhpam-7.7.0-sources/src/kie-api-parent-$VERSION/kie-api/src/main/java/org/kie/api/runtime/rule/RuleContext.java に移動してください。
以下の例は kcontext.getKieRuntime() の変数とメソッドの組み合わせで使用できる一般的なメソッドです。
-
kcontext.getKieRuntime().halt(): ユーザーまたはアプリケーションで以前にfireUntilHalt()が呼び出されている場合は、ルールの実行を終了します。このメソッドは、drools.halt()メソッドと同等です。ユーザーまたはアプリケーションがfireUntilHalt()を呼び出す場合、デシジョンエンジンは アクティブモード で開始され、ユーザーまたはアプリケーションがhalt()を明示的に呼び出すまでルールの評価を継続します。それ以外の場合、デシジョンエンジンはデフォルトで パッシブモード で実行され、ユーザーまたはアプリケーションが明示的にfireAllRules()を呼び出す場合にのみルールを評価します。 kcontext.getKieRuntime().getAgenda(): KIE セッションAgendaへの参照を返し、次にルールアクティベーショングループ、ルールアジェンダグループ、およびルールフローグループにアクセスできるようにします。アジェンダグループ「CleanUp」にアクセスしてフォーカスを設定する呼び出しの例
kcontext.getKieRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus();
この例は、
drools.setFocus( "CleanUp" )と同等です。-
kcontext.getKieRuntime().getQueryResults(<string> query): クエリーを実行し、結果を返します。このメソッドはdrools.getKieRuntime().getQueryResults()と同等です。 -
kcontext.getKieRuntime().getKieBase():KieBaseオブジェクトを返します。KIE ベースはルールシステムのすべてのナレッジのソースであり、現在の KIE セッションの開始元です。 -
kcontext.getKieRuntime().setGlobal()、~.getGlobal()、~.getGlobals(): グローバル変数を設定するか、または取得します。 -
kcontext.getKieRuntime().getEnvironment(): 使用するオペレーティングシステム環境と類似したランタイムのEnvironmentを返します。
2.9.3. 条件付きおよび名前付きの結果を伴う高度なルールアクション
一般的に、有効なルールアクションは小規模かつ宣言的であり、可読性があります。ただし、場合によっては、ルールごとに結果を 1 つに制限することが困難であり、以下のルールの例のように、ルールの構文が冗長になったり、繰り返しが多くなる可能性があります。
冗長で繰り返しの構文の多いルールの例
rule "Give 10% discount to customers older than 60"
when
$customer : Customer( age > 60 )
then
modify($customer) { setDiscount( 0.1 ) };
end
rule "Give free parking to customers older than 60"
when
$customer : Customer( age > 60 )
$car : Car( owner == $customer )
then
modify($car) { setFreeParking( true ) };
end
繰り返しを部分的に解決する手段として、以下の変更例にあるように、2 番目のルールで最初のルールを拡張します。
拡張された条件を使用して部分的に強化されたルールの例
rule "Give 10% discount to customers older than 60"
when
$customer : Customer( age > 60 )
then
modify($customer) { setDiscount( 0.1 ) };
end
rule "Give free parking to customers older than 60"
extends "Give 10% discount to customers older than 60"
when
$car : Car( owner == $customer )
then
modify($car) { setFreeParking( true ) };
end
より効率的な代替方法として、以下の例に示すように、変更した条件およびラベルが付与された対応するルールアクションを使用して、2 つのルールを 1 つのルールに統合することができます。
条件および名前付きの結果を使用した統合されたルールの例
rule "Give 10% discount and free parking to customers older than 60"
when
$customer : Customer( age > 60 )
do[giveDiscount]
$car : Car( owner == $customer )
then
modify($car) { setFreeParking( true ) };
then[giveDiscount]
modify($customer) { setDiscount( 0.1 ) };
end
このルールの例では、通常のデフォルトアクションと、giveDiscount という名前のもう 1 つの別のアクションが使用されています。giveDiscount アクションは、KIE ベースで年齢が 60 歳を超えた顧客が見つかると、その顧客が車を所有しているかどうかにかかわらず、キーワード do でアクティブにされます。
名前付きの結果のアクティベーションは、次の例の if ステートメントのように、追加の条件を使用して設定できます。if ステートメント内の条件は、その直前にあるパターンで常に評価されます。
追加の条件が指定される統合されたルールの例
rule "Give free parking to customers older than 60 and 10% discount to golden ones among them"
when
$customer : Customer( age > 60 )
if ( type == "Golden" ) do[giveDiscount]
$car : Car( owner == $customer )
then
modify($car) { setFreeParking( true ) };
then[giveDiscount]
modify($customer) { setDiscount( 0.1 ) };
end
以下のより複雑な例のように、ネストされた if および else if 構成を使用して、さまざまなルール条件を評価することもできます。
より複雑な条件を使用して統合されたルールの例
rule "Give free parking and 10% discount to over 60 Golden customer and 5% to Silver ones"
when
$customer : Customer( age > 60 )
if ( type == "Golden" ) do[giveDiscount10]
else if ( type == "Silver" ) break[giveDiscount5]
$car : Car( owner == $customer )
then
modify($car) { setFreeParking( true ) };
then[giveDiscount10]
modify($customer) { setDiscount( 0.1 ) };
then[giveDiscount5]
modify($customer) { setDiscount( 0.05 ) };
end
このルールの例では、60 歳を超えた Golden 顧客には 10% の割引きと無料駐車サービスが提供されますが、Silver 顧客に提供されるのは 5% の割引きのみで、無料駐車サービスは提供されません。このルールでは、do ではなく break というキーワードによって、giveDiscount5 という名前の結果がアクティブにされます。キーワード do は、デシジョンエンジンのアジェンダで結果をスケジュール設定し、ルール条件の残りの部分が引き続き評価されるようにします。一方、break は追加の条件の評価を行いません。名前付きの結果が do のいずれの条件にも一致しないが、break でアクティブにされる場合、ルールの条件部分に到達することはないため、ルールはコンパイルされません。
2.10. DRL ファイルのコメント
DRL では、二重のスラッシュ (//) が先頭に付けられた単一行コメントと、スラッシュおよびアスタリスク (/* … */) で囲まれた複数行のコメントがサポートされます。DRL のコメントを使用すると、DRL ファイル内のルールまたは関連するコンポーネントに対して注釈を付けることができます。DRL ファイルが処理される際、デシジョンエンジンはこれらのコメントを無視します。
コメントが使用されるルールの例
rule "Underage"
// This is a single-line comment.
when
$application : LoanApplication() // This is an in-line comment.
Applicant( age < 21 )
then
/* This is a multi-line comment
in the rule actions. */
$application.setApproved( false );
$application.setExplanation( "Underage" );
end
DRL コメントでは、ハッシュ記号 # はサポートされていません。
2.11. DRL トラブルシューティングのエラーメッセージ
Red Hat Process Automation Manager は、DRL エラーに関する標準メッセージを提供します。これらのメッセージは DRL ファイルで問題をトラブルシューティングして解決するのに役立ちます。エラーメッセージでは、以下の形式が使用されています。
図2.1 DRL ファイルの問題に関するエラーメッセージの形式

- 最初のブロック: エラーコード
- 2 番目のブロック: DRL ソースのエラーが発生している行および列
- 3 番目のブロック: 問題の説明
- 4 番目のブロック: DRL ソース内のエラーが発生しているコンポーネント (ルール、関数、クエリー)
- 5 番目のブロック: DRL ソース内のエラーが発生しているパターン (該当する場合)
Red Hat Process Automation Manager では、以下の標準化されたエラーメッセージがサポートされます。
- 101: no viable alternative
パーサーが決定ポイントに到達したが、選択肢を特定できなかったことを示します。
誤ったスペリングを含むルールの例
1: rule "simple rule" 2: when 3: exists Person() 4: exits Student() // Must be `exists` 5: then 6: end
エラーメッセージ
[ERR 101] Line 4:4 no viable alternative at input 'exits' in rule "simple rule"
ルール名がないルールの例
1: package org.drools.examples; 2: rule // Must be `rule "rule name"` (or `rule rule_name` if no spacing) 3: when 4: Object() 5: then 6: System.out.println("A RHS"); 7: endエラーメッセージ
[ERR 101] Line 3:2 no viable alternative at input 'when'
この例では、パーサーがキーワード
whenを検出しましたが、予想していたのはルール名であったため、パーサーはwhenに予想外の不適切なトークンというフラグを付けます。誤った構文が使用されるルールの例
1: rule "simple rule" 2: when 3: Student( name == "Andy ) // Must be `"Andy"` 4: then 5: end
エラーメッセージ
[ERR 101] Line 0:-1 no viable alternative at input '<eof>' in rule "simple rule" in pattern Student
注記列と行の値が
0:-1の場合、パーサーがソースファイルの終わり (<eof>) に到達したが、不完全な構成を検出したことを示します。よくある原因として、引用符"…"、アポストロフィー'…'、または括弧(…)が欠落して場合があります。- 102: mismatched input
パーサーが特定の記号を予想していたが、これが現在の入力位置で欠落していることを示します。
不完全なルールステートメントが使用されるルールの例
1: rule simple_rule 2: when 3: $p : Person( // Must be a complete rule statementエラーメッセージ
[ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule "simple rule" in pattern Person
注記列と行の値が
0:-1の場合、パーサーがソースファイルの終わり (<eof>) に到達したが、不完全な構成を検出したことを示します。よくある原因として、引用符"…"、アポストロフィー'…'、または括弧(…)が欠落して場合があります。誤った構文が使用されるルールの例
1: package org.drools.examples; 2: 3: rule "Wrong syntax" 4: when 5: not( Car( ( type == "tesla", price == 10000 ) || ( type == "kia", price == 1000 ) ) from $carList ) // Must use `&&` operators instead of commas `,` 6: then 7: System.out.println("OK"); 8: endエラーメッセージ
[ERR 102] Line 5:36 mismatched input ',' expecting ')' in rule "Wrong syntax" in pattern Car [ERR 101] Line 5:57 no viable alternative at input 'type' in rule "Wrong syntax" [ERR 102] Line 5:106 mismatched input ')' expecting 'then' in rule "Wrong syntax"
この例では、構文に関する問題が原因で相互に関連する複数のエラーメッセージが発生しています。
&&演算子で,を置き換えるという 1 つの解決策により、すべてのエラーが解消されます。複数のエラーが発生した場合に、エラーが前のエラーの結果である場合があるため、一度に 1 つずつ解決します。- 103: failed predicate
セマンティクスの述語の検証が
falseと評価されたことを示します。これらのセマンティクスの述語は一般にdeclare、rule、exists、notなど、DRL ファイルのコンポーネントのキーワードを特定するために使用されます。無効なキーワードが使用されているルールの例
1: package nesting; 2: 3: import org.drools.compiler.Person 4: import org.drools.compiler.Address 5: 6: Some text // Must be a valid DRL keyword 7: 8: rule "test something" 9: when 10: $p: Person( name=="Michael" ) 11: then 12: $p.name = "other"; 13: System.out.println(p.name); 14: end
エラーメッセージ
[ERR 103] Line 6:0 rule 'rule_key' failed predicate: {(validateIdentifierKey(DroolsSoftKeywords.RULE))}? in ruleSome textの行は、DRL キーワード構成で始まっていないか、または DRL キーワード構成の一部ではないため、パーサーが DRL ファイルの残りの部分の評価に失敗します。注記このエラーは
102: mismatched inputに似ていますが、通常は DRL キーワードが関係しています。- 104: trailing semi-colon not allowed
ルール条件の
eval()句でセミコロン;が使用されていますが、セミコロンは使用できません。eval()と末尾のセミコロンが使用されているルールの例1: rule "simple rule" 2: when 3: eval( abc(); ) // Must not use semicolon `;` 4: then 5: end
エラーメッセージ
[ERR 104] Line 3:4 trailing semi-colon not allowed in rule "simple rule"
- 105: did not match anything
パーサーが文法内で、少なくとも 1 回は代替の選択肢に一致する必要があるサブルールに到達したが、サブルールがいすれにも一致しなかったことを示します。パーサーは出口のないブランチに入ります。
空の条件内に無効なテキストがあるルールの例
1: rule "empty condition" 2: when 3: None // Must remove `None` if condition is empty 4: then 5: insert( new Person() ); 6: end
エラーメッセージ
[ERR 105] Line 2:2 required (...)+ loop did not match anything at input 'WHEN' in rule "empty condition"
この例では、条件は空であることが意図されていますが、
Noneという単語が使用されています。このエラーは、DRL の有効でないキーワード、データタイプ、またはパターン構成であるNoneを削除することによって解決できます。
解決できないその他の DRL エラーメッセージが発生した場合は、Red Hat のテクニカルアカウントマネージャーにお問い合わせください。
2.12. DRL ルールセットのルールユニット
ルールユニットは、データソース、グローバル変数、および DRL ルールのグループで、特定の目的に向けて互いに機能し合います。ルールユニットを使用して、ルールセットを小さなユニットに分割し、それらのユニットにさまざまなデータソースをバインドしてから、個別のユニットを実行します。ルールユニットは、実行制御用のルールアジェンダグループまたはアクティブ化グループなどの、ルールをグループ化する DRL 属性に代わるものとして、強化されています。
ルールユニットは、ルールの実行を調整することで、あるルールユニットが完全に実行されると別のルールユニットの開始をトリガーする場合などに便利です。たとえば、データ強化用の一連のルール、そのデータを処理する別の一連のルール、および処理されたデータを抽出して出力する別の一連のルールがあるとします。これらのルールセットを 3 つの異なるルールユニットに追加する場合、これらのルールユニットを調整することで、1 つ目のユニットが完全に実行されると 2 つ目のユニットの開始をトリガーし、2 つ目のユニットが完全に実行されると 3 つ目のユニットの開始をトリガーすることができます。
ルールユニットを定義するには、以下の例に示すように RuleUnit インターフェースを実装します。
ルールユニットクラスの例
package org.mypackage.myunit;
public static class AdultUnit implements RuleUnit {
private int adultAge;
private DataSource<Person> persons;
public AdultUnit( ) { }
public AdultUnit( DataSource<Person> persons, int age ) {
this.persons = persons;
this.age = age;
}
// A data source of `Persons` in this rule unit:
public DataSource<Person> getPersons() {
return persons;
}
// A global variable in this rule unit:
public int getAdultAge() {
return adultAge;
}
// Life-cycle methods:
@Override
public void onStart() {
System.out.println("AdultUnit started.");
}
@Override
public void onEnd() {
System.out.println("AdultUnit ended.");
}
}
この例では、persons はタイプ Person のファクトのソースです。ルールユニットのデータソースは、指定のルールユニットで処理されるデータのソースで、デシジョンエンジンがルールユニットの評価に使用するエントリーポイントを表します。adultAge グローバル変数は、このルールユニットに属するすべてのルールからアクセスできます。最後の 2 つのメソッドは、ルールユニットのライフサイクルの一部で、デシジョンエンジンによって呼び出されます。
デシジョンエンジンは、以下のようなルールユニットのオプションのライフサイクルメソッドをサポートします。
表2.3 ルールユニットのライフサイクルメソッド
| メソッド | 呼び出されるタイミング |
|---|---|
|
|
ルールユニット実行開始時 |
|
|
ルールユニット実行終了時 |
|
|
ルールユニット実行の一時停止時 ( |
|
|
ルールユニット実行の再開時 ( |
|
|
ルールユニットにおけるルールの結果が異なるルールユニットの実行をトリガー |
ルールユニットに、ルールを 1 つ以上追加することができます。デフォルトでは、DRL ファイルのすべてのルールは、DRL ファイル名の命名規則に従うルールユニットに自動的に関連付けられます。DRL ファイルが同じパッケージにあり、RuleUnit インターフェースを実装するクラスと同じ名前を持つ場合、その DRL ファイルのすべてのルールは、そのルールユニットに暗黙的に属します。たとえば、org.mypackage.myunit パッケージの AdultUnit.drl ファイルにあるすべてのルールは、自動的にルールユニット org.mypackage.myunit.AdultUnit の一部となります。
この命名規則をオーバーライドし、DRL ファイル内のルールが属するルールユニットを明示的に宣言するには、DRL ファイル内でキーワード unit を使用します。unit 宣言は、すぐに package 宣言に従い、DRL ファイルのルールが一部となっているパッケージ内のクラス名を含む必要があります。
DRL ファイルのルールユニット宣言の例
package org.mypackage.myunit
unit AdultUnit
rule Adult
when
$p : Person(age >= adultAge) from persons
then
System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
同じ KIE ベースで、ルールユニットありのルールとルールユニットなしのルールを混在させないでください。KIE ベースで 2 つのルールのパラダイムを混在させると、コンパイルエラーが発生します。
以下の例のように OOPath 表記を使用して、より便利な方法で同じパターンを書き換えることもできます。
OOPath 表記を使用する DRL ファイルのルールユニット宣言の例
package org.mypackage.myunit
unit AdultUnit
rule Adult
when
$p : /persons[age >= adultAge]
then
System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
OOPath は、DRL ルールの条件の制約でオブジェクトのグラフを参照するために設計された XPath のオブジェクト指向構文の拡張です。OOPath は、コレクションおよびフィルター制約を処理する間に XPath からのコンパクト表記を使用して関連要素を移動します。また、OOPath はとくにオブジェクトグラフの場合に役に立ちます。
この例では、ルール条件で一致するファクトはすべて、ルールユニットクラスの DataSource 定義で定義される persons のデータソースから取得されます。ルール条件およびアクションは、グローバル変数が DRL ファイルレベルで定義されるのと同じ方法で adultAge 変数を使用します。
KIE ベースに定義されたルールユニットを 1 つ以上実行するには、KIE ベースにバインドされている新規の RuleUnitExecutor クラスを作成し、関連するデータソースからルールユニットを作成して、ルールユニットエグゼキューターを実行します。
ルールユニット実行の例
// Create a `RuleUnitExecutor` class and bind it to the KIE base: KieBase kbase = kieContainer.getKieBase(); RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase ); // Create the `AdultUnit` rule unit using the `persons` data source and run the executor: RuleUnit adultUnit = new AdultUnit(persons, 18); executor.run( adultUnit );
ルールは RuleUnitExecutor クラスによって実行されます。RuleUnitExecutor クラスは KIE セッションを作成し、必要な DataSource オブジェクトをこれらのセッションに追加してから、run() メソッドにパラメーターとして渡される RuleUnit に基づいてルールを実行します。
例の実行コードは、関連する Person ファクトが persons データソースに挿入されると、以下の出力を生成します。
ルールユニット実行出力の例
org.mypackage.myunit.AdultUnit started. Jane is adult and greater than 18 John is adult and greater than 18 org.mypackage.myunit.AdultUnit ended.
ルールユニットインスタンスを明示的に作成するのではなく、エグゼキューターにルールユニット変数を登録し、実行するルールユニットクラスをエグゼキューターに渡すと、エグゼキューターがルールユニットのインスタンスを作成します。続いて、ルールユニットを実行する前に DataSource 定義および他の変数を設定できます。
登録変数を含む別のルールユニット実行オプション
executor.bindVariable( "persons", persons );
.bindVariable( "adultAge", 18 );
executor.run( AdultUnit.class );
RuleUnitExecutor.bindVariable() メソッドに渡す名前は、実行時に、同じ名前のルールユニットクラスのフィールドに変数をバインドするために使用されます。前述の例では、RuleUnitExecutor は、新しいルールユニットに "persons" の名前にバインドされているデータソースを挿入します。また、AdultUnit クラス内の対応する名前のフィールドに、文字列 "adultAge" にバインドされている値 18 を挿入します。
このデフォルトの変数バインディング動作をオーバーライドするには、@UnitVar アノテーションを使用してルールユニットクラスの各フィールドに対して論理バインディング名を明示的に定義します。たとえば、以下のクラスのフィールドバインディングは、代替名で再度定義されます。
@UnitVar を使用した変数バインディング名を変更するコード例
package org.mypackage.myunit;
public static class AdultUnit implements RuleUnit {
@UnitVar("minAge")
private int adultAge = 18;
@UnitVar("data")
private DataSource<Person> persons;
}
次に、これらの代替名を使用して、変数をエグゼキューターにバインドし、ルールユニットを実行できます。
変更した変数名を使用したルールユニット実行の例
executor.bindVariable( "data", persons );
.bindVariable( "minAge", 18 );
executor.run( AdultUnit.class );
ルールユニットは、run() メソッド (KIE セッションで fireAllRules() を呼び出す場合と同じ) を使用して パッシブモード で、または runUntilHalt() メソッド (KIE セッションで fireUntilHalt() を呼び出す場合と同じ) を使用して アクティブモード で実行できます。デフォルトでは、デシジョンエンジンは パッシブモード で実行され、ユーザーまたはアプリケーションが明示的に run() (標準ルールでは fireAllRules()) を呼び出す場合にのみルールユニットを評価します。ユーザーまたはアプリケーションがルールユニットに runUntilHalt() (標準ルールでは fireAllRules()) を呼び出す場合、デシジョンエンジンは アクティブモード で開始し、ユーザーまたはアプリケーションが明示的に halt() を呼び出すまで、継続的にルールユニットを評価します。
runUntilHalt() メソッドを使用する場合は、メインスレッドをブロックしないように、別の実行スレッド上でメソッドを呼び出します。
別のスレッド上の runUntilHalt() を使用したルールユニットの実行例
new Thread( () -> executor.runUntilHalt( adultUnit ) ).start();
2.12.1. ルールユニットのデータソース
ルールユニットのデータソースは、指定のルールユニットが処理したデータのソースで、デシジョンエンジンがルールユニットの評価に使用するエントリーポイントを表します。ルールユニットは、ゼロまたは複数のデータソースを持つことができ、ルールユニット内で宣言された各 DataSource の定義は、ルールユニットエグゼキューターへの異なるエントリーポイントに対応することができます。複数のルールユニットは、単一データソースを共有できます。ただし、各ルールユニットは、別々のエントリーポイントを使用しなければなりません。このエントリーポイントを介して同じオブジェクトが挿入されます。
以下の例で示すように、ルールユニットクラスの固定されたデータセットを使用して DataSource 定義を作成できます。
データソース定義の例
DataSource<Person> persons = DataSource.create( new Person( "John", 42 ),
new Person( "Jane", 44 ),
new Person( "Sally", 4 ) );
データソースはルールユニットのエントリーポイントを表すため、ルールユニットでファクトを挿入、更新、または削除できます。
ルールユニットでファクトを挿入、更新、削除するコード例
// Insert a fact: Person john = new Person( "John", 42 ); FactHandle johnFh = persons.insert( john ); // Modify the fact and optionally specify modified properties (for property reactivity): john.setAge( 43 ); persons.update( johnFh, john, "age" ); // Delete the fact: persons.delete( johnFh );
2.12.2. ルールユニットの実行制御
一方のルールユニットの実行により、もう一方のルールユニットの開始がトリガーされるようにルールの実行を調整する必要がある場合に、ルールユニットは役に立ちます。
ルールユニットの実行制御を容易にするために、デシジョンエンジンは以下のルールユニットメソッドをサポートします。このメソッドは、DRL ルールアクションで使用して、ルールユニットの実行を調整することができます。
-
drools.run(): 指定されたルールユニットクラスの実行をトリガーします。このメソッドでは、ルールユニットの実行を命令的に中断し、他の指定されたルールユニットを有効化します。 -
drools.guard(): 関連付けられたルール条件が満たされるまで、指定されたルールユニットクラスが実行されないようにします (保護します)。このメソッドは、他の指定されたルールユニットの実行を宣言的にスケジュールします。デシジョンエンジンが、保護ルールの条件に対して少なくとも 1 つの一致をもたらす場合、保護されたルールユニットは有効とみなされます。ルールユニットには、複数の保護ルールを含めることができます。
drools.run() メソッドの例として、それぞれが指定されたルールユニットに属す以下の DRL ルールを検討してください。NotAdult ルールは drools.run( AdultUnit.class ) メソッドを使用して AdultUnit ルールユニットの実行をトリガーします。
drools.run() を使用した制御された実行を含む DRL ルールの例
package org.mypackage.myunit
unit AdultUnit
rule Adult
when
Person(age >= 18, $name : name) from persons
then
System.out.println($name + " is adult");
end
package org.mypackage.myunit
unit NotAdultUnit
rule NotAdult
when
$p : Person(age < 18, $name : name) from persons
then
System.out.println($name + " is NOT adult");
modify($p) { setAge(18); }
drools.run( AdultUnit.class );
end
この例では、これらのルールからビルドされた KIE ベースから作成された RuleUnitExecutor クラスと、これにバインドされている persons の DataSource 定義も使用します。
ルールエグゼキューターとデータソース定義の例
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Person> persons = executor.newDataSource( "persons",
new Person( "John", 42 ),
new Person( "Jane", 44 ),
new Person( "Sally", 4 ) );
この例では、RuleUnitExecutor クラスから DataSource 定義を直接作成し、これを単一ステートメントで "persons" 変数にバインドします。
例の実行コードは、関連する Person ファクトが persons データソースに挿入されると、以下の出力を生成します。
ルールユニット実行出力の例
Sally is NOT adult John is adult Jane is adult Sally is adult
NotAdult ルールは、"Sally" という人物の評価時に一致を検出します。この人物は 18 歳未満です。続いてこのルールは、この人物の年齢を 18 に変更し、drools.run( AdultUnit.class ) メソッドを使用して AdultUnit ルールユニットの実行をトリガーします。AdultUnit ルールユニットには、DataSource 定義の 3 人の persons 全員に対して実行可能となったルールが含まれています。
drools.guard() メソッドの例として、以下の BoxOffice クラスと BoxOfficeUnit ルールユニットクラスを検討してください。
BoxOffice クラスの例
public class BoxOffice {
private boolean open;
public BoxOffice( boolean open ) {
this.open = open;
}
public boolean isOpen() {
return open;
}
public void setOpen( boolean open ) {
this.open = open;
}
}
BoxOfficeUnit ルールユニットクラスの例
public class BoxOfficeUnit implements RuleUnit {
private DataSource<BoxOffice> boxOffices;
public DataSource<BoxOffice> getBoxOffices() {
return boxOffices;
}
}
また、この例では、以下の TicketIssuerUnit ルールユニットクラスを使用して、少なくとも 1 つのボックスオフィス (チケット売り場) が営業中である限り、ボックスオフィスでのイベントチケットの販売を続行します。このルールユニットは persons および tickets の DataSource 定義を使用します。
TicketIssuerUnit ルールユニットクラスの例
public class TicketIssuerUnit implements RuleUnit {
private DataSource<Person> persons;
private DataSource<AdultTicket> tickets;
private List<String> results;
public TicketIssuerUnit() { }
public TicketIssuerUnit( DataSource<Person> persons, DataSource<AdultTicket> tickets ) {
this.persons = persons;
this.tickets = tickets;
}
public DataSource<Person> getPersons() {
return persons;
}
public DataSource<AdultTicket> getTickets() {
return tickets;
}
public List<String> getResults() {
return results;
}
}
BoxOfficeUnit ルールユニットには、BoxOfficeIsOpen DRL ルールが含まれます。これは、drools.guard( TicketIssuerUnit.class ) メソッドを使用して、イベントチケットを配布する TicketIssuerUnit ルールユニットの実行を保護します。以下に DRL ルールの例を示します。
drools.guard() を使用した制御された実行を含む DRL ルールの例
package org.mypackage.myunit;
unit TicketIssuerUnit;
rule IssueAdultTicket when
$p: /persons[ age >= 18 ]
then
tickets.insert(new AdultTicket($p));
end
rule RegisterAdultTicket when
$t: /tickets
then
results.add( $t.getPerson().getName() );
end
package org.mypackage.myunit;
unit BoxOfficeUnit;
rule BoxOfficeIsOpen
when
$box: /boxOffices[ open ]
then
drools.guard( TicketIssuerUnit.class );
end
この例では、少なくとも 1 つのボックスオフィスが open である限り、保護された TicketIssuerUnit ルールユニットが有効なため、イベントチケットは配布されます。open 状態のボックスオフィスがなくなると、保護された TicketIssuerUnit ルールユニットは実行されなくなります。
以下のクラスの例は、より完全なボックスオフィスのシナリオを説明しています。
ボックスオフィスシナリオのクラスの例
DataSource<Person> persons = executor.newDataSource( "persons" );
DataSource<BoxOffice> boxOffices = executor.newDataSource( "boxOffices" );
DataSource<AdultTicket> tickets = executor.newDataSource( "tickets" );
List<String> list = new ArrayList<>();
executor.bindVariable( "results", list );
// Two box offices are open:
BoxOffice office1 = new BoxOffice(true);
FactHandle officeFH1 = boxOffices.insert( office1 );
BoxOffice office2 = new BoxOffice(true);
FactHandle officeFH2 = boxOffices.insert( office2 );
persons.insert(new Person("John", 40));
// Execute `BoxOfficeIsOpen` rule, run `TicketIssuerUnit` rule unit, and execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "John", list.get(0) );
list.clear();
persons.insert(new Person("Matteo", 30));
// Execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "Matteo", list.get(0) );
list.clear();
// One box office is closed, the other is open:
office1.setOpen(false);
boxOffices.update(officeFH1, office1);
persons.insert(new Person("Mark", 35));
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "Mark", list.get(0) );
list.clear();
// All box offices are closed:
office2.setOpen(false);
boxOffices.update(officeFH2, office2); // Guarding rule is no longer true.
persons.insert(new Person("Edson", 35));
executor.run(BoxOfficeUnit.class); // No execution
assertEquals( 0, list.size() );
2.12.3. ルールユニットのアイデンティティーの競合
保護されたルールユニットを使用したルール実行のシナリオでは、1 つのルールが複数のルールユニットを保護することができます。同時に、複数のルールが 1 つのルールユニットを保護してから有効化することもできます。このような 2 通りの保護シナリオでは、ルールユニットには、アイデンティティーの競合を避けるための明確に定義されたアイデンティティーが必要です。
デフォルトでは、ルールユニットのアイデンティティーはルールユニットクラス名で、RuleUnitExecutor によりシングルトンクラスとして処理されます。この識別動作は、RuleUnit インターフェースの getUnitIdentity() のデフォルトメソッドにエンコードされています。
RuleUnit インターフェースのデフォルトのアイデンティティーメソッド
default Identity getUnitIdentity() {
return new Identity( getClass() );
}
場合によっては、ルールユニット間のアイデンティティーの競合を避けるために、このデフォルトの識別動作をオーバーライドする必要があります。
たとえば、以下の RuleUnit クラスには、あらゆる種類のオブジェクトを許可する DataSource 定義が含まれています。
Unit0 ルールユニットクラスの例
public class Unit0 implements RuleUnit {
private DataSource<Object> input;
public DataSource<Object> getInput() {
return input;
}
}
このルールユニットには、2 つの条件 (OOPath 表記)に基づいて別のルールユニットを保護する、以下の DRL ルールが含まれています。
ルールユニットの GuardAgeCheck DRL ルールの例
package org.mypackage.myunit
unit Unit0
rule GuardAgeCheck
when
$i: /input#Integer
$s: /input#String
then
drools.guard( new AgeCheckUnit($i) );
drools.guard( new AgeCheckUnit($s.length()) );
end
保護された AgeCheckUnit ルールユニットは、一連の personsの年齢を検証します。AgeCheckUnit には、確認用の persons の DataSource の定義、検証用の minAge 変数、および結果を集計する List が含まれます。
AgeCheckUnit ルールユニットの例
public class AgeCheckUnit implements RuleUnit {
private final int minAge;
private DataSource<Person> persons;
private List<String> results;
public AgeCheckUnit( int minAge ) {
this.minAge = minAge;
}
public DataSource<Person> getPersons() {
return persons;
}
public int getMinAge() {
return minAge;
}
public List<String> getResults() {
return results;
}
}
AgeCheckUnit ルールユニットには、データソースの persons の検証を実行する以下の DRL ルールが含まれます。
ルールユニットの CheckAge DRL ルールの例
package org.mypackage.myunit
unit AgeCheckUnit
rule CheckAge
when
$p : /persons{ age > minAge }
then
results.add($p.getName() + ">" + minAge);
end
この例では、RuleUnitExecutor クラスを作成し、これらの 2 つのルールユニットが含まれる KIE ベースにクラスをバインドして、同じルールユニットの DataSource 定義を 2 つ作成します。
executor 定義とデータソース定義の例
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Object> input = executor.newDataSource( "input" );
DataSource<Person> persons = executor.newDataSource( "persons",
new Person( "John", 42 ),
new Person( "Sally", 4 ) );
List<String> results = new ArrayList<>();
executor.bindVariable( "results", results );
一部のオブジェクトを入力データソースに挿入し、Unit0 ルールユニットを実行できるようになりました。
挿入されたオブジェクトを使用したルールユニット実行の例
ds.insert("test");
ds.insert(3);
ds.insert(4);
executor.run(Unit0.class);
実行結果一覧の例
[Sally>3, John>3]
この例では、AgeCheckUnit という名前のルールユニットはシングルトンクラスと見なされ、1 回のみ実行されます。この時、minAge 変数は 3 に設定されます。入力データソースに挿入された文字列 "test" および整数 4 の両方は、minAge 変数が 4 に設定された 2 回目の実行をトリガーする可能性もあります。しかし、同じアイデンティティーを持つ別のルールユニットがすでに評価されているため、2 回目の実行はありません。
このルールユニットのアイデンティティーの競合を解決するには、AgeCheckUnit クラスの getUnitIdentity() メソッドをオーバーライドして、ルールユニットアイデンティティーに minAge 変数も含めます。
getUnitIdentity() メソッドをオーバーライドする変更された AgeCheckUnit ルールユニット
public class AgeCheckUnit implements RuleUnit {
...
@Override
public Identity getUnitIdentity() {
return new Identity(getClass(), minAge);
}
}
このオーバーライドにより、以前のルールユニットの実行例は、以下の出力を生成します。
変更したルールユニットの実行結果一覧の例
[John>4, Sally>3, John>3]
minAge が 3 と 4 に設定されたルールユニットは、2 つの異なるルールユニットと見なされるようになり、両方とも実行されます。