Flylib.com

Books Software

 
 
 

Conclusions


Conclusions

The Tiles tag library enables you to totally abstract your page layout by defining logical names in an XML layout file. Because layouts can be extended from other layouts, you define a foundation layout and base all the other page layouts on that one, only having to define what is different about the other pages.

To use Tiles effectively with Struts, you must use the Tiles plug-in. This enables you to use a logical Tiles layout name in place of a JSP filename anywhere in the strutsconfig.xml file.


Chapter 17. DynaForm s and the Validator

IN THIS CHAPTER

  • DynaForm s: Forms Without Java

  • DynaBeans and Struts

  • The Validator: Automating Field Checking

  • Conclusions

It might sound like a comic book superhero, but a DynaForm can't leap a tall building in a single method invocation. On the other hand, it can reduce a lot of the drudgework of developing Struts applications.

Similarly, the Struts Validator framework can eliminate many common form validation tasks , leaving you to concentrate on the business logic. However, the validator must be used with care, because it doesn't handle all the more complex validations you could encounter during development.

By the end of this chapter, you'll have seen a few examples of how to create and use DynaForm s, and how to integrate them with the Validator to create practically Java-less validating forms.


DynaForm s: Forms Without Java

DynaForm s are an extension of the Apache Commons Beanutils project. As part of the org.apache.commons.beanutils package, an interface called DynaBean was created. Unlike normal JavaBeans, which require explicit get XXX () and set XXX () methods to be written for each property, a DynaBean uses a generic get() and set() method with the property name as the first argument.

A fuller description of the DynaBean package can be found on the Jakarta Web site at http://jakarta.apache.org/commons/beanutils.html.

For example, in a traditional JavaBean, you would say

myBean.setType("kidneybean");

Using DynaBean s, you would say

myBean.set("type", "kidneybean");

This approach has both advantages and disadvantages. The major advantage is that you don't have to declare all your properties explicitly at compile-time; they can be loaded dynamically during execution. This can be very handy to quickly create new beans or new properties of beans.

The disadvantage is that you lose a lot of compile-time error checking. Let's say you are thick-fingered, and enter

myBean.set("tpye", "kidneybean");

The compiler won't catch this. As far as it's concerned , this is perfectly legal Java. It's not until run-time that you'll suddenly find yourself with a null pointer exception. Similarly, all get() and set() methods take and return the Object type, so you lose the strong type-checking of traditional beans.

DynaBeans and Struts

One place that DynaBeans make a lot of sense is in Struts. If you use DynaBeans correctly, you can reduce the size of your ActionForm s by 80 “90%. This is because you can use DynaBeans to eliminate all of your form getters and setters.

To see how this works, you'll rewrite the two ActionForm s for the new account pages of the StockTrack application. To begin, take a look at one of the existing ActionForm s and the same form rewritten with DynaForm s (Listing 17.1 and 17.2).

Listing 17.1 NewUserAddressForm.java
package stocktrack.struts.form;

import javax.servlet.http.*;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionForm;
import stocktrack.struts.form.BaseForm;

/**
 * stocktrack.struts.form.NewUserAddressForm class.
 * this class used by Struts Framework to store data from newUserAddressForm
 *
 * struts-config declaration:
 * <form-bean name="newUserAddressForm"
 *         type="stocktrack.struts.form.NewUserAddressForm" />
 *
 * @see org.apache.struts.action.ActionForm org.apache.struts.action.ActionForm
 * Generated by StrutsWizard.
 */

