Constructing the User Site Components


Constructing the User Site Components

Once the application skeleton is in place, additional view components are added in a piecewise fashion to flesh out the application. The construction of the user site in bigrez.com was broken down into three primary sections:

  • Reservation information components responsible for the display of the reservation information area on the page and for the handling of user actions in that area

  • Core reservation process components providing the main site functionality of finding properties, selecting dates, room types, and rates and making reservations

  • Targeted offers components generating the targeting marketing offers in the left gutter depending on the user s recent search results and selections

In the following sections, we ll examine each of these sections of the user site in some detail, highlighting key components and techniques in each section.

Reservation Information Components

Creating a reservation requires a multiple-step process. Intermediate results must be stored in the HttpSession on behalf of the user, a technique much like a shopping cart in an e-commerce site. The bigrez.com application uses a serializable value object called ReservationInfo to store this information in the session. As the user selects a property, selects dates, selects a room type, and signs in to the site, the related information is saved in the HttpSession in the ReservationInfo object and its child ReservationRateInfo objects. These classes are simple value objects with private attributes and appropriate get and set methods , so complete listings are not required. For reference, the ReservationInfo class has the following attributes:

 private String lastSearchCity; private String lastSearchState; private int propertyId; private String propertyDescription; private int roomTypeId; private String roomTypeDescription; private int guestProfileId; private String firstName; private String lastName; private String phone; private String email; private String cardType; private String cardExp; private String cardNum; private Date arriveDate; private Date departDate; private Collection rezRates; 

The ReservationRateInfo class has these attributes:

 private Date startDate; private int numNights; private float rate; 

The current reservation information is displayed on the left side of the screen on every page in the user site in a small reservation information area generated by the RezInfo.jsp display JSP page. Figure 3.2 in the previous chapter showed this reservation information area in the context of the overall display. As the user selects a property, selects dates, or completes additional steps in the process, the reservation information area changes to reflect these selections.

The RezInfo.jsp page generates this area using a jsp:useBean tag to declare the existence of the ReservationInfo object in the HttpSession and provide a local page variable, rezinfo , for accessing the attributes of the object:

 <jsp:useBean id=rezinfo scope=session              class=com.bigrez.val.user.ReservationInfo /> 

The rezinfo variable may now be used in simple jsp:getProperty tags to retrieve specific attributes from the object and display them on the page:

 <jsp:getProperty name="rezinfo" property="propertyDescription"/> 

Collections contained in the rezinfo variable may be examined using Struts logic:iterate tags:

 <jsp:useBean id=rezrates class=java.util.ArrayList scope=page>   <% rezrates = (ArrayList) rezinfo.getRezRates(); %> </jsp:useBean> ... <tr>   <td>     <span class=sidebar-title>Rate:</span>     <logic:iterate id=rezrate        type=com.bigrez.val.user.ReservationRateInfo         collection=<%= rezrates %>>       <br>&nbsp;<span class=sidebar-data>       <jsp:getProperty name=rezrate property=numNights/>       nts&nbsp;@&nbsp;$       <jsp:getProperty name=rezrate property=rate/>/nt</span>     </logic:iterate>   </td> </tr> 

The rezinfo variable may also be used in logic:equal or logic:notEqual tags to display information conditionally. For example, we want to display the string Choose Property for the property description if the user has not yet selected a property for this reservation. Rather than performing this logic in the ReservationInfo value object or creating a separate Helper class, we ve made use of these logic tags to control the display:

 <a class=sidebar-link href=/RezInfoAction.do?action=property>   <logic:equal name=rezinfo property=propertyId value=0>     Choose Property   </logic:equal>   <logic:notEqual name=rezinfo property=propertyId value=0>     <jsp:getProperty name=rezinfo property=propertyDescription/>   </logic:notEqual> </a> 
Best Practice  

Do not place conditional display logic, such as replacing empty values with default messages, in value objects. Use custom tags or other view components to create conditional displays.

The displayed values in the reservation information area are also used as navigation links, allowing the user to jump back to a previous decision or log in early. As the code snippet shows, the target URL for the property description is RezInfoAction.do . All hyperlinks in this JSP page and every other JSP page in the site use this < PageName > Action.do approach rather than hard-coding page names in the display JSP pages. As discussed in Chapter 2, these *.do locations are mapped in the web.xml file to the default ActionServlet in Struts, which then instantiates and uses an Action class ”in this case RezInfoAction ”to handle the request.

