J2EE Strategies Part III

Input Validation

This is Part III in a IV part series on J2EE Design.

Input validation is always required even in simple web applications. The question is where do you do it: client or server-side? Each has advantages. Client side validation is nice because it provides instant feedback for the user. However, the downside to client validation is that it must be written in a scripting language, typically Javascript. Scripting languages are good for very small scripts (hence the name), but don’t scale well to serious development. They don’t have good variable scoping, strong types, or a sufficient object model.

Server-side validation allows you to use the full facilities of the Java language, which is of course quite robust. However, to do server-side validation requires a round trip to the server, meaning that you lose the nice instant gratification of client-side validation. Which should you use?

Client-side Validation

Because client-side validation must rely on scripting languages (whose short comings are listed above), it should always be thought of a part of the view of the application, not the model. In other words, you should never embed any business logic in JavaScript. It is fine for simple validations like "Must be all characters" or "Must be in phone number format". However, it should never be used for logic such as "No customer can have a credit limit over $1000". This is a business rule because it has more to do with why you are writing the application than how you are writing it. These business rules are prone to change and therefore should be consolidated in model classes. They should never be scattered throughout the presentation layer of your application in Javascript!

Server-side Validation

All business-level validations should be handled by model JavaBeans (or Enterprise JavaBeans) on the server. By placing the code in a central, logical location, you create a much more maintainable web application. Of course, this means that you frequently have a 2 tiered approach to validation: the client handles formatting and simple validations while the server code does the business level validation.

Forms and Validation with Struts

Again, Struts provides a nice illustration on how to design server-side validation in a flexible way. One of the common chores in web applications is the handling of forms. The JSP mechanism of automatically populating the fields of a Java bean with form post parameters goes a long way in reducing the tedious code that would have to be written otherwise. Struts has built on top of this mechanism to provide an easy way to handle populating beans in wizard style interfaces and handling validation.

When creating a Struts form, you have the option of creating a Struts form bean. This is a regular Java bean with typical accessors and mutators for the fields of the bean (and very little other code) that subclasses the org.apache.struts.action.ActionForm base class. Note that these beans should not connect to a database to populate themselves or provide a lot of other infrastructure code. They should be very lightweight classes that basically encapsulate entity information. Other beans should handle persistence and other duties. In fact, it is typical to have one bean that acts as a collection of the entities represented by a Struts form bean (like the ScheduleBean used in the Action class discussed above).

One of the additional sections in the struts-config XML document allows you to declare a class as a form-bean. Look at the top of the sample struts-config document above. A form-bean declaration allows you to specify a name for the form bean and map that to a specific class (in this case, schedule.ScheduleItem). In the action-mapping section of the config file, you can automatically associate a form bean with an action. Notice the "add" action in the config file. The additional "name" parameter allows you to specify a form bean to use with that action. Once you have a form bean associated with an action, Struts will perform the following services for you before invoking the action method:

  • Check the user’s session for an instance of the bean under the name specified in the struts-config file. If one doesn’t yet exist, Struts creates one and adds it to the user’s session
  • For every request parameter that matches one of the setXXX methods of the bean, the appropriate set method will be called
  • The updated ActionForm bean is passed to the Action as a parameter

This is similar to the standard JSP behavior of handling request parameters that map to fields of a JavaBean. However, Struts performs more services for you. Struts also comes with a collection of custom JSP tags, split into 4 categories. One of the categories allows you to replace standard HTML tags for input elements with "smarter" Struts tags. If you use the Struts HTML tags, it will also automatically populate the input fields on the form from the ActionForm whenever the page is visited. This makes it really easy to handle wizard style interfaces. Notice that an ActionForm bean doesn’t have to correspond to a single page. More typically, it corresponds to a single set of user information. So, you can have an ActionForm that spans multiple pages. Using the Struts HTML tags, the input fields the user has already filled in will be automatically populated as the user moves back and forth between the pages of the wizard. For an example of a JSP that uses the custom tags, see the following listing.

Listing 10: An HTML form using custom Struts tags


<%@ taglib uri="/WEB-INF/struts-html.tld"
    prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld"
    prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts.tld"
    prefix="struts" %>
