Chapter 19. Struts Framework

This chapter builds on the experience you gained in Chapter 18, "Web Frameworks," by looking at a framework that has become very popular in the J2EE development community. Apache Struts gained prominence in the J2EE community through its evolution as a Jakarta Project, and as of early 2004 has become an official Apache Project.

This chapter will introduce Struts to you by first outlining how it fulfills the MVC Model 2 architecture for applications. As this is a JSP book, we will then look at some of the custom tags that come with the framework.

Finally, this chapter will show you how to develop a small application along the same lines as those in Chapter 18. Like any rapidly maturing technology, various methods become available to solve common problems such as validation and form bean implementation. This chapter covers these variances where space permits.

Following are the main topics of this chapter:

  • A brief walk through the Struts framework.

  • An explanation of Struts Actions, Forms and View components

  • An example application using Struts

Introducing Struts

Apache Struts assists application developers in implementing an MVC Model 2 architecture in their J2EE applications. It provides them with a Controller-centric infrastructure that offers the usual lineup of MVC framework features:

  • Internationalization

  • Various validation techniques

  • Flexibility to interact with the model layer

  • Templating (Tiles)

  • Configuration infrastructure for everything from the Controller to database connections

  • JSP custom tags to integrate with the framework

  • Application flow control

  • Resource bundles

What differentiates Struts from the gamut of other MVC frameworks available today are the following important factors:

  • A large volume of documentation available both on the Internet and in printed publications.

  • An extremely active community. An indication of this is the amount of activity on the Struts User and Developer mailing lists.

  • Simple, easy-to-use (and understand) components.

  • Its frequent use in real-world application implementations. Struts is now a common item on many J2EE developers' resumes.

The popularity of Struts seems self-perpetuating; the more popular it gets, the more popular it gets, and so on. This is not to say that Struts doesn't have its drawbacks; many developers have run into weaknesses and problems with the Struts implementation. These are sometimes addressed specifically in other frameworks.

This chapter uses Struts 1.2.4, the latest production release at the time of writing. You can download Struts from http://struts.apache.org.

The example application discussed at the end of this chapter utilizes a basic starting point that can be downloaded from the Web site for this book. This download includes the necessary files to get this application running, but it does not include the entire Struts installation.

A Struts walkthrough

After reading Chapter 18, the general plan of attack used by Struts will be somewhat familiar to you.

In simple terms, a single servlet is configured (referred to as the Struts servlet) in the container. This servlet is configured for a certain pattern of URL that the application receives in a request from a browser. This Struts servlet is defined in the web.xml file in the same way that other servlets of an application are defined.

The Struts servlet is further configured via a Struts configuration file to pass control to an action. An action represents some business functionality that the application undertakes in response to the request from the user. Actions are implemented by the developer to respond to the request of the user and usually determine the appropriate View (usually a JSP page) or further action that will be invoked to fulfill the business purpose.

Actions can receive input from the request in a structured format. Think of a form that is filled out on a Web page and then submitted. A single object (usually created by the application developer) represents the different properties of the submitted form. Each property of the HTML form is associated with a property of this object. Struts handles the binding of the elements in the HTML form with the properties in the object.

Depending on the logic implemented in the receiving action, a view may be displayed to complete the response back to the client. This view may be provided by the action with resources to assist in display, such as data. The view, in turn, makes use of the many available Struts custom tags that provide for the following:

  • Logic such as iteration and conditional processing.

  • HTML elements such as forms. HTML custom tags are used by Struts to bind form elements with properties of the aforementioned Form objects. Other HTML elements are also catered to, such as images and links.

  • Bean processing.

The view is then presented to the user as a result of all the processing that has occurred, providing the response.

Validation can be invoked at different stages of the preceding process, configurable by the developer. One strength of Struts is the various methods it provides to fulfill the validation requirements of the application.

The diagram shown in Figure 19-1 offers a simplistic representation of this chain of events.

Tracing a request through an application using Struts

Figure 19.1. Tracing a request through an application using Struts

The Struts components are configured in a specific Struts configuration file called struts-config.xml located alongside the web.xml file in the WEB-INF directory of the Web application. You can refer to this diagram as you continue through the chapter. It will help you to develop an understanding of how the different pieces fit together.

Controller: actions and forms

The Struts servlet is the basis of the Controller component of the MVC framework provided by Struts. The Struts servlet is actually an ActionServlet class provided by Struts that is configured for the application by the developer when the application is set up. The ActionServlet is defined in the web.xml file located in the WEB-INF directory of the application. The following code illustrates such an entry:

...
  <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
...
  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
...

In this example, you can see that the <servlet-mapping> entry has provided a URL pattern to associate with the action servlet. When a request is received that matches this URL pattern, the ActionServlet is put to work performing or delegating much of the setup work required for the application to function correctly. In the preceding example, the action servlet is configured to respond to requests that match the pattern: *.do; therefore, the following URL will invoke the ActionServlet configured in this way:

http://localhost:8080/yourapplication/login.do

When this URL is received, ActionServlet will initiate the following series of steps:

  1. Establish some basic information, such as the path that has been invoked, the content type, and other request-specific information such as cache settings.

  2. Determine any mappings to actions (ActionMapping) that are associated with the request that has been received. For instance, our previous example requested a resource called login. This is an alias to an ActionMapping that has been defined for this ActionServlet to recognize.

  3. Create, populate, and validate any ActionForm objects that have been associated with the ActionMapping, according to the Struts configuration.

  4. If necessary, forward the request to a resource if this has been configured.

  5. Invoke the Action class created by the application developer. The specific functionality defined by the developer is executed here.

The following section describes actions in more detail.

Actions

Actions form the application-specific tasks that the application performs in response to requests. Action classes are developed by the application developer. Typically, an action will be created for tasks that represent some piece of work the application must undertake in response to the request. For instance, when the user clicks a link on a Web page to log in, pointing to login.do, a LoginAction class will be invoked. When the user uses some sort of search facility in an application, a DoSearchAction class may be used, and so on. How does the framework know which class to use? The answer lies in the ActionMappings we define in the Struts configuration. Each action is defined within a list of action mappings. An action mapping is a definition of an action within the application. Each configured action can have certain properties that are defined with the action element of the struts-config.xml file.

The following listing shows the definition for our LoginAction in the struts-config.xml file:

<struts-config>
...
<action-mappings>
    <action path="/login"
        type="com.wrox.begjsp.ch19.struts.LoginAction"
        name="loginForm"
        input="/login.jsp"
        scope="request">
        <forward name="success" path="/welcome.jsp"/>
    </action>
...
</action-mappings>
...
</struts-config>

The order of the items added to the struts-config.xml file is very important. Placing form beans, action mappings, and other elements in this file in an incorrect order can result in some strange errors. The order of items must be as follows:

  1. data sources

  2. form beans

  3. global forwards

  4. action mappings

  5. controller

  6. message resources

  7. plug-in

Nested within the Action mapping is a forward element that defines a possible result of the action. This forward is named success and will be used in the action when determining which page to display next.

The key parameters to the action definition are as follows:

  • path: The relative path to the action, in the context of the application; therefore, the previous action would be invoked with the following: http://localhost:8080/yourapplication/login.do.

  • type: The package and class name of the action class created for this action.

  • name: The mapping name of the form bean used for this action.

  • validate: Whether validation is to be invoked when this action is requested.

  • input: The action mapping or JSP file that provided input for this action.

  • scope: The scope of this action, i.e., request or session scope.

  • forward: The path to the JSP file or action that this action should forward to after processing has completed.

Action mappings can be simple, as illustrated previously, or very sophisticated, to cater to the wide variety of possible scenarios that developers may need when defining the workflow of the application and its interaction with the user.

These actions developed for an application are subclasses of a class provided by Struts that forms the basis of all actions in the framework. This class is called Action (not surprisingly).

When a developer creates an action for an application, this new class extends the Action class provided by Struts and must implement a specific method for the framework to execute. The signature of this method is listed here:

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

