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 the main() 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 class AbstractTableModel.
  • 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 the Vector object stock 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 the TableModel 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 file PetStore.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 and textArea hold references to the Swing components JFrame and JTextArea components that were previously passed on by the Java code calling the setGlobal() 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 the checkOut 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 tank Product 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 to true.
  • 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 to java.
  • 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 when fireAllRules() is called from the Java code.
  • kcontext....setFocus() sets the focus to the show items and evaluate 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. The textArea variable used for this is a global variables.
  • The evaluate Agenda group also gains focus from the Explode 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 global frame 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 Swing JTextArea using the textArea 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:
  1. 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.
  2. A new PetStoreUI object has been created and given a handle to the Rule Base, for later use.
  3. Swing components are deployed and the console waits for user input.
  • The file PetStore.java contains a main() 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:
  1. Method CheckOutCallBack.checkout() is called by the Swing class waiting for the click on the "Checkout" button. This inserts the data from the TableModel object and inserts it into the Session's Working Memory. It then fires the rules.
  2. 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 the Show Items and Evaluation 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

  1. 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.
  2. The doCheckout() function sets the focus to the checkout agenda-group, giving the rules in that group the option to fire.
  3. The rules in the checkout agenda-group display the contents of the cart and apply the appropriate discount.
  4. Swing then waits for user input to either checkout more products (and to cause the rules to fire again), or to close the GUI.