ActionForm
s are key
components of any Struts application. They transport data between the
Controller—that is, your Action
s—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. ActionForm
s 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:
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 ActionForm
s,
how they work, and how to use them will make your Struts development
go a lot smoother.
You want to create ActionForm
s for use on your
pages without having to handcode a unique Java class for each form.
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.
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"); } }
Struts 1.1 introduced the ability to create
ActionForm
s 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.
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
DynaActionForm
—org.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
DynaActionForm
s officially support a number of
different types, you should stick with String
s and
Booleans.
DynaActionForm
s 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
String
s with an array size of three elements:
<form-property name="blindMice" type="java.lang.String[]" size="3"/>
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.
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.
You need to initialize the properties in a dynamic action form declared in the struts-config.xml file.
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>
DynaActionForm
s 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 Action
s 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
String
s, 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
String
s 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.
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.
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"); } }
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.
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.
Create the form property as an
indexed
property, backed by a java.util.List
, as shown in
Example 5-4.
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.
<%@ 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>
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); } }
Recipe 5.4 discusses how to use
Map
s 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.
You have a form with a
property
defined as a Map
, and you want to access a value
in that Map
using a key.
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.
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.
<%@ 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>
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 String
s. 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.
Recipe 5.3 shows how to use lists to back a form property.
You want to create a form where the properties are variable and completely determined at runtime.
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.
<%@ 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"/> <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.
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
ActionForm
s 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.
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.
You don’t want to have to write numerous getters and setters to pass data from your action forms to your business objects.
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
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
String
s 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.
Because BeanUtils knows how to handle DynaBean
s
and the DynaActionForm
implements
DynaBean
, the Solution will work unchanged for
DynaActionForm
s and normal
ActionForm
s.
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
.
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.
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.
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.
<%@ 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.
<%@ 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
DynaActionForm
s
becomes obvious.
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.
DynaActionForm
s are discussed in Recipe 5.1.
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.
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.
<?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>
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
ActionForm
s. 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
ActionForm
s that remain in synch with your
business objects. Formdef is in the early
development, so here’s your opportunity to
contribute!
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
ActionForm
s.
3.133.158.32