The execute method receives the following parameters:

  • ActionMapping: This represents the mapping configuration that led to this request finding its way to this Action class.

  • ActionForm: This represents a form bean that may have been configured to be received by this Action.

  • HttpServletRequest: This is an important native J2EE object that represents the HTTP request received by the application.

  • HttpServletResponse: This is another important native J2EE object that represents the response to the client when all processing has been completed.

Within the execute method, the developer performs the logic required for this particular action. The execute method then returns an ActionForward object. An ActionForward object represents another resource configured in Struts. This usually points to a JSP file or even another Action class. For instance, in a login scenario, there may be one ActionForward returned if the login was successful, and another if not. The following code snippet shows a LoginAction class that might fulfill this scenario:

package com.wrox.begjsp.ch19.struts.simpleform;

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.ActionMessage;
import org.apache.struts.action.ActionMessages;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class LoginAction extends Action
{
    public ActionForward execute(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception
    {
        ActionMessages errors = new ActionMessages();
        LoginForm loginForm = (LoginForm) form;
        String userName = (String) loginForm.getUsername();
        String password = (String) loginForm.getPassword();

        ActionForward returnForward = null;

        if ("admin".equals(userName) && "opensesame".equals(password))
        {
            returnForward = mapping.findForward("success");
        }
        else
        {
            returnForward = mapping.getInputForward();
            errors.add(ActionMessages.GLOBAL_MESSAGE,
                new ActionMessage("error.login.invalid"));
        }

        if (!errors.isEmpty())
        {
            saveErrors(request, errors);
        }

        return returnForward;
    }
}

You can see in this simple example that we are conditionally returning one of two possible ActionForward objects based on the success of the login (highlighted in the code example). The success String points to an ActionForward configured by the developer in the Struts configuration file (remember that this action was defined in the struts-config.xml file). The unsuccessful ActionForward is found by making a call to the ActionMapping object that this method received. This is an ActionForward configured as the input for this particular action.

It is important to note that the ActionForwards used represent other resources configured for the application. In the preceding scenario, the success forward would point to a JSP file (using the configuration file action mapping entry illustrated, this JSP file would be welcome.jsp) or another action, and could be the first screen the user sees after login.

The forward used in this example is available only to the login action. There are also other types of forwards called global forwards that are available from all actions. A typical example of one of these is a global forward pointing to an error.jsp page. Here is the struts-config.xml mapping for this entry:

<global-forwards>
    <forward name="error" path="/error.jsp"/>
</global-forwards>

This new error global forward can now be referenced from all actions in the application. Naming these forwards and not just using the resource name directly is an important feature of many Web frameworks. The benefit is immediately apparent if you have ever had to change the name of a JSP page in a large application where such abstractions are not used. For example, if error.jsp were to change its name to, say, errorPage.jsp, only a single change would be required.

Getting back to the preceding action example, notice that there is also some validation added. When the login is not successful, an ActionMessage is added to an ActionMessages object called errors, instantiated at the beginning of the method. ActionMessages is simply a collection of messages that are appropriate to the results of this action. When the next resource is displayed, the ActionMessage objects provided for it can be displayed appropriately. The message to be displayed is a message resource (error.login.invalid); it would be defined in a messages.properties file. Notice in the action that the ActionMessages object is saved with a call to the saveErrors method provided by Struts. There is a similar method called saveMessages, where normal information messages can be saved. These methods modify a list of messages maintained by Struts that can be displayed in the view. You will see examples of this when Struts tags are explained later on.

Other data that is not represented in the form object can also be placed within the request scope of the JSP page referred to by this action. Providing the JSP file access to this data is simply a matter of adding an object to the HttpServletRequest object using the setAttribute method. For instance, within the LoginAction, a String object can be passed to the JSP page in the request scope using the following syntax:

String exampleObject = new String("this is my String");
      request.setAttribute("testObject", exampleObject);

This object could then be accessed inside the JSP page as a bean via the alias it has been given, testObject. Similarly, the object could also be added to the session scope using the following:

HttpSession session = request.getSession();
    session.setAttribute("user", user);

Note how Action classes enable the developer to trigger and implement business and control logic, away from the view layer of the application. Decisions about what should happen next, which data should be available, and whether the user has access to the application at all can all be made at this level.

Forms

HTML forms in an application can be represented by form beans. Form beans are classes implemented or defined by the developer to represent the particular forms in the application. All such beans extend a Struts-provided class such as ActionForm, ValidatorForm, or ValidatorActionForm, among others.

Form beans are typically a simple class with a number of properties, with each property being accessed via a getter and setter method. Each property represents an element of the HTML form. For instance, to continue our login example from before, a LoginForm class was used to represent the input from users when they logged in.

The HTML form submitted contained a username field and a password field. These fields would be represented by equivalent properties in the LoginForm object. The translation between the HTML form and the LoginForm object is handled by Struts. Struts matches the field names used in the HTML form with the setter and getter methods for their equivalent properties used in the form bean class that has been implemented.

The result of this translation is illustrated by the preceding code sample of the LoginAction. The execute method received an ActionForm object as one of its parameters, and this was simply cast to a LoginForm object. Getter and setter methods of this object were then used to obtain the content the user entered when the HTML form was submitted. The diagram in Figure 19-2 illustrates this transition for you.

Transition from an HTML form to a Struts form object

Figure 19.2. Transition from an HTML form to a Struts form object

The correct object (LoginForm) was instantiated by the Struts framework because of the particular configuration settings established for that action.

Like actions, form beans developed for the application must also be configured in the struts-config.xml file. An entry for the form bean illustrated here would look like the following:

<struts-config>
...
<form-beans>
    <form-bean name="loginForm"
               type="com.wrox.begjsp.ch19.struts.LoginForm"/>
...
</form-beans>
...
</struts-config>

Note that the name given to this form bean is loginForm, which (not coincidentally) maps to the name attribute configured for the login action defined earlier.

Notice, in an architectural sense, the place that form beans take in an application. It is better to think of them as simply a vehicle to move data input from the HTML form to the brains of your application. They do not represent business objects of your application, and hence they do not fit in the Model layer; they are simply a convenient Controller layer data vehicle.

Certain JavaBean conventions must be adhered to in order for ActionForm subclasses to work consistently with a Struts application. When a tag or some part of a configuration file references a property of a form bean, it will use a name such as username. The framework then relies on this exact case-sensitive spelling to access this property via a setter method named setUsername and a getter method called getUsername. The internal name of the property is not important, only that the reference and associated getter and setter methods are synchronized with this pattern in mind. The framework relies on this convention throughout.

Our LoginForm object would appear as follows:

package com.wrox.begjsp.ch19.struts.simpleform;

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

import javax.servlet.http.HttpServletRequest;


public class LoginForm extends ActionForm
{
    private String _username;
    private String _password;

    public LoginForm()
    {
    }

    public String getPassword()
    {
        return _password;
    }

    public void setPassword(String password)
    {
        _password = password;
    }

    public String getUsername()
    {
        return _username;
    }

    public void setUsername(String username)
    {
        _username = username;
    }
}

Extending ActionForm allows the Struts framework to utilize the LoginForm class appropriately. Contrast this with the Command objects implemented in the Spring framework in Chapter 18. Command objects in Spring do not have to extend any framework class in order to function correctly. This has been a criticism of Struts. It is, however, a small price to pay, and in retrospect, probably forces the developer to structure the application in a more adept fashion. Forcing the extension of the ActionForm class (or ValidatorForm or ValidatorActionForm) encourages developers to define their own classes to represent business objects in the application, and leaves the form beans as merely transports for data from the HTML form to the form bean, and no more.

Besides being a convenient representation of the data entered into an HTML form, ActionForm subclasses can also be extended to perform simple validation. You will see an example of this in the "Validation" section of this chapter.

DynaActionForm

Implementing form beans with a concrete class for every form in an application, as just described, could become quite cumbersome. Struts has provided a way to represent these structures for the application at runtime by using definitions entered in the Struts configuration file. This is convenient when the classes you would otherwise implement are only providing getter and setter methods for your form properties.

If the LoginForm discussed previously were to be implemented as a DynaActionForm, the appropriate entry in the struts-config.xml file for this instance would be as follows:

<form-bean name="loginForm"
           type="org.apache.struts.validator.DynaActionForm">
  <form-property name="username" type="java.lang.String"/>
  <form-property name="password" type="java.lang.String"/>
</form-bean>

The form bean definition here has two major differences from the form bean definition for the custom-made ActionForm class LoginForm. First, note that the type property now points to a Struts class DynaActionForm, and not LoginForm. Second, note the specification of two form properties listed within the form bean definition. These properties correspond to the elements of the HTML form this form bean represents. Note also in the property definitions that the type of the property has been specified (java.lang.String). DynaActionForms support a range of different types in the type attribute for properties, such as Boolean, Integer, Double, Long, Short, Byte, Character, java.util.Date, and java.sql.Timestamp, among others. You can also place primitive types in this field, such as int, double, long, short, and boolean.

This definition of type is very convenient for retrieving values from the object when the form it represents is submitted. Typically in Web applications, all values from the HTTP request are provided as a string, and the developer must provide the appropriate translation into the correct type; here, Struts has done the hard work. This is obviously also an advantage when using form beans normally.

Another difference that occurs when using DynaActionForms in an application is the technique for accessing form properties within the action class that receives the form bean. In the LoginAction example illustrated earlier, the properties of the LoginForm class were accessed like normal Java object method calls:

...
public class LoginAction extends Action
{
    public ActionForward execute(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception
    {
        ActionMessages errors = new ActionMessages();
LoginForm loginForm = (LoginForm) form;
        String userName = loginForm.getUsername();
        String password = loginForm.getPassword();
...

When a DynaActionForm is used, the properties entered must be accessed via a common get(String) method. This method call retrieves an Object from an internal collection of the values defined for this form. The preceding example would be rewritten with a DynaActionForm as follows:

...
public class DynaLoginAction extends Action
{
    public ActionForward execute(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception
    {
        ActionMessages errors = new ActionMessages();
        DynaActionForm loginForm = (DynaActionForm) form;
        String userName = (String) loginForm.get("username");
        String password = (String) loginForm.get("password");
...

You can see there is not much of a change. The internal collection of properties retained by the DynaActionForm enables the retrieval of property values via a key, the name designated for that property in the struts-config.xml file discussed earlier.

Model

The Model layer of an application should be notionally separated from the Controller and View aspects of a Struts application. As you learned in Chapter 18, "Web Frameworks," the Model layer of an application is deliberately unaware of any of the Controller or View components that are implemented. This provides for a simpler design with fewer couplings (dependencies) between different layers.

The View and Controller layers utilize certain resources and logic provided by the Model layer of an application via whatever accessibility has been deemed appropriate. For instance, various Model layer services (objects) may provide for the retrieval of data from a database. They will return Model layer representations of the data (as objects) to the Controller and View layers; however, these layers will be completely unaware of how they were retrieved or even where they came from.

Once a Model layer object—for instance, a Customer object—has found its way to the Controller and View layers of the application, it may be accessed, manipulated, and displayed as the application sees fit. The business decisions concerning the Customer object, however, are left in the hands of the Model layer.

The Model layer represents the business state and business activities of the system, and as such it is decoupled from the View and Controller layers. Smaller, simple applications may integrate Model layer functions within action classes, but generally it is safer to encourage a decoupled Model layer and leave the Controller and View layers to interpret what the Model layer provides.

Struts provides a flexible infrastructure that leaves the implementation of the Model layer as a separate concern.

View

JSP pages form the basis of the View layer discussed in this section. Struts provides the developer with a rich set of tools to develop the View layer of an application:

  • Form bean interaction

  • Internationalization

  • Struts Tag Library

  • Validation

Form bean interaction

A Struts application requires some degree of structured communication between the View layer (JSP files, etc.) and the Controller layer (actions and form beans). An HTML form designed to interact with a form bean configured in a Struts application must use the html series of custom tags provided by Struts. These tags enable Struts to transform the properties defined in form beans as values of the HTML form defined in the View layer, and vice versa.

Struts provides a collection of tags designed to provide this interaction. For instance, when creating a new form in a JSP page (such as login.jsp from the login example being used) using Struts, the html:form tag would be used:

<html:form action="/login" method="POST">
...
</html:form>

Note that the value assigned to the action attribute is the name of the action defined for our LoginAction in the struts-config.xml file.

Struts uses the property attribute of HTML form tags to identify the property of the form bean to retrieve and populate where appropriate. Without Struts doing this interaction for us, as well as validation that will be explained later, JSP pages would become quite complicated.

The form elements of a page are therefore displayed using a series of HTML tags provided by Struts. The elements of the login form discussed in this chapter would be defined within login.jsp as follows:

<html:text property="username"/>
<html:password property="password"/>

Note that the property attribute corresponds directly to the name of the setter and getter methods defined in the LoginForm bean. When these form elements are populated, the values contained in those properties of the form bean will be displayed. Without Struts, these form elements might appear as follows:

<input type="text" name="username" value="<%=loginForm.getUsername()%>">
<input type="password" name="password" value="<%=loginForm.getPassword()%>">

Of course, when these form elements are provided to the user's browser in the response they are rendered as standard HTML; there is no Struts magic occurring over there.

On the return trip, when the form is submitted, the names of the properties in the form are used to repopulate the form bean defined for the action, and so on. In the case illustrated here, the form is submitting to the action /login.do. This action has been defined as using a form bean, loginForm. Therefore, when the action is invoked, it will attempt to create a new LoginForm object with the appropriate properties contained in the request, such as username=admin and password=opensalami.

If you remember the code defined earlier for the LoginAction, the properties used in the form were tested to determine whether a successful login occurred. If you look at the code, you can see the password opensalami is incorrect:

...
public ActionForward execute(ActionMapping mapping, ActionForm form,
    HttpServletRequest request, HttpServletResponse response)
    throws Exception
{
    ActionMessages errors = new ActionMessages();
    LoginForm loginForm = (LoginForm) form;
    String userName = loginForm.getUsername();
    String password = loginForm.getPassword();

    ActionForward returnForward = null;

    if ("admin".equals(userName) && "opensesame".equals(password))
    {
        returnForward = mapping.findForward("success");
    }
    else
    {
        returnForward = mapping.getInputForward();
        errors.add(ActionMessages.GLOBAL_MESSAGE,
            new ActionMessage("error.login.invalid"));
    }

    if (!errors.isEmpty())
    {
        saveErrors(request, errors);
    }

    return returnForward;
}
...

Where the password is incorrect, the ActionForward to be displayed next is in fact the input attribute for this action—in this case, the JSP page that has just been submitted, login.jsp. This means that the login form will be redisplayed to the user.

Upon the second loading of this page, the LoginForm object has the values that were typed in by the user originally, before the form was submitted. The HTML form defined for this page knows to populate itself with values for this form bean. It will also display any errors that have been saved as a result of the action being executed. In the preceding case, a new ActionMessage was added to an ActionMessages collection. The error defined to display is shown as error.login.invalid. This key points to a value in the messages.properties file associated with this application. This ActionMessages collection can be displayed, as errors, on the JSP page using a Struts tag designed specifically for this purpose: html:errors.

If this description of events seems complicated, the diagram in Figure 19-3 may help.

Login form submission and return via the LoginAction

Figure 19.3. Login form submission and return via the LoginAction

This section has provided a brief introduction to the interaction between the form beans defined for an application and the HTML forms they represent. You will examine a more complete example later in the chapter.

Internationalization

Like many Web application frameworks, Struts provides for the internationalization of messages within an application. By configuring the application to be aware of message resource files, the application is able to request the value of messages to be displayed in a language appropriate for the user viewing the site, based on their browser settings. You saw an example of this in the previous chapter, where a simple form interface was displayed in French (probably bad French, but you get the idea!).

Besides the internationalization benefits this system provides, storing common labels, error messages, and so on in central file(s) is convenient should these messages ever need to be changed. For instance, in the action described earlier, an error message was sent to the JSP page via a Struts ActionMessage object:

errors.add(ActionMessages.GLOBAL_MESSAGE,
           new ActionMessage("error.login.invalid"));

The key used in this example is error.login.invalid, which corresponds to an entry in a properties file defined for the application as follows:

error.login.invalid=Login invalid

When this ActionMessage is displayed on the JSP page, the text Login invalid is displayed. If we were to simply pass the text Login invalid as a string in the action instead of referencing the messages resource, and a change was required for this message, the class would have to be changed and then recompiled. The class would then have to be redeployed to the live environment. In a large, important application, the downtime associated with this interruption may be unacceptable.

Messages for internationalization are stored in properties files that are located in the classpath, much like the class files of the application. The framework is then configured to know where these resources are located via an entry in the struts-config.xml file:

<struts-config>
...
<message-resources  parameter="messages"/>
...
</struts-config>

This would tell the framework that the properties resource (messages.properties) is located at the root of the classpath (the default package). The parameter value could also be com.wrox.begjsp.ch19.Messages, which would denote the file's location within that package. Other properties files could be added for the various languages the application should support, using a standard file-naming structure that indicates the language for which the file is responsible. Some examples might be messages_fr.properties for French, messages_de.properties for German, and so on.

Struts Tag Library

Some of the Struts custom tags have been explained already in form bean interactions. In that section, you saw some of the Struts specific custom tags in action. This section examines the most commonly used tags provided by Struts, and provides a brief explanation of their syntax and usage.

The tag libraries provided by Struts are, to say the least, extensive. They cover the following areas:

  • html: A set of tags to render HTML elements, including HTML form elements that enable form bean interaction and other conveniences. The explanation of these tags later in this chapter has been separated into form tags and other tags.

  • logic: A set of tags to provide conditional, iterative, and workflow-related tools for JSP pages. Much of the functionality provided by this set of tags is also covered by the JSTL.

  • bean: A set of tags to provide for the declaration and accessing of beans in various scopes. Much of the functionality provided by this set of tags is also covered by the JSTL.

  • tiles: Tiles is a templating tool that accompanies Struts (as of version 1.1). A set of Tiles tags is therefore provided. These tags are not included in this discussion; please refer to Chapter 20, "Layout Management with Tiles," for a full discussion of Tiles.

It is worth noting that many of the attributes available to Struts tags can take runtime expressions as their value. Consult the Struts documentation for specifics. For the examples included here, runtime expressions are not used.

HTML form tags

As already noted, there are Struts tags corresponding to most if not all of the traditional HTML form elements, as well as some other HTML fragments. Listed in the following table are some of the more commonly used HTML form tags and their common usage. For a complete list of the many tags provided by Struts, as well as the list of possible attributes, refer to the online documentation at http://struts.apache.org/userGuide/index.html.

It is important to note that many of the tags included in the Struts HTML tag library have many attributes to allow the developer full control over the functional and presentation capabilities of a JSP page employing these tags. For instance, many of the HTML tags allow the following attributes to be used:

Attribute(s)

Description

onsubmit, onclick, ondblclick, onchange, onkeypress, onkeyup, onmousedown, onmousemove, onmouseup, onblur

For the execution of JavaScript routines as a result of one of these events.

style, styleClass

For association of CSS classes with the element.

tabIndex

For assigning a tag key index with the element.

titleKey, title

Value of a mouse-over label for this element. Can be either the key of a value in messages.properties or a free-hand value.

disabled

Attribute to disable a form element from user input.

All the items listed in this section must be used within the confines of an html:form element. Generally, with HTML form tags, a property attribute denotes the associated form bean property that this element represents.

html:form

The html:form tag, shown in the following code, defines a Struts-powered form in a JSP page and indicates to the framework that this entry and its corresponding closing tag contain form elements that will be populated by a Struts form bean object. Once processed, the tag will render a typical <form..> HTML tag to be used appropriately by the browser. The value of the action attribute must correspond to a defined action in struts.

<html:form action="/login" method="POST">
...
</html:form>

html:text

This tag renders an input type text form element.

<html:text property="firstName"/>

html:password

This tag renders an input type password form element. This tag is basically the same as the html:text tag except that the value is not readable to the user. This element can also be set to be blanked out if the form is redisplayed because of some validation error with the redisplay attribute.

<html:password property="password" redisplay="true"/>

html:textarea

This tag is used to render a text area HTML element based on the property specified.

<html:textarea property="comments"></html:textarea>

html:hidden

This tag renders a hidden HTML form element for the specified property.

<html:hidden property="id"/>

html:checkbox

This tag renders an HTML check box form element, with its state appropriate to the form bean property specified. The form bean should use a boolean type property to denote such values.

An interesting problem often associated with a check box is that were it to be deselected by the user and submitted, there would be no corresponding request attribute associated with this form element. Struts handles this problem by advising that the property should be set to false in the form bean's reset() method. This enables the property to reflect the desired value when the form is submitted.

<html:checkbox property="approved"/>

html:radio

This tag renders an HTML radio button element based on the property specified.

<html:radio property="sex" value="M"/>
<html:radio property="sex" value="F"/>

html:select and html:options

<html:select property="name">
    <html:options collection="myList" property="value" labelProperty="label"/>
</html:select>

This extremely convenient tag combination renders a select list of the objects stored in the Collection object nominated under the collection attribute. In the preceding example, the name myList refers to an ArrayList of LabelValueBean objects that has been added to the scope of the JSP page. The property attribute in the html:select tag refers to the property of the form bean this select list represents, while the property and labelProperty attributes of the html:options tag represent values of each object rendered within the select list. Therefore, you can surmise from this that each LabelValueBean has at least two getter methods, getValue() and getLabel(). Using this tag enables the developer to select the current value in the provided list, corresponding to the current value in the form.

LabelValueBean is a convenience class provided by Struts for just this purpose, but an html:select tag could iterate over a collection of any object you nominate, as long as the property and labelProperty attributes correspond to getter methods of that object. You will see an example of this tag in the "Example application" section toward the end of this chapter.

html:submit

This tag renders an HTML Submit button for the form.

<html:submit value="Save"/>

Other HTML tags

As well as the form elements of the HTML set of tags, Struts provides another set of tags that help render HTML elements. These tags are described in the following sections.

html:link

This is a useful tag for rendering a link to the specified URL:

<html:link page="/somepage.jsp" name="myMap">my link</html:link>

Developers can dynamically provide a list of parameter name-value pairs in order to append to the link, so the preceding example might render the following in plain HTML:

<a href="/somepage.jsp?param1=value1&param2=value2">my link</a>

where the parameter names and values are stored in a Map object added to the scope of this page, in this case represented by the bean myMap.

html:errors

This very useful tag renders either a collection of error messages placed within the scope of the page or an individual error message from that collection. The property attribute denotes the name of the error to be displayed. Not using the property attribute will result in all errors being displayed. An example of an ActionMessages collection of ActionMessage objects being placed within the scope of a JSP page using the saveErrors method was illustrated earlier in the login form example.

<html:errors property="name"/>

The display of this tag can be enhanced by placing some specifically named properties within the messages.properties file for this application. Including the following properties and their example values will mean that each error message displayed will be contained within an HTML unordered list, and that each element of the list will be red:

errors.header=<ul>
errors.prefix=<li><font color=red>
errors.suffix=</font></li>
errors.footer=</ul>

html:messages

There are two basic examples of this:

Example A:

<html:messages id="message">
    <bean:write name="message"/>
</html:messages>

Example B:

<html:messages id="message" property="welcomeNote">
    <bean:write name="message"/>
</html:messages>

This is similar to the html:errors tag but is used to reference ActionMessages added to a scope of the JSP page as an iterable collection (example A), or to access a specific message, as in example B, where the ActionMessage "welcomeNote" is printed out. These messages may have been saved in a Struts action using the saveMessages method.

html:javascript

Use this tag to render dynamic validation configured for the form bean nominated in the formName attribute. An example of how this is configured will be introduced in the next section. The staticJavascript attribute alerts the tag to allow the developer to include custom JavaScript entries, in addition to the dynamic scripts generated by the framework.

<html:javascript
      formName="loginForm"
      dynamicJavascript="true"
      staticJavascript="false"/>

Logic tags

The logic tags provided by Struts offer much of the functionality provided within the JSTL. Indeed, the Struts documentation recommends that the JSTL be used wherever possible in preference to the Struts tags.

The logic tags provided by Struts deal with conditional and iterative operations, such as looping over a collection of objects and testing the value of bean properties.

logic:equal

The logic:equal tags allow for the test of a property of a bean within a certain scope. In the following example, the LoginForm bean's username property is being tested. If the value results in "Nicholas", then the special message is displayed. All the attributes for this tag can utilize real-time expressions as values. This tag is equivalent to the c:if tag in JSTL.

<logic:equal name="loginForm"
             property="username"
             scope="request"
value="Nicholas">

    This is a special message for Nicholas.

</logic:equal>

logic:notEqual

This tag is the opposite of the logic:equal tag.

<logic:notEqual name="loginForm" property="username" value="Rupert">

This is a message for people who don't have Rupert as their username:

</logic:notEqual>

logic:match

This tag is used to test whether a property of a bean contains a substring, in this case uper. Other attributes such as location enable a substring match to be tested at a certain point in the property.

<logic:match name="loginForm" property="username" value="uper">
    This is special message for usernames with 'uper' in them
</logic:match>

logic:notMatch

This tag is basically the reverse of logic:match. The content is evaluated if the value being tested (in the property attribute) does not include as a substring the value of the value attribute.

<logic:notMatch name="loginForm" property="username" value="Jon">
    This is a special message for people without 'Jon' in their login name
</logic:notMatch>

logic:forward

This tag redirects the request to the global action forward nominated in the name attribute. The name attribute must refer to a configured global ActionForward entry.

<logic:forward name="error"/>

logic:redirect

This tag performs a redirect to a location it specifies. The three key attributes that specify that location are forward, href, and page. At least one of these must be specified. Parameters can be appended to the resulting URLs of these attributes by including a reference to a Map object of name-value pairs in the name attribute, similar to the html:link tag described earlier.

Alternatively, a single parameter can be added by specifying the name of the parameter in the paramId attribute and the corresponding value of the bean specified in the paramName attribute, as in the following example. Here, favouriteColor is a String bean whose value may resolve to red or blue and so on. The resulting URL would be /login.do?color=red.

<logic:redirect forward="error"/>
<logic:redirect name="myParameterMap" href="http://jakarta.apache.org/index.html"/>
<logic:redirect paramId="color" paramName="favouriteColor" page="/login.do"/>

logic:iterate

As the name suggests, the logic:iterate tag is used to iterate over its content for each item contained in the collection specified. The collection specified must be one of the following:

  • An object that implements the interface java.util.Collection, such as java.util.ArrayList

  • An object that implements the interface java.util.Enumeration, such as java.util.StringTokenizer

  • An object that implements the interface java.util.Iterator (which can be retrieved from a Collection object)

  • An object that implements the interface java.util.Map, such as java.util.HashMap

In the following example, the ArrayList used in the html:select example from earlier is used. The name of the bean referring to this collection is specified in the name attribute. The id element gives each member of the collection a name to refer to, scoped only within the boundaries of the tag. The type parameter tells the logic:iterate tag the class of the objects the collection contains. Each object is then accessed within the body of the tag by using the bean:write tag, which is explained in the next section. (For now, it just means "print this.")

<logic:iterate name="myList"
               id="thisElement"
               type="org.apache.struts.util.LabelValueBean">

   <bean:write name="thisElement" property="label"/> -
   <bean:write name="thisElement" property="value"/><br>

</logic:iterate>

This tag also supports various other convenient functions, such as starting the iteration at a certain index in the collection (offset attribute) and setting a maximum for the number of items that will be iterated over (length attribute).

logic:messagesPresent and logic:messagesNotPresent

There are two examples for this tag.

Example A:

<logic:messagesPresent property="name" message="false">
   <tr>
        <td><html:errors property="name"/></td>
   </tr>
</logic:messagesPresent>

Example B:

<logic:messagesNotPresent message="false">
   <tr>
       <td>So far, there have been no errors</td>
   </tr>
</logic:messagesNotPresent>

When messages are introduced to the scope of a JSP page, either as errors or as informational messages to the user, their presence, or absence, can be tested with these two complementary tags. In example A, the code is testing for the presence of an ActionMessage (assigned as an error—saveErrors) given the name "name". In example B, the code is testing for the absence of any message assigned as an error within the scope of the page.

These two examples relate specifically to errors added to the scope of the JSP page. What if the presence of normal informational messages (saveMessages) needs to be tested? By changing the message attribute to true, the test would restrict itself to just these messages.

Bean tags

An additional set of tags provided by Struts relates to the manipulation of bean objects within the JSP page. Much of the functionality provided here is also available via the JSTL tags. The Struts documentation recommends using the JSTL tags over the Struts tags wherever possible.

Strut's bean tags provide the capability to create and access objects or beans within a JSP page. The tags are designed to allow this interaction across the multiple scopes that are available: page, request, session, or application. The beans these tags provide for could come from within the JSP page or as objects made available by an action class.

bean:define

The bean:define tag is used to define and manipulate beans from or in any scope. This tag can be used in many capacities, three of which are illustrated by the following examples.

Example A:

<bean:define id="testVariable" value="This is a new String"/>
<bean:write name="testVariable" scope="page"/>

Example B:

<bean:define id="newSessionBean"
             name="testVariable"
             scope="page"
             toScope="session"/>
<bean:write name="newSessionBean" scope="session"/>

Example C:

<bean:define id="customerName" name="myCustomer" property="name" scope="request"/>

In the first example, a new String bean has been defined in the JSP page, and then its value has been rendered for display to the browser in the response using the bean:write tag.

In the second example, the bean created in example A has been copied as a new bean into the session scope, available via the id newSessionBean. It has been printed out using the bean:write tag as well, but this time telling the tag that the bean it is looking for is in the session scope.

In the final example, a Customer object has been added to the request scope of this JSP page. A property from this object has been defined as a new bean within the JSP page, called customerName. The use of the property attribute tells us that there must be a getName method in the customer object for this value to be accessed.

The bean:define tag is similar in some ways to the jsp:useBean tag.

bean:write

You saw some examples of bean:write tags in the previous set of examples. As is obvious by now, the bean:write tag is used to render values to the browser in the response. In addition to rendering text, this tag can also be used to format its output as desired. This is illustrated in the following example:

<bean:write name="customerName" scope="page"/>
<bean:write name="money" format="$###,###.00" ignore="true"/>

The bean:write tag has made use of its format attribute to determine how the value of a Float object should be displayed. If the Float object represented 100.2, then this tag would print out $100.20. The values to place in the format attribute use a formatting syntax common to some of the formatting objects available in the Java language. For more details on this syntax, have a look at the Java API documentation for the DecimalFormat or SimpleDateFormat classes.

This example also uses the ignore attribute. This indicates to the JSP compiler that should the money bean not be found in the designated scope, nothing should be printed. If this were left as the default false, a JSP compile error would result when this page first loaded and no money bean was found.

bean:include

The bean:include tag enables the response from a request to another Web resource to be the value of a new bean; in the following example this is called theAge. The external resource can be referenced using one of three attributes:

  • href: A fully qualified URL, as in the preceding example

  • forward: The response generated from a global forward ActionForward defined in the application, such as error

  • page: A resource located within the context of the current application, such as /error.jsp

<bean:include id="theAge" href="http://www.theage.com.au"/>
<bean:write name="theAge"/>

bean:message

This is another tag that provides the capability to display internationalized messages from the resources provided by the application.

<bean:message key="application.name"/>

<bean:message key="application.welcome" arg0="Peter"/>

The first example has simply rendered a value from the messages.properties file for this application called application.name. The entry in messages.properties would be something like the following:

application.name=My Application

The second example shown also renders an entry in the messages.properties file but it adds a parameter to the message. This way, the JSP page can display messages from the messages.properties file with runtime data intermingled into the message. In this example, the application.welcome message is being displayed and the parameter "Peter" is being added using the attribute arg0. The definition of the application.welcome message in the messages.properties file would be as follows:

application.welcome=Welcome {0} to My Application

This would render on the browser as Welcome Peter to My Application. Very handy. Up to four arguments can be specified using the attributes arg0 through to arg4; these are replaced in the property using the {0}..{3} syntax to denote each parameter.

This section has introduced you to some of the more commonly used custom tags provided by Struts. It is by no means an exhaustive list; readers should consult the Struts documentation for that. What the documentation doesn't provide are clear concrete examples to solidify understanding. We hope that this discussion has achieved that for you.

Our tour through the Struts View layer is not yet over. The next section introduces some of the validation mechanisms available to application developers.

Validation

Validation of user input is a key requirement for many Web applications and presents the developer with many challenges. Struts provides a number of strategies to help you overcome these challenges while keeping the implementation clear and simple. Often, hand-crafted validation techniques leave the validation logic littered all over the application. Using a validation framework avoids this.

Validation can be divided into two areas:

  • Validating the input from the user in the context of the form only. Examples might include verifying the following:

    • That the data was entered in the correct format (date, numeric, alphabetic, e-mail addresses, passwords, and so on).

    • That data was entered at all.

    • That a certain combination of data was entered correctly. For example, if the user entered a value in form element A, then form element B should be empty, and so on.

  • Validating that the input from the user does not violate any business rules defined in the system:

    • If a new user is registering on a Web site, has the login name entered been used before?

    • Does the username and password entered match a valid user record in the database?

    • Was the credit card transaction approved?

The second category could be filled with endless business logic scenarios for which the answer lies not only in the form the user has just filled out and submitted but also in the state of the application and its underlying data at that time. Struts concentrates predominantly on the first category of validation, and provides two methods for this. These methods are described in this section. This validation discussion ends with a suggestion for how to handle business logic validation.

Generally, the goal of validation is to check the input from the user and, if a rule has been violated, display a message and redisplay the form page once again. This flow was described in the LoginAction discussion, where the login of the user was checked and if it was found to be incorrect, the previous page was redisplayed with an appropriate error message. Although this is an example of business logic validation, it illustrates the flow of events that is often desirable in a Web application.

The following sections describe the strategies Struts provides to validate input from the user.

Form bean validation

Form bean validation places the validation rules within the form bean class itself (LoginForm from the previous example). More specifically, it places it within a special method of the form bean that the Struts framework knows to call before the execute method of the Action class. The appropriate form bean and whether to call this special method are determined in the struts-config.xml file. Of course, this method does not apply to DynaActionBeans.

Here we build on the LoginForm example described earlier; the validation rules we may want to implement extend only to checking that the two fields, username and password, are not empty. With this knowledge, we can implement a validate method in the LoginForm class as follows:

...
public class LoginForm extends ActionForm
{
    private String _username;
    private String _password;

    public LoginForm()
    {
    }

...
    public ActionErrors validate(ActionMapping mapping,
        HttpServletRequest request)
    {
        ActionErrors errors = new ActionErrors();

        if ((_username == null) || (_username.length() < 1))
        {
            errors.add("username",
                new ActionMessage("error.login.username.missing"));
        }

        if ((_password == null) || (_password.length() < 1))
        {
            errors.add("password",
                new ActionMessage("error.login.password.missing"));
        }

        return errors;
    }
}

This new method is executed after the form is submitted but before the execute method of the LoginAction is invoked by the framework. If the ActionErrors object this class returns is null or empty, then processing continues on the LoginAction class as normal. If the ActionErrors object this method returns contains errors, then the input page that submitted the form is redisplayed and the values of the form are populated with what was originally entered. We should point out here the use of the ActionErrors object to house ActionMessages. ActionErrors is a subclass of ActionMessages. Some of the areas where ActionErrors have usually been used, such as in an Action class, have been deprecated in favor of the ActionMessages collection. For the time being, though, the validate method used here will need to return an ActionErrors collection. If you're using an older or a future version of Struts, make sure that you check the usage of these objects; otherwise, you may encounter problems. The Struts documentation alludes to a general theme of not using the ActionErrors object where possible, in favor of the ActionMessages object.

In the validate method in the previous code, the two fields _username and _password are checked to ensure they are not null and not empty, meaning that at least something has been typed in by the user. If either of the fields is found to be empty, a new error is added to the ActionErrors object. When adding the error to the ActionErrors object, the add method is used. This method, in this instance, takes two parameters. The first indicates the property for which this error applies. The second is the ActionMessage itself. The ActionMessage object is constructed with the name of an appropriate message property key available within the application. When the validate method returns a populated ActionErrors object to the input page, these messages are available to inform the user of what happened. To show these messages to the user, the html:errors custom tag is used:

...
<html:errors property="username"/><br>
Username: <html:text property="username"/><br>
<P>
<html:errors property="password"/><br>
Password: <html:password property="password"/><br>
...

In the preceding example, the code to display the appropriate error messages above the associated form element has been highlighted.

So how does the framework know which bean's validate method to invoke? And where is this input page thing? Let's recall the action-mapping in struts-config.xml for the LoginAction:

...
<action-mappings>
  <action path="/login"
          type="com.wrox.begjsp.ch19.struts.simpleform.LoginAction"
          name="loginForm"
          input="/login.jsp"
          validate="true"
          scope="request">
    <forward name="success" path="/welcome.jsp"/>
  </action>
</action-mappings>
...

The three attributes responsible for making this validation flow work have been highlighted. The name attribute indicates which form-bean entry maps to this action. The input attribute indicates the path that invoked this action (and where to go if an error occurs), and the validate attribute being set to true tells the framework that when this action is invoked, the validate method of the form-bean should be used.

As you can see, setting up basic form validation in this way is very simple and straightforward. It also ensures that the logic to do this for each interface is in one place and that the workflow from form submission to form error is clear and concise when reading the code. Of course, this is not the only way to implement framework-sponsored validation using Struts.

Struts Validator

Using the form bean validation method means that all the validation logic, and in a big application it can be a lot of such logic, is localized in the ActionForm classes. Logic in this location is very difficult to change in a live application, and the person changing it must be a Java programmer. In response to these constraints, a more configurable validation mechanism has evolved.

The Struts Validator has evolved from a set of validation classes available under the Jakarta Commons project, and it has since been adopted into Struts 1.1 with some enhancements. Struts Validator places the validation rules for form beans inside an XML file, validation.xml. The rules defined in this XML file utilize a set of predefined validation mechanisms defined in a file provided by Struts, called validator-rules.xml. Put another way, the validation.xml file defines which rules will govern validation for forms and how they should behave, whereas validator-rules.xml defines these rules and references appropriate Struts objects to encapsulate the logic.

The validation provided by the Struts Validator can be called in two ways: on the client side or on the server side. Client-side validation is made possible by the fact that the validation rules provided by default with Struts also define equivalent JavaScript functions that can be placed on the page if required. This JavaScript is placed on the JSP page by using the Struts html:javascript tag:

<html:javascript dynamicJavascript="true"
                 formName="loginForm"/>

You can see from the preceding example that the dyanamicJavascript attribute of true indicates to the framework that the Validator-generated JavaScript should be placed here (you would put the tag above the HTML <body> tag). The formName attribute identifies the form bean to which the rules apply.

Leaving the preceding tag out of the JSP page leaves the validation to occur on the server side. In simple terms, each validation rule provided by Struts references an object that performs the validation based on the parameters it has been provided by the validation.xml file, and in so doing behaves almost exactly like the LoginForm validation that was implemented earlier.

The Struts Validator expects the form bean being validated to be of a certain type; therefore, the form bean used to represent the HTML form must now extend the org.apache.struts.validator.ValidatorForm (or org.apache.struts.validator.ValidatorActionForm) class instead of the ActionForm class.

If a DynaActionForm is currently being used, the org.apache.struts.validator.DynaValidatorForm (or org.apache.struts.validator.DynaValidatorActionForm) class must now be specified as the type attribute for the form-bean entry in the struts-config.xml file instead of the DynaActionForm class.

Defining the validation rules

So how do we actually define the validation rules? First, the Struts Validator is a plug-in to Struts, so it must have a corresponding entry in the struts-config.xml file:

<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
    <set-property property="pathnames"
                  value="/WEB-INF/validator-rules.xml,
                         /WEB-INF/validation.xml"/>
</plug-in>

This entry tells the framework where these two all-important files are located. The preceding entry places them in the /WEB-INF/ directory of the application.

As described, the validation.xml file tells the framework the rules that apply to each of the fields in each of the forms that the application uses. Let's look at the contents of this file for our login example. The validation rules implemented here specify that both of the fields, username and password, cannot be empty, i.e., that they are required:

<form-validation>
    <formset>
        <form name="loginForm">
          <field property="username" depends="required">
            <msg name="required" key="error.login.username.missing"/>
          </field>

          <field property="password" depends="required">
            <msg name="required" key="error.login.password.missing"/>
          </field>
        </form>
    </formset>
</form-validation>

The simple XML structure here is quite easy to read. The entry first defines a formset, the set of forms that this file defines validation for, and within this structure is a corresponding entry for each form. Within the form definition is an entry for each field of the form, and it is this entry that defines the validation rules and corresponding error messages for each of the form elements. Note the use of the depends attribute in the form element. In plain English, this would mean that this field depends on this rule. The depends attribute can also take a comma-separated list of rules that the field must satisfy. The nested msg element within the field entry nominates messages to be displayed for each of the rules specified in the depends attribute. Where no message is specified for a rule, the Struts Validator will search for a default message key in the messages.properties file. A list of these keys can be found in the validator-rules.xml file.

The required rule is a very simple one: The field is either empty or not. Other rules need variables to define their behavior, such as minlength. This rule ensures that the length of data placed in a field must be at least a certain length.

So what is the minimum length and how is this specified? Let's look at an example to explain this. If the login form needed the password to not only be required but also to be, for example, at least six characters long, the field entry would appear as follows:

<field property="password"
       depends="required,minlength">
 <msg name="required" key="error.login.password.missing"/>
 <msg name="minlength" key="error.login.password.length"/>
 <arg0 name="minlength" key="${var:minlength}" resource="false"/>
 <var>
        <var-name>minlength</var-name>
        <var-value>6</var-value>
 </var>
</field>

Whoa! Our simple validation entry just got a lot more complicated. Let's walk through this bit by bit. The field entry now depends on two rules: required and minlength:

<field property="password" depends="required,minlength">

A new error message has been added for cases when the minlength rule is broken:

<msg name="required" key="error.login.password.missing"/>
<msg name="minlength" key="error.login.password.length"/>

This is defined in the messages.properties file as follows:

error.login.password.length=The password must be {0} characters long

Note the parameter placeholder in the message. This is filled in by the next entry, an argument for the minlength msg entry:

<arg0 name="minlength" key="${var:minlength}" resource="false"/>

This passes a value to the error message for the minlength rule. The parameter passed to the message is represented by the value specified in the key attribute. And as with parameters passed in the html:message tag described earlier, up to four arg parameters (arg0 ... arg3) can be used. In this case, the value is actually a reference to the next entry, where the minlength rule variable is specified:

<var>
    <var-name>minlength</var-name>
    <var-value>6</var-value>
</var>

One of the strengths of the Struts Validator is that it allows for some very sophisticated validation logic to be implemented, even when just using the default set of validation rules provided by Struts. Following is a list of the other rules that can be used with the default validator-rules.xml:

  • maxlength: Similar to minlength. It takes only one variable, the maximum length for the field.

  • mask: Allows a validation rule to be specified with regular expressions. Regular expressions are patterns designed to match strings of text. Regular expressions are an extremely powerful method for identifying a particular pattern of text. For instance, the following mask example requires that the field in question must contain only numbers and alphabetic characters, but nothing else:

    <field property="username" depends="mask">
      <msg name="mask" key="errors.login.username.mask"/>
      <var>
        <var-name>mask</var-name>
        <var-value>^[0-9A-Za-z]*$</var-value>
      </var>
    </field>

The mask rule takes one variable, the mask regular expression to be tested.

  • byte, short, integer, float, double, long: All these rules ensure that the value entered in a field can be converted to the specified type. None of these rules require variables.

  • date: Date validation is challenging on many levels. This rule ensures that at least what has been entered can be converted to a java.util.Date object. The date rule uses the SimpleDateFormat class to confirm that the text entered can become a Date object. It also allows two variables to be optionally specified:

    • dateFormat: The format in which the date should be entered

    • datePatternStrict: This specifies whether the rule should be strictly applied, such as cases where the dateFormat dictates that the day field in the pattern dd/MM/yyyy should be two characters but the user enters 3/11/2005, i.e., only one character for the day. true or false are valid values here.

  • intRange, floatRange, and doubleRange: Tests that the field is within a specified range of values defined by two variables. Fields that use these rules must also depend on the integer, float and double rules, respectively. The two variables required for these rules are as follows:

    • min: The minimum value for the field

    • max: The maximum value for the field

  • creditCard: Validates the format of a credit card. The Struts Validator uses the Luhn Formula to determine whether the information is correct. The Luhn Formula is a credit card number validation technique supported by most major credit card companies.

  • email: Validates that the field is a properly structured e-mail address. There are no guarantees, however, that the address actually exists!

  • requiredif: This rule enables the developer to conditionally provide validation for a field based on conditions concerning the values of other fields. Note that this rule will be deprecated in the first Struts release after 1.1 in favor of a new rule called validwhen, and therefore may be removed in any future release after that.

As you can see, the validation rules provided by the Struts Validator can be very sophisticated and cover many common scenarios required by applications. If you find that the default set of rules does not satisfy your scenario, then you might consider adding your own Validator. The Struts documentation provides some brief instructions on doing this. Unfortunately, this topic is out of the scope of this book.

Business logic validation

Struts doesn't provide a mechanism to validate the business rules of an application simply because it shouldn't or can't. Rather, the breadth of validation required to ensure that the business rules of an application are kept in check is beyond the scope of an application framework like Struts. All the framework can do, and Struts does this admirably, is to provide a structure and infrastructure within which such rules can be tested, and if broken, dealt with.

One common strategy used in Struts applications is to perform the form element validation in the ways just described and to perform, or call on, business logic validation within the actions themselves.

Suppose you have a system dealing with new customer registration. The validation you need to implement falls into the two categories identified: The general form validation would cover problems such as empty fields, passwords that are not complicated enough, or invalid e-mail addresses. The business logic validation would cover more involved problems such as a login name that is already taken by another customer, or an invalid ZIP code

The register customer form would submit to an appropriate action, maybe saveNewCustomer.do (SaveNewCustomerAction class). Within the execute method of this class, other objects developed for the application could be called to do the appropriate checks and then return a List of error message keys back to the Action class. These error message keys would then be added to an ActionMessages object in the way you have already seen.

The key objectives you want to achieve in structuring business logic validation in this way are as follows:

  • Encapsulating the business logic (away from the concerns of the front end): This helps for reuse elsewhere in the application. Imagine if the business logic rules for what constituted a valid new customer were locked away in the front end of an application and another mechanism other than the Web site needs to create a new customer. The logic isn't very reusable in the Action class.

  • Separation of concerns: You don't want the business logic of the application to know or care about the front end. If it is not dependent on the front end, it can be utilized from other sources, or even separated and implemented elsewhere. This objective also promotes a clean and clearly structured implementation of the application.

The preceding example is just one suggestion for how such validation could be dealt with; there would literally be millions out there. It is a testament to the flexibility of Struts that it enables applications to be implemented in many different ways.

This concludes the discussion about the different layers of an MVC application developed using the Struts framework. A lot has been covered in this section, so it may be helpful to briefly recap the main points before moving on to the examples.

In the Controller layer, you saw the role that the ActionServlet and Action classes play in defining the flow of an application. You also learned how Struts encapsulates HTML forms with ActionForm and DynaActionForm beans. The Model layer was defined within the structure of an application, and it was ascertained that Struts doesn't play a big part in defining its use and architecture.

In the View layer, many points were covered. You first saw how the ActionForm and DynaActionForm beans can interact with the elements of an HTML form. Internationalization was then discussed, with reference to messages provided to the user. You also took a look at the Struts Tag Library, and saw how some of the more commonly used tags were applied. The View discussion was rounded off with validation techniques using Struts and a suggestion for how business logic may be implemented.

Example application

Our attention now turns to providing a simple example of a Struts application and its development. Chapter 18 introduced a simple form submission application to illustrate WebWork and Spring. We will continue using this example application in this section.

The following discussion details the steps involved in setting up a base version of this small application. If you missed the discussion in Chapter 18, this application is a simple form inviting users to enter their name and some comments into a form. The form can then be submitted. If the user has not entered a name, the application informs the user of the error and redisplays the form.

Once you have established that this base application is working with Tomcat and that you can deploy the application using Ant, you can begin to implement some enhancements.

How It Works

Before we have a look at how the application has been put together, submit the form. You should be presented with a validation message just above the Name field. If you enter your name and click Submit again, you will be presented with a mostly blank page. Pretty boring, huh?

Open the c:struts-basewebindex.jsp file and have a look at what's currently implemented:

<html:form action="/test" method="POST">
    <table width="350" border="0" cellpadding="3" cellspacing="0">
    <logic:messagesPresent message="false">
    <tr>
        <td colspan=2><b><bean:message key="errors.global"/></b></td>
    </tr>
    </logic:messagesPresent>
    <logic:messagesPresent property="name">
    <tr>
          <td colspan=2><html:errors property="name"/></td>
    </tr>
  </logic:messagesPresent>
    <tr>
        <td><bean:message key="form.name.title"/></td>
        <td><html:text altKey="form.name.title" property="name" size="30"/></td>
    </tr>
    <tr>
        <td valign="top"><bean:message key="form.comments.title"/></td>
        <td>
            <html:textarea property="comments" cols="30" rows="8"></html:textarea>
        </td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td align="center"><html:submit/></td>
    </tr>
    </table>
</html:form>

You will notice a simple form that posts to an action: /test. The form also has Struts HTML tags for the two fields, Name and Comments, as well as a placeholder for an error message for the Name field. There is a logic:messagesPresent test around this.

This form is represented by a DynaValidatorForm configured in the struts-config.xml file:

<form-beans>

    <form-bean       name="testForm"
                     type="org.apache.struts.validator.DynaValidatorForm">
      <form-property name="name" type="java.lang.String"/>
      <form-property name="comments" type="java.lang.String"/>
    </form-bean>

</form-beans>

A plug-in entry has also been added to the struts-config.xml file for validation rules:

<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
    <set-property property="pathnames"
              value="/WEB-INF/validator-rules.xml,
                     /WEB-INF/validation.xml"/>
</plug-in>

A messages resource has also been added:

<message-resources parameter="messages" null="false"/>

This denotes that a file called messages.properties will be located in the root package of the classpath. If you open the file located at c:struts-basesrcmessages.properties you will see the messages presented on the page, labels, the page title, and so on, as well as the error message that was displayed.

In the validation.xml file, also located in the c:struts-basewebWEB-INF directory, rules for the form have been added. They currently ensure only that a value is entered in the Name field:

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE form-validation PUBLIC
   "-//Apache Software Foundation//DTD Commons Validator Rules Configuration
    1.1.3//EN"
   "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<form-validation>
    <formset>
        <form name="testForm">
              <field property="name" depends="required">
                <msg name="required" key="form.error.name.missing"/>
              </field>
        </form>
    </formset>
</form-validation>

The TestAction that has been implemented is about as basic as it gets; the entire class listing is as follows:

package com.wrox.begjsp.ch19.struts;

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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestAction extends Action
{
public ActionForward execute(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception
    {
        return mapping.findForward("success");
    }
}

This action as well as the success forward, is configured in the struts-config.xml as follows:

...
<action-mappings>
  <action path="/test"
                type="com.wrox.begjsp.ch19.struts.TestAction"
                name="testForm"
                validate="true"
                input="/index.jsp"
                scope="request">
         <forward name="success" path="/details.jsp"/>
  </action>
</action-mappings>
...

You can see that the testForm form bean is referenced along with the validate=true setting, ensuring that this action validates the input as soon as it is invoked.

Adding enhancements

The discussion from here on will focus on adding some features to this simple application in order to use some of the Struts skills you have learned in this chapter. A modest list of enhancements to implement is as follows:

  • Add validation for the Comments field, making sure it has a value when the form is submitted.

  • Add validation for the Name field, making sure the name is only alphabetic characters when the form is submitted.

  • Add a new field to the form for the user's e-mail address. Add validation to this field to ensure that only a properly structured e-mail address can be entered.

  • Add another field to the form from which users select their favorite sport from a list of possible sports. Add validation to ensure that a sport is selected.

  • Display all the information in the form on the following page after the form has been submitted.

The following example will lead you through making these changes to the struts-base application. If you'd like to skip ahead, you could download the completed application in the struts-enhanced.zip file from the Web site for this book. Simply follow the same steps as outlined previously to get it going.

How It Works

This change simply configures the Validator to ensure that the Comments field is submitted with a value. The JSP page was modified to ensure that an error message appears when an error for the Comments field is detected.

How It Works

In order to enforce the requirement for the name field we have used a mask validation entry to the validation.xml file. This validation entry specifies that an error should be raised if the name field's value is something other than an upper- or lowercase alphabetic character. The ^ character tells the validation mechanism that errors are raised for characters not in the trailing expression [a-zA-Z]. This new validation rule is associated with the mask msg specified in the third line of the last listing.

How It Works

The new field was added to the definition of the form in the struts-config.xml file. With this new field available, we were able to add appropriate validation to the validation.xml file. In order to ensure that a properly formed e-mail address is entered by the user, the e-mail validation setting was used in the depends attribute for this field. The new field was also added to the index.jsp file so that the user has somewhere to enter the value. Messages were added to the messages.properties file so that validation and labels used in this change could be displayed where appropriate.

Summary

While a lot has been covered in this chapter, there are still more aspects of Struts that deserve attention, including the following:

  • Data source specification

  • More complex validation techniques using the Struts Validator

  • More Struts Custom Tags

  • Struts EL

  • Struts modules

  • Tiles

Unfortunately, these topics are beyond the scope of this chapter. For more information on Struts, you are encouraged to check out the voluminous documentation available at the Struts Web site (http://jakarta.apache.org/struts/userGuide/index.html) or refer to Professional Jakarta Struts (ISBN 0-7645-4437-3).

After working through this chapter, you should have an understanding of the following:

  • The purpose of Struts with regard to its use as an MVC framework

  • How Struts works and Validator configuration

  • The role of Actions, form beans, and JSP files in a Struts application

  • The different validation approaches available with Struts

  • The many custom tags provided with Struts

Exercises

Further enhance the example application with the following exercises:

  1. Add a new field to the form to collect the user's age, and add validation to the field requiring a valid numeric value that restricts the range of acceptable values to between 0 and 100, inclusive.

  2. Add a login page to protect the application. An unauthenticated user should not be able to get to any page without being logged in.

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

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