Red Hat Training
A Red Hat training course is available for JBoss Enterprise SOA Platform
Chapter 27. Pet Store Example
27.1. Pet Store Example
All of the Java code for the Pet Store Example is contained in the file
PetStore.java
. It defines the following principal classes (in addition to several classes to handle Swing Events):
Petstore
contains themain()
method.PetStoreUI
is responsible for creating and displaying the Swing based GUI. It contains several smaller classes, mainly for responding to various GUI events such as mouse button clicks.TableModel
holds the table data. It is a JavaBean that extends the Swing classAbstractTableModel
.CheckoutCallback
allows the GUI to interact with the Rules.Ordershow
keeps the items that the customer wishes to buy.Purchase
stores details of the order and the products the customer is buying.Product
is a JavaBean holding details of the product available for purchase and its price.
27.2. Pet Store Example: Creating the PetStore RuleBase in PetStore.main
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClassPathResource( "PetStore.drl", PetStore.class ), ResourceType.DRL ); KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() ); // Create the stock. Vector<Product> stock = new Vector<Product>(); stock.add( new Product( "Gold Fish", 5 ) ); stock.add( new Product( "Fish Tank", 25 ) ); stock.add( new Product( "Fish Food", 2 ) ); // A callback is responsible for populating the // Working Memory and for firing all rules. PetStoreUI ui = new PetStoreUI( stock, new CheckoutCallback( kbase ) ); ui.createAndShowGUI();
- The code shown above loads the rules from a DRL file on the classpath. It does so via the second last line where a
PetStoreUI
object is created using a constructor. This accepts theVector
objectstock
that collects the products. - The
CheckoutCallback
class contains the Rule Base that has been loaded.
27.3. Pet Store Example: Firing Rules from CheckoutCallBack.checkout()
public String checkout(JFrame frame, List<Product> items) { Order order = new Order(); // Iterate through list and add to cart for ( Product p: items ) { order.addItem( new Purchase( order, p ) ); } // Add the JFrame to the ApplicationData to allow for user interaction StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); ksession.setGlobal( "frame", frame ); ksession.setGlobal( "textArea", this.output ); ksession.insert( new Product( "Gold Fish", 5 ) ); ksession.insert( new Product( "Fish Tank", 25 ) ); ksession.insert( new Product( "Fish Food", 2 ) ); ksession.insert( new Product( "Fish Food Sample", 0 ) ); ksession.insert( order ); ksession.fireAllRules(); // Return the state of the cart return order.toString(); }
- The Java code that fires the rules is within the
CheckoutCallBack.checkout()
method. This is triggered (eventually) when the Checkout button is pressed by the user. - Two items get passed into this method. One is the handle to the
JFrame
Swing component surrounding the output text frame, at the bottom of the GUI. The second is a list of order items. This comes from theTableModel
storing the information from the "Table" area at the top right section of the GUI. - The for loop transforms the list of order items coming from the GUI into the
Order
JavaBean, also contained in the filePetStore.java
. - All states in this example are stored in the Swing components. The rules are effectively stateless.
- Each time the "Checkout" button is pressed, the code copies the contents of the Swing
TableModel
into the Session's Working Memory. - There are nine calls to the Working Memory. The first creates a new Working Memory as a Stateful Knowledge Session from the Knowledge Base. The next two pass in two objects that will be held as global variables in the rules. The Swing text area and the Swing frame used for writing messages.
- More inserts put information on products into the Working Memory and the order list. The final call is the standard
fireAllRules()
.
27.4. Pet Store Example: Package, Imports, Globals and Dialect from PetStore.drl
package org.drools.examples import org.drools.WorkingMemory import org.drools.examples.petstore.PetStoreExample.Order import org.drools.examples.petstore.PetStoreExample.Purchase import org.drools.examples.petstore.PetStoreExample.Product import java.util.ArrayList import javax.swing.JOptionPane; import javax.swing.JFrame global JFrame frame global javax.swing.JTextArea textArea
- The first part of file
PetStore.drl
contains the standard package and import statements to make various Java classes available to the rules. - The two globals
frame
andtextArea
hold references to the Swing componentsJFrame
andJTextArea
components that were previously passed on by the Java code calling thesetGlobal()
method. These global variables retain their value for the lifetime of the Session.
27.5. Pet Store Example: Java Functions in the Rules Extracted from PetStore.drl
function void doCheckout(JFrame frame, WorkingMemory workingMemory) { Object[] options = {"Yes", "No"}; int n = JOptionPane.showOptionDialog(frame, "Would you like to checkout?", "", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); if (n == 0) { workingMemory.setFocus( "checkout" ); } } function boolean requireTank(JFrame frame, WorkingMemory workingMemory, Order order, Product fishTank, int total) { Object[] options = {"Yes", "No"}; int n = JOptionPane.showOptionDialog(frame, "Would you like to buy a tank for your " + total + " fish?", "Purchase Suggestion", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); System.out.print( "SUGGESTION: Would you like to buy a tank for your " + total + " fish? - " ); if (n == 0) { Purchase purchase = new Purchase( order, fishTank ); workingMemory.insert( purchase ); order.addItem( purchase ); System.out.println( "Yes" ); } else { System.out.println( "No" ); } return true; }
- Having these functions in the rules file makes the Pet Store example more compact.
- You can have the functions in a file of their own, within the same rules package, or as a static method on a standard Java class, and import them using
import function my.package.Foo.hello
. doCheckout()
displays a dialog asking users whether they wish to checkout. If they do, focus is set to thecheckOut
agenda-group, allowing rules in that group to (potentially) fire.requireTank()
displays a dialog asking users whether they wish to buy a tank. If so, a new fish tankProduct
is added to the order list in Working Memory.
27.6. Pet Store Example: Putting Items Into Working Memory from PetStore.drl
// Insert each item in the shopping cart into the Working Memory rule "Explode Cart" agenda-group "init" auto-focus true salience 10 dialect "java" when $order : Order( grossTotal == -1 ) $item : Purchase() from $order.items then insert( $item ); kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "show items" ).setFocus(); kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "evaluate" ).setFocus(); end
- The first extract fires first because it has the
auto-focus
attribute set totrue
. - This rule matches against all orders that do not yet have their
grossTotal
calculated . It loops for each purchase item in that order. Some parts of the "Explode Cart" rule should be familiar: the rule name, the salience (suggesting the order for the rules being fired) and the dialect set tojava
. agenda-group
init
defines the name of the agenda group. In this case, there is only one rule in the group. However, neither the Java code nor a rule consequence sets the focus to this group, and therefore it relies on the next attribute for its chance to fire.auto-focus
true
ensures that this rule, while being the only rule in the agenda group, can fire whenfireAllRules()
is called from the Java code.kcontext....setFocus()
sets the focus to theshow items
andevaluate
agenda groups in turn, permitting their rules to fire. In practice, you can loop through all items on the order, inserting them into memory, then firing the other rules after each insert.
27.7. Pet Store Example: Show Items in the GUI from PetStore.drl
rule "Show Items" agenda-group "show items" dialect "mvel" when $order : Order( ) $p : Purchase( order == $order ) then textArea.append( $p.product + "\n"); end
- The
show items
agenda-group has only one rule, called "Show Items" (note the difference in case). For each purchase on the order currently in the Working Memory (or Session), it logs details to the text area at the bottom of the GUI. ThetextArea
variable used for this is a global variables. - The
evaluate
Agenda group also gains focus from theExplode Cart
rule.
27.8. Pet Store Example: Evaluate Agenda Group from PetStore.drl
// Free Fish Food sample when we buy a Gold Fish if we haven't already bought // Fish Food and don't already have a Fish Food Sample rule "Free Fish Food Sample" agenda-group "evaluate" dialect "mvel" when $order : Order() not ( $p : Product( name == "Fish Food") && Purchase( product == $p ) ) not ( $p : Product( name == "Fish Food Sample") && Purchase( product == $p ) ) exists ( $p : Product( name == "Gold Fish") && Purchase( product == $p ) ) $fishFoodSample : Product( name == "Fish Food Sample" ); then System.out.println( "Adding free Fish Food Sample to cart" ); purchase = new Purchase($order, $fishFoodSample); insert( purchase ); $order.addItem( purchase ); end // Suggest a tank if we have bought more than 5 gold fish and don't already have one rule "Suggest Tank" agenda-group "evaluate" dialect "java" when $order : Order() not ( $p : Product( name == "Fish Tank") && Purchase( product == $p ) ) ArrayList( $total : size > 5 ) from collect( Purchase( product.name == "Gold Fish" ) ) $fishTank : Product( name == "Fish Tank" ) then requireTank(frame, drools.getWorkingMemory(), $order, $fishTank, $total); end
The rule
"Free Fish Food Sample"
will only fire if:
- The store does not already have any fish food, and
- The store does not already have a free fish food sample, and
- The store does have a Gold Fish in its order.
The rule
"Suggest Tank"
will only fire if
- The store does notalready have a Fish Tank in its order, and
- The store does have more than five Gold Fish Products in its order.
- If the rule does fire, it calls the
requireTank()
function . This shows a Dialog to the user, and adding a Tank to the order and Working Memory if confirmed. - When calling the requireTank() function the rule passes the global frame variable so that the function has a handle to the Swing GUI.
- If the rule does fire, it creates a new product (Fish Food Sample), and adds it to the order in Working Memory.
27.9. Pet Store Example: Doing the Checkout Extract from PetStore.drl
rule "do checkout" dialect "java" when then doCheckout(frame, drools.getWorkingMemory()); end
- The rule
"do checkout"
has no agenda group set and no auto-focus attribute. As such, it is deemed part of the default (MAIN) agenda group. This group gets focus by default when all the rules in agenda-groups that explicitly had focus set to them have run their course. - There is no LHS to the rule, so the RHS will always call the
doCheckout()
function. - When calling the
doCheckout()
function, the rule passes the globalframe
variable to give the function a handle to the Swing GUI. - The
doCheckout()
function shows a confirmation dialog to the user. If confirmed, the function sets the focus to the checkout agenda-group, allowing the next lot of rules to fire.
27.10. Pet Store Example: Checkout Rules from PetStore.drl
rule "Gross Total" agenda-group "checkout" dialect "mvel" when $order : Order( grossTotal == -1) Number( total : doubleValue ) from accumulate( Purchase( $price : product.price ), sum( $price ) ) then modify( $order ) { grossTotal = total }; textArea.append( "\ngross total=" + total + "\n" ); end rule "Apply 5% Discount" agenda-group "checkout" dialect "mvel" when $order : Order( grossTotal >= 10 && < 20 ) then $order.discountedTotal = $order.grossTotal * 0.95; textArea.append( "discountedTotal total=" + $order.discountedTotal + "\n" ); end rule "Apply 10% Discount" agenda-group "checkout" dialect "mvel" when $order : Order( grossTotal >= 20 ) then $order.discountedTotal = $order.grossTotal * 0.90; textArea.append( "discountedTotal total=" + $order.discountedTotal + "\n" ); end
There are three rules in the checkout agenda-group:
Gross Total
accumulates the product prices into a total, puts it into Working Memory, and displays it via the SwingJTextArea
using thetextArea
global variable.- If the gross total is between 10 and 20,
Apply 5% Discount
calculates the discounted total and adds it to the Working Memory and displays it in the text area. - If the gross total is not less than 20,
Apply 10% Discount
calculates the discounted total and adds it to the Working Memory and displays it in the text area.
27.11. Pet Store Example: Running PetStore.java
To use PetStore.java, the following conditions must be met:
- The
main()
method has run and loaded the Rule Base but not yet fired the rules. So far, this is the only code in connection with rules that has been run. - A new
PetStoreUI
object has been created and given a handle to the Rule Base, for later use. - Swing components are deployed and the console waits for user input.
- The file
PetStore.java
contains amain()
method, so that it can be run as a standard Java application, either from the command line or via the IDE. This assumes you have your classpath set correctly. - The first screen that appears is the Pet Store Demo. It has a list of available products, an empty list of selected products, checkout and reset buttons, and an empty system messages area.
Pressing the "Checkout" button fires the business rules:
- Method
CheckOutCallBack.checkout()
is called by the Swing class waiting for the click on the "Checkout" button. This inserts the data from theTableModel
object and inserts it into the Session's Working Memory. It then fires the rules. - The first rule to fire will be the one with
auto-focus
set to true. It loops through all the products in the cart, ensures that the products are in the Working Memory, and then gives theShow Items
andEvaluation
agenda groups a chance to fire. The rules in these groups add the contents of the cart to the text area (at the bottom of the window), decide whether or not to give the user free fish food, and to ask us whether they want to buy a fish tank.
27.12. Pet Store Example: The Do Checkout Rule
- The Do Checkout rule is part of the default (MAIN) agenda group. It always calls the doCheckout() functionwhich displays a 'Would you like to Checkout?' dialog box.
- The
doCheckout()
function sets the focus to thecheckout
agenda-group, giving the rules in that group the option to fire. - The rules in the
checkout
agenda-group display the contents of the cart and apply the appropriate discount. - Swing then waits for user input to either checkout more products (and to cause the rules to fire again), or to close the GUI.