<jsp:useBean id="scheduleItem" scope="request"
             class="schedule.ScheduleItem" />
<jsp:useBean id="scheduleBean" scope="page"
             class="schedule.ScheduleBean" />
<% pageContext.setAttribute("eventTypes",
 scheduleBean.getEventTypes()); %>
<HTML>
<HEAD>
<TITLE>
ScheduleEntryView
</TITLE>
</HEAD>
<BODY>
<H1>
Add Schedule Item
</H1>
<hr>
<html:errors/>
<html:form action="add.do">
<table border="0" width="30%" align="left">
  <tr>
    <th align="right">
      <struts:message key="prompt.duration"/>
    </th>
    <td align="left">
      <html:text property="duration"
                     size="16"/>
    </td>
  </tr>
  <tr>
    <th align="right">
      <struts:message key="prompt.eventType"/>
    </th>
    <td align="left">
      <html:select property="eventType">
        <html:options collection="eventTypes"
                 property="value"
                 labelProperty="label"/>
      </html:select>
    </td>
  </tr>
  <tr>
    <th align="right">
      <struts:message key="prompt.start"/>
    </th>
    <td align="left">
      <html:text property="start"
                         size="16"/>
    </td>
  </tr>
  <tr>
    <th align="right">
      <struts:message key="prompt.text"/>
    </th>
    <td align="left">
      <html:text property="text"
                         size="16"/>
    </td>
  </tr>
  <tr>
    <td align="right">
      <struts:submit>
        <bean:message key="button.submit"/>
      </struts:submit>
    </td>
    <td align="right">
      <html:reset>
        <bean:message key="button.reset"/>
      </html:reset>
    </td>
  </tr>
</table>
</html:form>
</BODY>
</HTML>

This listing shows some other features of Struts tags as well. One of the automatic features of Struts is form validation. The struts-config file allows you to flag an action associated with a form bean to enable validations. This assumes that you have added a validation method to your form bean class. A sample validation method is shown here.

Listing 11: The validate() method of a form bean.

public ActionErrors validate(
        ActionMapping actionMapping,
        HttpServletRequest request) {
    ActionErrors ae = new ActionErrors();
    if (duration < 0 || duration > 31) {
        ae.add("duration", new ActionError(
           "error.invalid.duration", "8"));
    }
    if (text == null || text.length() < 1) {
        ae.add("event text",
            new ActionError("error.no.text"));
    }
    return AE;
}

The validate() method is automatically called by the Action object after the population of the form bean fields but before any of the code in the Action is performed. If the validate() method returns either null or returns an empty ActionErrors collection, processing of the Action continues normally. If errors have been returned from validate(), Struts will automatically return the user to the input form, repopulate the fields from the form bean, and print out a list of the reported errors at the top of the page. For an example of this, see the figure below. This is a Struts form where the user has put in a negative duration and left the Text field blank. The errors listed at the top are automatically generated via the validate() method and the <html:errors/> tag at the top of the file (the position of this tag determines where the errors will appear on the page).

You will also notice that the error messages returned by the validate method aren’t the messages that appear on the page. The strings added in the ActionError constructor map to messages in a java.util.Properties file. This properties file is automatically referenced by Struts (this is one of the parameters of the ActionServlet), and allows for easy separation of the messages in the application from the code. This means that the text of the message can be easily changed without recompiling the application. This is also how Struts handles internationalization. Struts can be set up to look for a properties file that matches the locale encoding of the request to automatically provide text messages in the appropriate language. This is another service provided by the HTML tags. Notice in Listing Five that the input text references fields in the properties file.

If You Must Do Client-side Validation for Business Rules…

There are some web applications that simply must provide the instant feedback that you can only get with Javascript and client-side validation. In those cases, there is a solution. Because you must embed the business rules in Javascript, you should create a method on your model bean the outputs the necessary Javascript for the validation. This way, the model bean is still responsible for the validation, it just delegates it to the presentation layer. This still leaves the validation code itself in the business layer, where it can be easily changed.

Here is a simple example. In a model class named Order, the JavaScript validation for ensuring that a credit card field has a numeric value in it.

Listing 12: Model class that contains JavaScript to validate entry

