16.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
16.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 Decision Manager は標準の JDK Introspector
クラスを使用してこのマッピングを行うため、標準の 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 )
16.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
制約バインディングは、それに続く最初のアトミック式のみを考慮します。次の例では、パターンは人物の年齢のみを変数 $a
にバインドします。
Person( $a : age * 2 < 100 )
より明確で効率的なルール定義のために、制約バインディングと制約式を分離します。バインディングと式の組み合わせはサポートされますが、パターンが複雑になり、評価の効率に影響が及ぶ可能性があります。
// Do not use the following format: Person( $a : age * 2 < 100 ) // Use the following format instead: Person( age * 2 < 100, $a : age )
前の例で、変数 $a
に人の年齢の 2 倍をバインドする場合は、次の例に示すように、かっこで囲んでアトミック式にする必要があります。
Person( $a : (age * 2) )
デシジョンエンジンは同じ宣言に対するバインディングをサポートしませんが、複数のプロパティー間での引数の ユニフィケーション をサポートします。位置引数は、常にユニフィケーションで常に処理され、名前付き引数の場合はユニフィケーション記号 :=
が使用されます。
以下のパターンの例では、2 つの Person
ファクト間で age
プロパティーを統合します。
ユニフィケーションが使用されるパターンの例
Person( $age := age ) Person( $age := age )
ユニフィケーションは、シーケンスオカレンスのバインドされたフィールドの同じ値に対して、最初のオカレンスと制約のバインディングを宣言します。
16.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
と見なされます。
16.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" )
16.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 ) )
16.8.6. DRL のパターン制約における演算子の優先順位
DRL では、適用可能な制約演算子の場合は標準的な Java 演算子の優先順位をサポートしていますが、一部の例外と、DRL に固有の追加の演算子がいくつかあります。以下の表には、適用可能な DRL 演算子を優先順位の高いものから低いものの順で記載しています。
演算子のタイプ | 演算子 | 注記 |
---|---|---|
ネストされているか、null 安全なプロパティーアクセス |
| 標準の Java セマンティクスではない |
|
| 標準の Java セマンティクスではない |
制約のバインディング |
| 標準の Java セマンティクスではない |
乗法 |
| |
加法 |
| |
シフト |
| |
リレーショナル |
| |
等価性 |
|
標準の Java の |
非短絡 (Non-short-circuiting) |
| |
非短絡の排他的 |
| |
非短絡の包含的 |
| |
論理 |
| |
論理 |
| |
三項 |
| |
コンマ区切り |
| 標準の Java セマンティクスではない |
16.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
を使用したパターンの例//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
が使用されているルールを複数のサブルールに書き換えます。このプロセスにより、最終的には、ルートノードおよび各条件要素のサブルールとして、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. end
forall
と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. end
from
とグラフ表記を使用するルールの例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. end
EntryPoint オブジェクトが使用され、ファクトが挿入された 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. end
accumulate
オブジェクトのコレクションを反復処理し、各要素に対してカスタムアクションを実行し、結果オブジェクトを返します (制約が
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`. end
accumulate
の条件でカスタムかつドメイン固有の関数を使用するには、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
-
16.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 Decision 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 } }