Skip to main content

skip to main content

developerWorks  >  WebSphere  >

Using cookies to store the user's shopping information in WebSphere Commerce

developerWorks
Document options

Document options requiring JavaScript are not displayed

Discuss

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Andres Voldman (voldman@ca.ibm.com), Software Engineer, IBM

30 Jan 2008

Learn how you can customize the WebSphere® Commerce V6.0 Consumer-Direct sample store by using cookies to store the shopper's logon ID and the mini shopping cart.

Introduction

In the Consumer-Direct store, the state of the user (logged in, guest, or generic), order quantity, and subtotal are displayed in the header. This is a common practice in most e-commerce sites. In addition, if the user is logged in, many sites include the user name or logon ID in the header.

These user-specific elements are embedded in the pages and retrieved from the database or cache for every interaction that the user does with the store. This article will show:

  • Different techniques to maintain this data in client-side cookies.
  • How to ensure that the content is always accurate.
  • Advantages of this approach compared to the out-of-the-box model.

Figure 1 shows the standard header of the Consumer-Direct store that includes the mini shopping cart.


Figure 1. Consumer-Direct standard header
Figure 1. Consumer-Direct standard header


Back to top


How the mini shopping cart is implemented

The sample store implements the mini shopping cart as a self-executable JSP fragment that is imported by the header (CachedHeaderDisplay.jsp). MiniShopCartDisplay.jsp uses OrderListDataBean to find the subtotal and the quantity of items for the shopper's current pending order.

Because the mini cart accesses the database, but its contents only change when the order is updated, we recommend that you cache MiniShopCartDisplay.jsp using dynamic caching (dynacache). By adding this template to the cachespec.xml and not consuming the fragment, you ensure that the mini cart is retrieved from cache and is not executed. The code sample below shows the cache entry definition for the MiniShopCartDisplay.jsp that displays the mini shopping cart. This fragment is cached using the store ID, language, currency, and user ID.

<cache-entry>
	<class>servlet</class>
	<name>/Consumer-Direct/include/MiniShopCartDisplay.jsp</name>
	<property name=“do-not-consume”>true</property>
	<property name=“save-attributes”>false</property>
	<cache-id>
		<component id=“DC_storeId” type=“attribute”>
			<required>true</required>
		</component>
		<component id=“DC_userId” type=“attribute”>
			<required>false</required>
		<not-value>-1002</not-value>
		</component>
		<component id=“DC_lang” type=“attribute”>
			<required>true</required>
		</component>
		<component id=“DC_curr” type=“attribute”>
			<required>true</required>
		</component>
		<priority>1</priority>
		<timeout>3600</timeout>
		<inactivity>600</inactivity>	
	</cache-id>
</cache-entry>

For a complete cachespec.xml definition of the mini-cart and the invalidation, refer to this file: WCToolkitEE60\samples\dynacache\Consumer-Direct\cachespec.xml.



Back to top


Advantages of the cookies approach

Using the cookies approach provides several advantages to the JSP and dynacache approach. The main benefit is that it simplifies caching and improves performance. The advantages are:

  • In WebSphere Commerce version 6.0, you can enable a set of predefined cookies (refer to EdgeCacheCookieHelper in wc-server.xml). Although these cookies, such as USER_ID, allow you to push the fragment to the edge (the DC_userId request attribute is not accessible outside the servlet), implementing your own cookies is a better alternative because it eliminates the need for the fragments altogether. Having a single full page cache entry in the Edge per URL simplifies the caching architecture.
  • Storing user-specific data in the browser makes the system more scalable and allows you to maintain in memory cache objects that are more likely to be reused. It also reduces garbage collection because, if a new session is created when the in-memory cache is full, less objects are created and off-loaded (and later reloaded).
  • Updating the dynacache content is a synchronous operation. Every time a user starts a session or updates the cart, the cache is locked for all users for a number of milliseconds (it may be more, depending on whether the entry is on disk). Therefore, reducing the fragments will also reduce the synchronous waits.
  • Dynacache takes less time composing the pages because the user fragments are statically included, instead of self-executable fragments that are imported at execution time.

Disadvantages of the cookies approach

The disadvantages of the cookies approach are:

  • As with dynacache, you need to ensure that the cookie is recreated when the cart is updated. For example, after the order is placed (OrderOKView URL), the mini cart is empty and the cookie needs to be updated to show that there are no items in the cart.
  • You might have security concerns about storing data in cookies. In this case, the information written to the cookies is “read-only” and for display-only purposes. After they are written, the server does not consider the contents of the cookie at any point in time. If you write code that will be used during order processing, you need to carefully analyze if cookies are the solution for you.



