Requirement:

  • Create simple catalog page with hard coded list of models with an "Add to Cart" button.
  • The catalog model list page should contain a list of models with model number, description and price.
  • On clicking the "Add to Cart" button, the model should get added to the shopping cart.
  • The cart should display all the models added to it. For each line item, it should display model number, description and unit price of item and total price. It should also display the total price for the whole order.
  • For each item in the cart, there should be a delete button next to it for removing the item from cart.
  • For each item in the cart, there should be a text input to enter a quantity value with update button to update the quantity of item in the cart.
  • The cart should have a link back to catalog model list page.

Shopping Cart overview:

Shopping cart is the heart of any e-commerce application. It is something highly talked about. But, what do you mean by a session based shopping cart? Well, shopping carts can be mainly session based or database based. As the name indicates, session based carts are completely maintained in the user"s session. Only after the checkout is completed, the information is persisted in the database. On the other hand, a database based shopping cart is maintained in the database. Every time cart items are added or updated, it gets persisted in the database. Both of them have their own advantages and disadvantages. For each add or update in database based cart, there will be a hit in the database. On the other hand, it gives us the advantage of cart persistence. You can leave your shopping halfway and come back after sometime and resume the shopping session.

Analysis and Design:

Designing the components prior to coding is pretty useful. Better the designing, easier it is to code and lesser the rework. We will be digging pretty deep into the design to throw light on some of the design principles. If you are not interested in designing and stuff, you may want to glance through the UML diagrams and move on to code.

Identifying components:
Let us analyze the requirements, and try to identify the different components needed for this workshop. For our analysis, we try to identify components based on Model-View-Controller (MVC) design.

We need to create a ModelList page, with "Add to cart" button.

Technically speaking, what does a cart comprise? It is a collection of items added to the session. Let us use, ArrayList for our cart. So, the basic idea is to have an ArrayList added to session object.

We will refine this a bit. Going forward, we may need the cart item to hold other things than just CartItems. Something like Promotion code data. So, we will maintain a special cart object. And the cart will contain ArrayList of cart items.

But, what will the ArrayList comprise? String will not do for sure. Probably, we need a special item bean. Right, so we will have ItemBean added to ArrayList, and the ArrayList will be added to session. This sounds great!

We need a shopping cart jsp. It would need to pull ArrayList out of the session and iterate this, and display it in the JSP.

Aren't we missing something? We need something to control the events coming from the "Add to cart", "Update Item" and "Delete Item". We need a rocking controller to handle these events. The servlet should be the one to do this job.

Based on the above analysis, let us summarize the components we had identified:
1. ModelList.jsp
2. ShoppingCart.jsp
3. CartBean.java
4. CartItemBean.java
5. CartController.java

Identifying attributes for cart:
Now, let us try to identify more information for the cart related classes.

CartBean:

  • ArrayList of CartItemBean
  • totalCost of items in cart


CartItemBean:

  • Part number for the model
  • Model description
  • Unit cost
  • Quantity
  • Total item cost

Identifying methods:
CartController:
This servlet controller needs to implement the doGet/doPost methods. It needs to handle the events for addToCart, updateCart and deleteCart. It is a better design to handle each of these events separately. So, based on this discussion, the CartController will need to have the following methods:

  • doGet/doPost - Delegates request to different event methods like addToCart etc.
  • addToCart - Handle the add to cart functionality
  • updateCart - Handle the update cart item functionality
  • deleteCart - Handle delete cart item from cart functionality

Let us work through the "Calculate the cart total" and "Add to cart" functionality to analyze and come up with methods related to it.

Calculating the cart total:
This is a major functionality, which needs to be utilized every time an item is added, removed or updated. Let us discuss considering different design principles and try to come up with a realization for this functionality.

Information expert principle says that, any object which has the information to perform an operation should do the job.
For calculating the total price, we need to first know the total for each line item. So, let us apply expert principle to get the line item total. Since, CartItemBean has both quantity and unit cost, based on expert, it is the right candidate.

Just to understand the association between the different objects, we know, that CartBean houses a list of CartItemBean. The CartController on the other hand will be one to add the CartBean in session. Now, this means, CartBean is associated with CartItemBean and CartBean is associated also with CartController.

Coupling design principle says that, the association or coupling between different objects should be as less as possible. More the coupling, more spaghetti code syndrome. You make changes to code in one class; all the associated classes need changes.

Since CartController has access to CartBean, we can write a calculateTotal method in the CartController, where we will have to pull the list of CartItemBeans from CartBean in this method and calculate the total price. However, this means, iterating through the CartItemBean list in CartController and also calls on CartItemBean.getSubtotal(). This introduces CartItemBean to CartController coupling.

