Chapter 5. Processing Forms

Introduction

ActionForm s are key components of any Struts application. They transport data between the Controller—that is, your Actions—and the View—your JSP pages. They protect your business layer components by acting as a firewall; they only let in expected data received from the HTTP request.

An ActionForm is a JavaBean that extends the org.apache.struts.ActionForm class. ActionForms declare properties that correlate to form fields and data displayed on a presentation page using getter (getXXX( )) and setter (setXXX( )) methods. An ActionForm can override two additional methods that the Struts framework calls when processing a servlet request:

reset( )

Prepares an ActionForm to receive data from an HTTP request. This method primarily serves as a place for you to reset checkbox properties.

validate( )

Performs data validation on ActionForm properties before the data is sent to your Action.

In addition to providing JavaBean-style properties that accept HTTP request data, the ActionForm can contain additional data that don’t directly correspond to an HTML input field. This data could be used directly on the HTML form, such as the options displayed in an HTML select (drop-down) control. Typically, these “extra” ActionForm data provide contextual information for the form being displayed. Many developers prefer to use the ActionForm as the JavaBean that provides all data for a page: the properties on the form and properties for ancillary data. Others prefer to keep the ActionForm focused on providing only the properties needed to support the form. In this latter approach, the JSP page accesses the ancillary data through other scoped variables external to the ActionForm.

Both of these approaches are valid and acceptable. Either way, understanding the different types of ActionForms, how they work, and how to use them will make your Struts development go a lot smoother.

5.1. Creating Dynamic Action Forms

Problem

You want to create ActionForms for use on your pages without having to handcode a unique Java class for each form.

Solution

Create a form-bean in the struts-config.xml file using the built-in DynaActionForm type or any of its subclasses. Then define the properties of the form using nested form-property elements:

<form-beans>
  <form-bean name="MyForm" type="org.apache.struts.action.DynaActionForm">
    <form-property name="foo" type="java.lang.String"/>
    <form-property name="bar" type="java.lang.String"/>
    <form-property name="baz" type="java.lang.Boolean"/>
    <form-property name="blindMice" type="java.lang.String[]"
                   size="3"/>
  </form-bean>
<form-beans>

You can retrieve the data from the form using methods of the DynaActionForm or generically using the Jakarta Commons PropertyUtils class. The Action shown in Example 5-1 uses this technique to get the form property values by name.

Example 5-1. Using PropertyUtils with ActionForms
package com.oreilly.strutsckbk.ch05;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class ProcessAction extends Action {

  public ActionForward execute(ActionMapping mapping,
      ActionForm form,
      HttpServletRequest request,
      HttpServletResponse response) throws Exception {
        
    String foo = 
        (String) PropertyUtils.getSimpleProperty(form, "foo");
    String bar = 
        (String) PropertyUtils.getSimpleProperty(form, "bar");
    Boolean baz = 
        (Boolean) PropertyUtils.getSimpleProperty(form, "baz");
    String[] mice = 
        (String[]) PropertyUtils.getProperty(form, "blindMice");
    return mapping.findForward("success");
  }
}

Discussion

Struts 1.1 introduced the ability to create ActionForms without writing any Java code; you create the ActionForm declaratively in your struts-config.xml file. This feature simplifies the application and reduces the amount of Java source code that you have to write and maintain. Prior to Struts 1.1, you could only create a custom ActionForm by extending the Struts base class, org.apache.struts.action.ActionForm. Your custom class consisted of getter and setter methods for the form properties and validation for the property values. Then along came the Struts Validator. Developers no longer needed to code the validate( ) method; they could provide form validation using an XML-based configuration file. Once the validation was removed from the custom ActionForm, the class became a group of trivial getter and setter methods.

Struts 1.1 relieves the burden of writing these trivial classes through the use of dynamic action forms. The dynamic action form is implemented by the org.apache.struts.action.DynaActionForm class. This class implements the DynaBean interface provided by the Jakarta Commons BeanUtils package. The interface provides methods that allow a class to contain a dynamic set of properties, much like a java.util.Map. The main benefit of a DynaBean over a simple map is that its values can be accessed just like standard JavaBean properties using the BeanUtils introspection utilities.

Tip

If you aren’t using the Validator or you have to code a custom validate( ) method, you lose the benefit of the DynaActionForm and you might as well code a conventional ActionForm subclass.

You create a dynamic action form in the struts-config.xml file by specifying the value for the type attribute of the form-bean element as the fully qualified class name of the DynaActionFormorg.apache.struts.action.DynaActionForm—or one of its subclasses. The form-property elements specify the name and type of the properties that make up the form. While DynaActionForms officially support a number of different types, you should stick with Strings and Booleans.

