Construction of Administration Site Components


Construction of Administration Site Components

The construction of the administration site in bigrez.com is broken down into two primary sections:

  • Authorization/authentication components controlling access to site components and allowing properties to view and manage only their own information

  • Property maintenance components providing pages for creating, modifying, and deleting all of the property information required to drive the user site

Note that the administration site is designed to be a completely separate Web application deployed to WebLogic Server alongside the user site. The administration site has its own web.xml file, struts-config.xml file, and a completely independent set of display components.

We will now examine the two sections of the administration site, highlighting key components and techniques in each section as appropriate.

Authorization/Authentication Components

The administration site in bigrez.com is not available to the general user community. Two classes of users are allowed to access the administration site: hotel administrators and system administrators. Hotel administrators are allowed to maintain information for their own hotels, while system administrators are allowed to maintain information for all hotels as well as create new hotels and remove existing hotels.

We ve employed the standard J2EE Web application security mechanisms provided by WebLogic Server as a starting point for this application. More advanced WebLogic Server-specific security mechanisms will be discussed in Chapter 10. Standard Web application security relies on three primary components:

  • The definition of users and groups in the application server environment using administration tools provided by WebLogic Server

  • Declaring Web application security in the web.xml file for the application and specifying the roles having access to specific Web components

  • Defining the mapping between the roles defined in the web.xml file and the principals, either users or groups, defined in the environment

Using the WebLogic Server administration console, a HotelAdministrators group is created and a small set of hotel administrator users are added to the default realm for the application and made members of this group. A separate BigRezAdministrators group is also created in the realm, and a single administrator user is created and made a member of that group. Chapter 5 walks through this process in detail.

Web application security is then declared and configured in the web.xml descriptor file for the administration site using the standard descriptor elements. First, all of the pages and servlets in the administration site are secured by defining a security-constraint element containing all display resources and indicating with the auth-constraint tag that only the admin and hoteladmin roles may access these resources:

 <security-constraint>   <web-resource-collection>     <web-resource-name>Admin Pages</web-resource-name>     <url-pattern>*.jsp</url-pattern>     <http-method>GET</http-method>     <http-method>POST</http-method>   </web-resource-collection>   <web-resource-collection>     <web-resource-name>Page Display Servlet</web-resource-name>     <url-pattern>*.page</url-pattern>     <http-method>GET</http-method>     <http-method>POST</http-method>   </web-resource-collection>   <web-resource-collection>     <web-resource-name>Action Servlets</web-resource-name>     <url-pattern>*.do</url-pattern>     <http-method>GET</http-method>     <http-method>POST</http-method>   </web-resource-collection>   <auth-constraint>     <role-name>  admin  </role-name>     <role-name>  hoteladmin  </role-name>   </auth-constraint> </security-constraint> 

Next , the Web application is configured to use form-based authentication by including a login-config element defining the authentication method and the pages to use for requesting login information from the user and for reporting login problems:

 <login-config>   <auth-method>FORM</auth-method>   <form-login-config>     <form-login-page>/Login.page</form-login-page>     <form-error-page>/Login.page?error=true</form-error-page>   </form-login-config> </login-config> 

The Web container is responsible for displaying the specified login page whenever a user requests a resource in the site for which they are not authorized. Note that the Login.page URL specified for this page will actually cause the PageDisplayServlet to be invoked, which will then forward to the Master.jsp assembly page, which then finally includes the actual login page, Login.jsp , in the standard template for the site. We ll examine Login.jsp in a moment, but first let s finish the required descriptor entries in web.xml :

 <security-role>     <role-name>admin</role-name> </security-role> <security-role>     <role-name>hoteladmin</role-name> </security-role> 

These elements simply declare the existence of the security roles used in the auth-constraint elements earlier in the descriptor. Note that these security roles are not the same as the BigRezAdministrators and HotelAdministrators groups defined in the WebLogic Server realm. Although WebLogic Server will automatically map roles to groups in the realm if the names are identical, this is not a best practice. As discussed in Chapter 10, a separate set of elements in weblogic.xml should be used to map roles to principals, either groups or users, in the realm. For bigrez.com , these mapping elements in weblogic.xml look like this:

 <security-role-assignment>   <role-name>  admin  </role-name>   <principal-name>  BigRezAdministrators  </principal-name> </security-role-assignment> <security-role-assignment>   <role-name>  hoteladmin  </role-name>   <principal-name>  HotelAdministrators  </principal-name> </security-role-assignment> 