Hmmmm! Not that good news isn't it?

But, do we have a choice? Going again by expert, CartBean does have access to all CartItemBeans. So, it is an expert to calculate the order total. Also, it does not have a CartItemBean to CartController coupling. So, this is a better choice.

Add to Cart functionality:

We said that CartBean will house an ArrayList of CartItemBeans. So, we will have a getter/setter for setting this ArrayList inside CartBean. But, who will create the ArrayList of CartItemBeans and then call setCartItems? It has to be CartController. This intern means, CartItemBean creation, setting values and adding to ArrayList, needs to be performed in CartController. Hmmm….But, we have been working so hard eliminate the CartController to CartItemBean coupling while designing the "calculate order total" functionality. Even with this, creating the ArrayList of CartItemBeans and setting it in CartBean everytime is a tall order. We need something like addCartItem(CartItemBean) in CartBean. This will add to the ArrayList of CartItemBeans everytime this is called. So, the CartController does not have to worry above the ArrayList and setting the ArrayList using the setCartItem method. That is a great relief!!

Hey, hold on! But, when we still will call setters in CartItemBean from CartController and then call the CartBean.addCartItem(CartItemBean). Isn"t it not adding coupling between CartController and CartItemBean. Well, sad but true! Let us try to look for any some more options.

It would be dreamy, if CartBean can take complete control of the CartItemBean, without any CartController intervention.  Let us try to delegate the task of creating CartItemBean and setting values into it to CartBean. But, is it possible; is the question to be answered? How will the CartBean know, what should be the state of the CartItemBean created? Well, since the CartController has this information, it can pass on this information to CartBeans method as parameters. Like

  • addCartItem(String strModelNo, String strDescription, String strUnitCost, String strQuantity)

Passing 4 parameters in the method call may not be that bad! But, in a real production cart scenario, we may need more parameters. We can generally work around this by using a value object. But for our example this is fine.

Party time! We just designed the addToCart without increasing the coupling. Note here that, CartController is the information expert. The CartBean may be considered as a partial expert. However, we considered both coupling and expert to come up with this design. This leads us to something very important while using design principles. No single design principle can be used in isolation to design functionality. We need to consider different design principles to come up with a design.

We will use the same principle for updateCartItem and deleteCartItem functionality. You may want to think over it.

So, based on the above discussion, below the class diagram for our cart sub-system.

UML Class diagram for Shopping Cart

Sequence diagram for Add To Cart:

Add to cart - UML Sequence diagram

Setting up the environment for development:

We will be using Tomcat 6.x for running and testing our shopping cart. Refer "First JSP/Servlet workshop" for Tomcat installation details.
We will be developing using Eclipse 3.x integrated with Tomcat via Sysdeo plugin. Refer "Integration of Eclipse with Tomcat" article for details.

Let us create a new Tomcat Project, with context name as /ShoppingCart.
Set the source path and build path correctly. We will need JSTL libraries (jstl.jar and standard.jar) as we will be using JSTL/EL for the shopping cart page.

We should be able to start Tomcat from Eclipse, and access the any JSP page inside /ShoppingCart context which we created using the URL http://localhost:8080/ShoppingCart/<AnyFile.jsp>

The following will be approximate directory structure of the ShoppingCart project.

ShoppingCart
- ModelList.jsp
- ShoppingCart.jsp
WEB-INF
src
- in.techfreaks.shoppingcart.beans.CartBean.java
- in.techfreaks.shoppingcart.beans.CartItemBean.java
- in.techfreaks.shoppingcart.servlet.CartController.java
classes
lib
- jstl.jar
-standard.jar

 


Coding the Cart:
Such detailed design generally makes coding just a formality. Below is the code for each of the element identified during design. The code is pretty straight forward.


CartItemBean:

package in.techfreaks.shoppingcart.beans;

public class CartItemBean {
    private String strPartNumber;
    private String strModelDescription;
    private double dblUnitCost;
    private int iQuantity;
    private double dblTotalCost;
    
    public String getPartNumber() {
        return strPartNumber;
    }
    public void setPartNumber(String strPartNumber) {
        this.strPartNumber = strPartNumber;
    }
    public String getModelDescription() {
        return strModelDescription;
    }
    public void setModelDescription(String strModelDescription) {
        this.strModelDescription = strModelDescription;
    }
    public double getUnitCost() {
        return dblUnitCost;
    }
    public void setUnitCost(double dblUnitCost) {
        this.dblUnitCost = dblUnitCost;
    }
    public int getQuantity() {
        return iQuantity;
    }
    public void setQuantity(int quantity) {
        iQuantity = quantity;
    }
    public double getTotalCost() {
        return dblTotalCost;
    }
    public void setTotalCost(double dblTotalCost) {
        this.dblTotalCost = dblTotalCost;
    }
}