Back to top


Accessing the sample code

The sample_code.zip file includes all the new code and the Consumer-Direct files that were modified to implement the mini cart and shopper name using cookies. Step-by-step instructions to deploy the code are not provided in this article, but general guidelines are given. The architecture of the code is explained in the following sections.

Files created and updated for the sample code

This section lists all the files that were either created or updated. They have all been included in the sample_code.zip file. For those files that were updated, we recommend that you review the changes and back up your existing files before importing the sample into your workspace.

JSP changes

  • /Stores/WebContent/WEB-INF/web.xml
  • /Stores/WebContent/WEB-INF/struts-config-ext.xml
  • /Stores/WebContent/Consumer-Direct/include/styles/style1/CachedHeaderDisplay.jsp
  • /Stores/WebContent/Consumer-Direct/include/MiniShopCartDisplay.jspf
  • /Stores/WebContent/Consumer-Direct/include/UserProfileHeader.jspf
  • /Stores/WebContent/Consumer-Direct/javascript/Util.js

Java code

  • com.ibm.commerce.sample.cookies.CookieContants
  • com.ibm.commerce.sample.cookies.CookieFilter
  • com.ibm.commerce.sample.cookies.CookieHttpServletResponseWrapper
  • com.ibm.commerce.sample.cookies.CookieListener
  • com.ibm.commerce.sample.cookies.MiniCartCmd
  • com.ibm.commerce.sample.cookies.MiniCartCmdImpl
  • com.ibm.commerce.sample.cookies.MiniCartCookieAction
  • com.ibm.commerce.sample.cookies.MyExtPromotionEngineOrderCalculateCmdImpl


Back to top


Importing the code to the WebSphere Commerce Developer environment

Follow these steps to import the code in your development environment:

  1. Import all the com.ibm.commerce.sample.cookies.* classes to the WebSphereCommerceServerExtensionsLogic project.
  2. Back up your web.xml for the Stores.war project. Use the GUI to edit the deployment descriptor for the Stores project (web.xml), then create and install the com.ibm.commerce.sample.cookies.CookieFilter filter. Edit web.xml and ensure CookieFilter is first in the chain.
  3. Follow the instructions in Updating the Struts configuration to update your struts configuration. Use the version in the sample_code.zip file as an example. Remember to back up the existing struts-config files first.
  4. Back up your existing JSPs. Import CachedHeaderDisplay.jsp,MiniShopCartDisplay.jspf UserProfileHeader.jspf and Util.js to their respective directories in the Stores.war project.



Back to top


Implementing the DISPLAYNAME cookie

The sample code creates a cookie named DISPLAYNAME that contains the shopper’s logon ID, as shown in Figure 2. The header then uses JavaScript in the client to read the cookie and print the logon ID on the page.


Figure 2. Customized header that displays the logon ID
Figure 2. Customized header that displays the logon ID

The code uses HttpServletResponseWrapper, which provides a convenient implementation of the HttpServletResponse interface that developers can subclass to adapt the response from a servlet. In this case, we add a listener so that we are notified when a cookie is set.

public class CookieHttpServletResponseWrapper extends
		HttpServletResponseWrapper {
   ...

   public void addCookie(Cookie cookie) {	
      final String methodName = “addCookie(Cookie)”;
      if ( logger.isLoggable( Level.FINER )) {
         logger.entering(CLASS_NAME, methodName,”Cookie “+cookie.getName());
      }
      
      super.addCookie(cookie);
      CookieListener.newCookie( this, cookie );
   }
   ... 
}

The response wrapper is installed using a servlet filter. CookieFilter, which is the first filter in the chain, wraps the response with the extension and invokes the next filter with it.

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
   throws IOException, ServletException {
   final String methodName = “doFilter”;
   logger.entering( CLASS_NAME, methodName );
   CookieHttpServletResponseWrapper wresp =
      new CookieHttpServletResponseWrapper((HttpServletRequest)req,
         (HttpServletResponse)resp );
   chain.doFilter(req, wresp);
   logger.exiting( CLASS_NAME, methodName );
}

When CookieListener is executed, it checks if the new cookie is the authentication cookie. If the cookie starts with WC_AUTHENTICATION, the listerner refreshes the DISPLAYNAME cookie to contain the user’s logon ID. If the user is a guest user, the cookie contains the "guest" string in it.

