Implementing Promotions

I l @ ve RuBoard

Now you're ready to write the promotion class itself (see Listing 12.2).

Listing 12.2 Promotion.java
 package com.bfg.cart; import java.util.Vector; import java.util.HashMap; import java.util.Iterator; import java.text.NumberFormat; import com.bfg.product.Product; import com.bfg.product.Category; import org.apache.turbine.services.db.TurbineDB; import org.apache.turbine.util.db.pool.DBConnection; import org.apache.turbine.util.TurbineConfig; import com.bfg.exceptions.ProductActivityException; import java.sql.*; import java.util.ResourceBundle; public class Promotion  {     private static ResourceBundle sql_bundle =      ResourceBundle.getBundle("com.bfg.cart.SQLQueries");     static org.apache.log4j.Category cat = org.apache.log4j.Category.getInstance( graphics/ccc.gif Promotion.class);     static Vector promotions = new Vector();     static boolean loaded = false;     protected String promoName;     protected Product targetItem;     protected Category targetCategory;     protected Product giftItem;     protected int quantityRequired;     protected boolean categoryPromotion = false;     public String getPromoName() {      return promoName;     }     public boolean isCategoryPromotion() {      return categoryPromotion;     }     public Product getTargetItem() {      return targetItem;     }     public Category getTargetCategory() {      return targetCategory;     }     public Product getGiftItem() {      return giftItem;     }     public int getQuantityRequired() {      return quantityRequired;     }     public static Vector getPromotions() {      if (loaded) {          return promotions;      }  else {          loadPromotions();          loaded = true;          return promotions;      }     }     public static void loadPromotions() {      DBConnection dbConn = null;      try          {           dbConn = TurbineDB.getConnection();           if (dbConn == null) {               cat.error("Can't get database connection");               return;           }           PreparedStatement pstmt =               dbConn.prepareStatement(sql_bundle.getString("loadPromotions"));           ResultSet rs = pstmt.executeQuery();           while (rs.next()) {               Promotion promo = new Promotion();               promo.giftItem = Product.findProduct(rs.getString("GIFT_ID"));               promo.promoName = rs.getString("PROMO_NAME");               promo.categoryPromotion =                   (rs.getString("PROD_OR_CAT").equals("C"));               if (promo.categoryPromotion) {                   promo.targetCategory = Category.findCategoryById(rs.getInt( graphics/ccc.gif "CATEGORY_ID"));                       if (promo.targetCategory == null) continue;               }  else {                    promo.targetItem = Product.findProduct(rs.getString("PROD UCT_ISBN"));                if (promo.targetItem == null) continue;               }               promo.quantityRequired = rs.getInt("DISCOUNT_QUANT");               promotions.add(promo);           }           rs.close();           pstmt.close();          }      catch (Exception e)          {              cat.error("Error during loadPromotions", e);          }      finally          {           try               {                   TurbineDB.releaseConnection(dbConn);               }           catch (Exception e)               {                   cat.error("Error during releaseConnection", e);               }          }     }     public static void runPromotions(Cart cart) {      cart.removePromotionItems();      Iterator promos = getPromotions().iterator();      while (promos.hasNext()) {          Promotion promo = (Promotion) promos.next();          promo.runPromotion(cart);      }     }     public void runPromotion(Cart cart) {      Iterator items = cart.getItems();      int quant = 0;      while (items.hasNext()) {          CartItem item = (CartItem) items.next();          if (isCategoryPromotion()) {              if (targetCategory.getProducts().contains(item.getProduct())) {                  quant += item.getQuantity();              }          } else {              if (item.getProduct().equals(targetItem)) {                  quant += item.getQuantity();              }          }      }      if (quant >= quantityRequired) {          cart.addPromotionItem(giftItem, 1);      }     } } 

To create the promotion class, you start with your normal friends : logging initialization, getting your SQL statement bundle, and creating a bunch of bean properties.

getPromotions shows an alternative to the singleton pattern: It checks to see if it has already loaded the promotions. If not, it loads them before returning the vector.

Assuming that you need to load the promotions from the database, loadPromotions reads the entries from the database, finding either the product or the category, depending on the PROD_OR_CAT flag. If the product or category isn't found, it throws away the promotion.

OBJECTS THAT LOAD OTHER OBJECTS

You have to be rather careful when you have one object being loaded from the database that loads another object. For one thing, if the first loader hasn't closed its database connection before loading the child object (as is often the case when you're loading a number from one query), you'll consume two connections (or more, if that child loads other children). So, you need to make sure that you have enough connections available in your pool to avoid having to wait for a free one.