Tip

Booleans—that is, true/false values—are commonly used for checkboxes.

DynaActionForms do support other types, such as java.lang.Long and java.sql.Date. However, these types can cause problems with form validation and should be avoided in all action forms.

The DynaActionForm allows a property to be defined as a scalar, or single value, or as an array of values by using the [] notation following the type value. In the Solution, the form property blindMice is defined as an array of Strings with an array size of three elements:

<form-property name="blindMice" 
               type="java.lang.String[]"
               size="3"/>

Tip

The size attribute applies only to array properties.

When it comes to retrieving the values from the DynaActionForm, you can use the API of the DynaActionForm class or use the property accessor utility classes provided by the Jakarta Commons BeanUtils package.

See Also

Recipe 5.2 describes how to set initial values for form properties. Recipe 5.6 goes into greater detail about accessing the values from action forms.

Complete details on how the Jakarta Commons BeanUtils utility classes work can be found in the JavaDoc package description at http://jakarta.apache.org/commons/beanutils/api/index.html.

5.2. Setting DynaActionForm Initial Values

Problem

You need to initialize the properties in a dynamic action form declared in the struts-config.xml file.

Solution

Specify an initial value for a property of a DynaActionForm using the initial attribute of the form-property element.

<form-bean name="MyForm" type="org.apache.struts.action.DynaActionForm">
  <form-property name="firstName" type="java.lang.String"
              initial="George"/>
  <form-property name="lastName" type="java.lang.String"
              initial="Burdell"/>
  <form-property name="javaCoder" type="java.lang.Boolean"
              initial="true"/>
  <form-property name="friend" type="java.lang.String[]"
                 size="3" 
              initial="Larry,Moe,Curly"/>
</form-bean>

Discussion

DynaActionForms simplify your life as a Struts developer. There is no Java code to write, and when requirements change and you must add fields to a form, you need only change some XML and the Actions that use the form. When working with these forms, a common question that comes up is how to populate the form with initial values. The Solution shows how this can be done using the initial attribute of the form-property element. This attribute specifies a default value for a property. The property is set to this value when the form is created or when the initialize( ) method is called on the form. The time of creation depends on the scope of the form. If the scope is “request,” a new form is created for each new request that uses the form. For session scope, the form is created when it can’t be found in the current HTTP session.

The initial value, like any other XML attribute value, is specified as a double-quoted text string. Since most form properties should be Strings, specifying an initial value is straightforward. Care must be taken if the initial value contains embedded quotes or other special characters, though. These characters should be escaped by preceding the character with a leading backslash:

<form-property name="quotation" 
            initial="John Dunne wrote "No man is an island""/>

If the property type is java.lang.Boolean, then the initial value should be set to true or false, ignoring case. Boolean properties are false by default.

If the property is an array type, the value for the initial attribute can represent multiple values. String values are listed as comma or space-separated values. If the Strings contain spaces, then wrap the value in single quotes. If the value contains an embedded single or double quotation mark or other character that may cause parsing problems, then escape the character by preceding it with a backslash. If an array value should be blank, you use the empty String (“”). Here are some examples of acceptable syntax for the initial value of a String array property, and the equivalent String array literal:

initial="Larry,Moe,Curly" 
 {"Larry","Moe","Curly"}
initial="Fine,Horward" 
 {"Fine","Howard"}
initial="Larry Moe Curly" 
 {"Larry","Moe","Curly"}
initial="'Larry','','Curly'" 
 {"Larry","","Curly"}
initial="'O'Reilly','Struts','Cook Book'" 
 {"O'Reilly","Struts","Cook book"}

The size attribute of the form-property element applies to array-based properties. This attribute sets the number of elements in the array. If the initial value specifies more elements than the size attribute, the size of the array is set to the larger value—i.e., the number of initial values.

In a beta version of Struts 1.1, calling reset( ) on a DynaActionForm set the property values to the initial values. With Struts 1.1 final, this behavior was aligned with that of conventional action forms—i.e., the form’s initial values are only set when the form is created. The initialize( ) method, however, will reset the property values to their initial values. If you need the reset( ) method to work like Struts 1.1 beta, you can create a subclass of DynaActionForm that overrides the reset( ) method. Example 5-2 shows a DynaActionForm subclass that reinitializes the values on form reset.

Example 5-2. DynaActionForm extension for resetting properties
package com.oreilly.strutsckbk.ch05;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;

public class ResettingDynaActionForm extends DynaActionForm {

    public void reset(ActionMapping mapping, HttpServletRequest request) {
        initialize(mapping);
    }
}

You can specify this class as the value of the type attribute of the form-bean element.

