Once you’ve been using Struts for a while, you’ll begin to notice that you spend a lot of time creating ActionForm classes. While these classes are critical to the MVC architecture of Struts (as they implement the view portion), they are usually simply a collection of bean properties and a validate method (also sometimes a reset method.)
With the Struts 1.1 release, developers have a new set of options to create their view objects, based around DynaBeans. DynaBeans are dynamically configured Java Beans, which means that they take their properties from some variety of external configuration (usually XML) rather than by explicit methods defined in the class.
To see how DynaBeans (and the Struts implementation, Dynaforms) work, let’s begin with a simple Struts Form that records name, address, and telephone. Here’s how it’s currently implemented using ActionForm.
article1.CustomerForm
package article1; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionError; import javax.servlet.http.HttpServletRequest; public class CustomerForm extends ActionForm { protected boolean nullOrBlank (String str) { return ((str == null) || (str.length() == 0)); } public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors = new ActionErrors(); if (nullOrBlank(lastName)) { errors.add("lastName", new ActionError("article1.lastName.missing")); } if (nullOrBlank(firstName)) { errors.add("firstName", new ActionError("article1.firstName.missing")); } if (nullOrBlank(street)) { errors.add("street", new ActionError("article1.street.missing")); } if (nullOrBlank(city)) { errors.add("city", new ActionError("article1.city.missing")); } if (nullOrBlank(state)) { errors.add("state", new ActionError("article1.state.missing")); } if (nullOrBlank(postalCode)) { errors.add("postalCode", new ActionError("article1.postalCode.missing")); } if (nullOrBlank(phone)) { errors.add("phone", new ActionError("article1.phone.missing")); } return errors; } private String lastName; private String firstName; private String street; private String city; private String state; private String postalCode; private String phone; public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getState() { return state; } public void setState(String state) { this.state = state; } public String getPostalCode() { return postalCode; } public void setPostalCode(String postalCode) { this.postalCode = postalCode; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }
As you can see, it’s a standard JavaBean with the addition of the validate method, which makes sure that all the fields have been set up correctly.
The JSP page that interfaces with this bean is equally simple:
customer.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 standard Customer form</title> </head> <h1>Example of a standard Customer form</h1> <html:form action="/addCustomer"> Last Name: <html:text property="lastName"/> <html:errors property="lastName" /><br> First Name: <html:text property="firstName"/> <html:errors property="firstName" /><br> Street Addr: <html:text property="street"/> <html:errors property="street" /><br> City: <html:text property="city"/> <html:errors property="city" /><br> State: <html:text property="state" maxlength="2" size="2" /> <html:errors property="state" /><br> Postal Code: <html:text property="postalCode" maxlength="5" size="5" /> <html:errors property="postalCode" /><br> Telephone: <html:text property="phone" maxlength="11" size="11" /> <html:errors property="phone" /><br> <html:submit/> </html:form>
The Action for this page just sends the values to standard output (which will place them in the Catalina log file):
article1.AddCustomerAction
package article1; import org.apache.struts.action.Action; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionForm; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; import java.io.IOException; public class AddCustomerAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ CustomerForm custForm = (CustomerForm) form; System.out.println("lastName = " + custForm.getLastName()); System.out.println("firstName = " + custForm.getFirstName()); System.out.println("street = " + custForm.getStreet()); System.out.println("city = " + custForm.getCity()); System.out.println("state = " + custForm.getState()); System.out.println("postalCode = " + custForm.getPostalCode()); System.out.println("phone = " + custForm.getPhone()); return mapping.findForward("success"); } }
This is all tied together, as always with Struts, in the struts-config.xml file:
<struts-config> <form-beans> <form-bean name="customerForm" type="jdj.article1.Customer" /> </form-beans> <action-mappings> <action path="/addCustomer" type="article1.AddCustomerAction" name="customerForm" scope="request" input="/addCustomer.jsp"> <forward name="success" path="/addCustomerSucceeded.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" /> struts-config.xml</plug-in></struts-config>
<?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="customerForm" type="article1.CustomerForm" /> </form-beans> <action-mappings> <action path="/addCustomer" type="article1.AddCustomerAction" name="customerForm" scope="request" input="/customer.jsp"> <forward name="success" path="/addCustomerSucceeded.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>
The customerForm is linked to the CustomerForm class that was just defined, and the /addCustomer action is defined to use that form, and to use the article1.AddCustomerAction class to process the request.
When you bring up the form in your browser, you’ll get a blank form ready to fill out:
Example of a Standard Customer Form
If you submit the form with no values, you’ll get the following screen:
Example of a Standard Customer Form
And when you submit the form with filled-in values, the following will show up in your Web container log file (catalina.out under Tomcat):
lastName = Bush firstName = George street = 1600 Pennsylvania Avenue NW city = Washington state = DC postalCode = 20500 phone = 2024561414
So far, this is vanilla Struts that everyone should be familiar with. But, by using some of the new features of Struts 1.1, you can drastically reduce the amount of code you need to write. For example, we can begin be using the Dynaform extension to eliminate the need for an ActionForm class. To do this, we change the definition of customerForm in the struts-config.xml to use the org.apache.struts.action.DynaActionForm class (for this tutorial, we’ll actually create a new class and JSP page so that you can compare the two.)
By using DynaActionForm, you gain access to the form-property XML tag, which allows you to define the properties of a form directly in struts-config.xml . For our example, this looks like:
<form-bean name="dynaCustomerForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="lastName" type="java.lang.String"/> <form-property name="firstName" type="java.lang.String"/> <form-property type="java.lang.String" name="street"/> <form-property name="city" type="java.lang.String"/> <form-property name="state" type="java.lang.String"/> <form-property name="postalCode" type="java.lang.String"/> </form-bean>
There are no changes that need to be made to the JSP file; the use of DynaForms is transparent to the Struts HTML tag libraries. You do need to make a slight change to the Action, however, because you can no longer cast the form passed in to the execute() method directly to a class with accessors (get and set methods) for your data. Instead, you need to cast the form to DynaActionForm and use the generic get(fieldname) accessors. So the new version of the Action looks like this:
article1.AddDynaCustomerAction
package article1; import org.apache.struts.action.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; import java.io.IOException; public class AddDynaCustomerAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ DynaActionForm custForm = (DynaActionForm) form; System.out.println("lastName = " + custForm.get("lastName")); System.out.println("firstName = " + custForm.get("firstName")); System.out.println("street = " + custForm.get("street")); System.out.println("city = " + custForm.get("city")); System.out.println("state = " + custForm.get("state")); System.out.println("postalCode = " + custForm.get("postalCode")); System.out.println("phone = " + custForm.get("phone")); return mapping.findForward("success"); } }
As you can see, this eliminates an entire class (the ActionForm). However, we’ve lost something else in addition, the ability to validate the form data. There are two ways to regain this ability. One is to create a class which subclasses DynaActionForm and implements a validate() method. In our current example, it would look something like this:
article1.DynaCustomerForm
package article1; import org.apache.struts.action.*; import javax.servlet.http.HttpServletRequest; public class DynaCustomerForm extends DynaActionForm { protected boolean nullOrBlank (String str) { return ((str == null) || (str.length() == 0)); } public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors = new ActionErrors(); if (nullOrBlank((String)this.get("lastName"))) { errors.add("lastName", new ActionError("article1.lastName.missing")); } if (nullOrBlank((String)this.get("firstName"))) { errors.add("firstName", new ActionError("article1.firstName.missing")); } if (nullOrBlank((String)this.get("street"))) { errors.add("street", new ActionError("article1.street.missing")); } if (nullOrBlank((String)this.get("city"))) { errors.add("city", new ActionError("article1.city.missing")); } if (nullOrBlank((String)this.get("state"))) { errors.add("state", new ActionError("article1.state.missing")); } if (nullOrBlank((String)this.get("postalCode"))) { errors.add("postalCode", new ActionError("article1.postalCode.missing")); } if (nullOrBlank((String)this.get("phone"))) { errors.add("phone", new ActionError("article1.phone.missing")); } return errors; } }
Again, notice that we need to use the get() accessor rather than directly referencing instance variables. We also need to change the definition of the form in struts-config.xml to use this new class instead of the generic DynaActionForm class. When we do this, the validation functionality is returned, however; we’re back to defining explicit classes for each form again. The recommended way to validate under Struts 1.1 is to use the Struts Validator Framework, which will be described in a future article.
In the next article in this series, we’ll look at some more advanced uses of DynaForms. Specifically, we’ll see how you can use indexed properties and arrays of beans to implement complex master-detail forms.
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.