Best Practice  

Always map the roles defined in web.xml to principals (groups or users) defined in the realm using explicit security-role-constraint entries in weblogic.xml rather than relying on automatic matching of role names to principal names.

As shown in Listing 4.5, the Login.jsp page follows the basic rules of form-based authentication. It defines an HTML form with the action j_security_check containing input fields j_username and j_password . This page will be displayed automatically by the Web container whenever a user attempts to access any controlled resource in the application.

Listing 4.5:  Login.jsp.
start example
 <%@ page extends="com.bigrez.ui.MyJspBase" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <table width="100%" cellspacing="5" cellpadding="0">   <tr>     <td class="page-header" align="right">Login</td>   </tr>   <tr>     <td class="page-text">Please log in to Administration Site:</td>   </tr>   <logic:present parameter="error">     <tr><td>&nbsp;</td></tr>     <tr><td class="error-header2">       Invalid Administrator ID or Password. Please try again.     </td></tr>   </logic:present>   <tr><td>&nbsp;</td></tr>   <tr>     <td>       <form method="POST" action="j_security_check">         <table width="50%" border="0" cellspacing="0" cellpadding="0">           <tr>             <td width="50%" class="page-label">Administrator ID:</td>             <td width="50">               <input type="text" name="j_username" size="15"                       maxlength="15"                      value="<%= getr(request, "j_username") %>">             </td>           </tr>           <tr>             <td class="page-label">Password:</td>             <td>               <input type="password" name="j_password" size="15"                      maxlength="15"                       value="<%= getr(request, "j_password") %>">             </td>           </tr>           <tr><td colspan="2">&nbsp;</td></tr>           <tr>             <td align="center" colspan="2">                <input type="submit" value="Submit">             </td>           </tr>         </table>       </form>     </td>   </tr> </table> 
end example
 

When the user submits the form, the container intercepts the request and attempts to authenticate using the default security realm and the supplied username and password. If the supplied data is not correct, the container forwards the user to the page defined in the web.xml file in the form-error-page element, normally an error page of some sort . We ve added a twist here by defining the error page to be the Login.jsp page again with an error parameter:

 <form-error-page>/Login.page?error=true</form-error-page> 

In the Login.jsp page, we can now sense the presence of this error request parameter, using standard Struts tags or any normal scriptlet-based mechanism, and conditionally display an error message at the top of the page:

 <logic:present parameter="error">   <tr><td>&nbsp;</td></tr>   <tr><td class="error-header2">     Invalid Administrator ID or Password. Please try again.   </td></tr> </logic:present> 

The login form will therefore be redisplayed to the user in the case of login errors with this additional error message at the top. We added one final touch to the page by including in the input fields the previous submitted values using simple expression scriptlets:

 <input type="text" name="j_username" size="15" maxlength="15"         value="<  %= getr(request, "j_username") %  >"> 

getr() is a simple utility method in the JSP base class that returns the given request parameter as a String , with missing parameters returning an empty string. The page will now display the previous values in the form after an authentication error.

All of these components and configuration entries essentially define the authentication side of the security system. Only users who have a valid login and password in the default security realm and belong to one of the proper groups will be able to access administration site resources. This represents only half of the solution, however. We still need to define authorization logic restricting certain actions and allowing access to property data based on the user s group membership and other criteria.

Recall that there are two different types of users: hotel administrators and system administrators. Hotel administrators should be able to view and maintain only the data for their property, whereas system administrators have complete authority over all properties and related functions. We ve elected to implement substantially all of this authorization logic in our presentation- tier components as a starting point.