public class Order implements Serializable {
    private static final String JS_CC_FORM_VALIDATION =
        "<script>" +
        "   function verify(e) {" +
        "       if (e.value == null || isNaN(parseInt(e.value))) {" +
        "           alert('Field must be numeric');" +
        "           e.focus();" +
        "           return false;" +
        "       }" +
        "   }" +
        "</script>";
    public String getFormValidationForCC() {
        return JS_CC_FORM_VALIDATION;
    }
    //... more methods follow

To embed this code in the view, you can use the typical JSP tags to generate the code into the view.

Listing 13: JSP code that pulls JavaScript from the model for validations

<%-- get JavaScript validation code from Order class --%>
<jsp:getProperty name="order" property="formValidationForCC"
/>
<form action="CheckOut" method="post" onSubmit='verify
(this.ccExp)'>
 Credit Card # <input type="text" name="ccNum">
Credit Card Type <select name="ccType"> <option value="Visa">Visa</option> <option value="MC">MC</option> <option value="Amex">AMEX<option> </select> Credit Card Exp Date <input type="text" name="ccExp" > <input type="submit" value="Check out"> </form>

As you can see, this allows you to keep the business rules of the application in the model class but still get the benefits of client-side validation.

Caching using the Flyweight Design Pattern

The Flyweight design pattern appears in the Gang of Four book, which is the seminal work on patterns in software development. The pattern uses sharing to support a large number of fine-grained object references. With the Flyweight strategy, you keep a pool of objects available and create references to the pool of objects for particular views. This pattern uses the idea of canonical objects. A canonical object is a single representative object that represents all other objects of that type. For example, if you have a particular product, it represents all products of that type. In an application, instead of creating a list of products for each user, you create one list of canonical products and each user has a list of references to that list.

A typical e-commerce application is designed to hold a list of products for each user. However, that design is a waste of memory. The products are the same for all users, and the characteristics of the products change infrequently. This figure shows the current architectural relationship between users and the list of products in the catalog.

Each user holds a list of products.

The memory required to keep a unique list for each user is wasted. Even though each user has his or her own view of the products, only one list of products exists. Each user can change the sort order and the catalog page of products he or she sees, but the fundamental characteristics of the product remain the same for each user.

A better design is to create a canonical list of products and hold references to that list for each user.

In this scenario, each user still has a reference to a particular set of products (to maintain paging and sorting), but the references point back to the canonical list of products. This main list is the only actual product object present in the application. It is stored in a central location, accessible by all the users of the application.

Flyweight considerations

The effectiveness of the Flyweight pattern as a caching mechanism depends heavily on certain characteristics of the data you are caching:

  • The application uses a large number of objects.
  • Storage (memory) cost is high to replicate this large number for multiple users.
  • Either the objects are immutable or their state can be made external.
  • Relatively few shared objects may replace many groups of objects.

The application doesn’t depend on object identity. While users may think they are getting a unique object, they actually have a reference from the cache.

One of the key characteristics enabling this style of caching is the state information in the objects. In the previous example, the product objects are immutable as far as the user is concerned. If the user is allowed to make changes to the object, then this caching scenario wouldn’t work. It depends on the object stored in the cache being read-only. It is possible to store non-immutable objects using the Flyweight design pattern, but some of their state information must reside externally to the object.

It is possible to store the mutable information needed by the reference in a small class that is associated to the link between the Flyweight reference and the Flyweight object. A good example of this type of external state information in an ecommerce application is the preferred quantity for particular items. This is information particular to the user, so it should not be stored in the cache. However, there is a discrete chunk of information for each product. This preference (and others) would be stored in an association class, tied to the relationship between the reference and the product. When you use this option, the information must take very little memory in comparison to the Flyweight reference itself. Otherwise, you don’t save any resources by using the Flyweight.

The Flyweight design pattern is not recommended when the objects in the cache change rapidly or unexpectedly. It would not be a suitable caching strategy for the ecommerce application if the products changed several times a day. This solution works best when you have an immutable set of objects shared between most or all of your users. The memory savings are dramatic and become more pronounced the more concurrent users you have.

No comments yet

Leave a Reply

You must be logged in to post a comment.