The execute() method in RezInfoAction interrogates the request to determine the proper action and performs the required preparation and forwarding steps:

 public ActionForward execute(ActionMapping mapping,                              ActionForm form,                              HttpServletRequest request,                              HttpServletResponse response)     throws IOException, ServletException  {     String action = request.getParameter(action);     ReservationInfo rezinfo = getRezInfo(request);     ...     if (action.equals(dates)) {         if (rezinfo.getPropertyId() == 0) {             action = property; // cant do dates before property             ActionErrors errors = new ActionErrors();             errors.add(ActionErrors.GLOBAL_ERROR,                  new ActionError(error.rezinfo.propertybeforedates));             saveErrors(request, errors);         }          else {             // prepare the request with a SelectDatesForm object              // to populate fields             ActionUserHelper.loadSelectDatesForm(request, rezinfo);         }         return mapping.findForward(action);     }     ... } 

This simple controller behavior of the RezInfoAction class is depicted in Figure 4.2.

click to expand
Figure 4.2:  The RezInfoAction controller determines the display page.

As the code snippet indicates, there are many business rules in each if (action_.equals(...)) block in the class that modify this simple controller behavior. For example, if the user has not yet selected a property, the property search page must be displayed before selecting dates or room types. The key is that none of these business rules are contained in the RezInfo.jsp display page. Consistent with the model-view-controller approach, the controller component RezInfoAction is responsible for both navigation and the application of presentation-related business rules.

The RezInfoAction class also illustrates an important implementation detail. As discussed earlier, controller components are responsible for placing the required beans, forms, or value objects in the proper scope before forwarding control to the display JSP page. The RezInfoAction class forwards control to many different pages in the action-specific branching logic, so it must be capable of preparing many different types of objects before forwarding. The class uses a series of helper methods in the ActionUserHelper class to perform this preparation, thereby encapsulating the required business- tier interaction in these methods to foster reuse. For example, the following code prepares for the JSP page that displays a particular property by loading the PropertyBean local reference in the request:

 ActionUserHelper.loadPropertyBean(request, rezinfo.getPropertyId()); 

The loadPropertyBean() method in the helper class performs the key operations:

 ... PropertyLocal prop =      (PropertyLocal) Locator.getBean(PropertyLocal, id); request.setAttribute(prop, prop); ... 

We ll discuss specific display pages, their preparation requirements, and the contents of the ActionUserHelper class in more detail in the next section.

Best Practice  

Preparation of the request or session context prior to forwarding to a display JSP page should be performed in a common helper method. There may be multiple paths to the same display page, and all controllers leading to that page should use the same helper method to prepare the request or session.

One other interesting technique demonstrated in RezInfoAction is the use of the standard Struts error-handling functions to place messages on subsequent display JSP pages. If the user clicks on the Choose Dates link before a property is selected, we must display the property search page rather than the select dates page. Rather than simply sending the user to the property search screen without explanation, we will place a message in the normal location used for form-validation or form-submission errors and proceed to the new target page:

 if (action.equals("dates")) {     if (rezinfo.getPropertyId() == 0) {  action = "property"; // cant do dates before property   ActionErrors errors = new ActionErrors();   errors.add(ActionErrors.GLOBAL_ERROR,   new ActionError("error.rezinfo.propertybeforedates"));   saveErrors(request, errors);  }     else {         // prepare the request with a SelectDatesForm to populate fields         ActionUserHelper.loadSelectDatesForm(request, rezinfo);     }     return (mapping.findForward(action)); } 

The target page displays these errors by including the standard < html:errors/ > tag in the page definition.

In summary, the reservation information area of the bigrez.com user site is designed to display the current status of the reservation process and allow the user to jump directly to certain steps in the process, subject to business rules enforced in the RezInfoAction controller component. The display area is created by the RezInfo.jsp display JSP page using data stored in the HttpSession in the ReservationInfo and ReservationRateInfo value objects. All hyperlinks invoke the RezInfoAction controller class through the normal Struts action-mapping facilities, and this controller class is responsible for determining the proper JSP page to display.

The reservation information area design and the chosen implementation techniques provide a good model for a shopping cart or other multiple-step process in your J2EE Web applications.

Core Reservation Process Components

The core reservation process, illustrated in Figure 3.3, walks the user step-by-step through the selection of required elements of a reservation. In this section, we examine a few of the presentation-tier components in detail to illustrate the solutions employed for common requirements in J2EE Web applications.

Defining Navigation Paths

The reservation process involves a fair number of separate display JSP pages, controller Action classes, and related form beans and value objects. Table 4.1 provides a list of the primary components in the process. Note that not all display pages have form beans and that all pages, except the final ReservationThankYou page, have their own controller Action class for processing user actions on the page.

Table 4.1:  Core Reservation Process Primary Components

Display Component

Related Controller Component

Form Bean

PropertySearch.jsp

PropertySearchAction.java

PropertySearchForm.java

PropertyList.jsp

PropertyListAction.java

 

SelectDates.jsp

SelectDatesAction.java

SelectDatesForm.java

SelectRoomType.jsp

SelectRoomTypeAction.java

 

GuestInformation.jsp

GuestInformationAction.java

GuestInformationForm.java

ReviewReservation.jsp

ReviesReservationAction.java

 

ReservationThankYou.jsp

   
Best Practice  

Use a standard naming convention for display pages, Action controller classes, and form beans to make relationships between components clear without inspecting the configuration file. The chosen convention of appending Action and Form to the display page name is a reasonable choice.

As discussed in Chapter 2, the servlet-centric Web application architecture dictates certain rules and principles related to presentation-tier components and their relationships. One key principle is the separation of roles between display components, controller components, and navigational control facilities. The bigrez.com application meets the requirements by adopting the following rules:

  • Display JSP pages must use < PageName > Action.do controller invocations for all hyperlinks and form-posting targets. The controller is always responsible for determining the next page in the process based on the user s action, the current state, and navigation control information.

  • Controller components do not refer directly to display JSP page names when specifying the next page for display. Logical page names are used in controller code, and these logical names are mapped to actual page names in the Struts configuration files.

A critical aspect of this design is the Struts configuration file, struts-config.xml . This file defines the mapping between logical and actual JSP page names as well as the relationships between pages, form beans, and their controllers. Please download the struts-config.xml file for bigrez.com from the companion web site (http://www. wiley .com/compbooks/masteringweblogic) and review it before proceeding.

We ve also used the struts-config.xml file to define the basic course, or happy path , through the reservation process by defining success mappings in each core reservation page. For example, the next page after SelectDates is SelectRoomType , but rather than hard-coding this relationship in any JSP or controller component, the mapping is placed in the definition of the SelectDatesAction controller component and given the logical name success :

 <action path="/SelectDatesAction"         type="com.bigrez.ui.user.SelectDatesAction"         name="SelectDatesForm"         scope="request"         validate="true"         input="/SelectDates.page">   <  forward name="success" path="/SelectRoomType.page" redirect="false"/  > </action> 

As we ll examine in a moment, the controller component may now indicate that processing may continue with the next page in the reservation process by simply returning this mapping using the logical name:

 return (mapping.findForward(success)); 

In theory, you can change the order of the reservation process by simply modifying the entries in struts-config.xml to indicate the new success pages for each page in the process. Unfortunately, modifying the page flow also requires some changes in related controller classes to prepare the request properly, given the new success page. These kinds of controller changes demonstrate one problem inherent in the external definition of page flows: There are likely to be dependencies in the flow not represented in the page-flow definition. The bigrez.com application uses helper methods to prepare for subsequent pages, thereby reducing the amount of work required to change the flow. Other options include forwarding to preparation controller components capable of preparing the request for a particular new page, rather than preparing the request and forwarding directly to the page. A detailed discussion of these options is beyond the scope of this book.

Controller components that do not follow this simple success chain or that need to indicate a different target page also use logical page mappings defined in the configuration file. The RezInfoAction class already discussed had many examples of this technique.

Now that we ve discussed the basic approach to navigation in the user site, let s examine selected pages and controller components in the site to learn more about its construction.

Property Search/Selection Pages

As indicated in Table 4.1, the first page in the reservation process is PropertySearch.jsp , a simple search page allowing the user to pick the desired city or state to use in finding a property. This page uses a form bean and the standard Struts tags for creating HTML forms and input elements, as you can see in Listing 4.1.

Listing 4.1:  PropertySearch.jsp.
start example
 <%@ page extends="com.bigrez.ui.MyJspBase" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html:form action="/PropertySearchAction" method="get"> <table width="100%" cellspacing="5" cellpadding="0">   <tr>     <td class="page-header" align="right">Find a Property</td>   </tr>   <tr><td><html:errors/></td></tr>   <tr>     <td class="page-text">       Please enter the state or city you plan to visit:     </td>   </tr>   <tr><td>&nbsp;</td></tr>   <tr>     <td>       <table width="100%" cellspacing="6" cellpadding="0">         <tr>           <td width="25%" class="page-label" nowrap>State Code:</td>           <td width="75%">             <html:select property="stateCode">               <html:option value="">Choose...</html:option>               <html:options collection="stateCodeList"                              property="value" labelProperty="label"/>             </html:select>           </td>         </tr>         <tr>           <td class="page-label" nowrap>City:</td>           <td>             <html:select property="city">               <html:option value="">Choose...</html:option>               <html:options collection="cityList"                              property="value" labelProperty="label"/>             </html:select>           </td>         </tr>         <tr><td>&nbsp;</td></tr>         <tr>           <td colspan="2" align="left">             &nbsp;&nbsp;<input type="submit" value="Find Properties">           </td>         </tr>       </table>     </td>   </tr> </table> </html:form> 
end example
 

The drop-down list elements of valid state codes and city codes are generated using the html:select and html:option Struts tags. These tags look for the specified collections, either stateCodeList or cityList , in all of the scopes available to the page. In this case, we ve preloaded these collections in the application scope using an InitializationServlet loaded during application startup.

The PropertySearch.jsp page submits the contents of the HTML form to the standard ActionServlet defined in the Struts framework. The ActionServlet then forwards the contents to the PropertySearchAction controller class using the form bean defined for this page, PropertySearchForm . Note that the html:form tag in the page defines the method attribute to be a GET rather than a POST . We ve used the GET method on a number of the forms in the user site to allow more natural browser navigation without warning messages caused by POST actions.

Best Practice  

Use GET rather than POST when possible in form pages. Users will be able to navigate more freely and will be able to refresh pages without receiving form-posting warning messages from their browsers.

As before, the PropertySearchForm form bean is first given a chance to validate the HTML form contents using its validate() method:

 public ActionErrors validate(ActionMapping mapping,                               HttpServletRequest request)  {     ActionErrors errors = new ActionErrors();     if (isEmpty(city) && isEmpty(stateCode))         errors.add(ActionErrors.GLOBAL_ERROR,                     new ActionError(error.propertysearch.nocriteria));     return errors; } 

If the form-bean validation returns no errors, the execute() method in the controller class is invoked to complete the processing of this form submission. As shown in Listing 4.2, the execute() method uses the selected values of city and stateCode to execute a finder method directly on the PropertyHomeLocal interface:

 PropertyHomeLocal propertyhome =  (PropertyHomeLocal) Locator.getHome("PropertyHomeLocal"); Collection props = null; ... LOG.debug("Finding properties using city " + city);  props = propertyhome.findByCity(city);  ... 

This direct invocation of the entity bean finder method to retrieve a list of matching properties is consistent with the direct interaction approach we ve adopted for this application. The returned collection is actually a collection of local references, implementing the PropertyLocal interface, representing the related PropertyBean objects. Chapter 7 describes the declaration and definition of finder methods, such as this one, using EJB generation tools.

Listing 4.2:  PropertySearchAction.java.
start example
 package com.bigrez.ui.user; import java.io.IOException; import java.util.Collection; import javax.ejb.FinderException; import javax.naming.NamingException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import com.bigrez.ejb.PropertyHomeLocal; import com.bigrez.form.user.PropertySearchForm; import com.bigrez.utils.Locator; import com.bigrez.val.user.ReservationInfo; public final class PropertySearchAction extends BigRezUserAction  {     public ActionForward execute(ActionMapping mapping,                                  ActionForm form,                                  HttpServletRequest request,                                  HttpServletResponse response)         throws IOException, ServletException      {         LOG.info(">>> PropertySearchAction::execute()");         PropertySearchForm pform = (PropertySearchForm)form;         try {             String city = pform.getCity();             String stateCode = pform.getStateCode();             ReservationInfo rezinfo = getRezInfo(request);             rezinfo.setLastSearchCity(city);             rezinfo.setLastSearchState(stateCode);             PropertyHomeLocal propertyhome = (PropertyHomeLocal)                 Locator.getHome("PropertyHomeLocal");             Collection props = null;             if (!isEmpty(city)) {                 if (!isEmpty(stateCode)) {                     LOG.debug("Finding properties using city " + city +                               " and state " + stateCode);                     props =                        propertyhome.findByCityState(city, stateCode);                 }                 else {                     LOG.debug("Finding properties using city " + city);                     props = propertyhome.findByCity(city);                 }             }              else {                 LOG.debug("Finding properties using state " +                            stateCode);                 props = propertyhome.findByState(stateCode);             }             ActionUserHelper.loadPropertyBeans(request, props);         }          catch (NamingException ne) {             LOG.error("NamingException searching for properties", ne);             ActionUserHelper.loadJSPException(request, ne);             return (mapping.findForward("error"));         }         catch (FinderException fe) {             LOG.error("FinderException searching for properties", fe);             ActionUserHelper.loadJSPException(request, fe);             return (mapping.findForward("error"));         }         return mapping.findForward("success");     } } 
end example
 

The final step in handling the search request is placing the necessary objects in the HttpServletRequest context to prepare for the display JSP page showing the results. The display page, PropertyList.jsp , expects the matching properties to be located in the request using the props identifier, so we could easily place our collection of matching properties in the request manually using:

 request.setAttribute(props, props); 

The best practice presented earlier indicated that helper methods should be used for this purpose, so we ll delegate this preparation step to the ActionUserHelper class using the following invocation:

 ActionUserHelper.loadPropertyBeans(request, props); 

If you examine the code in ActionUserHelper you ll notice that loadPropertyBeans() performs the same request.setAttribute(...) statement in this simple case:

 public static void loadPropertyBeans(HttpServletRequest request,                                      Collection props)      throws FinderException, NamingException  {     request.setAttribute(props, props);     return; } 

Many other preparation methods in ActionUserHelper are more involved, as evidenced by the length and complexity of the code in the class. We make heavy use of these helper methods in other controller classes.

The PropertySearchAction controller forwards the request to the success mapping for this page, which according to the struts-config.xml file is PropertyList.page . WebLogic Server s servlet container intercepts this *.page request and passes it to the PageDisplayServlet according to the servlet-_mapping information in web.xml . The Master.jsp page is then invoked with the page request parameter set appropriately, and finally the user sees the matching properties displayed by PropertyList.jsp , as shown in Figure 4.3.

click to expand
Figure 4.3:  Property list page.

The PropertyList.jsp page is fairly straightforward, so rather than including a listing of the entire page, we ll examine a few key sections.

First, the page gains access to the collection of matching property references using the jsp:useBean element:

 <jsp:useBean id=props type=java.util.Collection scope=request/> 

Note that the collection must be present under the ID props , or this tag will fail attempting to instantiate a Collection object. This fact emphasizes again the importance of preparing data in the controllers before forwarding requests to the display JSP pages.

The collection is iterated through using the logic:iterate tag in Struts:

 <logic:iterate id="prop" type="com.bigrez.ejb.PropertyLocal"                 collection="<%= props %>"> ... </logic:iterate> 

In this loop, the attributes of each PropertyBean are displayed using jsp:_getProperty elements and the prop reference created by the iteration tag. Because we are interacting directly with the entity beans, all of this activity must be performed in the context of a transaction. The BeginTrans.jspf and EndTrans.jspf files are included at the top and bottom of the page, respectively, to ensure that we are in a transaction.

Consistent with our servlet-centric approach, all of the links on this page use PropertyListAction.do as the primary target, with different links specifying different action values and any required parameters. For example, the Select button for a property is defined in the page as follows :

 <a href="PropertyListAction.do?action=select&id=<jsp:getProperty          name="prop" property="id"/>">   <img src="/images/selectbutton.gif" alt="" border="0"> </a> 

Clicking Select for a given property invokes the PropertyListAction class, passing in the parameter action with a value of select and the parameter id with a value equal to the property primary key. The PropertyListAction class saves this property information in the ReservationInfo object in the session, prepares the necessary beans or forms, and forwards to the next page in the process:

 beginTrans(request); String propertyId = request.getParameter(id); int id = Integer.parseInt(propertyId); PropertyLocal prop = ActionUserHelper.loadPropertyBean(request, id); if (action.equals(select)) {     ReservationInfo rezinfo = getRezInfo(request);     if (id != rezinfo.getPropertyId()) {         rezinfo.setPropertyId(id);         rezinfo.setPropertyDescription(prop.getDescription());         rezinfo.clearRoomType();         rezinfo.clearRates();     }     ActionUserHelper.loadSelectDatesForm(request, rezinfo);     action = success; // next step in process     setRezInfo(request, rezinfo); } commitTrans(request); 

Note the use of helper classes and utility methods, such as getRezInfo() and setRezInfo() , rather than accessing attributes directly in the HttpServletRequest and HttpSession . It is always a good idea to reduce the number of places in your code that access these objects using key values to avoid unnecessary search-and-replace tasks whenever these key values need to change. String constants defined in a convenient location can also help minimize the effect of key changes.

The call to setRezInfo() (defined in the BigRezUserAction base class) performs a setAttribute() in the HttpSession to place the ReservationInfo object in the session:

 protected void setRezInfo(HttpServletRequest request, Object rezinfo)  {     // set the information in the session again to ensure replication     request.getSession(true).setAttribute(rezinfo, rezinfo); } 

Why do we have to do this again when the ReservationInfo object is already in the session and the only activity in this method was changing some of the attributes in the object? Calling setAttribute() is important when using session persistence because it tells WebLogic Server that you have changed something in the object stored in the HttpSession . You must call setAttribute() at some point during the processing to ensure that changes made to the object will be saved to the JDBC persistence store or propagated to the backup server.

Best Practice  

Make sure to call setAttribute() on any session variables that have changed during the processing of the request. WebLogic Server relies on this signal to persist session changes properly to the JDBC datastore or backup server in the cluster.

As shown in this partial source listing for ActionUserHelper.java , the load_SelectDatesForm() method prepares for the SelectDates.jsp page by creating a SelectDatesForm form bean and placing it in the request:

 public static void loadSelectDatesForm(HttpServletRequest request,                                        ReservationInfo rezinfo)  {     LOG.info(">>> loadSelectDatesForm");     // prepare the request with a SelectDatesForm to populate fields     SelectDatesForm sdform = new SelectDatesForm();     if (rezinfo.getArriveDate() != null) {         sdform.setArriveDate(DateHelper.format1(rezinfo.getArriveDate()));         sdform.setDepartDate(DateHelper.format1(rezinfo.getDepartDate()));     }     else {         Calendar now = Calendar.getInstance();         now.set(Calendar.HOUR, 0);// get midnight date/time         now.set(Calendar.MINUTE, 0);         now.set(Calendar.SECOND, 0);         sdform.setArriveDate(DateHelper.format1(now.getTime()));         now.add(Calendar.DAY_OF_MONTH, 1);         sdform.setDepartDate(DateHelper.format1(now.getTime()));     }  request.setAttribute("SelectDatesForm", sdform);  } 

Note that the loadSelectDatesForm() method prepopulates the form bean with dates based on the current date if no values are present in the ReservationInfo value object. We are now ready to proceed to the SelectDates page and obtain the user s desired arrival and departure dates.

Date Selection and Availability Display Pages

The SelectDates.jsp page presents the user with a simple form requesting an arrival and departure date (see Figure 4.4). Calendar icons next to each field use JavaScript to pop up a calendar window allowing the user to pick dates. We re not going to cover this feature in the book, but feel free to look at DatePicker.js in the downloadable code if you re interested in how these buttons work.

click to expand
Figure 4.4:  Select dates page.

The user chooses the desired dates and submits the form to the SelectDates_Action controller class. This form again uses the GET method rather than POST to improve browser navigation. Consistent with the servlet-centric architecture, all field validation takes place in the form bean validate() method or in the controller class. The validate() method in SelectDatesForm checks for empty or invalid dates, as well as reversed dates, and returns appropriate ActionError objects representing these errors:

 public ActionErrors validate(ActionMapping mapping,                               HttpServletRequest request)  {     LOG.info(>>> SelectDatesForm::validate());     ActionErrors errors = new ActionErrors();     if (assertNonEmpty(errors, arriveDate,                        error.selectdates.arriveempty)) {         assertValidDate(errors, arriveDate,                         error.selectdates.arriveinvalid);     }     if (assertNonEmpty(errors, departDate,                        error.selectdates.departempty)) {         assertValidDate(errors, departDate,                         error.selectdates.departinvalid);     }     try {         Date arrive = DateHelper.parse(arriveDate);         Date depart = DateHelper.parse(departDate);         if (arrive.equals(depart)  arrive.after(depart)) {             errors.add(ActionErrors.GLOBAL_ERROR, new ActionError(error.selectdates.arriveafterdepart));         }     }      catch (ParseException e) {         LOG.error(ParseException validating SelectDatesForm, e);         errors.add(ActionErrors.GLOBAL_ERROR,                     new ActionError(error.validationproblem));     }     return errors; } 

If the validate() method succeeds, the dates must be present and properly formatted. Processing can then continue in the execute() method of the SelectDates_Action class:

 public ActionForward execute(ActionMapping mapping,                              ActionForm form,                              HttpServletRequest request,                              HttpServletResponse response)     throws IOException, ServletException  {     LOG.info(">>> SelectDatesAction::execute()");     SelectDatesForm sdform = (SelectDatesForm)form;     try {         // save the selected dates in the ReservationInfo object         ReservationInfo rezinfo = getRezInfo(request);         // safety checks that we have property selection already         if (rezinfo.getPropertyId() == 0) {             return (mapping.findForward("property"));         }         rezinfo.setArriveDate(DateHelper.parse(sdform.getArriveDate()));         rezinfo.setDepartDate(DateHelper.parse(sdform.getDepartDate()));         rezinfo.setRezRates(new ArrayList());         setRezInfo(request, rezinfo);         // prepare the information required for next page         ActionUserHelper.loadPropertyBean(request, rezinfo.getPropertyId());         ActionUserHelper.loadRateAvailabilityInfos(request, rezinfo);     } catch (ParseException e) {         LOG.error("ParseException setting dates", e);         ActionUserHelper.loadJSPException(request, e);         return (mapping.findForward("error"));     } catch (NamingException e) {         LOG.error("NamingException setting dates", e);         ActionUserHelper.loadJSPException(request, e);         return (mapping.findForward("error"));     }     return mapping.findForward("success"); } 

The execute() method has two primary tasks: save the selected dates in the ReservationInfo object in the session and prepare the necessary beans in the request for the next page, SelectRoomType.jsp .

Saving the selected dates is a task made easy by the DateHelper helper class used to format the form attributes and by the getRezInfo() and setRezInfo() methods used to retrieve and set the information in the session.

Preparing for the next display page looks like another simple task, involving only two calls to the ActionUserHelper class to perform the preparation. But looks can be deceiving. The call to loadPropertyBean() simply places a local reference to the currently selected property bean in the request, but the call to loadRateAvailabilityInfos() does something we haven t encountered yet in our discussion:

 public static void loadRateAvailabilityInfos(HttpServletRequest request, ReservationInfo rezinfo)     throws NamingException  {     LOG.info(">>> loadRateAvailabilityInfos");  ReservationSessionLocal rezsession = (ReservationSessionLocal)   Locator.getSessionBean("ReservationSessionLocal");   PropertyLocal prop = (PropertyLocal)   Locator.getBean("PropertyLocal", rezinfo.getPropertyId());   Collection rainfos =   rezsession.calculateAllRateAvailabilityInfo(   prop, rezinfo.getArriveDate(), rezinfo.getDepartDate());  request.setAttribute("rainfos", rainfos); } 

This method makes use of a session bean called ReservationSession to retrieve a collection of objects for the given property and dates. The returned collection contains RateAvailabilityInfo objects, a class encapsulating information about the price and availability of a given room type in the selected hotel for the dates requested by the user. Rather than list the entire class, here are the attributes of a RateAvailabilityInfo object:

 private RoomTypeLocal roomType; private boolean availableFlag; private Collection rates; private Collection blockingControls; 

Each attribute of the RateAvailabilityInfo object plays a different role:

  • The roomType attribute is a reference to the RoomType entity bean this object represents.

  • The availableFlag attribute is a simple Boolean value indicating whether the room is available for the requested dates.

  • The rates collection contains RaeservationRateInfo objects representing date ranges and rates for this room type during the requested dates (recognize that rates could change during the length of the stay).

  • The blockingControls collection contains a list of Inventory entity bean references for days that cannot be booked at this hotel during the date range.

The creation of these objects and collections is covered in Chapter 7 when we examine the ReservationSession bean and the calculateAllRateAvailabilityInfo() method.

Why did we use a session bean here, and does this argue that the direct interaction approach is inappropriate for our application because it cannot be used for all business-tier interaction? We believe a session bean makes sense in this case because the work required to determine the rates and availability is complex enough to warrant some form of delegation, whether it be via a business delegate pattern, helper pattern, or the chosen session fa §ade pattern. A session bean is favored over these alternative delegation options because it provides pooling for efficiency and automatic, declarative transaction control through standard EJB descriptors. We ll use a session bean because it best accomplishes the goal of retrieving and returning a collection of objects suitable for the next step in the reservation process.

Best Practice  

Favor encapsulating complex business logic in session beans, even when using the direct interaction approach, to improve efficiency and maintainability.

Notice that the RateAvailabilityInfo objects returned by the session bean are hybrids rather than pure value objects because they contain entity-bean references rather than additional value objects where it is convenient to do so. This collection of objects is placed in the HttpServletRequest , just like any other entity bean or form bean, to make it available for use in the next page in the process, the SelectRoomType page.

As shown in Figure 4.5, the SelectRoomType page presents the user with a list of room types, rates, and availability information to assist them in choosing the desired room for their stay. Rooms that are not available for the entire duration of the stay are not available for selection and indicate the specific nights they are unavailable below their normal rates. We ll talk more about rates and availability when we walk through some pages in the administration site, so for now let s concentrate on how this content is built by the display JSP page.

click to expand
Figure 4.5:  Select room type page.

The SelectRoomType.jsp page is listed in Listing 4.3 in its entirety because it contains many interesting features and highlights some limitations of the JSP tag library when dealing with complex nested data structures.

Listing 4.3:  SelectRoomType.jsp.
start example
 <%@ page import="com.bigrez.ejb.*" %> <%@ page extends="com.bigrez.ui.MyJspBase" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <%@ include file="include/BeginTrans.jspf" %> <jsp:useBean id="prop" scope="request"              type="com.bigrez.ejb.PropertyLocal" /> <jsp:useBean id="rainfos" scope="request"               type="java.util.Collection" /> <table width="100%" cellspacing="5" cellpadding="0">   <tr>     <td class="page-header" align="right">Room Types</td>   </tr>   <tr>     <td class="page-text">The <jsp:getProperty name="prop"         property="description"/> has the following room types:     </td>   </tr>   <tr>   <td>   <table width="100%" cellspacing="0" cellpadding="3" border="0">   <logic:iterate id="rainfo" collection="<%= rainfos %>"                  type="com.bigrez.val.user.RateAvailabilityInfo">     <tr>       <td width="30%" align="left">         <a class="table-link"            href="SelectRoomTypeAction.do?action=select&id=<bean:write                  name="rainfo" property="roomType.id"/>">           <bean:write name="rainfo" property="roomType.description"/>         </a>       </td>       <td width="25%" class="table-data">         <bean:message key="<%= \"smoking\" +                             rainfo.getRoomType().getSmokingFlag() %>"/>       </td>       <td width="25%" class="table-data">         <bean:write name="rainfo"                     property="roomType.maxAdults"/>&nbsp;Adults Max       </td>       <logic:equal name="rainfo" property="availableFlag" value="true">         <td width="20%" align="center">             <a href="SelectRoomTypeAction.do?action=select&id=<bean:write             name="rainfo" property="roomType.id"/>">             <img src="/images/selectbutton.gif" alt="" border="0">           </a>         </td>       </logic:equal>       <logic:equal name="rainfo"                     property="availableFlag" value="false">         <td class="table-header" width="20%" align="center">             Unavailable         </td>       </logic:equal>     </tr>     <tr>       <td colspan="3" class="table-data">         <bean:write name="rainfo" property="roomType.features"/>       </td>     </tr>     <logic:iterate id="rate" collection="<%= rainfo.getRates() %>"                    type="com.bigrez.val.user.ReservationRateInfo" >       <tr>           <td colspan="3" class="table-data">             &nbsp;&nbsp;Rate: $<bean:write name="rate"                  property="rate" formatKey="float.price"/>/night             for&nbsp;             <jsp:getProperty name="rate" property="numNights"/> nts           </td>        </tr>     </logic:iterate>     <logic:iterate id="blocker" type="com.bigrez.ejb.InventoryLocal"                     collection="<%= rainfo.getBlockingControls() %>">       <tr>           <td colspan="3" class="table-data">             &nbsp;&nbsp;Not Available on <bean:write name="blocker"                 property="day" formatKey="date.format1"/>           </td>        </tr>     </logic:iterate>     <tr><td>&nbsp;</td></tr>   </logic:iterate>   </table>   </td>   </tr> </table> <%@ include file="include/EndTrans.jspf" %> 
end example
 

The SelectRoomType page first declares the existence of prop and rainfos beans using jsp:useBean tags:

 <jsp:useBean id="  prop  " scope="request"              type="com.bigrez.ejb.PropertyLocal" /> <jsp:useBean id="  rainfos  " scope="request" type="java.util.Collection"/> 

These variables are now available for use in either jsp:getProperty tags or Struts custom tags. The page next uses the logic:iterate tag to iterate through the rainfos collection, defining a new page bean called rainfo in the loop:

 <logic:iterate id="  rainfo  " collection="<%= rainfos %>"                type="com.bigrez.val.user.RateAvailabilityInfo"> ... </logic:iterate> 

Recall that each object in the rainfos collection is a RateAvailabilityInfo value object containing a reference to the related RoomType entity bean as well as rate and availability information. These other objects are nested in the RateAvailabilityInfo object, in a sense, so the page must traverse these internal nesting links to display the data related to the nested objects.

In the main loop, the page displays basic room type information by traversing the nested link in the RateAvailabilityInfo object to the RoomType bean using the bean:write tag provided by Struts:

 <bean:write name=rainfo property=roomType.description/> 

The bean:write tag, unlike the normal jsp:getProperty tag, allows nested attribute names in the property attribute of the tag. If a standard jsp:getProperty tag was used to display the nested data, the simple syntax you just saw would be replaced with the following code:

 <% loadPageObject(pageContext, roomType, rainfo.getRoomType()); %> <jsp:useBean id=roomType type=com.bigrez.ejb.RoomTypeLocal/> <jsp:getProperty name=roomType property=description/> 

The scriptlet code calls the loadPageObject() method in the JSP page base class, MyJspBase , in order to place the RoomType reference from the current rainfo object in the page context. This step is required because the jsp:useBean tag must find the variable already located in the desired scope, a limitation discussed earlier in this chapter. The jsp:useBean tag then declares the roomType variable, making it available for use by the subsequent jsp:getProperty tag. Finally, the jsp:getProperty tag displays the description value. This three-step process for accessing nested attributes in page beans argues strongly for the use of a custom-tag library, such as the bean or nested libraries in Struts, when accessing nested attributes.

Best Practice  

Use Struts bean:write or nested tags to access nested attributes and components in display JSP pages rather than standard jsp:getProperty tags to avoid complexity caused by the limitations of the standard jsp:useBean and jsp:getProperty tags.

Unlike some Struts applications, the bigrez.com application does not make heavy use of the bean:message tag to parameterize display strings and retrieve them from the ApplicationResources.properties file. This was a conscious decision to help maintain clarity by retaining actual display messages in the example JSP pages. So far, the only messages placed in the ApplicationResources.properties file have been error messages used by server-side form validation and other controller logic.

The SelectRoomType page uses the bean:message tag to demonstrate an interesting technique for converting Boolean bean attributes to a corresponding message in the JSP page. The RoomType bean in the rainfo object includes a smokingFlag Boolean attribute, and this page must display either Smoking or Non-Smoking depending on the value of this attribute. There are many ways to accomplish this, of course, including the ternary operator in JSP scriptlet code:

 <%= rainfo.getRoomType().getSmokingFlag().booleanValue()  ? Smoking:Non-Smoking %> 

A verbose set of logic tags would also work:

 <logic:equal name=rainfo property=roomType.smokingFlag value=true>   Smoking </logic:equal> <logic:equal name=rainfo property=roomType.smokingFlag value=false>   Non-Smoking </logic:equal> 

We ve chosen to employ the bean:message tag to accomplish this task by placing two entries in the ApplicationResources.properties file representing the strings to display for true and false conditions:

 smokingtrue=Smoking smokingfalse=Non-Smoking 

We then provide the bean:message tag a key containing the value of the smoking flag:

 <bean:message     key=<%= \smoking\+rainfo.getRoomType().getSmokingFlag() %>/> 

This solution is not much better than the JSP scriptlet technique, but it does illustrate the potential for displaying conditional messages using the bean:message tag. Note that the run-time expression defining the value for the key attribute includes a literal, smoking , requiring escaped double quotes to avoid JSP parsing errors.

The SelectRoomType page also demonstrates a more standard use of the ApplicationResources.properties file by performing the formatting of bean attributes using bean:write tags with display formats defined in the properties file. For example, the ApplicationResources.properties file contains the following entries:

 date.format1=MM/dd/yyyy date.format2=MMM dd, yyyy float.price=#.00 

Dates and prices are then displayed on the page using these formats in the bean:write tags:

 <bean:write name="rate" property="rate"  formatKey="float.price"  /> ... <bean:write name="blocker" property="day"  formatKey="date.format1"  /> 

Alternative techniques using ViewHelper objects are possible, but they require scriptlet code embedded in the HTML and appropriate < %@ page import ... % > directives at the top of the page, while the bean:write mechanism provides a cleaner and more flexible solution.

Best Practice  

Use bean:write tags with display formats defined in the application properties file to format display values such as dates and amounts.

The user examines the room types and rates displayed on this page and selects the desired room by clicking one of the Select buttons on the right side of the page. These buttons, like every other hyperlink in bigrez.com , are mapped to an Action controller class:

 <a href=SelectRoomTypeAction.do?action=select&id=          <bean:write name=rainfo property=roomType.id/>>   <img src=/images/selectbutton.gif alt= border=0> </a> 

A partial listing of the target SelectRoomTypeAction controller class is shown below:

 public ActionForward execute(ActionMapping mapping,                              ActionForm form,                              HttpServletRequest request,                              HttpServletResponse response)     throws IOException, ServletException  {     LOG.info(">>> SelectRoomTypeAction::execute()");     try {         int roomTypeId = Integer.parseInt(request.getParameter("id"));         ReservationInfo rezinfo = getRezInfo(request);         // safety checks that we have property and dates already         if (rezinfo.getPropertyId() == 0) {             return mapping.findForward("property");         }          else if (rezinfo.getArriveDate() == null) {             ActionUserHelper.loadSelectDatesForm(request, rezinfo);             return mapping.findForward("dates");               }         beginTrans(request);         ReservationSessionHomeLocal home =              (ReservationSessionHomeLocal)                 Locator.getHome("ReservationSessionHomeLocal");         ReservationSessionLocal rezsession = home.create();         RoomTypeLocal roomtype = (RoomTypeLocal)              Locator.getBean("RoomTypeLocal", roomTypeId);         Collection rezrates =              rezsession.calculateRates(roomtype,                                        rezinfo.getArriveDate(),                                       rezinfo.getDepartDate());         rezinfo.setRoomTypeId(roomTypeId);         rezinfo.setRoomTypeDescription(roomtype.getDescription());         rezinfo.setRezRates(rezrates);         setRezInfo(request, rezinfo);         // prepare the request with a GuestInformationForm object         ActionUserHelper.loadGuestInformationForm(request, rezinfo);         commitTrans(request);     }      catch (Exception e) {         ...     }     return mapping.findForward("success"); } 

SelectRoomTypeAction processes the room type selection by calling the ReservationSession bean to retrieve rate information, placing the selected room and rate information in the ReservationInfo session variable, preparing the required form bean, and forwarding to the next page in the reservation process.

The next page in the reservation process, GuestInformation , is a fairly straightforward HTML form used to collect guest information and credit-card information for the reservation. It is a typical Struts form using the GuestInformationForm form bean and submitting the data to the GuestInformationAction controller class, a pattern we ve already covered in some detail. You may examine these components in the downloaded example code if desired, but we will not discuss the GuestInformation page in this text.

Reservation Creation Process

The final step in the reservation process begins with a confirmation page, ReviewReservation , shown in Figure 4.6. The ReviewReservation.jsp display page simply displays information from the ReservationInfo object located in the HttpSession along with the standard property information from a PropertyLocal reference placed in the request by the controller responsible for forwarding to this page. The user examines the contents and clicks on the confirmation button to officially make the reservation.

The only wrinkle introduced on this page is the use of an HTML form, created using Struts form tags, even though there are no apparent input fields in the form. We will make use of the token-based form-posting logic built in to Struts to ensure that this form is submitted for processing once and only once. Rather than building this logic ourselves , the GuestInformationAction controller responsible for preparing the request for this page places a token in the request using the standard mechanism:

 saveToken(request) 

The controller component this page submits to, ReviewReservationAction , checks for the token during form processing:

 // Check for the transaction token set in previous action class if (!checkToken(request, "error.transaction.token")) {     return mapping.findForward("systemproblem"); } 
click to expand
Figure 4.6:  Review reservation page.

We ve used a helper method, checkToken , defined in the BigRezAction base class for all Action classes:

 protected boolean checkToken(HttpServletRequest request,                               String errorkey) {     if (!isTokenValid(request)) {         ActionErrors errors = new ActionErrors();         errors.add(ActionErrors.GLOBAL_ERROR,                     new ActionError(errorkey));         saveErrors(request, errors);         return false;     }      else {         resetToken(request);         return true;     } } 

If the token is not present or is not valid, the form submission is not accepted and the user is presented with an error message stating that the form may be submitted only once.

After validating and clearing the token, the ReviewReservationAction controller class is responsible for invoking the proper business components to create the final, persistent reservation. It passes a copy of the ReservationInformation value object to the ReservationSession session bean responsible for this task:

 public ActionForward execute(ActionMapping mapping,                              ActionForm form,                              HttpServletRequest request,                              HttpServletResponse response)     throws IOException, ServletException  {     LOG.info(">>> ReviewReservationAction::execute()");     try {         ...  ReservationSessionLocal rezsession = (ReservationSessionLocal)   Locator.getSessionBean("ReservationSessionLocal");   ReservationLocal reservation =   rezsession.createReservation(rezinfo);  // prepare for the thankyou page before clearing rezinfo         HttpSession session = request.getSession(true);         session.setAttribute("rezid", reservation.getId());         rezinfo.clearAllButProfile();         setRezInfo(request, rezinfo);     }      catch (BigRezBusinessException e) {       ...     }     catch (NamingException e) {       ...     }     catch (EJBException e) {       ...     }     return mapping.findForward("success"); } 

We have adopted the value object and session fa §ade pattern for the complex reservation creation operation for reasons we ve discussed previously. Note that the primary key of Reservation entity bean created and returned by the createReservation method is placed in the HttpSession rather than the HttpServletRequest in preparation for the ReservationThankYou page. This unusual choice was made to allow a redirect= true in the success mapping from this action to the ReservationThankYou page, thereby causing the transfer from the controller to the next page to be an HTTP redirect rather than a server-side forward operation. The resulting page display is better behaved should the user refresh the page or attempt to back up to it in the browser after moving somewhere else. A reference to the Reservation entity bean itself may not be placed on the HttpSession because local references are not serializable.

Before redirecting to the next page, all previous information in the session-based ReservationInfo object is cleared to indicate that the reservation process is complete. The user is now presented with the ReservationThankYou page presenting the data contained in the final Reservation bean. The process is complete!

Targeted Offers Components

To round out our discussion of the user site in bigrez.com , we will briefly examine the targeted offers area on the left side of the page. As shown in Figure 3.2 in the previous chapter, this area presents a small number of offers containing a graphic, caption, and related property name. Clicking an offer simply displays the normal property information page for the given hotel, a simplification we chose for this example application. In a real site, clicking an offer might display a special page with detailed information about the offer and provide a shortcut for selecting a specific rate or room type in the hotel, for example.

There are two interesting techniques demonstrated by the offers area:

  • Determining the offers to be displayed using a stateless session bean with method invocations placed directly on the display JSP page

  • Caching the displayed offers using WebLogic Server wl:cache custom tags

The specific offers presented to the user depend on the last city and state searched by the user and the current selected property, if any, in the ReservationInfo object. The business logic for selecting and ordering the offers is complex and is therefore encapsulated in a session bean, the OfferSessionBean , in a method called getOffersForDisplay() . The business-logic contained in this session bean is discussed in Chapter 7. The source code for Offers.jsp is shown in Listing 4.4.

Listing 4.4:  Offers.jsp.
start example
 <%@ page import="com.bigrez.utils.*, com.bigrez.ejb.*" %> <%@ page extends="com.bigrez.ui.MyJspBase" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/weblogic-tags.tld" prefix="wl" %> <jsp:useBean id="rezinfo" scope="session"              class="com.bigrez.val.user.ReservationInfo" /> <% loadPageObject(pageContext, "offerhash", rezinfo.getOfferHash());%> <wl:cache name="Offers" key="offerhash" timeout="30s" scope="session"> <% LOG.info("Creating Offers.jsp display for hash "+offerhash);%> <%@ include file="include/BeginTrans.jspf" %> <% loadSessionBean(pageContext, "offersession", "OfferSessionLocal");%> <jsp:useBean id="offersession" scope="page"              class="com.bigrez.ejb.OfferSessionLocal"/> <table width="100%" cellpadding="0" cellspacing="0" bgcolor="#EEEEEE">   <tr valign="top">     <!-- force offer block to be at least 200 high -->     <td width="1" bgcolor="#EEEEEE">       <img src="images/space.gif" width="1" height="200"></td>     <td>       <table width="100%" align="center" cellpadding="0"              cellspacing="5" bgcolor="#EEEEEE">       <logic:iterate id="offer" type="com.bigrez.ejb.OfferLocal"           collection="<%=               offersession.getOffersForDisplay(rezinfo, 2) %>">         <tr><td>           <img src="images/space.gif" width="1" height="5">         </td></tr>         <tr align="center">           <td>             <img src="/images/<jsp:getProperty name="offer"                                                property="imageFile"/>"                  alt="<jsp:getProperty name="offer"                                        property="description"/>"                  width="70" height="70">           </td>         </tr>         <tr align="center">           <td>             <a class="sidebar-link"                HREF="/OffersAction.do?id=<jsp:getProperty                      name="offer" property="id"/>">               <jsp:getProperty name="offer" property="caption"/><br>               <bean:write name="offer"                            property="property.description"/>             </a>           </td>         </tr>       </logic:iterate>       </table>     </td>   </tr> </table> <%@ include file="include/EndTrans.jspf" %> </wl:cache> 
end example
 

Unlike most display JSP pages in the site, there is no controller responsible for preparing the required Offer objects in the HttpServletRequest prior to forwarding to this page. The Offers.jsp display page is actually included by the Master_.jsp page-assembly template on every page request, so every controller would be responsible for creating the necessary data in the request if we adopted the normal design pattern. Instead, we allow the Offer.jsp page to call the stateless session bean directly to retrieve the collection of Offer objects to display based on the current ReservationInformation contents:

 <% loadSessionBean(pageContext, "offersession", "OfferSessionLocal");%> <jsp:useBean id="offersession" scope="page"              class="com.bigrez.ejb.OfferSessionLocal" /> ... <logic:iterate id="offer" type="com.bigrez.ejb.OfferLocal"      collection="<%=  offersession.getOffersForDisplay(rezinfo,2)  %>"> ... </logic:iterate> 

Note that we could have used a business delegate pattern at this point by encapsulating the session bean invocation in a page bean or other helper, but in the spirit of direct interaction, we will avoid introducing intermediate objects and helpers unless they encapsulate significant complexity or foster reuse. The code required to access the bean directly is fairly straightforward, the only trick being the use of a helper method defined on the JSP base page to create the stateless session bean and make it available in the PageContext before declaring it with a jsp:useBean element. The loadSessionBean() helper method is defined on MyJspBase.java, as shown here:

 protected void loadSessionBean(PageContext context,                                String name, String localname)     throws NamingException {     Object _temp = Locator.getSessionBean(localname);     context.setAttribute(name, _temp); } 

Once the JSP page has the offersession bean reference, the logic:iterate tag is employed to loop through the list returned by the getOffersForDisplay() method and display the contents of each Offer in the list. The objects returned in this list are actually local references to OfferBean entity beans, so the access must be within the context of a transaction. Displaying the offer data is performed with straightforward jsp:getProperty elements in the loop as well as a single bean:write tag used to access the description (name) of the property related to this offer:

 <bean:write name=offer property=property.description/> 

The second interesting technique demonstrated by Offers.jsp is the use of the WebLogic Server wl:cache tag to improve the performance of this page. We discussed the capabilities and limitations of the wl:cache tag in some detail in Chapter 1. The tag basically caches the HTML response created within the body of the tag for a specific duration, using the cached version of the response rather than evaluating the body on subsequent page requests. The goal in Offers.jsp is to reduce the number of hits to the session bean and the related Offer beans to improve performance.

If the offers displayed to the user were completely random and unrelated to the current ReservationInformation context, we might be tempted simply to surround the bulk of the Offer.jsp page with caching tags similar to the following:

 <wl:cache name=Offers timeout=30s scope=application> ... // Generate all HTML output ... </wl:cache> 

In this scenario, the cached content would expire after 30 seconds. The next page request would cause a reevaluation and recaching of the content, and all users would see a different set of offers for the next 30 seconds. Unfortunately, this simple caching isn t sufficient for our purposes. According to the business rules for the site, the displayed offers depend on selections made by the user. If the user chooses a particular city, state, or property during the reservation process, the offers must reflect these choices. How can we cache the list of displayed offers whenever possible while meeting this business requirement?

We could flush the cache every time the user makes a selection that affects the displayed offers. This technique was described in Chapter 1 and might be required in certain circumstances. There is, however, a better way: cache the response based on the current selection data.

As described in Chapter 1, the wl:cache tag includes a key attribute that can be used to specify the variables whose values should be used as the key for the cached contents. Essentially, the cached contents should depend on the values stored in the variables defined in the key attribute. In our case, the cached content depends on the recent city, state, and property selections made by the user. Rather than expose all of this complexity in the key definition, we ve introduced a hash function on the ReservationInfo class and used this hash as the key for the caching tag:

 <% loadPageObject(pageContext, "offerhash", rezinfo.getOfferHash()); %> <wl:cache name="Offers" key="  offerhash  "            timeout="30s" scope="application"> 

The getOfferHash() method in ReservationInfo simply appends the selection values together to form a String value representing the current user selections:

 public String getOfferHash()  {     // return a hash string of data used to fetch offers     return "[" + lastSearchCity + "," + lastSearchState + "," +            propertyId + "]"; } 

This string is placed in the page context by loadPageObject() using the name offerhash , and the wl:cache tag is then configured to control caching using this variable. If the application already has a response cached for that particular hash of selection information, the tag will use the previous response. If the cache does not contain a response for that selection information, the body of the tag will be evaluated and the response cached using the key. The response will therefore be generated each time the user changes a selection, but as long as that selection remains in effect the response will be retrieved from the cache, subject to the timeout value, of course.

Note that we ve defined scope= application in the tag, so response data will be cached at the application level. The application will therefore present the same set of selection-specific offers to every user of the site for the entire 30 seconds. In other words, every user who does a search for properties in a particular city or chooses a particular hotel will see the same offers during that 30-second period of time. Offers could also be cached on a per-user basis using the session scope, but placing cached information in the session impacts performance if session persistence is employed and should be avoided if possible.

Best Practice  

Avoid using session scope for response data cached using wl:cache tags. If session persistence is being employed in the application, the cached data will be treated like other session data and persisted to the JDBC datastore or replicated to backup servers, hurting performance. Consider an application- scope cache with a key that includes the session ID as one alternative.

Finally, the hyperlinks in the offers area invoke the controller object for this page, OffersAction , just like any other JSP page in the application. The controller retrieves the offer identifier from the request parameter, acquires a reference to the OfferBean entity bean, and uses the relationship get method on the OfferBean to determine the property to load in the request before forwarding to the property display page:

 String offerId = request.getParameter(id); int id = Integer.parseInt(offerId); OfferLocal offer = (OfferLocal)Locator.getBean(OfferLocal, id); PropertyLocal prop = offer.getProperty(); ActionUserHelper.loadPropertyBean(request, prop); 

That s it! We are now ready to proceed to the construction of the administration site components including pages for entering, updating, and deleting all of the information used by the bigrez.com user site.




Mastering BEA WebLogic Server. Best Practices for Building and Deploying J2EE Applications
Mastering BEA WebLogic Server: Best Practices for Building and Deploying J2EE Applications
ISBN: 047128128X
EAN: 2147483647
Year: 2003
Pages: 125

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