ヘッダーをスキップ
Oracle® Fusion Middleware Oracle Business Rulesランゲージ・リファレンス・ガイド
11gリリース1 (11.1.1.7)
B56239-05
  ドキュメント・ライブラリへ移動
ライブラリ
製品リストへ移動
製品
目次へ移動
目次
索引へ移動
索引

前
 
次
 

1 ルール・プログラミングの概念

この章では、Oracle Business Rules RL Language (RL Language)の概念について説明します。

この章の内容は次のとおりです。

1.1 Oracle Business Rules RL Languageコマンドラインの開始

Oracle Business Rules環境は、rl.jarで提供されるクラスによってJVMコンテナまたはJ2EEコンテナに実装されます。RL Languageコマンドライン・インタフェースは、次のコマンドを使用して開始します。

java -jar $ORACLE_HOME/soa/modules/oracle.rules_11.1.1/rl.jar -p "RL> "

ここで、ORACLE_HOMEは、SOAモジュールがインストールされる場所です(例: c:/Oracle/Middleware)。-pオプションはプロンプトを指定します。

RL Languageコマンドライン・インタフェースにより、Oracle Business Rules RuleSessionにアクセスできます。このRuleSessionは、JavaプログラマがJavaアプリケーションでRL LanguageにアクセスするためのAPIです(コマンドライン・インタフェースでは、RuleSessionを内部的に使用します)。

例1-1に示すように、RL>プロンプトにテキストを入力し、コマンドライン・インタフェースを使用してプログラムを実行できます。

例1-1 コマンドライン・インタフェースの使用

RL> println(1 + 2);
3
RL> final int low = -10;
RL> final int high = 10;
RL> println(low + high * high);
90
RL> exit;

関連項目:


1.2 ルールおよびルールセットの概要

RL Languageのルールセットでは、Javaパッケージと同様に、RLクラス、関数およびルール用の名前空間が提供されます。さらに、ルールセットを使用すると、ルールの起動順序を部分的に指定できます。ルールセットには、実行可能なアクションおよびその他のルールセットを含めたり、Javaのクラスやパッケージをインポートすることもできます。

RL Languageのルールは、ルール条件(fact-set-conditionとも呼ばれます)とaction-block(アクションのリスト)で構成されています。ルールは、ルール条件に続いてルール・アクションがあるif-then構造に従っています。

例1-2に、「Hello World」と出力するプログラムを示します。この例は、デフォルト・ルールセット(main)に1つの最上位のアクションがあるプログラムを示しています。例1-2には1つのアクションのみが含まれ、ルールは定義されていないため、アクションはコマンドラインで即時に実行されます。

例1-2 「Hello World」プログラミング例

RL> println("Hello World");Hello World
RL>

関連項目:

ルール起動の詳細は、「ルール起動の理解と制御」を参照してください。


1.2.1 ルール条件

ルール条件はルールの1コンポーネントで、ファクトを参照する複数の条件式で構成されています。

次の例では、条件式がファクト(Driverインスタンスのd1)を参照し、次に、そのファクトのデータ・メンバーageが16未満であることをテストします。

if (fact Driver d1 && d1.age < 16)

例1-3に、RL Languageで記述された完全なルールを示します(このルールには、1つのルール条件と1つのルール・アクションが含まれています)。

Oracle Rules Engineは、条件式がtrueになるようなファクトの組合せがあるたびにルールをアクティブにします。ルール条件はOracle Rules Engine内の使用可能なファクト全体に対する問合せのようなもので、問合せから戻される行のそれぞれに対してルールがアクティブになります。


注意:

ルールのアクティブ化とルールの起動は異なります。詳細は、第1.4項「ルール起動の理解と制御」を参照してください。


例1-3 運転者の年齢ルールの定義

RL> rule driverAge{
     if (fact Driver d1 && d1.age < 16)
     {
        println("Invalid Driver");
     }
}

1.2.2 ルール・アクション

ルール・アクションは、すべてのルール条件が満たされた場合にアクティブ化されます。ルールのaction-blockで実行されるアクションには、いくつかの種類があります。たとえば、ルールのaction-block内のアクションでは、assert関数をコールして新規ファクトを追加したり、retract関数をコールしてファクトを削除できます。また、アクションでJavaメソッドやRL Language関数を実行することもできます(例1-3ではprintln関数が使用されています)。アクションを使用すると、パターン一致に関連した任意のタスクを実行する関数をコールできます。

1.3 ファクトおよびRL Languageクラスの概要

この項では、Oracle Business Rulesのファクトについて次の各項で説明します。

1.3.1 ファクトとは

Oracle Business Rulesのファクトは、アサートされたオブジェクトです。Javaオブジェクトの場合、ファクトはオブジェクトのシャロー・コピーです。これは、可能な場合は各プロパティがクローニングされ、それが可能でない場合はファクトがJavaオブジェクト参照のコピーになることを意味します。

RL Languageでは、JavaオブジェクトはJavaクラスのインスタンスであり、RLオブジェクトはRL Languageクラスのインスタンスです。Javaクラスをクラスパスで使用したり、RL Languageクラスを定義してルールセットで使用できます。また、ファクト・クラス宣言を使用して、Javaクラスの既存のプロパティまたはメソッドに関連付けられている追加プロパティを宣言することもできます。さらに、ファクト・クラス宣言を使用して、ファクトで不要なJavaクラスのプロパティを非表示にできます。

RL Languageクラスは、メソッドを除いたJava Beanに類似しています。RLクラスには、複数の名前付きプロパティが含まれます。各プロパティのタイプは、RLクラス、Javaオブジェクトまたはプリミティブ型のいずれかになります。

Oracle Business Rulesを使用する際は、通常、Javaクラス(XMLの使用をサポートしているJAXB生成クラスを含む)を使用して、ルール対応アプリケーションにビジネス・オブジェクトを調べるルールを作成したり、その結果をアプリケーションに戻します。Oracle Rules Engineで他のルールをトリガーできる中間ファクトを作成するには、通常、RLクラスを使用します。

1.3.2 ファクトをアサートして作業メモリーに追加

Oracle Business Rulesでは、作業メモリーを使用してファクトを追加します(ファクトは作業メモリー以外には存在しません)。作業メモリーはRuleSessionに含まれます。