CartBean:

 package in.techfreaks.shoppingcart.beans;

import java.util.ArrayList;


public class CartBean {
 private ArrayList alCartItems = new ArrayList();
 private double dblOrderTotal ;
 
 public int getLineItemCount() {
  return alCartItems.size();
 }
 
 public void deleteCartItem(String strItemIndex) {
  int iItemIndex = 0;
  try {
   iItemIndex = Integer.parseInt(strItemIndex);
   alCartItems.remove(iItemIndex - 1);
   calculateOrderTotal();
  } catch(NumberFormatException nfe) {
   System.out.println("Error while deleting cart item: "+nfe.getMessage());
   nfe.printStackTrace();
  }
 }
 
 public void updateCartItem(String strItemIndex, String strQuantity) {
  double dblTotalCost = 0.0;
  double dblUnitCost = 0.0;
  int iQuantity = 0;
  int iItemIndex = 0;
  CartItemBean cartItem = null;
  try {
   iItemIndex = Integer.parseInt(strItemIndex);
   iQuantity = Integer.parseInt(strQuantity);
   if(iQuantity>0) {
    cartItem = (CartItemBean)alCartItems.get(iItemIndex-1);
    dblUnitCost = cartItem.getUnitCost();
    dblTotalCost = dblUnitCost*iQuantity;
    cartItem.setQuantity(iQuantity);
    cartItem.setTotalCost(dblTotalCost);
    calculateOrderTotal();
   }
  } catch (NumberFormatException nfe) {
   System.out.println("Error while updating cart: "+nfe.getMessage());
   nfe.printStackTrace();
  }
  
 }
 
 public void addCartItem(String strModelNo, String strDescription,
String strUnitCost, String strQuantity) {
  double dblTotalCost = 0.0;
  double dblUnitCost = 0.0;
  int iQuantity = 0;
  CartItemBean cartItem = new CartItemBean();
  try {
   dblUnitCost = Double.parseDouble(strUnitCost);
   iQuantity = Integer.parseInt(strQuantity);
   if(iQuantity>0) {
    dblTotalCost = dblUnitCost*iQuantity;
    cartItem.setPartNumber(strModelNo);
    cartItem.setModelDescription(strDescription);
    cartItem.setUnitCost(dblUnitCost);
    cartItem.setQuantity(iQuantity);
    cartItem.setTotalCost(dblTotalCost);
    alCartItems.add(cartItem);
    calculateOrderTotal();
   }
   
  } catch (NumberFormatException nfe) {
   System.out.println("Error while parsing from String to primitive types: "+nfe.getMessage());
   nfe.printStackTrace();
  }
 }
 
 public void addCartItem(CartItemBean cartItem) {
  alCartItems.add(cartItem);
 }
 
 public CartItemBean getCartItem(int iItemIndex) {
  CartItemBean cartItem = null;
  if(alCartItems.size()>iItemIndex) {
   cartItem = (CartItemBean) alCartItems.get(iItemIndex);
  }
  return cartItem;
 }
 
 public ArrayList getCartItems() {
  return alCartItems;
 }
 public void setCartItems(ArrayList alCartItems) {
  this.alCartItems = alCartItems;
 }
 public double getOrderTotal() {
  return dblOrderTotal;
 }
 public void setOrderTotal(double dblOrderTotal) {
  this.dblOrderTotal = dblOrderTotal;
 }
 
 protected void calculateOrderTotal() {
  double dblTotal = 0;
  for(int counter=0;counter<alCartItems.size();counter++) {
   CartItemBean cartItem = (CartItemBean) alCartItems.get(counter);
   dblTotal+=cartItem.getTotalCost();
   
  }
  setOrderTotal(dblTotal);
 }

}
 

CartController:

package in.techfreaks.shoppingcart.servlet;