Another possibility that can occur is a circular reference. For example, suppose that you had products store their primary category. When you load the categories, the products would load, too, because categories load a list of their member products. When you load the product, it would try to load the category, which would try to load the products, and so on. This is a good way to exercise the stack of your JDK to failure.

One way around stack failure is the approach taken here. You use a findProduct and a findCategory that store previously found entries on a cache and update them back to the database upon update (called a "write-through" cache). When the product asks for its category, it is given the value from the cache (assuming that you're smart and had the code store it in the cache before it goes looking for children).

When you need to have promotions run over a cart, the promotion engine first clears out any existing promotional products. Then it gets a list of all the promotions and tries each one in turn to see if it matches the contents of the cart.

The promotion logic itself is fairly simple. It iterates over the cart items, looking for items either that are in the promotion target category (if it's a category promotion) or that are the target product (for a product promotion). If the total quantity in the cart is greater than the threshold, the engine moves the gift item into the cart with the promotionalItem flag set.

This is a fairly simple design for the promotion engine. In a more elaborate implementation (with seven different types of promotions) that I did for a major drug store chain, I implemented a base class for promotions and then had a number of specialized subclasses, one for each type of promotion. I also used a special loader class that knew how to read the promotions from the database and instantiated the correct specific class depending on the type of the promotion.

You need to modify the cart (see Listing 12.3) to run the promotion engine when items are added or removed from the cart. You also need a special addPromotionItem that doesn't run the promotion engine; otherwise , when the promotion engine adds a promotion item, the item would be called again recursively, leading to a stack overrun .

Listing 12.3 Modifications to Cart.java
 public void addItem(Product item, int quantity) {  if (contents.get(item) != null) {     ((CartItem)contents.get(item)).addQuantity(quantity); }  else {     CartItem citem = new CartItem();     citem.setQuantity(quantity);     citem.setProduct(item);     contents.put(item, citem); } Promotion.runPromotions(this); } public void addPromotionItem(Product item, int quantity) {  CartItem citem = new CartItem();  citem.setQuantity(quantity);  citem.setProduct(item);  citem.setPromotionItem(true);  contents.put(item, citem); } public void removeItem(Product item) {  contents.remove(item);  Promotion.runPromotions(this); } public void removePromotionItems() {  Vector removals = new Vector();  Iterator items = getItems();  while (items.hasNext()) {      CartItem item = (CartItem) items.next();      if (item.isPromotionItem()) {       removals.add(item.getProduct());      }  }  items = removals.iterator();  while (items.hasNext()) {      Product item = (Product) items.next();      contents.remove(item);  } } 

CartItem needs to be told that it shouldn't charge for promotional items and to return Free for the string version of the price. This is accomplished with the code in Listing 12.4.

Listing 12.4 Additions to CartItem.java
 private boolean isPromotionItem; . . .     public boolean isPromotionItem() {      return isPromotionItem;     }     public void setPromotionItem(boolean isPromo) {      isPromotionItem = isPromo;     }     public double getLineItemPrice() {      if (isPromotionItem()) return 0D;      return getQuantity() * getProduct().getPrice();     }     public String getLineItemPriceString() {      if (isPromotionItem()) return "FREE!";      NumberFormat nf = NumberFormat.getCurrencyInstance();      return nf.format(getLineItemPrice());     } 

You also need to add some support in Category (see Listing 12.5) for finding categories by their ID; until now, you've only needed to search by name .

Listing 12.5 Changes to Category.java
 public static Category findCategoryById(int Id) throws ProductActivityException {      loadAllCategories();      Iterator it = getCategories().iterator();      while (it.hasNext()) {          Category c = findCategory((String)it.next());          if (c.getID() == Id) return c;      }      return null;     } 

Finally, make a small tweak to the shopping cart JSP code. The customer shouldn't be able to remove or change the quantity of free items (see Listing 12.6). Now the shopping cart will correctly compute and display promotions, as shown in Figure 12.1.

Figure 12.1. The shopping cart with promotions.

graphics/12fig01.jpg

Listing 12.6 Changes to ShoppingCart.jsp
 <% if (item.isPromotionItem()) { %>     <%= item.getQuantity() %> <% } else { %>     <INPUT NAME="ISBN<%= prod.getISBN() %>" TYPE="TEXT" SIZE=2     value=<%= item.getQuantity() %>> <% } %> 
I l @ ve RuBoard


MySQL and JSP Web Applications. Data-Driven Programming Using Tomcat and MySQL
MySQL and JSP Web Applications: Data-Driven Programming Using Tomcat and MySQL
ISBN: 0672323095
EAN: 2147483647
Year: 2002
Pages: 203
Authors: James Turner

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net