public class NewUserAddressForm extends BaseForm {
  public void reset(ActionMapping mapping, HttpServletRequest request) {
    streetAddress1 = "";
    streetAddress2 = "";
    city = "";
    state = "";
    postalCode = "";
    homePhone = "";
    workPhone = "";
    workExt = "";
  }
  public ActionErrors validate(ActionMapping mapping,
                               HttpServletRequest request) {
    ActionErrors errors = new ActionErrors();
    if (this.isBlankString(streetAddress1)) {
        errors.add("streetAddress1",
                   new ActionError("stocktrack.newuser.required"));
    }
    if (this.isBlankString(city)) {
        errors.add("city", new ActionError("stocktrack.newuser.required"));
   }
    if (this.isBlankString(state)) {
        errors.add("state", new ActionError("stocktrack.newuser.required"));
   } else {
      if (!this.isValidState(state)) {
        errors.add("state",
                   new ActionError("stocktrack.newuser.invalid.state"));
      }
   }
    if (this.isBlankString(postalCode)) {
        errors.add("postalCode",
                    new ActionError("stocktrack.newuser.required"));
   } else {
     if (!this.isValidPostalCode(postalCode)) {
         errors.add("postalCode",
                    new ActionError("stocktrack.newuser.invalid.postalCode"));
     }
   }
   if (this.isBlankString(homePhone)) {
       errors.add("homePhone", new ActionError("stocktrack.newuser.required"));
   } else {
       if (!this.isValidPhone(homePhone)) {
         errors.add("homePhone",
                    new ActionError("stocktrack.newuser.invalid.phone"));
       }
   }
   if (this.isBlankString(workPhone)) {
       errors.add("workPhone", new ActionError("stocktrack.newuser.required"));
   } else {
       if (!this.isValidPhone(workPhone)) {
         errors.add("workPhone",
                    new ActionError("stocktrack.newuser.invalid.phone"));
       }
   }
   return errors;
 }
 private String streetAddress1;
 private String streetAddress2;
 private String city;
 private String state;
 private String postalCode;
 private String homePhone;
 private String workPhone;
 private String workExt;
 public String getStreetAddress1() {
   return streetAddress1;
 }
 public void setStreetAddress1(String streetAddress1) {
   this.streetAddress1 = streetAddress1;
 }
 public String getStreetAddress2() {
   return streetAddress2;
 }
 public void setStreetAddress2(String streetAddress2) {
   this.streetAddress2 = streetAddress2;
 }
 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 getHomePhone() {
   return homePhone;
 }
 public void setHomePhone(String homePhone) {
   this.homePhone = homePhone;
 }
 public String getWorkPhone() {
   return workPhone;
 }
 public void setWorkPhone(String workPhone) {
   this.workPhone = workPhone;
 }
 public String getWorkExt() {
   return workExt;
 }
 public void setWorkExt(String workExt) {
   this.workExt = workExt;
 }
}
Listing 17.2 NewUserAddressForm.java as a DynaForm
package stocktrack.struts.form;

import javax.servlet.http.*;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionForm;
import stocktrack.struts.form.BaseForm;
import stocktrack.struts.form.BaseDynaForm;

/**
 * stocktrack.struts.form.NewUserAddressForm class.
 * this class used by Struts Framework to store data from newUserAddressForm
 *
 * struts-config declaration:
 * <form-bean name="newUserAddressForm"
 *         type="stocktrack.struts.form.NewUserAddressForm" />
 *
 * @see org.apache.struts.action.ActionForm org.apache.struts.action.ActionForm
 * Generated by StrutsWizard.
 */

public class NewUserAddressForm extends BaseDynaForm {

  public ActionErrors validate(ActionMapping mapping,
                               HttpServletRequest request) {
    ActionErrors errors = new ActionErrors();
    if (BaseForm.isBlankString(this.getString("streetAddress1"))) {
        errors.add("streetAddress1",
                   new ActionError("stocktrack.newuser.required"));
    }
    if (BaseForm.isBlankString(this.getString("city"))) {
        errors.add("city",
                   new ActionError("stocktrack.newuser.required"));
   }
    if (BaseForm.isBlankString(this.getString("state"))) {
        errors.add("state", new ActionError("stocktrack.newuser.required"));
   } else {
      if (!BaseForm.isValidState(this.getString("state"))) {
        errors.add("state",
                    new ActionError("stocktrack.newuser.invalid.state"));
      }
   }
    if (BaseForm.isBlankString(this.getString("postalCode"))) {
        errors.add("postalCode",
                   new ActionError("stocktrack.newuser.required"));
    } else {
      if (!BaseForm.isValidPostalCode(this.getString("postalCode"))) {
          errors.add("postalCode",
                     new ActionError("stocktrack.newuser.invalid.postalCode"));
      }
    }
    if (BaseForm.isBlankString(this.getString("homePhone"))) {
        errors.add("homePhone", new ActionError("stocktrack.newuser.required"));
    } else {
        if (!BaseForm.isValidPhone(this.getString("homePhone"))) {
          errors.add("homePhone",
                     new ActionError("stocktrack.newuser.invalid.phone"));
        }
    }
    if (BaseForm.isBlankString(this.getString("workPhone"))) {
        errors.add("workPhone", new ActionError("stocktrack.newuser.required"));
    } else {
        if (!BaseForm.isValidPhone(this.getString("workPhone"))) {
          errors.add("workPhone",
                     new ActionError("stocktrack.newuser.invalid.phone"));
        }
    }
    return errors;
  }

}

