7.5. ペットショップの例のデシジョン (アジェンダグループ、グローバル変数、コールバック、および GUI 統合)
ペットショップの例のデシジョンセットでは、ルールでのアジェンダグループとグローバル変数の使用方法と、Red Hat Decision Manager ルールとグラフィカルユーザーインターフェイス (GUI) の統合方法が分かります。今回は Swing ベースのデスクトップアプリケーションを使用します。また、この例では、コールバックを使用して実行中のデシジョンエンジンと通信し、ランタイム時に加えられたワーキングメモリー内の変更をもとに GUI を更新する方法を例示しています。
以下は、ペットショップの例の概要です。
-
名前:
petstore
-
Main クラス: (
src/main/java
内の)org.drools.examples.petstore.PetStoreExample
-
モジュール:
drools-examples
- タイプ: Java アプリケーション
-
ルールファイル: (
src/main/resources
内の)org.drools.examples.petstore.PetStore.drl
- 目的: ルールアジェンダグループ、グローバル変数、コールバック、および GUI 統合を例示します。
ペットショップの例では、PetStoreExample.java
クラス例を使用して (Swing イベントを処理する複数のクラスに加え)、以下のクラスを主に定義しています。
-
Petstore
にはmain()
メソッドが含まれます。 -
PetStoreUI
は Swing ベースの GUI を作成して表示します。このクラスには複数の小さいクラスが含まれており、マウスボタンのクリックなど、さまざまな GUI イベントに主に対応します。 -
TableModel
には表データが含まれています。このクラスは基本的に、Swing クラスAbstractTableModel
を拡張する JavaBean です。 -
CheckoutCallback
により、GUI がルールと対話できるようになります。 -
Ordershow
は購入するアイテムを保持します。 -
Purchase
には、注文の詳細と、購入する製品が保存されます。 -
Product
は、販売可能な商品と価格の詳細を含む JavaBean です。
この例の Java コードはほぼ、プレーンな JavaBean か Swing ベースとなっています。Swing コンポーネントの詳細は、Creating a GUI with JFC/Swing の Java チュートリアルを参照してください。
ペットショップの例でのルール実行動作
他の例のデシジョンセットではファクトがすぐにアサートされて実行されるのに対し、ペットショップの例では、ユーザーの対話をもとに他のファクトが収集されるまでルールが実行します。このルールでは、コンストラクターで作成される PetStoreUI
オブジェクトを使用してルールを実行し、Vector
オブジェクトの stock
を受けれ入れて商品を収集します。次に、この例では、以前の読み込まれたルールベースを含む CheckoutCallback
クラスのインスタンスを使用します。
ペットショップの KIE コンテナーおよびファクト実行の設定
ルールを実行する Java コードは CheckoutCallBack.checkout()
メソッドに含まれます。このメソッドは、ユーザーが UI で チェックアウト をクリックするとトリガーされます。
CheckoutCallBack.checkout() からのルール実行
このコード例では、2 つの要素を CheckoutCallBack.checkout()
メソッドに渡します。1 つ目の要素は、GUI の一番下にある出力テキストのフレームを囲む Swing コンポーネント JFrame
のハンドルです。2 つ目の要素は注文アイテムのリストで、GUI の右上のセクションにある Table
エリアからの情報を保存する TableModel
から取得します。
for
ループは GUI からの注文アイテム一覧を JavaBean Order
に変換します。これは、PetStoreExample.java
ファイルにも含まれています。
今回の例では、データはすべて Swing コンポーネントに含まれており、ユーザーが UI の チェックアウト をクリックしない限り実行しないため、ルールはステートレスの KIE セッションで実行します。ユーザーが チェックアウト をクリックするたびに、リストの内容を Swing TableModel
から KIE セッションのワーキングメモリーに移動し、ksession.fireAllRules()
メソッドで実行します。
このコード内には、KieSession
への呼び出しが 9 個あります。1 つ目は、KieContainer
から新しい KieSession
を作成します (この例では、main()
メソッドの CheckoutCallBack
クラスから KieContainer
に渡されます)。次の 2 つの呼び出しは、ルールでグローバル変数として保持されるオブジェクト を 2 つ渡します (メッセージの書き込みに使用する Swing テキストエリアと Swing フレーム)。より多くの商品の情報を KieSession
と注文リストに入力します。最後の呼び出しは、標準の fireAllRules()
です。
ペットショップのルールファイルのインポート、グローバル変数、Java 関数
PetStore.drl
ファイルには、さまざまな Java クラスをルールで利用できるように、標準のパッケージとインポートステートメントが含まれています。このルールファイルには、frame
、textArea
などのように、ルール内で使用する グローバル変数 が含まれています。グローバル変数では、Swing コンポーネント JFrame
と、setGlobal()
メソッドを呼び出した Java コードにより以前に渡された JTextArea
コンポーネントへの参照を保持します。ルールが実行するとすぐに失効するルールの標準変数とは異なり、グローバル変数は KIE セッションの有効期間中この値を保持します。これは、このグローバル変数の内容が、後続のすべてのルールで評価できることを意味します。
PetStore.drl パッケージ、インポート、およびグローバル変数
PetStore.drl
ファイルには、このファイル内のルールが使用する関数が 2 つも含まれています。
PetStore.drl Java 関数
この 2 つの関数は以下のアクションを実行します。
-
doCheckout()
は、チェックアウトするかどうかユーザーに尋ねるダイアログボックスを表示します。チェックアウトする場合は、フォーカスがcheckout
アジェンダグループに設定され、そのグループのルールを (今後) 実行できるようにします。 -
requireTank()
は、水槽を購入するかどうかを確認するダイアログを表示します。購入する場合は、新しい水槽のProduct
がワーキングメモリーの注文リストに追加されます。
この例では、効率化を図るため、すべてのルールと関数が同じルールファイルで実行しています。実稼働環境では、通常、ルールと関数を別のファイルに分けるか、静的な Java メソッドを構築して、import function my.package.name.hello
などのインポート関数を使用し、ファイルをインポートします。
アジェンダグループを使用したペットショップルール
ペットショップの例のルールはほぼ、アジェンダグループを使用してルールの実行を制御しています。アジェンダグループを使用すると、エンジンアジェンダを分割し、ルールのグループの実行を、詳細にわたり制御できるようになります。デフォルトでは、ルールはすべてアジェンダグループ MAIN
に含まれています。agenda-group
属性を使用して、ルールに異なるアジェンダグループを指定できます。
最初は、ワーキングメモリーは、アジェンダグループ MAIN
にフォーカスを当てます。アジェンダグループのルールは、グループがこのフォーカスを受けた場合のみ実行されます。setFocus()
メソッドか、auto-focus
ルール属性を使用してフォーカスを設定できます。auto-focus
属性を使用すると、ルールが一致してアクティベートされた場合のみ、ルールにアジェンダグループのフォーカスが自動的に当てられます。
ペットショップの例では、ルールに以下のアジェンダグループを使用します。
-
"init"
-
"evaluate"
-
"show items"
-
"checkout"
たとえば、同じルール "Explode Cart"
は "init"
のアジェンダグループを使用して、ショッピングカードのアイテムを起動して KIE セッションのワーキングメモリーに挿入するオプションが提供されるようにします。
ルール "Explode Cart"
このルールは、grossTotal
がまだ計算されていない全注文に対して照合が行われます。購入アイテムごとに、順番に実行がループされます。
ルールは、アジェンダグループに関連する以下の機能を使用します。
-
agenda-group "init"
はアジェンダグループの名前を定義します。この例では、グループにはルールが 1 つしかありません。ただし、Java コードもルール結果もこのグループにフォーカスされていないため、auto-focus
の属性により、ルールが実行されるかが決まります。 -
このルールはアジェンダグループで唯一のルールですが、
auto-focus true
を使用して、fireAllRules()
が Java コードから呼び出されると、必ず実行されるようにします。 -
kcontext….setFocus()
は、"show items"
および"evaluate"
アジェンダグループにフォーカスを設定し、ルールを実行できるようにします。実際には、すべての項目をその順序でループしてメモリーに挿入し、各挿入の後に他のルールを実行します。
"show items"
アジェンダグループには "Show Items"
というルールだけが含まれます。KIE セッションのワーキングメモリーに現在含まれる注文で購入があるたびに、このルールを使用して、ルールファイルに定義した textArea
変数をもとに、GUI の下の部分にあるテキストエリアに詳細がロギングされます。
ルール "Show Items"
また、"evaluate"
アジェンダグループにより、"Explode Cart"
ルールからフォーカスを取得します。このアジェンダグループには、2 つのルール ("Free Fish Food Sample"
と "Suggest Tank"
) が含まれます。この順番で実行されます。
ルール "Free Fish Food Sample"
ルール "Free Fish Food Sample"
は、以下の条件がすべて該当する場合のみ実行されます。
この注文ファクトが上記の要件すべてを満たす場合は、新しい商品 (Fish Food Sample) が作成され、ワーキングメモリーの注文に追加されます。
ルール "Suggest Tank"
ルール "Suggest Tank"
は以下の条件がすべて該当する場合のみ実行されます。
このルールが実行すると、ルールファイルに定義されている requireTank()
関数が呼び出されます。この関数により、水槽を購入するかどうかを尋ねるダイアログが表示されます。購入する場合は、新しい水槽の Product
がワーキングメモリーの注文リストに追加されます。ルールが requireTank()
関数を呼び出した場合は、このルールを使用して、関数に Swing GUI のハンドルが含まれるように、frame
のグローバル変数を渡します。
ペットショップの例の "do checkout"
ルールにはアジェンダグループや when
条件がないため、ルールは常に実行され、デフォルトの MAIN
のアジェンダグループの一部とみなされます。
ルール "do checkout"
このルールが実行されると、ルールファイルで定義されている doCheckout()
関数を呼び出します。この関数により、チェックアウトするかどうかをユーザーに尋ねるダイアログボックスが表示されます。チェックアウトする場合は、フォーカスが checkout
アジェンダグループに設定され、そのグループのルールを (今後) 実行できるようにします。このルールで doCheckout()
関数を呼び出し、この変数に Swing GUI のハンドルが含まれるように frame
グローバル変数を渡します。
この例では、結果が想定どおりに実行されない場合のトラブルシューティングの方法を例示します。ルールの when
ステートメントから条件を削除して、then
ステートメントのアクションをテストし、アクションが正しく実行されることを検証します。
"checkout"
アジェンダグループには、注文のチェックアウト処理、および割引の適用の 3 つのルール ("Gross Total"
、"Apply 5% Discount"
、および "Apply 10% Discount"
) が含まれています。
ルール "Gross Total"、"Apply 5% Discount"、および "Apply 10% Discount"
ユーザーがまだ総計を算出していない場合には、Gross Total
で、商品の価格を累積して合計を出し、この合計を KIE セッションに渡して、textArea
のグローバル変数を使用し、Swing JTextArea
で合計を表示します。
総計が 10
から 20
(通貨単位) の場合は、"Apply 5% Discount"
ルールで割引合計を計算し、KIE セッションに追加して、テキストエリアに表示します。
総計が 20
未満の場合は、"Apply 10% Discount"
ルールで割引合計を計算し、KIE セッションに追加して、テキストエリアに表示します。
ペットショップ例の実行
他の Red Hat Decision Manager のデシジョン例と同じように、お使いの IDE で org.drools.examples.petstore.PetStoreExample
クラスを Java アプリケーションとして実行し、ペットショップの例を実行します。
ペットショップの例を実行すると、Pet Store Demo
GUI ウィンドウが表示されます。このウィンドウでは、購入可能な商品 (左上)、選択済み商品の空白のリスト (右上)、チェックアウト および リセット ボタン (真ん中)、空白のシステムメッセージエリア (下) が表示されます。
図7.11 起動後のペットショップ例の GUI
この例では、以下のイベントが発生して、この実行動作を確立します。
-
main()
メソッドがルールベースの実行と読み込みを終えていますが、ルールは実行していない。今のところ、これが、実行されたルールに関連する唯一のコードになります。 -
新しい
PetStoreUI
オブジェクトが作成され、後で使用できるようにルールベースにハンドルを渡している。 - さまざまな Swing コンポーネントが関数を実行し、最初の UI 画面が表示され、ユーザーの入力を待っている。
リストからさまざまな商品をクリックして、UI 設定をチェックできます:
図7.12 ペットショップ例の GUI のチェック
ルールコードはまだ実行されていません。UI は Swing コードを使用してユーザーによるマウスクリックを検出し、選択済みの商品を TableModel
オブジェクトに追加して、UI の右上隅に表示します。この例では、Model-View-Controller 設計パターンを紹介しています。
チェックアウト をクリックすると、ルールが以下の方法で実行されます。
-
Swing クラスは Checkout がクリックされるまで待機して、(最終的に)
CheckOutCallBack.checkout()
メソッドを呼び出します。これにより、TableModel
オブジェクト (UI の右上隅) から KIE セッションのワーキングメモリーにデータを挿入します。その後に、メソッドによりルールが実行されます。 "Explode Cart"
ルールは、auto-focus
属性をtrue
に設定して最初に実行します。このルールは、カートの商品をすべて順にループしていき、商品がワーキングメモリーに含まれていることを確認し、アジェンダグループ"show Items"
と"evaluate"
に実行するオプションを提供します。このグループのルールは、カートのコンテンツをテキストエリア (UI の下) に追加して、魚の餌を無料で受け取る資格があるかどうかを評価し、また水槽購入の有無を尋ねるかどうかを決定します。図7.13 水槽の資格
-
現在、他のアジェンダグループにフォーカスが当たっておらず、
"do checkout"
ルールは、デフォルトのMAIN
アジェンダグループに含まれているため、次に実行されます。このルールは常にdoCheckout()
関数を呼び出し、この関数によりチェックアウトをするかどうかが尋ねられます。 -
doCheckout()
関数は、フォーカスを"checkout"
アジェンダグループに設定し、そのグループ内のルールに、実行するオプションを提供します。 -
"checkout"
アジェンダグループ内のルールは、カート内の内容を表示し、適切な割引を適用します。 Swing は、別の商品の選択 (およびもう一度ルールを実行) または GUI の終了のいずれかのユーザー入力を待ちます。
図7.14 全ルールが実行された後のペットショップ例の GUI
IDE コンソールでイベントのこのフローを例示するには、他の System.out
呼び出しを追加します。
IDE コンソールの System.out 出力
Adding free Fish Food Sample to cart SUGGESTION: Would you like to buy a tank for your 6 fish? - Yes
Adding free Fish Food Sample to cart
SUGGESTION: Would you like to buy a tank for your 6 fish? - Yes