<form-bean name="MyFooForm"
           type="com.oreilly.strutsckbk.ch05.ResettingDynaActionForm">
  <form-property name="foo" type="java.lang.String"
              initial="bar"/>
</form-bean>

The primary disadvantage of the initial attribute is that it forces you to hardcode the values. In many cases, this meets requirements; at other times, you want the values to be determined dynamically at runtime. In this situation, the DynaActionForm can be populated from Java code, typically in the Action preceding a forward to the JSP page that displays the form. The DynaActionForm values can be set using the DynaActionForm.set( ) methods or the introspection utilities provided by the PropertyUtils class in Jakarta Commons BeanUtils package. Example 5-3 shows both of these techniques.

Example 5-3. Populating a DynaActionForm
package com.oreilly.strutsckbk.ch05;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;

public class ViewFormAction extends Action {

    public ActionForward execute(ActionMapping mapping,
      ActionForm form,
      HttpServletRequest request,
      HttpServletResponse response) throws Exception {
        
        DynaActionForm myForm = (DynaActionForm) form;

        PropertyUtils.setSimpleProperty(myForm, "firstName", "Bill");

        myForm.set("lastName", "Siggelkow");
                
    return mapping.findForward("success");
    }
}

See Also

Recipe 5.5 shows how to create and use a dynamic form that allows for the properties to be determined completely at runtime. Recipe 5.6 shows additional solutions for populating forms in an Action.

The Struts User’s Guide discusses dynamic action forms in the section http://struts.apache.org/userGuide/building_controller.html#dyna_action_form_classes.

5.3. Using a List-Backed Form Property

Problem

You want to create and use a form property backed by a java.util.List. You want to be able to access the entire list of data through one method as well as specific values in the list using indexed properties.

Solution

Create the form property as an indexed property, backed by a java.util.List, as shown in Example 5-4.

Example 5-4. List-backed form property
package com.oreilly.strutsckbk.ch05;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;

public class ListForm extends ActionForm {
    
    private int size = 3;
    private List friends = new ArrayList(size);
    
    public List getFriends( ) { 
        return friends;
    }

    public String getFriend(int index) {
        return (String) friends.get(index);
    }

    public void setFriend(int index, String name) {
        friends.set(index, name);
    }
  
    public void reset(ActionMapping mapping, HttpServletRequest request) {
        // prepopulate the list with empty strings
        friends = new ArrayList( );
        for (int i=0; i<size;i++) friends.add("");
    }
}

Example 5-5 (list_form_test.jsp) shows how the property values can be accessed on a JSP page. The form is created by accessing individual elements as indexed properties. The results are output by iterating over the list.

Example 5-5. Accessing list-backed form properties
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
    <title>Struts Cookbook - Chapter 5 : List-backed Form Property</title>
</head>
<body>
    <h2>List Form Test</h2>
    <html:form action="/ProcessListForm">
        Who are your 3 friends:<br />
        Friend 1: <html:text property="friend[0]"/><br />
        Friend 2: <html:text property="friend[1]"/><br />
        Friend 3: <html:text property="friend[2]"/><br />
        <html:submit/>
    </html:form>
    <hr />
    <c:forEach var="item" items="${ListForm.friends}">
        <c:out value="${item}"/><br />
    </c:forEach>
</body>
</html>

Discussion

Sometimes you need a form property to represent a list of values instead of a single value. The value type may be simple like a String or number, or it may be a complex type composed of other types. An ActionForm can have a property of a simple or complex type that is backed by an implementation of a java.util.List.

So, how do you make a list-backed property accessible from an HTML form? First, you provide getter and setter methods that follow the JavaBean convention for indexed properties:

public Foo getFoo(int index);
public void setFoo(int index, Foo foo);

As long as the property follows this convention, it can be accessed regardless of the underlying implementation. If the property values are stored in an implementation of java.util.List, such as a java.util.ArrayList, then the getter method will call list.get( index ) and the setter method will call list.set( index, obj ).

Though a List, unlike an array, is resizable, you must ensure the getter and setter methods don’t attempt to access a value at an index greater than the List size. Doing so will result in an IndexOutOfBoundsException. You can preload the List in the ActionForm’s reset( ) method, or you can dynamically resize the list as needed in the getter and setter methods:

public Foo getFoo(int index) {
    if (index >= list.size(  )) {
        list.add(index, new Foo(  ));
    }
    return (Foo) list.get(index)
}

public void setFoo(int index, Foo foo) {
    if (index < list.size( )) {
        list.set(index, foo);
    }
    else {
        list.add(index, foo);
    }
}

See Also

