Skip to: Site menu | Main content

Drools

Java Rules Engine

Pet Store Example Print

Introduction

The Pet Store Example uses a Swing GUI to allow the user to add items to a shopping cart, when they have the items they want they can then proceed to the checkout - at which point the rules engine fires and processes the items prompting the user to buy more items if appropriate, applying discounts and free samples.

Running the Pet Store Example

Change into the drools-examples directory and run the Petstore example:

D:\java\workspaces\drools-2.0>maven petstore-java
 __  __
|  \/  |__ _Apache__ ___
| |\/| / _` \ V / -_) ' \  ~ intelligent projects ~
|_|  |_\__,_|\_/\___|_||_|  v. 1.1-SNAPSHOT

build:start:

(bunch of output)

test:test:
    [echo] No tests to run.

You will then be presented with a swing showing an empty shopping cart and a list of available items:

Using the Pet Store

Items can be added to the cart by selecting them in the list they can also be removed from the cart be selected them in the shopping cart:

When you are happy with your shopping cart you can proceed to checkout. At this point the rules engines asserts and processes the cart and if necessary prompts the user for feedback - in this case the user has 5 fishes and the shop is enquiring if the user would also like a fish tank:

Finally once the rules engine is finished the results are displayed in the lower text area:

The application also does some console output to give an insight as to what is happening internally:

petstore:
    [java] Applying 10% discount to cart
    [java] Examining each item in the shopping cart.
    [java] ------ Reset ------
    [java] SUGGESTION: Would you like to buy a tank for your 0 fish? - Yes
    [java] Applying 10% discount to cart
    [java] Examining each item in the shopping cart.
    [java] Adding free Fish Food Sample to cart
BUILD SUCCESSFUL
Total time: 15 minutes 2 seconds
Finished at: Wed Jun 23 11:55:46 CEST 2004

Understanding the Pet Store

The Rules

At the core of the Pet Store example are the following rules :

  • Free Fish Food Sample
  • Suggest Fish Tank
  • Apply 5% Discount
  • Apply 10% Discount

Free Fish Food Sample

If the user has at least one fish and has not bought any fish food it checks if a free sample has already been given and if not adds a free Fish Food Sample to the cart. Notice how it modifies the cart fact to notify the Working Memory that the fact space has been updated.

<rule name="Free Fish Food Sample">
    <parameter identifier="cart">
        <java:class>org.drools.examples.java.petstore.ShoppingCart</java:class>
    </parameter>
    <parameter identifier="item">
        <java:class>org.drools.examples.java.petstore.CartItem</java:class>
    </parameter>

    <java:condition>cart.getItems( "Fish Food Sample" ).size() == 0</java:condition>
    <java:condition>cart.getItems( "Fish Food" ).size() == 0</java:condition>
    <java:condition>item.getName().equals( "Gold Fish" )</java:condition>

    <java:consequence>
        System.out.println( "Adding free Fish Food Sample to cart" );
        cart.addItem( new org.drools.examples.java.petstore.CartItem( "Fish Food Sample", 0.00 ) );
        drools.modifyObject( cart );
    </java:consequence>

</rule>
_*Note: Prior to the beta-16 release, the syntax was simply "modifyObject(...)" instead of "drools.modifyObject(...)"._

Suggest Fish Tank

If the user has bought at least 5 Gold Fish and does not already have a Fish Tank ask the user if they would like a fish tank, if they do add one to the cart. The frame variable is introduced via the Application Data and is a reference to a JFrame allowing us to prompt the user with a dialogue box. We also notify Working Memory of a fact change. We use the state system to record if we have already prompted the user for a Fish Tank, and check the state of that in the conditions - otherwise the modifyObject would create an infinite recursion.

<rule name="Suggest Tank" salience="10">
    <parameter identifier="cart">
        <java:class>org.drools.examples.java.petstore.ShoppingCart</java:class>
    </parameter>

    <java:condition>cart.getState( "Suggested Fish Tank" ) == false</java:condition>
    <java:condition>cart.getItems( "Gold Fish" ).size() &gt;= 5</java:condition>
    <java:condition>cart.getItems( "Fish Tank" ).size() == 0</java:condition>

    <java:consequence>
        import javax.swing.JOptionPane;

        Object[] options = {"Yes",
                            "No"};
        int n = JOptionPane.showOptionDialog(frame,
                                             "Would you like to buy a tank for your " 
                                              + cart.getItems( "Gold Fish" ).size() + " 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 "
                          + cart.getItems( "tropical fish" ).size() + " fish? - " );
        if (n == 0) {
          cart.addItem( new org.drools.examples.java.petstore.CartItem( "Fish Tank", 25.00 ) );
          System.out.println( "Yes" );
        } else {
          System.out.println( "No" );
        }

        cart.setState( "Suggested Fish Tank", true );
        drools.modifyObject( cart );
    </java:consequence>
</rule>
_*Note: Prior to the beta-16 release, the syntax was simply "modifyObject(...)" instead of "drools.modifyObject(...)"._

Apply Discounts

The rules currently apply two discounts 5% and 10%. Both check if the Gross Cost is between a certain level and that the discount has not already been applied and if so applies the discount. If the user currently qualifies for a 5% discount and the system prompts them for a Fish Tank and they say yes the discount rules are checked again and if appropriate the 10% discount rule is applied.

<rule name="Apply 5% Discount">
    <parameter identifier="cart">
        <java:class>org.drools.examples.java.petstore.ShoppingCart</java:class>
    </parameter>

    <java:condition>cart.getGrossCost() &gt;= 10.00</java:condition>
    <java:condition>cart.getGrossCost() &lt; 19.9</java:condition>

    <java:condition>cart.getDiscount() &lt; 0.05</java:condition>

    <java:consequence>
        System.out.println( "Applying 5% discount to cart" );
        cart.setDiscount( 0.05 );
    </java:consequence>
</rule>

The example uses Swing to provide a GUI, explaining swing is beyond the scope of this tutorial. Please Sun's Java Swing Tutorials. To get the integration between the GUI and the Rules Engine we use a callback mechanism. When the user clicks the "Checkout" button it calls the callback passing it a reference to the current JFrame and also the list of chosen items. These items are added to a ShoppingCart object which is then asserted into the Working Memory. As mentioned previously we also give the consequences access to external data, in this case the JFrame, via the SetApplicationData method.

public String checkout( JFrame frame, List items ) throws FactException
{
ShoppingCart cart = new ShoppingCart();

//Iterate through list and add to cart
for ( int i = 0; i < items.size(); i++ )
{
	cart.addItem( (CartItem) items.get( i ) );
}

//add the JFrame to the ApplicationData to allow for user interaction
WorkingMemory workingMemory = ruleBase.newWorkingMemory();
workingMemory.setApplicationData( "frame", frame );
workingMemory.assertObject( cart );
workingMemory.fireAllRules();

//returns the state of the cart
return cart.toString();
}