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
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.
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.
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.
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:
Establish some basic information, such as the path that has been invoked, the content type, and other request-specific information such as cache settings.
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.
Create, populate, and validate any ActionForm
objects that have been associated with the ActionMapping
, according to the Struts configuration.
If necessary, forward the request to a resource if this has been configured.
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 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:
data sources
form beans
global forwards
action mappings
controller
message resources
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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 |
---|---|
| For the execution of JavaScript routines as a result of one of these events. |
| For association of CSS classes with the element. |
| For assigning a tag key index with the element. |
| Value of a mouse-over label for this element. Can be either the key of a value in |
| 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"/>
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"/>
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"/>
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¶m2=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"/>
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.
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 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 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.
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.
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.
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.
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.
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> </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.
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.
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.
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.
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.
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
Further enhance the example application with the following exercises:
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.
Add a login page to protect the application. An unauthenticated user should not be able to get to any page without being logged in.
3.134.104.188