Recipe 5.4 discusses how to use Maps for properties in a similar fashion. Recipe 5-5 discusses forms that are even more dynamic; the properties themselves are not determined until runtime.

Check out Recipe 3.4 for details on using multivalue properties on a form.

5.4. Using a Map-Backed Form Property

Problem

You have a form with a property defined as a Map, and you want to access a value in that Map using a key.

Solution

Define getter and setter methods that use the following pattern:

public Object getFoo(String key) {...}
public void setFoo(String key, Object value) {...}

In Example 5-6, the skill property is a Map-backed property.

Example 5-6. Map-backed form
package com.oreilly.strutsckbk.ch05;

import java.util.HashMap;
import java.util.Map;

import org.apache.struts.action.ActionForm;

public class MapForm extends ActionForm {

    private static String[] skillLevels = 
        new String[] {"Beginner","Intermediate","Advanced"};
    private Map skills = new HashMap( );

    public Object getSkill(String key) { 
        return skills.get(key);
    }

    public void setSkill(String key, Object value) { 
        skills.put(key, value); 
    }
  
    public Map getSkills( ) {
        return skills;
    }
    
    public String[] getSkillLevels( ) {
        return skillLevels;
    }
}

The Map-backed property value is accessed from the Struts tags on a JSP page (map_form_test.jsp) using the property="value(key)" syntax as shown Example 5-7.

Example 5-7. Accessing Map-backed form properties
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix=
  "html" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
  <title>Struts Cookbook - Chapter 5 : Map-backed Form</title>
</head>
<body>
    <h2>Map Form Test</h2>
    <html:form action="/ProcessMapForm">
        Java Skill: 
        <html:select property="skill(java)">
            <html:options property="skillLevels"/>
        </html:select><br />
        JSP Skill: 
        <html:select property="skill(jsp)">
            <html:options property="skillLevels"/>
        </html:select><br />
        Struts Skill: 
        <html:select property="skill(struts)">
            <html:options property="skillLevels"/>
        </html:select><br />
        <html:submit/>
    </html:form>
    <hr />
    <c:if test="${not empty MapForm.skills}">
        Java Skill: <c:out value="${MapForm.skills.java}"/><br />
        JSP Skill: <c:out value="${MapForm.skills.jsp}"/><br />
        Struts Skill: <c:out value="${MapForm.skills.struts}"/><br />
    </c:if>
</body>
</html>

Discussion

The value of a Map-backed form property is accessed using its key. The key and value are used to add or replace a value in the map. Map-backed form properties provide a good way of creating a property that can hold an indeterminate set of values.

In the Solution above, the MapForm action form defines the property skill. The getters and setters use the following pattern:

public Value getValue(String key);
public void setValue(String key, Object value);

Using this pattern allows the value to be accessed on a JSP page from most Struts tags using the property attribute syntax of value(key). value corresponds to the property name, and key corresponds to the actual key into the map. For Map-backed properties, the keys should always be Strings. A Map-backed property can be accessed in this way by any Struts tag where the property attribute is used to access JavaBean properties:

<html:text property="skill(java)"/>
<bean:write name="MapForm" property="skill(java)"/>

JSTL doesn’t support accessing properties that use the getValue(String key) pattern. Map-backed properties can, however, be accessed using a JSTL expression provided the actual Map is exposed via a public getter method. The Solution shows how the JSTL c:out tag is used to render the data from the map:

<c:out value="${MapForm.skills.java}"/>

JSTL accesses the Map directly and extracts values by the specified key. If you don’t need to use JSTL to access Map-backed properties, then exposing the Map in a public method is not required.

See Also

Recipe 5.3 shows how to use lists to back a form property.

5.5. Lazy Dynamic Action Forms

Problem

You want to create a form where the properties are variable and completely determined at runtime.

Solution

Use Niall Pemberton’s Lazy DynaBean forms available for download from http://www.niallp.pwp.blueyonder.co.uk/.

Declare the form-bean to use in the struts-config.xml:

<form-bean name="LazyForm" type="lib.framework.struts.LazyValidatorForm"/>

Then, use the form on a JSP page as you would a normal ActionForm. Example 5-8 shows a JSP page (lazy_form_test.jsp) that will utilize the LazyForm declared previously.

Example 5-8. Using the LazyValidatorForm on an HTML form
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix=
  "html" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix=
  "bean" %>
<html>
<head>
  <title>Struts Cookbook - Chapter 5 : Lazy Form</title>