We ll examine some of the authorization logic built into various maintenance pages in the next section as we examine the specific pages, but let s look at a typical example before moving on. The PropertyList page shows a list of all properties in the system, filtered according to authorization. System administrators, for example, see all properties in the system. The controller component responsible for preparing the HttpServletRequest prior to forwarding to this page fills the request with a collection of PropertyLocal references using one of two different versions of the finder method:

 String username = request.getRemoteUser().toUpperCase(); ... if (request.isUserInRole("admin")) {     props = phome.  findAll();  } else {     props = phome.  findByLoginId(username);  } request.setAttribute("props",props); 

The findByLoginId() finder method filters the returned property references using the loginId attribute in the property bean, thereby limiting the result set to those properties that have this user s name in that attribute. Hotel administrators will typically see only a single property displayed in the list, although there is no reason why multiple properties could not be managed by the same user by setting the loginId attribute in multiple properties to that user s name.

In the PropertyList display JSP page, we also want to prohibit hotel administrators from creating new properties. System administrators see a complete list of properties, as stated earlier, but also have a link available at the bottom of the page to create a new property. We simply place the hyperlink used to create a new property in conditional logic requiring a specific role using standard Struts tags:

 <  logic:present role="admin"  >   <tr>     <td>   <a class="table-link" href="PropertyListAction.do?action=create">         Create New Property       </a>     </td>   </tr> <  /logic:present  > 

Clearly, we must also check authorization in the related controller classes to supplement this display-side logic and avoid back-door attempts to access unauthorized functions. The PropertyListAction controller, for example, includes the following role check in the code that handles the creation of a new property:

 // we must check for proper role first if (!isAdminUser(request)) {     ActionErrors errors = new ActionErrors();     errors.add(ActionErrors.GLOBAL_ERROR,                 new ActionError(error.propertylist.createnotadmin));     saveErrors(request, errors);     return mapping.findForward(systemproblem); } ... process the create request ... 