RL Languageのファクトは、クラスのアサートされたインスタンスです。例1-4に、RLクラスenterRoomのインスタンスをファクトとして作業メモリーに追加するassert関数を示します。アサートされたファクトの基礎となるクラスは、JavaまたはRL Languageで定義できます。

例1-4では、sayHelloルールがタイプenterRoomのファクトに一致すると、一致したファクトごとにメッセージが出力されます。assert関数内のアクションnewは、enterRoomクラスのインスタンスを作成します。

例1-4では、run関数によってsayHelloルールが起動します。


注意:

RL LanguageのnewキーワードはJavaのnew機能の拡張で、プロパティに対して初期値を指定できます。


例1-4 RL Languageクラスによる定義したファクトの照合

RL> class enterRoom { String who; } 
RL> assert(new enterRoom(who: "Bob"));
RL> rule sayHello {
  if ( fact enterRoom ) {
    println("Hello " + enterRoom.who);
  }
}
RL> run();
Hello Bob
RL>

1.3.3 RL Languageクラスをファクトとして使用

RL Languageクラスをルール・プログラムで使用すると、Javaオブジェクトを提供するJavaアプリケーションのアプリケーション・コードを変更せずに、Javaアプリケーションのオブジェクト・モデルを補完できます。

例1-5に、顧客データcustを含むJavaクラスを使用するgoldCustルールを示します。このルールのアクションは、GoldCustomer RLクラスのインスタンスをアサートし、3か月間の支払が500ドルを超える顧客を示します。Java CustomerクラスにはメソッドSpentInLastMonthsが含まれています。このメソッドには、追加する顧客データの月数を示す整数を指定します。

例1-5 goldCustルール

rule goldCust {
  if (fact Customer cust && cust.SpentInLastMonths(3) > 500 ){
               assert (new GoldCustomer(cust: cust));
  }
}

例1-6に示すgoldDiscountルールは、RLファクトGoldCustomerを使用して、過去3か月間に$500の支払があった顧客には10%割引の資格があることを示しています。

例1-6 goldDiscountルール

rule goldDiscount {
   if (fact Order ord & fact GoldCustomer(cust: ord.customer) ) 
       {
            ord.discount = 0.1;
            assert(ord);
       }
}

例1-7に、GoldCustomer RLクラスの宣言を示します(ここでは、Customerクラスがクラスパスで使用可能であることを前提にしています)。

例1-7 RL Languageクラスの宣言

class GoldCustomer {
 Customer cust;
}

1.3.4 Javaクラスをファクトとして使用

アサートされたJavaオブジェクトは、ファクトとしてRL Languageプログラムで使用できます。Javaクラスを明示的に定義または宣言する必要はありません。ただし、プログラムを実行するときは、Javaクラスをクラスパスに含める必要があります。これによって、Javaクラスをルールで使用でき、ルール・プログラムは、Javaクラスに定義されているpublic属性、publicメソッドおよびBeanプロパティにアクセスして使用できます(一部のアプリケーションにはBeanプロパティが適切です。これは、JavaオブジェクトがPropertyChangeListenerをサポートしていることがOracle Rules Engineで検出される場合があるためです。この場合は、オブジェクトの変更時に通知するメカニズムが使用されます)。

さらに、ファクト・クラス宣言によって、プロパティをRLプログラムで使用できるように微調整できます。また、ファクト・クラス宣言は、継承が行われるいくつかの状況で必要になる場合があります。

Javaクラスを使用すると、import文を使用してパッケージ名を省略できます(例1-8を参照)。

例1-8 import文を使用したJavaファクトの例