</head>
<body>
  <h2>Lazy Form Test</h2>
  <html:form action="/ProcessLazyForm">
    What is your name:<br />
      First Name: <html:text property="firstName"/><br />
      Last Name: <html:text property="lastName"/><br />
    Do you want to subscribe to our newsletter?<br />
      <html:checkbox property="subscribe"/><br />
    Who are your 3 friends:<br />
      Friend 1: <html:text property="friend[0].name"/><br />
      Friend 2: <html:text property="friend[1].name"/><br />
      Friend 3: <html:text property="friend[2].name"/><br />
    <html:submit/>
  </html:form>
  <hr />
  Your name is: <bean:write name="LazyForm" property="firstName"/>&nbsp;
                <bean:write name="LazyForm" property="lastName"/><br />
  Your friends are:<br />
  <bean:write name="LazyForm" property="friend[0].name"/><br />
  <bean:write name="LazyForm" property="friend[1].name"/><br />
  <bean:write name="LazyForm" property="friend[2].name"/><br />
</body>
</html>

In the Action that receives the form, cast the form into a DynaBean (org.apache.commons.beanutils.DynaBean). You then access the properties using the get("name") method. Since the LazyValidatorForm extends ValidatorForm, you can define standard Struts Validator validation rules in your validation.xml file.

Discussion

Occasionally, you may need to display a group of data input fields that are not known until runtime. Consider an application that allows a potential buyer to configure a product for purchase—e.g., the online purchase of a computer. The available configuration options vary based on the product and current promotions; the configuration data actually comes from a database, so the page needs to be built on the fly.

The LazyValidatorForm and the LazyValidatorActionForm allow form properties to be created automatically as they are needed. In particular, indexed properties in these classes are backed by a LazyList. The list will resize as necessary instead of throwing an IndexOutOfBoundsException if you attempt to access an index beyond the current size. Like the DynaActionForm, these forms implement the DynaBean interface and store their properties in a Map. Unlike the DynaActionForm, however, new properties can be added to them at any time. In the Solution, you declare the form in your struts-config.xml file as you would define a custom ActionForm; no form-property elements are specified.

On the JSP page that uses the form, the Struts tags are used as if you were working with a normal ActionForm. In the Solution, two text fields (firstName and lastName), a Boolean field (subscribe), and three values of an indexed property (friend[ i ].name) are created.

You may not find common uses for these classes; however, they can be helpful in certain scenarios. One such idea is to use these classes when you are prototyping a new application or new feature. Once you nail down the requirements, you can replace the lazy action from with a normal ActionForm or DynaActionForm.

A word of warning: Using the l azy action forms removes the firewall that ActionForms usually provide. In other words, the lazy ActionForm instance will get populated with all the request parameters, whether or not you were expecting them. Make sure that you don’t blindly accept the values and pass them onto your business model. Instead, only pull out the specific values that you are expecting.

See Also

Niall Pemberton’s web site, http://www.niallp.pwp.blueyonder.co.uk/, has the latest information about these forms as well as some other useful Struts utilities.

Similar capabilities can be achieved using Map-backed form properties. Recipe 5.4 provides more details on this approach.

5.6. Populating Value Objects from ActionForms

Problem

You don’t want to have to write numerous getters and setters to pass data from your action forms to your business objects.

Solution

Use the introspection utilities provided by the Jakarta Commons BeanUtils package in your Action.execute( ) method:

               import org.apache.commons.beanutils.*;