isAdminUser() is defined in the BigRezAdminAction base class as follows:

 protected boolean isAdminUser(HttpServletRequest request) {     // check for administrator role by name     return request.isUserInRole(admin); } 
Best Practice  

The logic:present Struts tags provides a convenient mechanism for checking a user s authorization level in JSP pages. Make sure to supplement any role checks in JSP pages with verification checks in controller components to avoid back-door access to prohibited data or functions.

Now let s move on to discuss a few of the property maintenance components in the administration site to complete our examination of the bigrez.com Web application.

Property Maintenance Components

Table 4.2 lists the primary presentation-tier components responsible for maintenance of property information in the administration site. Once a property is chosen using the PropertyList page, the user may update five different types of information: main property information, room types in the property, rates for a given room type, availability of a given room type, and the targeted offers for the property.

Table 4.2:  Property Maintenance Primary Components

Display Component

Related Controller Component

Form Bean

PropertyList.jsp

PropertyListAction.java

 

PropertyMain.jsp

PropertyMainAction.java

PropertyMainForm.java

PropertyRooms.jsp

PropertyRoomsAction.java

 

PropertyRoom.jsp

PropertyRoomAction.java

PropertyRoomForm.java

PropertyRates.jsp

PropertyRatesAction.java

 

PropertyRate.jsp

PropertyRateAction.java

PropertyRateForm.java

PropertyAvails.jsp

PropertyAvailsAction.java

 

PropertyAvail.jsp

PropertyAvailAction.java

PropertyAvailForm.java

PropertyOffers.jsp

PropertyOffersAction.java

 

PropertyOffer.jsp

PropertyOfferAction.java

PropertyOfferForm.java

These maintenance components are intended to demonstrate proper use of the Struts framework and the direct interaction approach across a variety of different form types and update requirements. As shown in the table, all of the display JSP pages use a similarly named Action controller class, and all form pages use form beans. Consistent with the standard Struts approach, all relationships between these components are defined in the administration site struts-config.xml file.

The following sections examine selected pages in the administration site to highlight additional techniques and best practices.

Property Main Form

The main property maintenance page, PropertyMain.jsp , is a standard Struts HTML form page using a form bean and Action controller class to process updates. As shown in Figure 4.7, the page presents all of the basic property information for update by the user. System administrators may also delete the property completely or change the loginId attribute through this page.

click to expand
Figure 4.7:  Property main page.

The PropertyMain.jsp display page uses the Struts html:form tags and related input tags to create the HTML form. No JavaScript field validation was employed in these pages to keep them simple. The loginId field, editable only by system administrators, is defined in conditional logic based on the user s role:

 <tr>   <td class=page-label>Admin Login ID:</td>   <td>     <logic:present role=admin>       <html:text property=loginId size=20 maxlength=20 />     </logic:present>     <logic:notPresent role=admin>       <span class=table-data>         <bean:write name=PropertyMainForm property=loginId/>       </span>       <html:hidden name=PropertyMainForm property=loginId/>      </logic:notPresent>   </td> </tr> 

Form validation is performed by the validate() method in the form bean in a similar manner to previous form beans. The Action controller class, PropertyMainAction , accepts the submitted form once it has been validated and processes the changes in the execute() method, as shown in Listing 4.6.

Listing 4.6:  PropertyMainAction.java.
start example
 package com.bigrez.ui.admin; import org.apache.struts.action.*; import java.io.IOException; 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 org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForm; import com.bigrez.form.admin.PropertyMainForm; import com.bigrez.ejb.PropertyLocal; import com.bigrez.ejb.PropertyHomeLocal; import com.bigrez.utils.Locator; import com.bigrez.utils.CopyHelper; public final class PropertyMainAction extends BigRezAdminAction  {     public ActionForward execute(ActionMapping mapping,                                  ActionForm form,                                  HttpServletRequest request,                                  HttpServletResponse response)         throws IOException, ServletException     {         String action = request.getParameter("action");         LOG.info(">>> PropertyMainAction::execute() with action " +                  action);         PropertyMainForm pform = (PropertyMainForm)form;         // Check for the transaction token set in previous action class         if (!checkToken(request, "error.transaction.token")) {             return mapping.findForward("systemproblem");         }         try {             beginTrans(request);             PropertyLocal prop = null;             if (action.equals("update")) {                 if (pform.getId() == null                      pform.getId().intValue() == 0) {                     // create new property                     PropertyHomeLocal propertyhome = (PropertyHomeLocal)                         Locator.getHome("PropertyHomeLocal");                     prop = propertyhome.create(pform.getDescription(),                                                pform.getFeatures(),                                                pform.getAddress1(),                                                pform.getCity(),                                                pform.getStateCode(),                                                pform.getPostalCode(),                                                pform.getPhone(),                                                pform.getImageFile(),                                                pform.getLoginId());                     prop.setAddress2(pform.getAddress2());                     ActionAdminHelper.setPropertyId(request,                         prop.getId().intValue());                 }                 else {                     // update existing property                     int id = pform.getId().intValue();                     prop = (PropertyLocal)                          Locator.getBean("PropertyLocal", id);                     CopyHelper.copy(pform, prop);                     ActionAdminHelper.setPropertyId(request, id);                 }             }             else if (action.equals("delete")) {                 if (!isAdminUser(request)) {                     ActionErrors errors = new ActionErrors();                     errors.add(ActionErrors.GLOBAL_ERROR,                                 new ActionError("error.property.deletenotadmin"));                     saveErrors(request, errors);                     commitTrans(request);                     return mapping.findForward("systemproblem");                 }                 // delete the property                 int id = pform.getId().intValue();                 prop = (PropertyLocal)                     Locator.getBean("PropertyLocal", id);                 if (prop.getReservations().size() > 0) {                     ActionErrors errors = new ActionErrors();                     errors.add(ActionErrors.GLOBAL_ERROR,                                 new ActionError("error.property.reservationsexist"));                     saveErrors(request, errors);                     commitTrans(request);                     saveToken(request);                     return new ActionForward(mapping.getInput());                 }                 prop.remove(); // delete the property and cascade...                 ActionAdminHelper.clearPropertyId(request);             }             commitTrans(request);             ActionAdminHelper.loadPropertyList(request);         }         catch (Exception e) {             LOG.error("Exception saving property changes", e);             try {                  rollbackTrans(request);              }              catch (Exception ee) {                  LOG.error("Exception rolling back ",ee);             }             ActionAdminHelper.loadJSPException(request,e);             return mapping.findForward("error");         }         return mapping.findForward("success");     } } 
end example
 

The execute() method is a fairly complex method because it handles the creation, update, and deletion of properties while checking for valid submit tokens, the proper user role, and the presence of any reservations for this property. We can t provide a complete walkthrough of the code here, so we will touch on a few highlights that might not be obvious from the source code.

First, notice that the creation of a new Property bean is performed in this code rather than through a session bean fa §ade or other helper class. In the direct interaction approach, we are allowed to call the create() method for a bean directly.

Next, examine the code responsible for updating the Property bean with the form contents:

 // update existing property int id = pform.getId().intValue(); prop = (PropertyLocal)Locator.getBean(PropertyLocal, id); CopyHelper.copy(pform, prop); 

It doesn t seem long enough, does it? One of the benefits of the direct interaction approach is the simplicity of updates, where all that is required is a reference to the entity bean and the calling of its set methods . So where are the set method calls transferring the data from the PropertyMainForm form bean to the Property entity bean? We ve employed a handy helper class called CopyHelper for this purpose.

CopyHelper has a single static method, copy() , which uses reflection to find matching get and set methods on the two input Object references and transfer data using these matched methods. The class actually caches the matching set of methods using the two class names as a key to allow faster operation on subsequent calls. As long as attribute names and data types match in the beans, a single call to copy() is the only operation required to transfer all of the form bean data to the entity bean. When the transaction commits, ejbStore() will be called on the entity bean by the container, and all of the changes will be saved.

Best Practice  

Consider using a reflection-based utility, such as the CopyHelper class used in bigrez.com , to simplify the transfer of data between forms, entity beans, and value objects.

The last piece of code to examine in PropertyMainAction is the code to delete a property. After checking that the property does not have existing reservations, the property is simply deleted using the remove() method on the local interface:

 prop.remove(); 

What about all of the related beans such as RoomType , Rate , and Offer that have relationships and foreign-key constraints with this property? How can we delete the property without first deleting all of its children? As we will discuss in Chapter 7, the relationships between the Property bean and its children are all defined with a cascade-delete option on the child end of the relationship. WebLogic Server is responsible for traversing all relationships and automatically deleting all children and grandchildren objects before deleting the Property bean itself. All you have to do is call remove() on the Property bean interface.

One of the child beans of the Property , the Reservation bean, is not configured using cascade-delete in the relationship. In other words, existing reservations at a property are not automatically deleted by the container when the property is deleted. Because the database includes foreign-key constraints between the related tables, the database will throw an exception if the property is deleted leaving orphan reservation records. We avoid this error by including code in the PropertyMainAction class to ensure that a property has no reservations before attempting the deletion:

 // delete the property int id = pform.getId().intValue(); prop = (PropertyLocal) Locator.getBean("PropertyLocal", id);  if (prop.getReservations().size()  >  0) {  ActionErrors errors = new ActionErrors();     errors.add(ActionErrors.GLOBAL_ERROR,                 new ActionError("error.property.reservationsexist"));     saveErrors(request, errors);     commitTrans(request);     saveToken(request);     return (new ActionForward(mapping.getInput())); } prop.remove(); // delete the property and cascade... 

Now let s move on to a form page that demonstrates some date-handling logic and a few other tricks, the PropertyRate page.

Rate Maintenance Pages

The PropertyRates page displays a complete set of room types and rates available for the current property, as shown in Figure 4.8. Users click on any one of the existing rate entries for a given room type or on the link to create a new room type. All of this data is created in the display JSP page itself from a single object, the Property bean reference, placed in the HttpServletRequest by the upstream controller class. The property s getRoomTypes() method is used to retrieve room types and display their information, and the getRates() method is used in each room type to create the complete display of data.

click to expand
Figure 4.8:  Property rates page.

As usual, all hyperlinks on this page invoke actions on the PropertyRates_Action controller class, which loads the request with the proper information before forwarding to the PropertyRate page. As shown in Figure 4.9, the PropertyRate page combines the properties of a list page and a form page, allowing the user to modify or delete the currently selected rate or navigate to a different rate in the current room type by clicking on a different rate in the list.

click to expand
Figure 4.9:  Property rate page.

A complete examination of this page is beyond the scope of the discussion, but you are encouraged to examine the PropertyRate.jsp source code in the downloadable example code to gain additional insight. We ll jump ahead to the PropertyRate_Action controller class to examine briefly how the dates and other unique aspects of this page are handled in the execute() method in that class. Please download and examine the source code for the PropertyRateAction controller class before proceeding.

The execute() method in PropertyRateAction must handle many special cases and perform all create, update, and delete functionality for the associated PropertyRate form page. It s a long and complex method, so we ll just look at a couple of interesting sections.

First, note that form validation is being performed in the execute() method rather than in the form bean. We ve decided to bypass normal input validation for the delete action to avoid validation errors that do not matter during a delete, and this is one easy way to do it.

Next, notice that the update logic does not make use of the CopyHelper class in this particular case, but instead copies the data manually from the form bean to the entity bean:

 int id = prform.getId().intValue(); rate = (RateLocal) Locator.getBean(RateLocal, id); rate.setStartDate(startDate); rate.setEndDate(endDate); rate.setRate(prform.getRate()); 

The form bean uses String attributes to hold the start and end dates, and the Rate entity bean uses Date attributes, so the CopyHelper reflection mechanism will fail to transfer the date information properly between these beans because it cannot perform the necessary type conversions. We must transfer the data manually.

Finally, the delete logic removes the Rate bean from the system completely using a simple call to the remove() method on the bean reference:

 rate.remove(); // delete the rate completely.. 

This has the side effect of removing the Rate bean from its relationship with the parent RoomType bean automatically. In fact, if you attempt to eliminate the relationship manually before deleting the Rate bean itself, using code something like this, it won t work:

 roomtype.getRates().remove(rate); // this causes foreign-key error! rate.remove(); 

A foreign-key constraint error will be raised by the database. It turns out that WebLogic Server performs the update SQL statement required to eliminate the relationship before performing the delete SQL statement, and because the Rate table does not allow the ROOMTYPE_ID to be null, an error is raised. Remove children beans directly using their remove() method rather than trying to perform the delete in two steps.

Best Practice  

Remove child entity beans in one step by calling the remove() method on their local interface. Attempts to first remove them from relationships and then remove the bean from the system will cause foreign-constraint errors in databases employing these constraints.

Availability Maintenance Pages

We re down to one more set of maintenance pages to examine: the PropertyAvails list page and the PropertyAvail availability update page. These pages are designed to present the user with a high-level view of room availability at the current property and provide a mechanism for modifying that availability. Availability may be modified by closing out rooms on certain dates or limiting the number of rooms available on certain dates.

Availability in the bigrez.com system is stored in the Inventory table as a sparse series of rows linked to room types. For example, if the Deluxe room type is not available for the date 11/15/2003, there will be a row in the Inventory table linked to that room type having a date stamp of 11/15/2003 with a ROOMSAVAIL value of zero. The term sparse in the previous sentence indicates that the absence of a row in the database for a particular room type and date means no limit or problem with that date instead of implying that there is no inventory available on that date. Chapter 7 will discuss how this inventory data is queried and used to create the availability information employed by the reservation process itself as well as the maintenance pages in this section.

Let s look at the list page for availability, PropertyAvails.jsp . As shown in Figure 4.10, the PropertyAvails page presents the user with a high-level view of availability over a nine-month period, providing counts of closed days (days with zero inventory) and days with some control (either closed or limited remaining inventory).

click to expand
Figure 4.10:  Availability list page.

This display represents a great deal of information and a fair number of calculations, so we ve chosen to implement the business logic required to collect all of these counts as a session bean fa §ade and value object. Consistent with other pages in the site requiring complex value objects for display, the Action controller class responsible for preparing the request invokes the proper calculation method on the session bean and places the result on the HttpServletRequest before forwarding to the list page.

For example, in the controller class-handling actions for the top navigation bar, TopNavAction , the code appears as follows:

 Date start = DateHelper.getFirstDayOfMonthNoTime(); ActionAdminHelper.loadAvailSummaryInfos(request, start, id); 

The loadAvailSummaryInfos() method simply calls the InventorySession session bean to retrieve a collection of AvailabilitySummaryInfo value objects and places them on the request using the asinfos key value:

 InventorySessionLocal isession = (InventorySessionLocal)      Locator.getSessionBean("InventorySessionLocal"); PropertyLocal prop = (PropertyLocal)      Locator.getBean("PropertyLocal", propertyid);  Collection asinfos =   isession.calculateAllAvailSummaryInfo(prop, startDate, 9);   request.setAttribute("asinfos", asinfos);  request.setAttribute("startDate", AvailHelper.format(startDate)); 

The AvailabilitySummaryInfo object is a standard value object containing the following attributes with corresponding get and set methods:

 private RoomTypeLocal roomType; private Date startDate; private Collection controlCounts; private Collection closeoutCounts; 

To display the summary information, the PropertyAvails.jsp page gains access to the collection on the request with a jsp:useBean element and iterates through the AvailabilitySummaryInfo value objects:

 <jsp:useBean id=asinfos scope=request              class=java.util.Collection /> ... <logic:iterate id=asinfo collection=<%= asinfos %>                type=com.bigrez.val.admin.AvailabilitySummaryInfo> ... </logic:iterate> 

Typical bean:write tags are then used inside the loop to access and display the room-type description and feature attributes from the RoomType bean nested in the asinfo object:

 <tr>   <td>     <a class=table-link href=...>       <bean:write name=asinfo property=roomType.description/>     </a>   </td> </tr> <tr>   <td class=table-data>     <bean:write name=asinfo property=roomType.features/>   </td> </tr> 

Finally, nested logic:iterate tags are used to iterate over the collections stored in the asinfo object and create the summary information:

 <tr>   <td class="table-data">Controls:</td>    <logic:iterate id="controlCount" type="java.lang.Integer"                   collection="<%= asinfo.getControlCounts() %>">     <td class="table-data" align="center"><%= controlCount %></td>    </logic:iterate> </tr> <tr>   <td class="table-data">Closeouts:</td>    <logic:iterate id="closeoutCount" type="java.lang.Integer"                   collection="<%= asinfo.getCloseoutCounts() %>">     <td class="table-data" align="center"><%= closeoutCount %></td>    </logic:iterate> </tr> 

There are also hyperlinks on this page designed to drill in on a given month to view the details for the month and edit the availability as well as links to scroll forward and backward in time. Examine the source code for PropertyAvails.jsp in the downloadable example code to see how these links are created in the page.

For this discussion, we ll simply state that the user clicks a hyperlink on the summary page, which provides the underlying controller class, PropertyAvails_Action , with the parameters necessary to prepare the proper information in the request to forward to the maintenance page, PropertyAvail.jsp . This page uses the form bean PropertyAvailForm , which contains date attributes used for navigation and an ArrayList of values representing the inventory values for each day in the month:

 private String currentDate; private String editDate; private ArrayList control; 

The controller prepares the PropertyAvailForm form bean by copying the values from the appropriate Inventory beans to the proper locations in the control collection, a task contained in the loadPropertyAvailForm() method in the Admin_ActionHelper helper class:

 public static PropertyAvailForm loadPropertyAvailForm(HttpServletRequest request, int roomid,                  Date currentDate, Date editDate)     throws NamingException  {     LOG.info(">>> loadPropertyAvailForm with roomid " +              roomid + " and editDate "+editDate);     InventorySessionLocal isession = (InventorySessionLocal)          Locator.getSessionBean("InventorySessionLocal");     RoomTypeLocal roomtype = (RoomTypeLocal)         Locator.getBean("RoomTypeLocal", roomid);  Collection availList =   isession.calculateAvailList(roomtype, editDate);  PropertyAvailForm paform = new PropertyAvailForm();     // MM/YY format     paform.setCurrentDate(AvailHelper.format(currentDate));     // MM/DD/YYYY format     paform.setEditDate(DateHelper.format1(editDate));  paform.setControl((ArrayList)availList);  request.setAttribute("PropertyAvailForm", paform);     return paform; } 

As shown in the listing, a method called calculateAvailList() on the InventorySession session bean does the real work of creating the list of values for the given room type and month. The helper simply copies the list to the form bean in preparation for display.

The request now contains a PropertyAvailForm with an ArrayList of String objects suitable for display in the availability maintenance page, PropertyAvail.jsp . The Struts framework provides a mechanism for iterating through a List in a form bean and creating multiple input fields on the page. We ve added logic to break the list of days into columns for better usability, the net result being the form page shown in Figure 4.11. Examine the source code for the PropertyAvail.jsp page in the downloadable example code to see how this display is created using logic:iterate tags and nested html:input tags.

click to expand
Figure 4.11:  Availability maintenance page.
Best Practice  

Generate HTML forms containing a list of inputs using Struts logic:iterate tags with nested html:input tags. These tags provide automatic mapping to a List in a form bean and greatly simplify the process.

The HTML form is submitted to the controller class for this page, PropertyAvail_Action . The Struts framework takes care of populating the form bean with the contents of the HTML form, including the placement of each availability value on the form in the correct location in the control list in the form bean. The form is then validated by the validate() method within PropertyAvailForm :

 public ActionErrors validate(ActionMapping mapping,                               HttpServletRequest request) {     LOG.info(>>> PropertyAvailForm::validate());     ActionErrors errors = new ActionErrors();     Iterator i = control.iterator();     while (i.hasNext()) {         String value = (String)i.next();         if (!isEmpty(value)) {             try {                 Integer.parseInt(value);             }              catch (NumberFormatException e) {                 errors.add(ActionErrors.GLOBAL_ERROR,                             new ActionError(error.avail.nonnumeric));             }         }     }     return errors; } 

Once the form is validated, the execute() method in the PropertyAvailAction controller class is invoked to process the submission:

 public ActionForward execute(ActionMapping mapping,                              ActionForm form,                              HttpServletRequest request,                              HttpServletResponse response)     throws IOException, ServletException {     LOG.info(">>> PropertyAvailAction::execute()");     PropertyAvailForm paform = (PropertyAvailForm)form;     // Check for the transaction token set in previous action class     if (!checkToken(request, "error.transaction.token")) {         return mapping.findForward("systemproblem");     }     try {         int propertyid = ActionAdminHelper.getPropertyId(request);         int roomid = Integer.parseInt(request.getParameter("id"));         Date editDate = DateHelper.parse(paform.getEditDate());         Date currentDate = AvailHelper.parse(paform.getCurrentDate());         ArrayList controls = paform.getControl();         LOG.info("controls: " + controls);         beginTrans(request);         InventorySessionLocal isession = (InventorySessionLocal)             Locator.getSessionBean("InventorySessionLocal");         RoomTypeLocal roomtype = (RoomTypeLocal)             Locator.getBean("RoomTypeLocal", roomid);         // Perform the real update work here  isession.updateInventory(roomtype, editDate, controls);  // must commit before reloading data for next page!         commitTrans(request);          beginTrans(request);         ActionAdminHelper.loadPropertyBean(request, propertyid);         ActionAdminHelper.loadAvailSummaryInfos(request, currentDate,                                                 propertyid);         commitTrans(request);     }     catch (Exception e) {         ...     }     return mapping.findForward("success"); } 

We ve elected to use a method on the InventorySession stateless session bean to perform the update operation rather than creating, updating, and removing Inventory beans directly in the controller component.

Note that the transaction containing the bean data must be committed before attempting to load the HTTP request with new summary information. The queries used by loadAvailSummaryInfos() will not reflect the newly inserted or updated bean data until the transaction is committed and the ejbStore() methods have been called for the beans involved in the current transaction.

Best Practice  

Be careful when executing finder methods or other queries from within transactions affecting the beans in the query. Either commit the transaction before executing the queries to update the database and ensure accurate queries, or use the include-updates capability of WebLogic Server to flush the updates in the transaction for selected finders . See Chapter 6 for a discussion of the include-updates feature.




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