import in.techfreaks.shoppingcart.beans.CartBean;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class CartController extends HttpServlet {
 
 //public static final String addToCart
 
 public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

  String strAction = request.getParameter("action");
  
  
  if(strAction!=null && !strAction.equals("")) {
   if(strAction.equals("add")) {
    addToCart(request);
   } else if (strAction.equals("Update")) {
    updateCart(request);
   } else if (strAction.equals("Delete")) {
    deleteCart(request);
   }
  }
  response.sendRedirect("../ShoppingCart.jsp");
 }
 
 protected void deleteCart(HttpServletRequest request) {
  HttpSession session = request.getSession();
  String strItemIndex = request.getParameter("itemIndex");
  CartBean cartBean = null;
  
  Object objCartBean = session.getAttribute("cart");
  if(objCartBean!=null) {
   cartBean = (CartBean) objCartBean ;
  } else {
   cartBean = new CartBean();
  }
  cartBean.deleteCartItem(strItemIndex);
 }
 
 protected void updateCart(HttpServletRequest request) {
  HttpSession session = request.getSession();
  String strQuantity = request.getParameter("quantity");
  String strItemIndex = request.getParameter("itemIndex");
 
  CartBean cartBean = null;
  
  Object objCartBean = session.getAttribute("cart");
  if(objCartBean!=null) {
   cartBean = (CartBean) objCartBean ;
  } else {
   cartBean = new CartBean();
  }
  cartBean.updateCartItem(strItemIndex, strQuantity);
 }
 
 protected void addToCart(HttpServletRequest request) {
  HttpSession session = request.getSession();
  String strModelNo = request.getParameter("modelNo");
  String strDescription = request.getParameter("description");
  String strPrice = request.getParameter("price");
  String strQuantity = request.getParameter("quantity");
  
  CartBean cartBean = null;
  
  Object objCartBean = session.getAttribute("cart");

  if(objCartBean!=null) {
   cartBean = (CartBean) objCartBean ;
  } else {
   cartBean = new CartBean();
   session.setAttribute("cart", cartBean);
  }
  
  cartBean.addCartItem(strModelNo, strDescription, strPrice, strQuantity);
 }

}
  • Do not ever maintain any state in servlet"s instance variables. You may find mixing of data between requests or user sessions. The best part of this is that, it will only happen in a production environment and you win a severity 1 ticket and that too completely free!

 ModelList.jsp

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>www.tech-freaks.in - Model List</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>

<body>
<p><font size="3" face="Verdana, Arial, Helvetica, sans-serif"><strong>Model List
  </strong></font></p>
<a href="/ShoppingCart.jsp" mce_href="ShoppingCart.jsp">View Cart</a>
<p/>    
<table width="75%" border="1">
  <tr>
    <td><form name="modelDetail1" method="POST" action="servlet/CartController">
 <font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Model:</strong>
        TF-Model1</font><input type="hidden" name="modelNo" value="TF-MODEL1">
      <p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Description:</strong>
        Tech-Freaks imaginary model 1. </font><input type="hidden" name="description" value="Tech-Freaks imaginary model 1."></p>
      <p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Quantity:<input type="text" size="2" value="1" name="quantity"></strong></font></p>
      <p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Price:</strong>
        $10.00</font><input type="hidden" name="price" value="10"></p><input type="hidden" name="action" value="add"><input type="submit" name="addToCart" value="Add To Cart">
      </form></td>
    <td><form name="modelDetail2" method="POST" action="servlet/CartController"><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Model</strong>:
      TF-Model2 </font><input type="hidden" name="modelNo" value="TF-MODEL2">
<font face="Verdana, Arial, Helvetica, sans-serif">
      <p><font size="2"><strong>Description</strong>: Tech-Freaks imaginary model
        2. </font><input type="hidden" name="description" value="Tech-Freaks imaginary model 2."></p>
      <p><font size="2"><strong>Quantity</strong>: <input type="text" size="2" value="1" name="quantity"></font></p>
      <p><font size="2"><strong>Price</strong>: $20.00<input type="hidden" name="price" value="20"></font></p>
           <input type="hidden" name="action" value="add">
             <input type="submit" name="addToCart" value="Add To Cart">
      </font></form></td>
  </tr>
  <tr>
    <td><form name="modelDetail3" method="POST" action="servlet/CartController"><p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Model:</strong>
        TF-Model3</font><input type="hidden" name="modelNo" value="TF-MODEL3"></p>
      <p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Description:</strong>
        Tech-Freaks imaginary model 3. </font><input type="hidden" name="description" value="Tech-Freaks imaginary model 3."></p>
      <p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Quantity:</strong></font> <input type="text" size="2" value="1" name="quantity"></p>
      <p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Price: $30.00</strong></font><input type="hidden" name="price" value="30"></p>        <input type="hidden" name="action" value="add">
        <input type="submit" name="addToCart" value="Add To Cart">
