This is the second article in a three-part series on the Struts framework. In the first article, Introduction to Jakarta Struts Framework I defined the Struts framework,
discussed what it can accomplish, and
provided an overview of the various components used throughout the framework.
In this article I describe building a simple sample
application from scratch using Struts 1.0. The third article will show you how to use the Struts tags to access the ApplicationResource file from a JSP.
Taking a step-by-step approach should open some new doors and show the potential of using Struts in your application development. If you aren't familiar with Struts terminology, take a quick read through the introductory article.
This article assumes a working knowledge of JSP, Servlets, Custom Tag libraries, and XML. Additionally, I will be using a number of Jakarta projects throughout this article, including Tomcat 3.2.1, the servlet container used in the official reference implementation for the Java Servlet and JSP technologies (the latest version, 3.2.3, implements some security fixes to 3.2.1), and Ant, a Java-based build tool.
As a developer who has written hundreds of applications using leading- (and usually bleeding-) edge technology, I am a strong believer in understanding the logical flow of new technology development. While it's possible to just dive in and learn as you go (which all of us are guilty of), I'd like to take this opportunity to go through a typical development flow when using Struts. I'll apply this development flow to our simple sample, but you will be able to apply these steps to larger, more complicated development scenarios. I have followed these steps on large projects, and they work.
Anyone who has written a business or Web application knows the requirements list is always somewhat dynamic; having a development flow to follow will at least help you identify what additional tasks need to be added to the list. Let's list the flow, and then I will explain and apply it to our sample.
Throughout this article I will be using code snippets from the
StrutsSample application. The full code, including the build.xml file to build
and deploy the application, is available for download here.
As you can tell by the release number, Struts is relatively new. There are a number of components that work together, but knowing when you have to complete each component can save much time, to say nothing of your sanity. I've written a number of applications that utilize Struts, and I've found that following an approach similar to this one is effective:
struts-config.xml, web.xml.|
Also in JSP and Servlets: Learning the New Jakarta Struts 1.1, Part 2 |
The first step in any application development is to gather the application requirements. However logical this might sound, it is sometimes harder then it seems. Having a written set of requirements is important not just for development purposes, but also for identifying places that need further definition.
The application requirement for our project StrutsSample is as follows:
Provide a sample application allowing a user to log in, in order to demonstrate a complete logic flow using the Struts framework. Do not bog the sample down with concerns about security, database interaction, EJB development or any other ancillary technology that might be used in a more complicated application.
This application will have three screens:
|
|
Related Reading
|
ActionMappings are basically the roadmap for the application. The
ActionMappings define what happens at any given point in an application.
ActionMappings are defined in the struts-config.xml file. ActionsMappings work
with forwards to provide the flow of the application.
Usually you build up ActionMapping information, such as the form name or
input, as you continue your development. Primarily, this development step is
meant to start forming the strawman of the struts-config.xml file. The forwards
for an action indicate what will happen when the action class returns one of the
defined MappingForwards. It is entirely possible to have many forwards available
to an action. Each action class can define specific forwards that are relevant
to that class. Also, keep in mind that it is possible to have global forwards.
Think of it as an include file for each action. If a returned forward is
not specific to the ActionMapping but is defined as a global forward, then
the global definition will be used. Local forwards can always be used to
override global forwards. In our sample below, if an "error" mapping is returned
from an action, then Errorpage.jsp will be displayed, even though that
forward is not specific to any action.
You will see as you continue your development that this config file can get
rather detailed in larger projects. In the sample below, taken from the
StrutsSample struts-config.xml file, we can see global forward definitions and a
sample action mapping. This mapping defines an action called "login" that is an
instance of the class com.oreilly.actions.LoginAction.
On a successful login the "success"
forward will display Welcome.jsp. If a "failure"
forward occurs, Login.jsp will be redisplayed. If an "error" forward is
returned, then the global forward will be used and display
Errorpage.jsp
<!-- ========== Global Forward Definitions -->
<global-forwards>
<forward name="login" path="/Login.jsp"/>
<forward name="error" path="/Errorpage.jsp"/>
</global-forwards>
<!-- ========== Action Mapping Definitions -->
<action-mappings>
<!-- Attributes of the <action> element -->
<!--
For other available attributes see the JavaDocs for
org.apache.struts.action.ActionMapping
path - The request URI path that is matched to select
this mapping.
type - Fully qualified Java class name of the
Action implementation class used by this mapping.
name - The name of the form bean defined in the
config file that this action will use.
unknown - Set to true if this action should be
configured as the default for this application to handle
all requests not handled by another action. Only one
action can be defined as a default within a single
application.
scope - Identifier of the scope ("request" or "session"
within which the form bean, if any, associated with this
action will be created
input - Context-relative path of the input form to which
control should be returned if a validation error is
encountered.
validate - Set to true if the validate() method of the
action associated with this mapping should be called.
forward elements - The set of ActionForwards locally
associated with this mapping.
-->
<!-- =================== -->
<!-- O'Reilly Struts Sample Main Actions -->
<!-- =================== -->
<action path="/login"
type="com.oreilly.actions.LoginAction"
name="loginForm"
scope="request"
input="/Login.jsp">
<forward name="success" path="/Welcome.jsp"/>
<forward name="failure" path="/Login.jsp"/>
</action>
</action-mappings>
As we have already talked about in the first article, struts-config.xml is the
controller in the MVC model. It is well worth spending development time on this
step to make sure your actions match your application requirements. Once all the pieces come into place, you will want to avoid a situation where you have to completely reorganize your actions due to poor definition at the beginning of the project.
Our StrutsSample application requires only one action -- the login. Depending on the size of your application, there can be many actions required and defined. Once the ActionMappings are defined, you can go through each one and start putting together the ActionMapping classes and form classes.
|
All form classes in Struts extend org.apache.struts.action.ActionForm. The
form class is used to correspond to the data fields on a screen. The form class
can also be used for validation on form fields. The Setter/Getter accessor
methods are called automatically on an HTML form submit, thereby being available
to the action class for future use. Usually, an ActionForm class handles all of
the variables on a form, and has the necessary validation for a specific screen.
I tend to keep all of my form and action classes in separate packages; otherwise, it can get too confusing once you get more than a couple of screens in place. The
following is a sample ActionForm class. This is the form that is used for the
login page.
/*
* LoginForm.java
*/
package com.oreilly.forms;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
/**
* Form bean for the Login main page. There are two
* fields on this form used for authentication
* <ul>
* <li>username - the username to login
* <li>password - the password to authenticate
* </ul>
*/
public final class LoginForm extends ActionForm {
private String userName = null;
private String password = null;
/**
* Get the userName
*@return String
*/
public String getUserName() {
return (userName);
}
/**
* Set the userName.
* @param userName
*/
public void setUserName(String newUserName) {
userName = newUserName;
}
/**
* Get the password
*@return String
*/
public String getPassword() {
return (password);
}
/**
* Set the password.
* @param password
*/
public void setPassword(String newPassword) {
password = newPassword;
}
/**
* Reset all properties to their default values.
*
* @param mapping The mapping used to select this instance
* @param request The servlet request we are processing
*/
public void reset(ActionMapping mapping, HttpServletRequest request) {
userName = null;
password = null;
}
/**
* Validate the properties that have been set from this
* HTTP request, and return an
* <code>ActionErrors</code> object that
* encapsulates any validation errors that have been
* found. If no errors are found, return
* <code>null</code> or an
* <code>ActionErrors</code> object with no
* recorded error messages.
*
* @param mapping The mapping used to select this instance
* @param request The servlet request we are processing
*/
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
// For this form, only a username is required
if( userName == null || userName.length()==0 ){
errors.add("userName",new ActionError("error.userName.required"));
}
return (errors);
}
}
There are a couple of interesting points to be made here. The reset method is called if there is a reset button on a form. It sets all of the variables back to their defaults. The validate method is used for any form-specific validation. In this case, we are only interested if the user has provided their username. It might be possible to have no password, so we will let the application logic handle that. Without a username, the user can't be authenticated, so we are required to throw an error.
Errors in Struts can be handled using ActionErrors. Typically, anything ending in "s" is a collection of those objects. So ActionErrors is a collection of ActionError. You can process an entire form and send back multiple errors to display. This is convenient on entry forms that have many required fields, so that the user doesn't have to submit the form five times to get five different errors.
Also note that the errors shown in this sample use the
ApplicationResource.properties file. The properties file is defined in the
web.xml file used by the entire application when Tomcat starts. Usually there is
a web.xml file in the WEB-INF directory of each application. For more
information on how application structures are set up for deployment, see the
user's guide available with the Tomcat download.
The ApplicationResource file allows for all strings displayed in an
application to be defined. A lookup is done with a key. In this sample, the
error.userName.required key will be replaced with the text for that error
message -- "A username is required." This is especially handy for providing
multilingual support for your application. Only the ApplicationResource needs to
be translated, and you can then have a Spanish, German, or French version of your
application.
This can be used for anything that is text displayed on a screen. The ApplicationResource file associated with this project looks like this:
login.title=Login Struts Sample
error.userName.required=A username is required
error.login.authenticate=Invalid username/password
errors.footer=</ul><hr>
errors.header=<h3><font color="red">Page
Validation</font></h3>Please correct the
following error(s) before contiuing:<ul>
applicationResources=Cannot load application resources bundle
{0}
The title of pages, buttons, and anything requiring text can be accessed from this file. We'll show how to use the Struts tags to access the file from a JSP when we get to that development step in the final article of this series.
Sue Spielman is an associate editor for ONJava.com, covering JSP and Servlets technologies. She is also President and Senior Consulting Engineer for Switchback Software LLC.
Read more JSP and Servlets columns.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.