The first thing to notice here is that the class has been changed from BaseForm (which, as you might recall, is simply ActionForm with a few helper functions added for validation) to BaseDynaForm , which is a different helper class that extends DynaActionForm instead. All the getters and setters have been removed, and the validate function now uses this.getString to get the values of the properties rather than accessing the local variables of the class.

getString() is in fact not a part of DynaActionForm , which only defines generic get() and set() methods that take and return Object s. However, rather than cast to String all the time, I've created BaseDynaForm , which adds the getString() versions. Listing 17.3 shows this simple class.

Listing 17.3 BaseDynaForm.java
package stocktrack.struts.form;

import org.apache.struts.action.DynaActionForm;

public class BaseDynaForm extends DynaActionForm {
  public String getString(String name) {
    return (String) this.get(name);
  }

  public String getString(String name, int ind) {
    return (String) this.get(name, ind);
  }
}

In addition, now that the class no longer derives from BaseForm , you need to call out explicitly to BaseForm to get helper functions such as nullOrVoid . Because they were originally defined nonstatic, you must go into BaseForm and declare them static. I suppose that we could have copied the code from BaseForm to BaseDynaForm , but that would have meant maintaining the same code in two places.

The <form-property> Tag

So, if all the getters and setters have been removed from the class, how does the class know what its legal properties are? The answer lies in the form-bean tag in struts-config.xml .

Until now, the forms you've defined in the file looked like this:

<form-bean name="newUserAddressForm"
           type="stocktrack.struts.form.NewUserAddressForm" />

But now, you're going to add some new tags, form-property tags, to the form-bean (see Listing 17.4).

Listing 17.4 The Rewritten newUserAddress Form
<form-bean name="newUserAddressForm" type="stocktrack.struts.form.NewUserAddressForm">
  <form-property name="streetAddress1" type="java.lang.String"/>
  <form-property name="streetAddress2" type="java.lang.String"/>
  <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-property name="homePhone" type="java.lang.String"/>
  <form-property name="workPhone" type="java.lang.String"/>
  <form-property name="workExt" type="java.lang.String"/>
</form-bean>

Inside the form-bean , all the properties have been listed along with their type. This allows the DynaForm to populate the fields. You can use pretty much any class you want in the type value, as well as primitives such as int , Boolean , and so on.

You can also specify initializations for properties via the initial keyword. For example, the following form-property sets up a property called opt-in , which defaults to true:

<form-property name="homePhone" type="boolean" initial="true"/>

The initial keyword is also the only way to create an array or list of a given size. Imagine that you have a form with 10 lines to fill in the names , ages, and genders of the user 's dependents. You'd define it like so:

<form-property name="depName" type="java.lang.String[]"
   initial="{'','','','','','','','','',''}"/>
<form-property name="depAge" type="java.lang.String[]"
   initial="{'','','','','','','','','',''}"/>
<form-property name="depGender" type="java.lang.String[]"
   initial="{'','','','','','','','','',''}"/>

Whatever the type requested , if you want to specify an initial value, there must be a converter defined from String to that class in the Commons Beanutils package. You can add your own converters, so just about any class can be initialized via a form-property tag.

String (and other) arrays are accessed using get() and set() , the same as any other DynaBean . However, you use a second argument, which specifies the index into the array.