Java EJB Succeeding With Struts: Indexed Properties and Beans as Properties

Succeeding With Struts: Indexed Properties and Beans as Properties

In last month’s article
we looked at how to use DynaForms to replace standard form beans with
dynamically created ones.  This month we can take it one step further and
examine how to implement master-detail records, something that occurs frequently
in web applications.

For example, suppose you’re implementing a purchase-order form.  You
have 4 rows, each one of which has multiple columns (part number, quantity,
price, etc.)  Using a brute for approach, you could implement it like so:








struts-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC 
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" 
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
 <form-beans>
  <form-bean name="purchaseOrderForm" type="org.apache.struts.action.DynaActionForm">
    <form-property name="partNumber1" type="java.lang.String"/>
    <form-property name="quantity1" type="java.lang.String"/>
    <form-property name="price1" type="java.lang.String"/>
    <form-property name="partNumber2" type="java.lang.String"/>
    <form-property name="quantity2" type="java.lang.String"/>
    <form-property name="price2" type="java.lang.String"/>
    <form-property name="partNumber3" type="java.lang.String"/>
    <form-property name="quantity3" type="java.lang.String"/>
    <form-property name="price3" type="java.lang.String"/>
    <form-property name="partNumber4" type="java.lang.String"/>
    <form-property name="quantity4" type="java.lang.String"/>
    <form-property name="price4" type="java.lang.String"/>
  </form-bean>
 </form-beans>
 <action-mappings>
  <action path="/generatePO" type="article2.GeneratePO" 
          name="purchaseOrderForm" scope="request" input="/purchaseOrder.jsp">
      <forward name="success" path="/displayPurchaseOrder.jsp" redirect="false" />
  </action>
 </action-mappings>
 <message-resources parameter="ApplicationResources" />
 <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
   <set-property value="/WEB-INF/validator-rules.xml" property="pathnames" />
 </plug-in>
</struts-config>

This would tie into a JSP page, which needs to explicitly call out each
field:

purchaseOrder.jsp

<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib prefix="fmt" uri="/WEB-INF/fmt.tld" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<head>
<title>Example of a Brute Force Purchase Order</title>
</head>
<h1>Example of a Brute Force Purchase Order</h1>
<html:errors/>
<html:form action="/generatePO">
<TABLE WIDTH="80%" BORDER="1">
<TR><TH align="left">Part Number</TH>
<TH align="left">Quantity</TH>
<TH align="left">Price</TH></TR>
<TR><TD><html:text property="partNumber1"/></TD>
<TD><html:text property="quantity1"/></TD>
<TD><html:text property="price1"/></TD></TR>
<TR><TD><html:text property="partNumber2"/></TD>
<TD><html:text property="quantity2"/></TD>
<TD><html:text property="price2"/></TD></TR>
<TR><TD><html:text property="partNumber3"/></TD>
<TD><html:text property="quantity3"/></TD>
<TD><html:text property="price3"/></TD></TR>
<TR><TD><html:text property="partNumber4"/></TD>
<TD><html:text property="quantity4"/></TD>
<TD><html:text property="price4"/></TD></TR>
</table>
<html:submit/>
</html:form>

And, of course, the appropriate Action to process the form:

GeneratePO.java

package article2;

import org.apache.struts.action.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
import article2.Utils;