// other imports omitted

  public ActionForward execute(ActionMapping mapping,
      ActionForm form,
      HttpServletRequest request,
      HttpServletResponse response) throws Exception {

    BusinessBean businessBean = new BusinessBean( );
    BeanUtils.copyProperties(businessBean, form);

    // ... rest of the Action

Discussion

A significant portion of the development effort for a web application is spent moving data to and from the different system tiers. Along the way, the data may be transformed in one way or another, yet many of these transformations are required because the tier to which the data is moving requires the information to be represented in a different way.

Data sent in the HTTP request is represented as simple text. For some data types, the value can be represented as a String object throughout the application. However, many data types should be represented in a different format in the business layer than on the view. Date fields provide the classic example. A date field is retrieved from a form’s input field as a String. Then it must be converted to a java.util.Date in the model. Furthermore, when the value is persisted, it’s usually transformed again, this time to a java.sql.Timestamp. Numeric fields require similar transformations.

The Jakarta Commons BeanUtils package supplied with the Struts distribution provides some great utilities automating the movement and conversion of data between objects. These utilities use JavaBean property names to match the source property to the destination property for data transfer. To leverage these utilities, ensure you give your properties consistent, meaningful names. For example, to represent an employee ID number, you may decide to use the property name employeeId. In all classes that contain an employee ID, you should use that name. Using empId in one class and employeeIdentifier in another will only lead to confusion among your developers and will render the BeanUtils facilities useless.

The entire conversion and copying of properties from ActionForm to business object can be performed with one static method call:

               BeanUtils.copyProperties(
               businessBean
               , 
               form
               );

This copyProperties( ) method attempts to copy each JavaBean property in form to the property with the same name in businessBean. If a property in form doesn’t have a matching property in businessBean, that property is silently ignored. If the data types of the matched properties are different, BeanUtils will attempt to convert the value to the type expected. BeanUtils provides converters from Strings to the following types:

  • java.lang.BigDecimal

  • java.lang.BigInteger

  • boolean and java.lang.Boolean

  • byte and java.lang.Byte

  • char and java.lang.Character

  • java.lang.Class

  • double and java.lang.Double

  • float and java.lang.Float

  • int and java.lang.Integer

  • long and java.lang.Long

  • short and java.lang.Short

  • java.lang.String

  • java.sql.Date

  • java.sql.Time

  • java.sql.Timestamp

While the conversions to character-based and numeric types should cover most of your needs, date type fields (as shown in Recipe 3-13) can be problematic. A good solution suggested by Ted Husted is to implement transformation getter and setter methods in the business object that convert from the native type (e.g. java.util.Date) to a String and back again.

Tip

Because BeanUtils knows how to handle DynaBeans and the DynaActionForm implements DynaBean, the Solution will work unchanged for DynaActionForms and normal ActionForms.

As an example, suppose you want to collect information about an employee for a human resources application. Data to be gathered includes the employee ID, name, salary, marital status, and hire date. Example 5-9 shows the Employee business object. Most of the methods of this class are getters and setters; for the hireDate property, however, helper methods are provided that get and set the value from a String.

Example 5-9. Employee business object
package com.oreilly.strutsckbk.ch05;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Employee {
    
    private String employeeId;
    private String firstName;
    private String lastName;    
    private Date hireDate;
    private boolean married;
    private BigDecimal salary;

    public BigDecimal getSalary( ) {
        return salary;
    }
    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
    public String getEmployeeId( ) {
        return employeeId;
    }
    public void setEmployeeId(String employeeId) {
        this.employeeId = employeeId;
    }
    public String getFirstName( ) {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName( ) {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public boolean isMarried( ) {
        return married;
    }
    public void setMarried(boolean married) {
        this.married = married;
    }
    public Date getHireDate( ) {
        return hireDate;
    }
    public void setHireDate(Date HireDate) {
        this.hireDate = HireDate;
    }
    public String getHireDateDisplay( ) {
        if (hireDate == null)
            return "";
        else 
            return dateFormatter.format(hireDate);
    }
    public void setHireDateDisplay(String hireDateDisplay) {
        if (hireDateDisplay == null) 
            hireDate = null;
        else {
            try {
                hireDate = dateFormatter.parse(hireDateDisplay);
            } catch (ParseException e) {
                e.printStackTrace( );
            }
        }
    }

    private DateFormat dateFormatter = new SimpleDateFormat("mm/DD/yy");
}

Example 5-10 shows the corresponding ActionForm that will retrieve the data from the HTML form. The hire date is represented in the ActionForm as a String property, hireDateDisplay. The salary property is a java.lang.String, not a java.math.BigDecimal, as in the Employee object of Example 5-9.

Example 5-10. Employee ActionForm
package com.oreilly.strutsckbk.ch05;

import java.math.BigDecimal;

import org.apache.struts.action.ActionForm;

public class EmployeeForm extends ActionForm {
    
    private String firstName;
    private String lastName;    
    private String hireDateDisplay;
    private String salary;
    private boolean married;

    public String getEmployeeId( ) {
        return employeeId;
    }
    public void setEmployeeId(String employeeId) {
        this.employeeId = employeeId;
    }
    public String getFirstName( ) {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName( ) {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public boolean isMarried( ) {
        return married;
    }
    public void setMarried(boolean married) {
        this.married = married;
    }
    public String getHireDateDisplay( ) {
        return hireDateDisplay;
    }
    public void setHireDateDisplay(String hireDate) {
        this.hireDateDisplay = hireDate;
    }
    public String getSalary( ) {
        return salary;
    }
    public void setSalary(String salary) {
        this.salary = salary;
    }
}

If you wanted to use a DynaActionForm, you would configure it identically as the EmployeeForm class. The form-bean declarations from the struts-config.xml file show the declarations for the EmployeeForm and a functionally identical DynaActionForm:

<form-bean name="EmployeeForm" 
           type="com.oreilly.strutsckbk.ch05.EmployeeForm"/>
<form-bean name="EmployeeDynaForm" 
           type="org.apache.struts.action.DynaActionForm">
    <form-property name="employeeId" type="java.lang.String"/>
    <form-property name="firstName" type="java.lang.String"/>
    <form-property name="lastName" type="java.lang.String"/>
    <form-property name="salary" type="java.lang.String"/>
    <form-property name="married" type="java.lang.Boolean"/>
    <form-property name="hireDateDisplay" type="java.lang.String"/>
</form-bean>

The following is the action mapping that processes the form. In this case, the name attribute refers to the handcoded EmployeeForm. You could, however, change this to use the EmployeeDynaForm without requiring any modifications to the SaveEmployeeAction or the view_emp.jsp JSP page:

<action    path="/SaveEmployee"
           name="EmployeeForm"
           scope="request"
           type="com.oreilly.strutsckbk.ch05.SaveEmployeeAction">
    <forward name="success" path="/view_emp.jsp"/>
</action>

The data is converted and copied from the form to the business object in the SaveEmployeeAction shown in Example 5-11.

Example 5-11. Action to save employee data
package com.oreilly.strutsckbk.ch05;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class SaveEmployeeAction extends Action {

    public ActionForward execute(ActionMapping mapping, 
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) 
                         throws Exception {

        Employee emp = new Employee( );
        
        // Copy to business object from ActionForm
        BeanUtils.copyProperties( emp, form );

        request.setAttribute("employee", emp);        
        return mapping.findForward("success");
        
    }
}

Finally, two JSP pages complete the example. The JSP of Example 5-12 (edit_emp.jsp) renders the HTML form to retrieve the data.

Example 5-12. Form for editing employee data
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix=
  "bean" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix=
  "html" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
  <title>Struts Cookbook - Chapter 5 : Add Employee</title>
</head>
<body>
  <h2>Edit Employee</h2>
  <html:form action="/SaveEmployee">
    Employee ID: <html:text property="employeeId"/><br />
    First Name: <html:text property="firstName"/><br />
    Last Name: <html:text property="lastName"/><br />
    Married? <html:checkbox property="married"/><br />
    Hired on Date: <html:text property="hireDateDisplay"/><br />
    Salary: <html:text property="salary"/><br />
    <html:submit/>
  </html:form>
</body>
</html>

The JSP in Example 5-13 (view_emp.jsp) displays the results. This page is rendering data from the business object, and not an ActionForm. This is acceptable since the data on this page is for display purposes only. This approach allows for the formatting of data, (salary and hireDate) to be different than the format in which the values were entered.

Example 5-13. View of submitted employee data
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix=
  "bean" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
  <title>Struts Cookbook - Chapter 5 : View Employee</title>
</head>
<body>
  <h2>View Employee</h2>
    Employee ID: <bean:write name="employee" property="employeeId"/><br />
    First Name: <bean:write name="employee" property="firstName"/><br />
    Last Name: <bean:write name="employee" property="lastName"/><br />
    Married? <bean:write name="employee" property="married"/><br />
    Hired on Date: <bean:write name="employee" property="hireDate" 
                             format="MMMMM dd, yyyy"/><br />
    Salary: <bean:write name="employee" property="salary" format="$##0.00"/
    ><br />
</body>
</html>

When you work with this example, swap out the handcoded form for the DyanActionForm to see how cleanly BeanUtils works. When you consider how many files need to be changed for one additional form input, the use of BeanUtils in conjunction with DynaActionForms becomes obvious.

See Also

Recipe 3.13 discusses additional considerations when working with Date fields specifically. Complete documentation on the BeanUtils package can be found at http://jakarta.apache.org/commons/beanutils/api/org/apache/commons/beanutils/BeanUtils.html.

DynaActionForms are discussed in Recipe 5.1.

5.7. Automatically Creating ActionForms

Problem

You want to create a DynaActionForm (or subclass) automatically from your business objects without having to configure the form-bean and form-property elements ahead of time.

Solution

Use Hubert Rabago’s Formdef plug-in. You can download formdef.jar from https://formdef.dev.java.net/ and copy it to your application’s WEB-INF/lib directory. Formdef allows you to define form definitions in an XML file, as shown in Example 5-14.This file should be placed in the WEB-INF folder of your web application and given a meaningful name such as form-defs.xml.

Example 5-14. Formdef form definitions
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE form-definition PUBLIC "-//FormDef//FormDef Form Definition//EN" 
                                 "form-defs_0_5.dtd">
<form-definition>
    <formset>
        <form name="EmployeeForm"
          beanType="com.oreilly.strutsckbk.ch05.EmployeeFd"/>
    </formset>
</form-definition>

The form element maps a form named with the name element to a business object whose class is specified by the beanType attribute.

Next, add a plug-in element for Formdef to your struts-config.xml file:

<plug-in className="formdef.plugin.FormDefPlugIn">
    <set-property property="defnames"
                     value="/WEB-INF/form-defs.xml"/>
</plug-in>

The form named in the form element can be used in your application as if you had explicitly declared the form using a form-bean element in the struts-config.xml file. Here’s an action that uses the EmployeeFdForm:

<action    path="/SaveEmployeeFd"
           name="EmployeeFdForm"
          scope="request"
           type="com.oreilly.strutsckbk.ch05.SaveEmployeeFdAction">
    <forward name="success" path="/view_emp_fd.jsp"/>
</action>

Discussion

Formdef was created by Hubert Rabago, who remains the project lead. Formdef is an open-source project licensed under the Apache Software License. At the time of this writing, the latest available version was 0.5.

Formdef maps the business objects of your application’s domain model map to your ActionForms. With Struts 1.1, dynamic action forms (see Recipe 5.1) made life easier by allowing you to create an ActionForm declaratively. The Formdef Struts extension takes this notion further by allowing you to create a DynaActionForm behind the scenes that maps to the properties of a business object. An XML file holds the relationship between the form name and the business object.

Foremost, Formdef provides a runtime-created DynaActionForm for a specified business object. Using Formdef, you save yourself the hassle of specifying form-bean and form-property elements in the struts-config.xml file. More importantly, when a new property is added to the business object, you don’t have to add a corresponding form-property in the struts-config.xml. The new property will get picked up at runtime and will be available to your form. In this respect, Formdef is similar to capabilities discussed in Recipe 5.5.

Formdef allows you to group together, in the formdefs.xml file, a form definition and its corresponding Struts Validator rules. This feature can make a project more manageable since related data is kept together.

Mapping business objects to web forms is a big affair. Business objects tend to represent data in native formats; forms represent data as strings. A date is represented by the java.util.Date, and numbers are represented by primitives or classes such as java.math.BigDecimal. Business objects can be composed of other business objects. A Person object contains an Address object, and that object, in turn, contains separate properties for the street, city, state, and postal code.

Formdef addresses these issues by allowing you to define how data is converted from a business object to an ActionForm and back again. A Formdef form definition can define and include converters, specialized classes that convert data from one type or format to another.

Formdef includes some utility methods that will transfer data from an ActionForm to your business object applying the specified conversions where needed. These utilities replace the use of the BeanUtils.copyProperties() method. If your business object contains complex types, properties with types other than primitives and Strings. If you want to perform intelligent data transfer between the ActionForm and the business object, you will need to use a converter. Here’s one way you could define a converter for a field of type java.util.Date:

<form     name="EmployeeFdForm"
      beanType="com.oreilly.strutsckbk.ch05.Employee"/>
    <field property="hireDate">
        <converter param="mm/DD/yy"/>
    </field>
</form>

You can localize property conversion by using the key attribute of the converter element. At runtime, the key attribute is used to retrieve the format string from your Struts MessageResources properties file.

<form     name="EmployeeFdForm"
      beanType="com.oreilly.strutsckbk.ch05.Employee"/>
    <field property="hireDate">
        <converter key="format.date.us"/>
    </field>
</form>

In the MessageResources properties file, you would have an entry like:

format.date.us=mm/DD/yyyy

In this case, the converter uses the default date converter provided with Formdef. The converter element is used to specify the expected format of the date field. You can register your own converter. A converter can be defined as global; it will apply to any property that has the type or name that the converter is defined for. Say you wanted to be able to convert a form input into a phone number object, where the phone number object maintained separate properties for area code, seven-digit number, and extension.

<global-converter for="property-type"
        target="com.foo.PhoneNumber"
        type="com.foo.util.PhoneNumber "/>

The for="property-type" indicates that the converter is to be applied to all properties of the type specified by the target attribute. Converters can be defined for a target property name.

<global-converter for="converter-name"
        target="salary"
        param="###,###,##0.00"/>

This converter will be applied to any property named salary, regardless of the property’s type.

Keep your eye one the Formdef project. If your application frequently maps business objects to forms on a one-to-one basis, then Formdef may provide a good solution. It allows rapid development by automatically creating ActionForms that remain in synch with your business objects. Formdef is in the early development, so here’s your opportunity to contribute!

See Also

At the time of this writing, the Formdef project was being migrated from Hubert Rabago’s site (http://www.rabago.net/struts/formdef/) to Java.net (https://formdef.dev.java.net/).

Like Recipe 5.5, this recipe shows ways to reduce the manual effort needed to create ActionForms.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.158.32