if ( newCookie.getName().startsWith( “WC_AUTHENTICATION_”) 
	) {   HttpServletRequest req = resp.getRequest();
     CommandContext cmdCtxt = (CommandContext) 
     req.getAttribute(“CommandContext”);
      if ( cmdCtxt != null ) {
      String displayName = GUEST;
      try {
         if ( cmdCtxt.getUserId().longValue() != GENERIC_ID 
         &&
         cmdCtxt.getUser().getRegisterType().compareTo(GUEST_USER) 
         != 0 ) {
           displayName = cmdCtxt.getUser().getDisplayName();
            if (logger.isLoggable(Level.FINER))
               logger.logp( Level.FINER, CLASS_NAME, methodName,
                  “display name=“ + displayName );
         }
         Cookie logonCookie = createCookie( newCookie, 
         DISPLAYNAME, displayName );
         resp.addCookie( logonCookie, false );
      } catch ( Exception e) {
         logger.logp(Level.WARNING,CLASS_NAME,methodName,"cannot 
         read display name",e);
      }
   }
}

See UserProfileHeader.jspf for details on how the JSP is implemented to read the DISPLAYNAME cookie. When DISPLAYNAME is not “guest”, the page is dynamically updated using JavaScript to include the “My Account” and “Order Status” links and to change the “Sign In” link with “Welcome <UserName>“ and the log off link.

Using the CookieListener approach helps extend the Logon commands. You can ensure that the listener works in every situation, including returning remembered users, log ins and log offs, session time-outs, and so on.



Back to top


Implementing the SHOPPINGCART cookie

This code creates a new session cookie named SHOPPINGCART<STOREID>. The cookie contains the number of items and the subtotal for the order. Figure 3 shows the characteristics of the SHOPPINGCART cookie. The format used to store the quantity and subtotal makes it easier to parse the values in JavaScript by creating a hash.


Figure 3. The SHOPPINGCART cookie
Figure 3. The SHOPPINGCART cookie

The key for implementing this cookie is to refresh it when the user or order changes. The most common implementation is extending the order commands, such as OrderItemAdd, OrderItemUpdate, OrderProcess, and updating the cookie after performExecute. The disadvantage of this approach is that it requires you to extend many commands, which is a cumbersome process.

In this case, to avoid changing the commands themselves, the sample will update the struts configuration. This allows you to configure when a command updates the cookie. The com.ibm.commerce.sample.cookies.MinicartCookieAction class extends com.ibm.commerce.struts.BaseAction and overwrites the postProcess() method, which checks whether or not the cookie needs to be updated.

Using this extension, you can configure a generic command to update the SHOPPINGCART cookie in three different ways:

  1. Adding the updateMiniCart=yes parameter to the request parameters, such as https://localhost/webapp/wcs/stores/servlet/OrderCalculate?URL=OrderItemDisplay&storeId=10101&catalogId=10101&updateMiniCart=yes&orderId".
  2. Adding the updateMiniCart attribute to the request using the MiniCartCookieAction.updateMinicart(CommandContext, boolean) helper method.
  3. In the struts configuration, by setting updateMiniCart=true in the default properties:
    	
    <action path=“/OrderOKView” 
    type=“com.ibm.commerce.sample.cookies.MiniCartCookieAction”>
     <set-property property=“https” value=“0:1” />
     <set-property property=“defaultProperties” 
     value=“updateMiniCart=true” />
    </action>
    

    You can add the updateMiniCart default property using the editor for the struts configuration file or directly in the xml. Figure 4 shows the Action Mappings editor in Rational® Application Developer.

    Figure 4. Struts Action Mappings configuration
    Figure 4. Struts Action Mappings configuration

Instead of updating the cookie every time the order is updated, the sample code refreshes the cookie before the shopping cart is displayed (OrderItemDisplay). This approach works well in the sample store because most of the commands that update the order are redirected to the shopping cart page. Other commands that update the order, but are not redirected to the shopping cart, such as OrderOKView, require that you set the updateMiniCart default property to “true” in the struts configuration.

The code also updates the mini cart when the authentication cookie changes. To do this, the cookie listener invokes MiniCartCookieAction.updateMinicart(HttpServletRequest,boolean) to add the “updateMiniCart” attribute to the request. This is then used in MinicartCookieAction.postProcess() to ensure that the cookie needs to be recreated.

Alternatively, instead of refreshing the cookie in OrderItemDisplay, you have the option to either:

  • Update the struts configuration for all the commands that update the order, such as OrderItemAdd, OrderItemUpdate, OrderItemDelete, and so on.
  • Or, force the recreation of the cookie in a command, such as the Promotion Engine that is invoked when the order is updated.

The following example shows an extension of PromotionEngineOrderCalculate:

public class MyExtOrderProcessCmdImpl extends
 PromotionEngineOrderCalculateCmdImpl {
 public void performExecute() throws ECException {
  super.performExecute();
  MiniCartCookieAction.updateMinicart( getCommandContext(), true );   
   }
}

Updating the struts configuration

The sample code updates the struts configuration in struts-config-ext.xml as follows:

  1. Find all the occurrences of type=“com.ibm.commerce.struts.BaseAction” and replace it with type=“com.ibm.commerce.sample.cookies.MiniCartCookieAction”.
  2. Add the following property to OrderOKView: <set-property property=“defaultProperties” value=“updateMiniCart=true” />.
  3. Copy the action definition for OrderItemDisplayCmd from struts-config.xml to struts-config-ext.xml and add the updateMiniCart=yes property:
    				
    <action parameter=“com.ibm.commerce.orderitems.commands.
    OrderItemDisplayCmd”
      path=“/OrderItemDisplay” type=“com.ibm.commerce.sample.cookies.
      MiniCartCookieAction”>
      <set-property property=“authenticate” value=“0:0”/>
      <set-property property=“https” value=“0:1”/>
      <set-property property=“defaultProperties” value=“updateMiniCart=yes”/>
    </action>
    

Implementing MiniShopcartDisplay.jspf

After configuring struts to generate the SHOPPINGCART cookie, you need to update the logic of the store front to print the values of the cookie in the page. Because the MiniShopcartDisplay.jsp no longer needs to be a self-executable JSP (a requirement for caching fragments with dynacache), you can redesign it to be a JSP fragment (.jspf) instead.

<table id=“WC_MiniShopCartDisplay_Table_1” cellpadding=“0” 
cellspacing=“0” border=“0”>
<tbody>
<tr>
  <td align=“right” class=“s_text” 
  id=“WC_MiniShopCartDisplay_TableCell_3”>
    <a href=““ class=“s_link” id=“WC_MiniShopCartLink_1”>
      <img src=“<c:out value=“${jspStoreImgDir}${vfileColor}cart.gif”/>“ border=“0”
         alt=“<fmt:message key=“MINI_CART” bundle=“${storeText}” />“ >
    </a>
  </td>
  <td align=“left” valign=“middle” class=“s_text”
       id=“WC_MiniShopCartDisplay_TableCell_3”>
      <a href=““ class=“s_link” id=“WC_MiniShopCartLink_2”></a>
  </td>
</tr>
</tbody>
</table>

JavaScript reads the value of the cookie into a hash and dynamically updates the contents of the <a> element to reflect the values in the cookie:

<script language=“javascript”>
// Cart: {0} item(s)
var numItemsInCart = “<fmt:message key=“NumItemsInCart” 
bundle=“${storeText}”/>“;
var subtotal = “<fmt:message key=“Subtotal” bundle=“${storeText}”/>“;

function getMiniCart() {
   var cart = getCookie( “SHOPPINGCART” + GLOBAL_STORE_ID );
   if ( cart == null ) {
      cart = getCookie( “SHOPPINGCART” );
      // if cookie is missing, use this default. Please note that currency symbol
      // might not be correct but the cookie should not be missing to start with.
      if ( cart == null ) {
         cart = “{quantity:’0’, subtotal:’$0.00’}”;
      }
   }
   return eval(‘(‘ + cart + ‘)’);
}

var minicart = getMiniCart();

var link1  = document.getElementById(“WC_MiniShopCartLink_1”);
var link2  = document.getElementById(“WC_MiniShopCartLink_2”);

var shopCartLink = “OrderCalculate?URL=OrderItemDisplay&storeId=“ 
+ GLOBAL_STORE_ID
+ “&catalogId=“ + GLOBAL_CATALOG_ID + “&orderId=.”;

link1.href = shopCartLink;
link2.href = shopCartLink;
link2.innerHTML = numItemsInCart.replace(“{0}”, minicart[‘quantity’] ) + ‘ ‘
                + subtotal +  minicart[‘subtotal’];
</script>



Back to top


Conclusion

Cookies are an efficient alternative for storing user-specific data. With cookies, sites are more scalable and allow for a better caching strategy. This article provided an approach for implementing cookies on the sample store that you can use as a template and extend when customizing your own site.




Back to top


Download

DescriptionNameSizeDownload method
Code samplecode_sample.zip38 KBHTTP
Information about download methods


Resources



About the author

Author photo

Andres Voldman is a Software Engineer at the IBM Toronto Lab, Canada. He is part of the WebSphere Commerce Advanced Technical Services team, which specializes in performance and stability.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top