ruleset main
{
  import example.Person;
  import java.util.*;
  rule hasNickNames
  {
    if (fact Person p && ! p.nicknames.isEmpty() )
    {
       // accessing properties as fields:
       println(p.firstName + " " + p.lastName + " has nicknames:");
       Iterator i = p.nicknames.iterator();
       while (i.hasNext())
       {
          println(i.next());
       }
    }
}

1.4 ルール起動の理解と制御

内容は次のとおりです。

1.4.1 ルールのアクティブ化とアジェンダ

Oracle Rules Engineは、作業メモリーの状態に変化があると、ファクトをすべてのルールのルール条件(fact-set-condition)と照合します。作業メモリー内に変更が生じた場合(通常はファクトがアサートまたは取り消された場合)、Oracle Rules Engineでは、ルール条件に一致するかどうかのみがチェックされます。特定のルール条件がtrueとなるファクトのグループをファクト・セット行と呼びます。ファクト・セットは、特定のルールに対するすべてのファクト・セット行のコレクションです。したがって、ファクト・セットは、1つのルールに含まれる複数のルール条件に一致するファクトで構成されます。ファクト・セット内のファクト・セット行ごとに、ファクト・セット行とルールへの参照で構成されたアクティブ化アジェンダに追加されます(アジェンダには、アクティブ化の完全リストが含まれます)。

図1-1に示すRuleSessionには、作業メモリー内に、アクティブ化が含まれるアジェンダがあります。

図1-1 作業メモリーとアクティブ化が含まれるアジェンダがあるRuleSession

図1-1の説明が続きます
図1-1「作業メモリーとアクティブ化が含まれるアジェンダがあるRuleSession」の説明

runrunUntilHaltおよびstepの各関数は、アジェンダにあるアクティブ化を実行します。つまり、これらのコマンドによってルールが起動します(指定した数のアクティブ化を起動するには、stepコマンドを使用してください)。

Oracle Rules Engineがアクティブ化を削除すると、アジェンダからアクティブ化を削除し、ルールのアクションを実行して、ルールが起動します。

Oracle Rules Engineでは、ルール条件が満たされなくなると、ルールを起動せずにアクティブ化を削除できます。たとえば、ファクトが変更された場合、またはルールが消去された場合は、ルールを起動せずにアクティブ化を削除できます。さらに、ファクト・セット行で参照されるファクトが変更された場合、またはファクトが取り消された場合はルール条件に一致しなくなるため、Oracle Rules Engineではアクティブ化がアジェンダから削除されます(新規ファクトがアサートされた場合、および!演算子が適用された場合も同様です)。

ルールのアクティブ化については、次の点に注意してください。

  1. アクティブ化が作成されてルールが起動するのは、ファクトがアサート、変更または取り消された場合のみです(それ以外の場合、ルールは連続して起動します)。

  2. ルール条件に記述されているファクトがルールによってアサートされた後も、ルール条件がtrueの場合は、新規のアクティブ化がアジェンダに追加され、ルールが再度起動します(この場合、ルールは連続して起動します)。多くの場合、この動作は不適切です。

  3. ルール起動に関連したアクションによってファクトが変更、アサートまたは取り消されると、アジェンダにある一連のアクティブ化が変更される場合があり、これによって起動するルールの順序が変更される場合があります。

  4. ルールは、同時ではなく順番に起動します。

1.4.2 ファクト、ルールおよびルールのアクティブ化の監視

watchActivationswatchFactswatchRulesおよびshowFactsの各関数は、RL Languageプログラムの記述とデバッグに有用です。

内容は次のとおりです。

1.4.2.1 作業メモリー内のファクトの監視と表示

例1-9に、watchFacts関数を示します。この関数は、作業メモリーとの間で追加および削除されたファクトに関する情報を出力します。

例1-9に示すように、このwatchFacts関数はファクトがアサートされると==>を出力します。各ファクトにはf-で始まる短い識別子が割り当てられるため、該当するファクトを参照できます。たとえば、アクティブ化にファクトへの参照を含めて、ルール・アクションに渡すことができます。

例1-9では、デフォルト・ルールセットmainがプログラムで使用されていることに注意してください。このルールセットには、enterRoomクラスが含まれています。

例1-9 enterRoomファクトでのwatchFactsの使用

RL> watchFacts();
RL>  class enterRoom {String who;}
RL> assert(new enterRoom(who: "Rahul"));
 ==> f-1 main.enterRoom(who : "Rahul")
RL> assert(new enterRoom(who: "Kathy"));
 ==> f-2 main.enterRoom(who : "Kathy")
RL> assert(new enterRoom(who: "Tom"));
 ==> f-3 main.enterRoom(who : "Tom")
RL>

showFactsを使用すると、作業メモリー内の現在のファクトを表示できます。例1-10に、Oracle Rules Engineによる初期ファクトf-0のアサートを示します(Oracle Rules Engineはこのファクトを内部的に使用します)。

例1-10 作業メモリー内のファクトの表示

RL> showFacts();
f-0   initial-fact()
f-1   main.enterRoom(who : "Rahul")
f-2   main.enterRoom(who : "Kathy")
f-3   main.enterRoom(who : "Tom")
For a total of 4 facts.

作業メモリーからファクトを削除するには、例1-11に示すようにretractを使用します。watchFactsが有効な場合は、ファクトを取り消すと、Oracle Rules Engineは<==を出力します。

例1-11 作業メモリーからのファクトの取消し

RL> watchFacts();
RL> retract(object(2));
 <== f-2 main.enterRoom(who : "Kathy")
RL> showFacts();
f-0   initial-fact()
f-1   main.enterRoom(who : "Rahul")
f-3   main.enterRoom(who : "Tom")
For a total of 3 facts.

1.4.2.2 アクティブ化およびルール起動の監視

watchActivations関数はOracle Rules Engineを監視し、アジェンダとの間で追加および削除されたルールのアクティブ化に関する情報を出力します。watchRules関数は、ルール起動に関する情報を出力します。

例1-12に、run関数を使用してアクティブ化を起動する方法を示します。最初に入室したRahulへの応対が最後になっていることに注意してください(これは、起動順序のためです)。


注意:

アクティブ化は、関連するファクトが条件を満たさなくなった場合、起動する前にアジェンダから削除できます。


例1-12 WatchActivationsおよびWatchRulesの使用

RL> clear;
RL> class enterRoom {String who;}
RL> assert(new enterRoom(who: "Rahul"));
RL> assert(new enterRoom(who: "Kathy"));
RL> assert(new enterRoom(who: "Tom"));
RL> watchActivations();
RL> rule sayHello {
if (fact enterRoom) {
      println("Hello " + enterRoom.who);
   }
}
==> Activation: main.sayHello :  f-1
==> Activation: main.sayHello :  f-2
==> Activation: main.sayHello :  f-3
RL> watchRules();
RL> run();
Fire 1 main.sayHello f-3
Hello Tom
Fire 2 main.sayHello f-2
Hello Kathy
Fire 3 main.sayHello f-1
Hello Rahul
RL>

1.4.3 ルール起動の順序付け

アジェンダにあるルールのアクティブ化を起動する順序付けアルゴリズムを理解するために、ルールセット・スタックについて説明します。各RuleSessionには、1つのルールセット・スタックが含まれています。RuleSessionのルールセット・スタックには、フォーカス・ルールセットと呼ばれる最上位のスタックがあります。このルールセット・スタックには、フォーカス・ルールセット以外のルールセットも含まれます。ルールセット・スタックにルールセットを追加するには、pushRulesetまたはsetRulesetStack組込み関数のいずれかを使用します。ルールセット・スタックのルールセットを管理するには、clearRulesetStackpopRulesetおよびsetRulesetStack関数を使用できます。この場合、ルールセット・スタックのフォーカスは、ルールセット・スタック内の現行の最上位ルールセットになります(例1-13を参照)。

例1-13 ルールセット・スタック - 図

RuleSet Stack

           Focus Ruleset  -->     Top_Ruleset          
                                  Next_down_Ruleset    
                                  Lower_Ruleset        
                                  Bottom_Ruleset       

アジェンダにアクティブ化がある場合、Oracle Rules Engineは、run、runUntilHaltまたはstepが実行されるとルールを起動します。Oracle Rules Engineは、次の順序付けアルゴリズムを使用して、アジェンダにあるすべてのアクティブ化からルールのアクティブ化を順に選択します。

  1. Oracle Rules Engineは、フォーカス・ルールセット(つまり、ルールセット・スタックの最上位にあるルールセット)のすべてのルールのアクティブ化を選択します(pushRulesetおよびsetRulesetStack組込み関数を参照)。

  2. フォーカス・ルールセットに関連付けられた複数のアクティブ化内では、ルールの優先度によって起動順序が指定されます。優先度の高いルールのアクティブ化が、優先度の低いルールのアクティブ化よりも先に選択されて起動します(デフォルトの優先度レベルは0です)。

  3. フォーカス・ルールセット内で複数のルールのアクティブ化が同じ優先度の場合は、最後に追加されたルールのアクティブ化から起動します。ただし、複数のアクティブ化がアジェンダに同時に追加された場合、それらのアクティブ化の起動順序は定義されません。

  4. 現行フォーカス・ルールセット内のルールのアクティブ化がすべて起動すると、Oracle Rules Engineはルールセット・スタックを削除し、プロセスは現行フォーカスで、ステップ1に戻ります。

R1という名前の一連のルールは、R2という名前の一連のルールよりも前にすべて起動する必要がある場合、次のいずれかの処理方法があります。

  • 1つのルールセットを使用し、R1内のルールの優先度をR2内のルールの優先度よりも高く設定します。

  • R1とR2の2つのルールセットを使用し、ルールセット・スタックにR2を追加してからR1を追加します。

一般的には、1つのルールセットを使用して優先度を設定するよりも、ルールセット・スタックで2つのルールセットを使用する方が柔軟にルールの起動順序を制御できます。たとえば、R1内のすべてのルールが起動する前に、R1内のルールRが、R2内のあるルールをトリガーする必要がある場合は、R内のreturnアクションによってルールセット・スタックが削除されるため、R2内のルールを起動できます。

ルールの2つのセット間で実行を交互に実行する必要がある場合(たとえば、ファクトを作成するルールとファクトを削除するルール)は、異なる2つの優先度を使用するよりも、2つのルールセットを使用する方がフローの流れが簡単になります。

例1-14では、keepGaryOutルールの優先度がhighに設定されています。これはsayHelloルールの優先度よりも高くなっています(デフォルトの優先度は0です)。両方のルールのアクティブ化がアジェンダにある場合は、優先度の高いルールが先に起動します。runをコールする前に、sayHelloには2つのアクティブ化がアジェンダにあることに注意してください。keepGaryOutが最初に起動するため、enterRoom(who: "Gary")ファクトが取り消され、これによって対応するsayHelloアクティブ化が削除されます。その結果、1つのsayHelloのみが起動します。

例1-14に示すルールでは、次の2つのRL Language機能が示されています。

  1. fact演算子(ファクト・セット・パターンとも呼ばれます)は、オプションのvarキーワードを使用し、一致するファクトにバインドする変数(ここでは変数g)を定義します。

  2. 作業メモリー内のファクトは、retract関数を使用して削除できます。

例1-14 keepGaryOutルールでのルール優先度の使用

RL> final int low = -10;
RL> final int high = 10;
RL> rule keepGaryOut {
  priority = high;
  if (fact enterRoom(who: "Gary") var g) {
    retract(g);
  }
}
RL> assert(new enterRoom(who: "Gary"));
==> f-4 main.enterRoom(who: "Gary")
==> Activation: main.sayHello : f-4
==> Activation: main.keepGaryOut : f-4
RL> assert(new enterRoom(who: "Mary"));
==> f-5 main.enterRoom(who: "Mary")
==> Activation: main.sayHello : f-5
RL> run();
Fire 1 main.keepGaryOut f-4
<== f-4 main.enterRoom(who: "Gary")
<== Activation: main.sayHello : f-4
Fire 2 main.sayHello f-5
Hello Mary
RL>

例1-15に、アサートされたenterRoomファクトと一致する条件を含むsayHelloルールを示します。この一致によって、アクティブ化がアジェンダに追加されます。例1-15では、次のRL Languageプログラミング機能を示します。

  1. Oracle Rules Engineは、作業メモリーの状態に変化があると、ファクトをすべてのルールのルール条件(fact-set-condition)と照合します。したがって、ファクトがアサートされるのがルールの定義前か後かは関係ありません。

  2. run関数は、アジェンダにあるすべてのアクティブ化を処理します。runのコール前にアジェンダのアクティブ化が処理されることはありません。

例1-15 sayHelloルールを伴うenterRoomクラス

RL> class enterRoom { String who; } 
RL> rule sayHello {
  if ( fact enterRoom ) {
    println("Hello " + enterRoom.who);
  }
}
RL> assert(new enterRoom(who: "Bob"));
RL> run();
Hello Bob
RL>

ルール起動の順序付けに関する注意

  1. returnアクションを使用すると、ルール起動の動作が変更されます。ルール内のreturnアクションでは、ルールセット・スタックからルールセットが削除されるため、実行は、現在ルールセット・スタックの最上位にあるルールセットからのアジェンダに存在するアクティブ化で続行されます。

    ルールの実行がrunまたはstep関数で開始され、returnアクションによってルールセット・スタックから最後のルールセットが削除された場合、制御はrunまたはstep関数のコール元に戻されます。

    ルールの実行がrunUntilHalt関数で開始された場合、returnアクションではルールセット・スタックから最後のルールセットは削除されません。最後のルールセットは、残っているアクティブ化がなくなったときにrunUntilHaltで削除されます。その後、Oracle Rules Engineは次のアクティブ化の出現を待機します。アクティブ化が出現すると、ルールセットの起動が再開される前に、ルールセット・スタックに最後のルールセットが配置されます。

  2. ルールの優先度は、指定したルールセット内のルールにのみ適用されます。したがって、別のルールセット内のルールの優先度とは比較できません。

1.5 有効日の使用

デフォルトでは、有効日の値は、ルール・エンジンによって暗黙的に管理されます。この場合、組込み関数のrunファミリが起動されると、有効日が現在のシステム日付に更新されます。これは、任意のルールが起動される前に実行されます。そのため、新しい有効日は、ルールの起動が開始される前に適用されます。runUntilHaltの場合は、アジェンダにあるルールが0個から1個以上に遷移するたびに、この更新が実行されます。

Oracle Business Rules RL Languageでは、有効開始日、有効終了日およびactiveプロパティのみがルールに適用されます(ルールセットには適用されません)。ルールの有効開始日および有効終了日のプロパティは、ルール内に指定できます。

例を次に示します。

rule myrule2 {
   active = true;
   effectiveDateForm = Rule.EDFORM_DATETIME:
   effectiveStartDate = JavaDate.fromDateTimeString("2008-11-01");
   effectiveEndDate = JavaDate.fromDateTimeString("2008-11-16");

   if (fact Foo)
   {
    .
    .
   }

}

If you use the RuleSession Java API, you can access the effective start and end date.

RL Languageからプロパティを設定するには、長い式またはいくつかの文が必要です。


たとえば、次のようなルールセットがあるとします。

ruleset MyRules {
  rule myRule { if fact foo { }}
}

activeプロパティを設定するには、次を使用します。

Rule r = getRuleSession().getRuleset("MyRules").getRule("myRule");

r.setActive(false);

1.6 RL LanguageプログラムとJavaプログラムの統合

この項では、RL LanguageプログラムとJavaプログラムの統合について説明します。内容は次のとおりです。


関連項目:

『Oracle Business Rulesユーザーズ・ガイド』のRules SDKデシジョン・ポイントAPIの使用に関する項


1.6.1 アサートされたJava Beanをファクトとして使用

例1-16に、単純なBeanのJavaソースを示します。例1-16に示したBean example.Personjavacコマンドを使用してディレクトリ・ツリーにコンパイルします。

次に、このBeanにアクセスするためのRL Languageコマンドラインの開始方法を示します。

java -classpath $ORACLE_HOME/soa/modules/oracle.rules_11.1.1/rl.jar;BeanPath oracle.rules.rl.session.CommandLine -p "RL> "

BeanPathは提供されているJava Beanクラスへのクラスパス・コンポーネントです。

例1-16 Person BeanクラスのJavaソース

package example;
import java.util.*;
public class Person
{
  private String firstName;
  private String lastName;
  private Set nicknames = new HashSet();

  public Person(String first, String last, String[] nick) {
    firstName = first; lastName = last;
    for (int i = 0; i < nick.length; ++i)
      nicknames.add(nick[i]);
  }
  public Person() {}
  public String getFirstName() {return firstName;}
  public void setFirstName(String first) {firstName = first;}
  public String getLastName() {return lastName;}
  public void setLastName(String last) {lastName = last;}
  public Set getNicknames() {return nicknames;}
}

例1-17に、example.Personを使用するRL LanguageプログラムをRL Languageコマンドラインで実行する方法を示します。Javaと同様に、import文を使用し、example.PersonのかわりにPersonを使用すると、Personクラスへの参照が可能になります。ルールは、Person Beanクラス、およびそのプロパティとメソッドを参照します。Personファクトを作成するためには、Java Person Beanをアサート(assert)する必要があります。

例1-17では、new演算子を使用して、peopleという名前のPersonオブジェクトの配列を作成します。people配列でfinalが宣言されているため、resetによるpeopleの追加作成はありません。numPeople変数ではfinalが宣言されていないため、resetによってassertPeople関数が再起動し、既存のPersonオブジェクトを使用してPersonファクトが再度アサートされます。

例1-17 Person Beanクラスを使用するルールセット

ruleset main
{
  import example.Person;
  import java.util.*;
  rule hasNickNames
  {
    if (fact Person(nicknames: var nns) p && !nns.isEmpty())
    {
             // accessing properties as fields:
       println(p.firstName + " " + p.lastName + " has nicknames:");
       Iterator i = nns.iterator();
       while (i.hasNext())
       {
          println(i.next());
       }
    }
}
rule noNickNames
{
   if fact Person(nicknames: var nns) p && nns.isEmpty()
   {
          // accessing properties with getters:
   println(p.getFirstName() + " " + p.getLastName() + " does not have nicknames");
   }
}
  final Person[] people = new Person[] {
new Person("Robert", "Smith", new String[] { "Bob", "Rob" }), // using constructor
new Person(firstName: "Joe", lastName: "Schmoe") // using attribute value pairs
};

function assertPeople(Person[] people) returns int 
{
   for (int i = 0; i < people.length; ++i) {
      assert(people[i]);
}
  return people.length;
  }
  int numPeople = assertPeople(people);
  run();
}

Java Beanをファクトとして使用する場合は、次の点に注意してください。

  1. fact演算子では、一致するパターンを含めたり、Beanプロパティを取得できます。プロパティは、Beanクラスのgetterおよびsetterメソッドによって定義されます。

  2. new演算子では、デフォルトの引数なしのコンストラクタを起動した後にプロパティ値を設定するパターンを含めたり、引数をユーザー定義コンストラクタに渡すことができます。

  3. factおよびnew演算子の外側では、getterおよびsetterメソッドを使用するか、あるいはフィールドのようにプロパティ名を使用して、Beanプロパティを参照または更新できます。

  4. Beanに同じ名前のプロパティとフィールドがある場合、そのフィールドはRL Languageでは参照できません。

例1-17を実行した後に、同じRuleSessionを使用して例1-18を実行した場合、出力は例1-17の結果と同じになります(両方のpersonファクトが再アサートされます)。


注意:

RL Languageコマンドライン・インタプリタは、起動時(およびclearコマンドの使用時)にRuleSessionを内部的に作成します。


例1-18 RuleSessionでのresetの使用

reset();
run();

1.6.2 RuleSessionオブジェクトのJavaアプリケーションでの使用

Javaプログラムでは、RuleSessionインタフェースを使用して、ルールセットの実行、Javaオブジェクトを引数として渡すRL Language関数の起動、およびRL Languageのwatchおよびprintln出力のリダイレクトを行うことができます。例1-19および例1-20に、「hello world」を出力するRuleSessionを使用するJavaプログラムのフラグメントを示します。多くのJavaプログラムのフラグメントと同様に、これらの例もRL Languageプログラムとして有効です。

RL Language環境では、複数のルール・セッションを使用できます。各ルール・セッションは複数のスレッドで使用できますが、ルールは一度に1つのスレッドで起動します。

各ルールのRuleSessionには、ファクトとルールの独自のコピーがあります。ファクトをJavaオブジェクトから作成するには、次のようなコールを使用します。

rs.callFunctionWithArgument("assert", Object;);

ルール、関数またはRL Languageクラスを作成するには、ルールセットを含む文字列を定義し、executeRulesetメソッドを使用します。

例1-19 callFunctionWithArgumentを使用するRuleSessionオブジェクトの使用

import oracle.rules.rl.*;
try {
  RuleSession rs = new RuleSession();
  rs.callFunctionWithArgument("println", "hello world");
} catch (RLException rle) {
   System.out.println(rle);
}

例1-20 executeRulesetを使用するRuleSessionの使用

import oracle.rules.rl.*;
try { 
   RuleSession rs = new RuleSession();
   String rset = 
     "ruleset main {" +
     " function myPrintln(String s) {" +
     " println(s);" +
     " }" +
     "}";
   rs.executeRuleset(rset); 
   rs.callFunctionWithArgument("myPrintln", "hello world");
} catch (RLException rle) {
  System.out.println(rle);
}

1.7 デシジョン・トレースの使用方法

Oracle Business Rulesでは、デシジョン・トレースはルール・エンジンの実行のトレースです。これには、ルールを起動したときのファクトの状態など、ルール・エンジンの状態に関する情報が含まれます。Oracle Business Rulesのルール・エンジンは、デシジョン・トレースXMLスキーマから、JAXBで生成されたJavaクラスを使用してデシジョン・トレースを構成し、戻します。

1.7.1 ルール・エンジン・レベルのデシジョン・トレースの概要

デシジョン・トレースをビジネス・アナリストにわかりやすく表示するには、関連付けられているルール・ディクショナリを利用できる必要があります。トレースに関連付けられているルール・ディクショナリを使用すると、トレースの出力に、トレース内にある関連するディクショナリのすべてのコンテンツ情報を含める必要がなくなるため、より柔軟性があり効率的なアプローチが可能になります。

XMLスキーマは、ファイルdecisiontrace.xsd内にあり、これはJarファイルrl.jarの一部です(oracle/rules/rl/trace/decisiontrace.xsd)。該当するパッケージは、oracle.rules.rl.traceoracle.rules.rl.extensions.traceおよびoracle.rules.sdk2.decisiontraceです。decisiontrace XMLスキーマから生成されたJavaクラス・パッケージは、パッケージoracle.rules.rl.trace内にあり、Javadocに含まれています。詳細は、Oracle Business Rules Java APIリファレンスを参照してください。

1.7.2 ルール・エンジン・レベルのデシジョン・トレースの使用方法

デシジョン・トレースは、一連のXML要素であり、ルールの評価中に発生したルール・エンジン・イベントを示しています。デシジョン・トレースに追加されるイベントの種類は、指定したトレース・レベルによって決まります。次のようなイベントが含まれます。

  • ファクト操作(アサート、撤回、変更)

  • ルールの起動

  • アジェンダで追加または削除されたルールのアクティブ化

  • ルールセット・スタックの変更

  • ルールのコンパイル

  • リセット(デシジョン・トレース分析で状態を維持するために必要)

各トレースには、特定のイベントに関する情報が含まれます。たとえば、ファクト操作のイベント・エントリは次の内容で構成されます。

  • 操作の種類(アサート、変更、撤回)

  • 作業メモリー内のファクトのID

  • ファクト・タイプ名(RL内のファクト・クラス)

  • ランタイム・オブジェクト・タイプ名

  • ファクト・オブジェクト・データ(ゼロ個以上のファクト・プロパティのプロパティ名と値など)

  • 操作がルール・アクションの結果であった場合は、ルール名、RL名

  • ミリ秒単位のタイムスタンプ

ファクト操作のイベント・トレースでは、ファクト・オブジェクトのコンテンツはJava Beanとしてのオブジェクトの構造を反映します。Beanプロパティが他のBeanへの参照の場合、関連するBeanのコンテンツがトレースに含まれます。Beanプロパティの値は、次のいずれかになります。

  • プロパティの文字列表現。これは、java.*およびjavax.*パッケージのプリミティブ型およびクラスである場合です。

  • それぞれのプロパティ値を持つ、ネストされたBeanオブジェクト。

  • ファクトID。これは、プロパティ値がオブジェクトであり、オブジェクト自身がファクトとしてアサートされている場合に発生します。トレースの時点でのファクトのデータは、トレース分析時のファクトIDを使用することでRuleEngineStateから取得できます。

  • トレースでListとしてアクセスされる値のコレクション。

  • トレースでListとしてアクセスされる値の配列。

トレースに含まれる値がどれになるかを実行時に調べるには、nullに対してテストします。null以外の値を持つのは、正しい値のみです。

表1-1に、ルール・エンジンでデシジョン・トレースを制御したりデシジョン・トレースにアクセスするRL関数を示します。

表1-1 RLデシジョン・トレース関数

関数 説明

getDecisionTrace

現在のトレースを戻し、新しいトレースを開始します。

getDecisionTraceLevel

現在のデシジョン・トレース・レベルを取得します。

getDecisionTraceLimit

トレース内のイベント数に対する現在の制限値を戻します。

setDecisionTraceLevel

デシジョン・トレース・レベルを指定したレベルに設定します。

setDecisionTraceLimit

トレース内のイベント数の制限値を設定します。

デフォルトの制限値は10000です。


デシジョン・トレース・レベルは、setDecisionTraceLevel関数を起動することで設定できます。RuleSession.CFG_DECISION_TRACE_LEVEL初期化パラメータをインクルードして、RuleSessionまたはRuleSessionPoolコンストラクタに渡される構成マップのレベルを指定することで、RuleSessionまたはRuleSessionPoolの初期トレース・レベルを構成することもできます。これはRuleSessionを作成する時点でのデシジョン・トレース・レベルを設定します。

setDecisionTraceLevel関数は初期化後にRuleSessionまたはRuleSessionPoolオブジェクトで起動できます。reset()を起動すると、この関数によりデシジョン・トレース・レベルが構成値に戻されます(ルール実行中にレベルが変更された場合)。したがって、reset()関数はデシジョン・トレースの制限を、RuleSessionまたはRuleSessionPoolの初期化で設定した値にリセットします。このような場合、reset()は初期化パラメータを使用して指定された値をリストアします。


注意:

こうしたRuleSessionに対するreset()のセマンティクスは、CFG_DECISION_TRACE_LIMITまたはCFG_DECISION_TRACE_LEVELあるいはその両方の初期化パラメータを使用して初期化された (または、プールがCFG_DECISION_TRACE_LIMITまたはCFG_DECISION_TRACE_LEVELあるいはその両方の初期化パラメータを使用して作成されている場合に、RuleSessionPoolから取得した)RuleSessionに対してのみ有効です。


トレースのサイズは、デシジョン・トレース内のエントリ数を制限することで制限します。JVM内で使用可能なすべてのヒープを消費するトレースを作成することから、ルール内の不具合の可能性があるため、ルール起動の無限ループを回避するために制限が必要となります。トレースの制限は、setDecisionTraceLimit関数を使用して設定します。この制限は、RuleSession (またはRuleSessionPool)で構成することも可能です。構成するには、RuleSessionまたはRuleSessionPoolコンストラクタに渡される構成マップに目的の制限を指定したRuleSession.CFG_DECISION_TRACE_LIMIT初期化パラメータをインクルードします。

runUntilHaltを使用するルール・アプリケーションでは、トレースの制限に達する前にアプリケーションでgetDecisionTraceを呼び出す必要があります。

デシジョン・トレースは、トレース・データに構造を提供し、これをプログラム的に操作できるようにします。ただし、トレース自体は分析するには、扱いにくい場合があります。トレース分析クラス(oracle.rules.rl.extensions.trace.TraceAnalysis)は、デシジョン・トレースを分析し、トレースの内容を調べやすくします。このクラスを使用して作業メモリー、アジェンダおよびルールセット・スタックの状態をトレースから構成します。

TraceAnalysis APIは次をサポートします。

  • トレース内に現れるファクト・タイプのリストの取得。

  • トレース内で起動されたルールの名前のリストの取得。

  • 特定のファクト・タイプの各インスタンスに対する最終ファクト・トレースのリストの取得。

  • ファクトIDで指定された特定のファクトに対する最終ファクト・トレースの取得。

  • ファクトIDで指定されたファクトに対するすべてのファクト・トレースの取得。

  • ファクト・トレースで、ファクト・トレースがルール・アクションによって作成された場合に、アクションが実行されるルールの起動のルール・トレースの取得。

  • ルール・トレースで、一致してルールが起動された各ファクトに対するファクト・トレースのリストの取得。

  • 次または前のトレースの取得。トレースの調査は通常、トレースの反復ではありません。たとえば、あるファクト・トレースからルール・トレースを取得することは、本質的にはそのルール・トレースへのジャンプです。そのルール・トレースの周囲のトレースは直接調べることができます。

  • 名前で指定されたルールに対するルール・トレースのリストの取得。

  • トレース・エントリに対するルール・エンジンの状態の取得。ルール・エンジンの状態は、トレースを生成したアクティビティの後のルール・エンジンの状態を反映します。このAPIを使用することで、各トレースの時点でのルール・エンジンの状態を調べることができます。このAPIは開発レベルのトレースで最も役立ちます。本番レベルのトレースでは、追跡できるのは作業メモリー内にあるファクトのみで、それにはファクトの内容は含まれていません。

例1-21に、デシジョン・トレース分析APIを使用したコード・サンプルを示します。

例1-21 デシジョン・トレース分析APIの使用方法

DecisionTrace trace;
...
TraceAnalysis ta = new TraceAnalysis(trace);
// Get all of the last fact traces for a fact type.
List<FactTrace> factTraces = ta.getLastFactTraces("com.example.MyFactType");
// From a particular fact trace, how it was arrived at may be explored, first by
// obtaining the rule that asserted or modified the fact.
// From the FactRecord, the rule that resulted in the record can be obtained.
FactTrace factTrace = factTraces.get(0);  // assumes there is one
RuleTrace ruleTrace = ta.whichRule(factTrace);
  // The ruleTrace will be null if the fact operation was not part of a rule action.
System.out.print("Fact " + factTrace.getFactId() + ", a " + factTrace.getFactType() + " " + factRecord.getFactOp());
if (ruleTrace != null)
    System.out.println(" by rule " + ruleTrace.getRuleName());
else
    System.out.println("");
// The analysis can continue by obtaining the list of FactRecords that matched the rule and
// proceeding to analyze the trace back in time.
List<FactTrace> matchingFacts = ta.getRuleMatchedFacts(ruleTrace);

1.7.3 本番レベルおよび開発レベルのトレース用のデシジョン・トレースのサンプル

例1-22に、本番レベルのトレース・ドキュメントのサンプルを示します。

例1-22 本番レベルのデシジョン・トレースのサンプル

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<decision-trace xmlns="http://xmlns.oracle.com/rules/decisiontrace">
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975549890</timestamp>
        <rule-name>OrderDiscount.goldCustomer</rule-name>
        <token-time>0</token-time>
        <sequence-number>1</sequence-number>
    </trace-entries>
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975549893</timestamp>
        <rule-name>OrderDiscount.goldCustomerDiscount</rule-name>
        <token-time>0</token-time>
        <sequence-number>2</sequence-number>
    </trace-entries>
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975549894</timestamp>
        <rule-name>OrderDiscount.applyDiscount</rule-name>
        <token-time>0</token-time>
        <sequence-number>3</sequence-number>
    </trace-entries>
</decision-trace>

例1-23 開発レベルのデシジョン・トレースのサンプル

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<decision-trace xmlns="http://xmlns.oracle.com/rules/decisiontrace">
    <trace-entries xsi:type="fact-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491008</timestamp>
        <fact-id>1</fact-id>
        <operation>assert</operation>
        <fact-type>com.example.Customer</fact-type>
        <object-type>com.example.Customer</object-type>
        <fact-object>
            <properties>
                <name>YTDOrderAmount</name>
                <value>
                    <string>2000.0</string>
                </value>
            </properties>
            <properties>
                <name>level</name>
                <value>
                    <string>null</string>
                </value>
            </properties>
            <properties>
                <name>name</name>
                <value>
                    <string>OneLtd</string>
                </value>
            </properties>
            <properties>
                <name>pastDue</name>
                <value>
                    <string>false</string>
                </value>
            </properties>
        </fact-object>
    </trace-entries>
    <trace-entries xsi:type="activation-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491024</timestamp>
        <rule-name>OrderDiscount.goldCustomer</rule-name>
        <token-time>2</token-time>
        <fact-ids>1</fact-ids>
        <operation>add</operation>
    </trace-entries>
    <trace-entries xsi:type="fact-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491025</timestamp>
        <fact-id>2</fact-id>
        <operation>assert</operation>
        <fact-type>com.example.Order</fact-type>
        <object-type>com.example.Order</object-type>
        <fact-object>
            <properties>
                <name>customerName</name>
                <value>
                    <string>OneLtd</string>
                </value>
            </properties>
            <properties>
                <name>discount</name>
                <value>
                    <string>0.0</string>
                </value>
            </properties>
            <properties>
                <name>grossAmount</name>
                <value>
                    <string>400.0</string>
                </value>
            </properties>
            <properties>
                <name>netAmount</name>
                <value>
                    <string>0.0</string>
                </value>
            </properties>
            <properties>
                <name>number</name>
                <value>
                    <string>1001</string>
                </value>
            </properties>
        </fact-object>
    </trace-entries>
    <trace-entries xsi:type="activation-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491035</timestamp>
        <rule-name>OrderDiscount.goldCustomerDiscount</rule-name>
        <token-time>5</token-time>
        <fact-ids>2</fact-ids>
        <fact-ids>1</fact-ids>
        <operation>add</operation>
    </trace-entries>
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491036</timestamp>
        <rule-name>OrderDiscount.goldCustomerDiscount</rule-name>
        <token-time>5</token-time>
        <fact-ids>2</fact-ids>
        <fact-ids>1</fact-ids>
        <sequence-number>2</sequence-number>
    </trace-entries>
...
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491036</timestamp>
        <rule-name>OrderDiscount.applyDiscount</rule-name>
        <token-time>7</token-time>
        <fact-ids>2</fact-ids>
        <sequence-number>3</sequence-number>
    </trace-entries>
...
    <trace-entries xsi:type="ruleset-stack-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491037</timestamp>
        <operation>pop</operation>
        <ruleset-name>OrderDiscount</ruleset-name>
    </trace-entries>
</decision-trace>

1.8 硬貨カウンタ・ルール・プログラムの作成

この項では、RL Languageを使用して次のパズルを解く例を示します。

50個の硬貨を使用して合計金額を$1.50にする方法は何とおりあるか?

このパズルを解くルール・プログラムには、ルールベース・プログラミングの重要なポイントが含まれています。知識表現、つまり、選択するファクト・クラスが設計上の重要な問題になる可能性があります。ルールで照合および処理するためにデータを便利なフォーマットに整えるには、多くの場合、手続きコードを記述することが有効です。

この例を使用するには、最初に、例1-25に示したRL Languageプログラムをcoins.rlという名前のファイルにコピーします。このプログラムは、RL Languageコマンドラインからincludeコマンドを使用してインクルードできます。coinsプログラムをインクルードする前に、次のようにclear;コマンドを使用して、現行のルール・セッション内をすべて消去します。

RL> clear;
RL> include file:coins.rl;
RL>

例1-24に示したデバッグ関数は、硬貨カウンタに関する、硬貨カウントのサンプル・ファクト、アクティブ化およびルールを示しています。すべてのファクトはアサートされ、すべての解答のアクティブ化がアジェンダに配置されます。ファクトはpopulate_factsで生成されると、ルール条件と照合され、find_solutionでは一致が出力されることに注意してください。

例1-24 coinsプログラム例でのデバッグ関数の使用

RL> watchFacts();
RL> watchActivations();
RL> watchRules();
RL> reset();
RL> showActivations();
RL> run();
The rule is fired for each activation, printing out the solutions
RL>

例1-25では、coinCounttotalAmountなどのグローバル変数定義の先頭にあるキーワードfinalによって、Javaと同様に、変数が定数としてマークされます。ルール条件の定数は参照できますが、変数は参照できません。

RL Languageでは、すべての変数を初期化する必要があります。final変数を初期化する式は、変数の定義時に1回評価されます。final以外の変数を初期化する式は、変数の定義時およびreset関数がコールされるたびに評価されます。reset関数によってすべてのファクトが作業メモリーから取り消されるため、初期ファクトは、グローバル変数を初期化する式でアサート(assert)することをお薦めします。これによって、resetがコールされるとファクトが再度アサートされます。

例1-25に、グローバル変数を初期化する式の使用方法を示します。initializedグローバル変数は、populate_facts関数を使用して初期化されます。この関数は、resetがコールされるたびに再実行されます。このpopulate_facts関数には、forループ内にネストされたwhileループがあります。このforループは、硬貨の額面を示す文字列の配列に対して反復されます。whileループは、硬貨の額面ごとに、カウントと合計(合計金額は$1.50を超えません)を表すファクトをアサートします。たとえば、50セント硬貨の場合は次のようになります。

coin(denomination "half-dollar", count:0, amount:0)
coin(denomination "half-dollar", count:1, amount:50)
coin(denomination "half-dollar", count:2, amount:100)
coin(denomination "half-dollar", count:3, amount:150)

このようなファクトを作業メモリーで使用すると、カウントの合計をcoinCountに、金額の合計をtotalAmtにする必要がある条件を使用して、ルールfind_solutionが各額面に対して照合されます。find_solutionのアクティブ化は、run関数によって起動します。

例1-25 硬貨カウントのプログラム・ソース

final int coinCount = 50;
final int totalAmt = 150;
final String[] denominations = new String[] 
{"half-dollar" , "quarter", "dime", "nickel", "penny" };
class coin {
   String denomination;
   int count;
   int amount;
}
function populate_facts() returns boolean
{
   for (int i = 0; i < denominations.length; ++i) {
      String denom = denominations[i];
      int count = 0;
      int total = 0;
      int amount = 0;
      if (denom == "half-dollar" ) { amount = 50; }
      else if (denom == "quarter" ) { amount = 25; }
      else if (denom == "dime" ) { amount = 10; }
      else if (denom == "nickel" ) { amount = 5; }
      else { amount = 1; }

      while (total <= totalAmt && count <= coinCount)
      {
       assert(new coin(denomination: denom, 
          count : count, 
          amount : total));
          total += amount;
          count ++;
     }
   }
return true;
}
boolean initialized = populate_facts();
rule find_solution 
{
   if(fact coin(denomination: "penny") p
   && fact coin(denomination: "nickel") n
   && fact coin(denomination: "dime") d
   && fact coin(denomination: "quarter") q
   && fact coin(denomination: "half-dollar") h
   && p.count + n.count + d.count + q.count + h.count == coinCount
   && p.amount + n.amount + d.amount + q.amount + h.amount == totalAmt)
   {
      println("Solution:"
             + " pennies=" + p.count
             + " nickels=" + n.count
             + " dimes=" + d.count
             + " quarters=" + q.count
             + " half-dollars=" + h.count
      );
   }
}
run();