public class GeneratePO extends Action {
   public ActionForward execute(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException{

      DynaActionForm poForm = (DynaActionForm) form;
      if (!Utils.nullOrBlank((String)poForm.get("partNumber1"))) {
          request.setAttribute("total1", computeTotal(poForm, "price1", "quantity1"));
      }
      if (!Utils.nullOrBlank((String)poForm.get("partNumber2"))) {
          request.setAttribute("total2", computeTotal(poForm, "price2", "quantity2"));
      }
      if (!Utils.nullOrBlank((String)poForm.get("partNumber3"))) {
          request.setAttribute("total3", computeTotal(poForm, "price3", "quantity3"));
      }
      if (!Utils.nullOrBlank((String)poForm.get("partNumber4"))) {
          request.setAttribute("total4", computeTotal(poForm, "price4", "quantity4"));
      }

      return mapping.findForward("success");
   }

   private Double computeTotal (DynaActionForm form, String price, String quantity) {
      return new Double(article2.Utils.parseFormFieldToInt(form, quantity) *
                        article2.Utils.parseFormFieldToDouble(form, price));

   }
}

This approach, however, is both inelegant and inflexible, in order to add
another line you’d need to manually add three more form-properties, and
change both your JSP form and Action to know about them.  Luckily,
Struts 1.1 offers a convenient way to both deal with this type of form, and to
expand it dynamically (or even at runtime) without having to change any code or
JSP.

To begin, we define a simple Java Bean that holds the data on one line of the
form:

POLine.java

package article2;

public class POLine {
  private String partNumber;
  private String quantity;
  private String price;
  private double total;

  public String getPartNumber() {
    return partNumber;
  }

  public void setPartNumber(String partNumber) {
    this.partNumber = partNumber;
  }

  public String getQuantity() {
    return quantity;
  }

  public void setQuantity(String quantity) {
    this.quantity = quantity;
  }

  public String getPrice() {
    return price;
  }

  public void setPrice(String price) {
    this.price = price;
  }

  public double getTotal() {
    return total;
  }