</form></td>
    <td><form name="modelDetail4" method="POST" action="servlet/CartController"><p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Model</strong>:
        TF-Model4</font><input type="hidden" name="modelNo" value="TF-MODEL4"></p>
      <p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Description</strong>:
        Tech-Freaks imaginary model 4. </font><input type="hidden" name="description" value="Tech-Freaks imaginary model 4."></p>
      <p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Quantity</strong>:</font> <input type="text" size="2" value="1" name="quantity"></p>
      <p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><strong>Price</strong>: $40.00</font><input type="hidden" name="price" value="40"></p>
   <input type="hidden" name="action" value="add"><input type="submit" name="addToCart" value="Add To Cart"></form></td>
  </tr>
</table>
<p> </p>
</body>
</html>

 

ShoppingCart.jsp

For iterating the elements in the cart session, we are using simple JSTL tags with EL.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>www.tech-freaks.in - Shopping Cart</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>

<body>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<p><font face="Verdana, Arial, Helvetica, sans-serif"><strong>Shopping Cart</strong></font></p>
<p><a href="/ModelList.jsp" mce_href="ModelList.jsp">Model List</a> </p>
<table width="75%" border="1">
  <tr bgcolor="#CCCCCC">
    <td><strong><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Model
      Description</font></strong></td>
    <td><strong><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Quantity</font></strong></td>
    <td><strong><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Unit
      Price</font></strong></td>
    <td><strong><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Total</font></strong></td>
  </tr>
  <jsp:useBean id="cart" scope="session" class="in.techfreaks.shoppingcart.beans.CartBean" />
  <c:if test="${cart.lineItemCount==0}">
  <tr>
  <td colspan="4"><font size="2" face="Verdana, Arial, Helvetica, sans-serif">- Cart is currently empty -<br/>
  </tr>
  </c:if>
  <c:forEach var="cartItem" items="${cart.cartItems}" varStatus="counter">
  <form name="item" method="POST" action="servlet/CartController">
  <tr>
    <td><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><b><c:out value="${cartItem.partNumber}"/></b><br/>
      <c:out value="${cartItem.modelDescription}"/></font></td>
    <td><font size="2" face="Verdana, Arial, Helvetica, sans-serif"><input type='hidden' name='itemIndex' value='<c:out value="${counter.count}"/>'><input type='text' name="quantity" value='<c:out value="${cartItem.quantity}"/>' size='2'> <input type="submit" name="action" value="Update">
 <br/>         <input type="submit" name="action" value="Delete"></font></td>
    <td><font size="2" face="Verdana, Arial, Helvetica, sans-serif">$<c:out value="${cartItem.unitCost}"/></font></td>
    <td><font size="2" face="Verdana, Arial, Helvetica, sans-serif">$<c:out value="${cartItem.totalCost}"/></font></td>
  </tr>
  </form>
  </c:forEach>
  <tr>
    <td colspan="2"> </td>
    <td> </td>
    <td><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Subtotal: $<c:out value="${cart.orderTotal}"/></font></td>
  </tr>
</table>
</body>
</html>

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

  <display-name>Shopping Cart</display-name>
  <description>
    Simple Shopping Cart
  </description>

  <servlet>
    <servlet-name>CartController</servlet-name>
    <servlet-class>in.techfreaks.shoppingcart.servlet.CartController</servlet-class>
  </servlet>

  <!-- Define the Manager Servlet Mapping -->
  <servlet-mapping>
    <servlet-name>CartController</servlet-name>
      <url-pattern>/servlet/*</url-pattern>
  </servlet-mapping>

</web-app>

Putting the cart to test:
We mainly want to test the below three functionalities:
1. Add to cart
2. Update the quantity
3. Remove an item from cart

Start Tomcat from Eclipse and access URL http://localhost:8080/ShoppingCart/ModelList.jsp to test the above functionality.

Change requests:
Change is the essence of life! Same happens to business requirements and code. This means, that a "hardly working" developer has to become "hardworking". So, we would like you to be the "hardworking" programmer to add the below functionality in this existing cart.
1. Currently, if the same item is again added to cart, a new line item is added. Modify this in such a way that, if the same item is added again, the existing item's quantity should be incremented by one. All other state of the cart/cart items should be adjusted accordingly.
2. In the cart page, provide a new text box for redeeming promotion codes. The discount of 5% should be provided on the order total, when any of the promotion code from a list of pre-defined codes is used.

Feel free to write to us at This email address is being protected from spambots. You need JavaScript enabled to view it. for any queries regarding this change request.

Enjoy! Coding and rock n roll, always rulz!

UPDATE: Checkout our latest article on Session Based Shopping Cart using Struts 1 Framework.