  public void setTotal(double total) {
    this.total = total;
  }

}

With this in place, we change our struts-config.xml
so that the form uses an array of these beans, rather than explicit
values for each field.

struts-config.xml version 2

<struts-config>
 <form-beans>
   <form-bean name="purchaseOrderBeanForm" type="org.apache.struts.action.DynaActionForm">
     <form-property name="lines" type="article2.POLine[]" size="4" />
   </form-bean>
  </form-beans>
 <action-mappings>
  <action path="/generateBeanPOForm" forward="/purchaseOrderBean.jsp"
          name="purchaseOrderBeanForm" scope="request"/>
  <action path="/generateBeanPO" type="article2.GenerateBeanPO"
          name="purchaseOrderBeanForm" scope="request" input="/purchaseOrderBean.jsp">
      <forward name="success" path="/displayPurchaseOrderBean.jsp" redirect="false"/>
  </action>
 </action-mappings>
 <message-resources parameter="ApplicationResources"/>
</struts-config>

A few notes are required here in explanation. The size parameter is
late addition to Struts 1.1, it works like this.  If the type
parameter of a form-property is an array, and the size parameter
is present, Struts will construct an array of that size, and use the
zero-argument instatiator of the class to populate the array.  In this
case, it will create a 4-element array of type POLine
and use the POLine() instantiator to create values
for each slot.  If you don’t specify a size parameter, no array will
be created (useful for dynamically generated forms, which will be discussed
later in this article.)

In order to have the array available for the form, we need to precreate it
before the JSP page is displayed.  To do this, we create an action called
/geneateBeanPOForm
which doesn’t actually call an Action class.  Instead, it
instantiates the form in request scope and then immediately forwards to

purchaseOrderBean.jsp.

purchaseOrderBean.jsp

<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib prefix="fmt" uri="/WEB-INF/fmt.tld" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<head>
<title>Example of a Bean Based Purchase Order</title>
</head>
<h1>Example of a Bean Based Purchase Order</h1>
<html:form action="/generateBeanPO">
<TABLE WIDTH="80%" BORDER="1">
<TR><TH align="left">Part Number</TH>
<TH align="left">Quantity</TH>
<TH align="left">Price</TH></TR>
<c:forEach var="lines" items="${purchaseOrderBeanForm.map.lines}" >
  <TR><TD><html:text indexed="true" name="lines" property="partNumber"/></TD>
  <TD><html:text indexed="true" name="lines" property="quantity"/></TD>
  <TD><html:text indexed="true" name="lines" property="price"/></TD></TR>
</c:forEach>
</table>
<html:submit/>
</html:form>

Notice how much cleaner this code is.  We use the JSTL
forEach iterator to get a handle on each element of
the POLine array, and bind it to the variable
lines.  Then we can use the indexed
parameter of the html:text tag to indicate that each
field is actually being indexed by the surrounding forEach
loop.  A quirk of the html:text tag is
that the name parameter (and by extension, the variable you use in the
var
field of the forEach) must match the name of
the form-property.

When this is executed, the resulting HTML looks like:

<head>
<title>Example of a Bean BasedPurchase Order</title>
</head>
<h1>Example of a Bean Bease Purchase Order</h1>
<form name="purchaseOrderBeanForm" method="post"  action="/jdj/generateBeanPO.do">
<TABLE WIDTH="80%" BORDER="1">
<TR><TH align="left">Part Number</TH>
<TH align="left">Quantity</TH>
<TH align="left">Price</TH></TR>

<TR><TD><input type="text" name="lines[0].partNumber" value=""></TD>
<TD><input type="text" name="lines[0].quantity" value=""></TD>
<TD><input type="text" name="lines[0].price" value=""></TD></TR>

<TR><TD><input type="text" name="lines[1].partNumber" value=""></TD>
<TD><input type="text" name="lines[1].quantity" value=""></TD>
<TD><input type="text" name="lines[1].price" value=""></TD></TR>

<TR><TD><input type="text" name="lines[2].partNumber" value=""></TD>
<TD><input type="text" name="lines[2].quantity" value=""></TD>
<TD><input type="text" name="lines[2].price" value=""></TD></TR>

<TR><TD><input type="text" name="lines[3].partNumber" value=""></TD>
<TD><input type="text" name="lines[3].quantity" value=""></TD>
<TD><input type="text" name="lines[3].price" value=""></TD></TR>

</table>
<input type="submit" value="Submit">
</form>

Notice that nowhere in the JSP do we specify the number of lines, the
forEach loop figures it out automatically. 
This means that if you want to add more lines, you just change the size
parameter, and it all updates automatically.

Finishing things out, here’s the Action to process the form
submission:

GenerateBeanPO.java

package article2;

import org.apache.struts.action.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

import article2.Utils;

public class GenerateBeanPO extends Action {
   public ActionForward execute(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request, HttpServletResponse response)
     throws ServletException, IOException {

     DynaActionForm poForm = (DynaActionForm) form;
     POLine[] lines = (POLine[]) poForm.get("lines");
     for (int i = 0; i < lines.length; i++) {
       if (!Utils.nullOrBlank(lines[i].getPartNumber())) {
           try {
              double total = Integer.parseInt(lines[i].getQuantity()) *
              Double.parseDouble(lines[i].getPrice());
              lines[i].setTotal(total);
           } catch (NumberFormatException ex) {
           }
        }
      }

     return mapping.findForward("success");
     }
}

Once again, the code is totally general, there’s no need to specify how many
lines to iterate over.

You can even dynamically specify the size of the form (and prepopulate it) at
run time.  I’ve recently used this technique with an insurance application
where the user has to choose various parameters such as copays and deductables
for a claim modeling system.  by using an Action instead of a
forward to precreate the form-property value, and leaving the size
parameter out.  The actual can then create an array of the appropriate size
and prepopulate values.  The only trick is that the form must be placed in
session scope, because it needs to be available and of the right size to accept
the submitted values.

In the next edition of Succeeding with Struts, we’ll tie this all together by
looking at how to use the Validator Framework to handle both simple and
bean-based validation, and take a look at the new validWhen validation
which has just been added to Struts.

About the author

James Turner is the Director of Software Development for
Benefit Systems, Inc. He is also a contributor to the Apache
Struts project. He has two books out on web-facing Java
technologies, MySQL and JSP Web Applications, and Struts Kick
Start. His third book, Java Server Faces Kick Start, will be
published by Sams in the 4th quarter of 2003.

Latest Posts

Related Stories