Programming Jakarta Struts, 2nd Edition [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming Jakarta Struts, 2nd Edition [Electronic resources] - نسخه متنی

Chuck Cavaness

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








7.2 What Are ActionForms?


Almost every web application has to
accept input from users. Some examples of user input are usernames
and passwords, credit card information, and billing and shipping
address information. HTML provides the necessary components to render
the input fields in a browser, including text boxes, radio buttons,
checkboxes, and buttons. When you're building these
types of pages, you must nest the input components inside HTML form
elements. Example 7-1 illustrates a very basic
sign-in page, similar to the one used
in the Storefront application.


Example 7-1. A simple sign-in page

<html>
<head>
<title>Example 7-1. OReilly Struts Book</title>
<link rel="stylesheet" href="/image/library/english/10003_main.css" type="text/css">
</head>
<body>
<form method="post" action="/action/signin">
<!-- The table layout for the email and password fields -->
<table BORDER="0" cellspacing="0" cellpadding="0">
<tr>
<td>Email: </td>
<td>&nbsp; </td>
<td>
<input type="text" name="email" size="20" maxlength="20"/>
</td>
</tr>
<tr>
<td>Password:</td>
<td>&nbsp; </td>
<td class="alignformslist">
<input type="text" name="password" size="20" maxlength="25"/>
</td>
</tr>
<!-- The table layout for the signin button -->
<table width="250" border="0">
<tr>
<td>
<input type="submit" name="Submit" value="Signin" class="Buttons">
</td>
</tr>
</table>
</form>
</body>
</html>

When the user presses the Signin button on the HTML form from Example 7-1, the values within the fields are submitted
along with the HTTP request. The server application can retrieve the
values that were entered, perform input validation on the data, and
then pass the data to another component in the application where the
actual authentication process occurs. If the input data fails the
input validation rules, the application should return to the previous
location, redisplay some or all of the values entered, and display an
error message indicating that the login attempt failed.

Manually performing all of this functionality, retrieving the values,
executing the validation, and displaying error messages on failure
can be a daunting task. This type of behavior is performed in many
places throughout a web application, and it would be nice to have it
taken care of by the framework and to be able to reuse it across
applications.

Fortunately, the Struts framework does provide this functionality and
will handle these tasks on behalf of your application. The Struts
framework relies on the
org.apache.struts.action.ActionForm class as the
key component for handling these tasks.

The
ActionForm
class is used to capture input data from an HTML form and transfer it
to the Action class. Because users often enter
invalid data, web applications need a way to store the input data
temporarily so that it can be redisplayed when an error occurs. In
this sense, the ActionForm class acts as a buffer
to hold the state of the data the user entered while it is being
validated. The ActionForm also acts as a
"firewall" for your application in
that it helps to keep suspect or invalid input out of your business
tier until it has been scrutinized by the validation rules. Lastly,
when data is returned from the business tier, a particular
ActionForm can be populated and used by a JSP page
to render the input fields for an HTML form. This allows more
consistency for your HTML forms, as they always pull data from the
ActionForm, not from different JavaBeans.

When the user-input data does pass input validation, the
ActionForm is passed into the execute(
)
method of the Action class. From
there, the data can be retrieved from the
ActionForm and passed on to the business tier.


Because the ActionForm imports packages from the
Servlet API, you shouldn't pass the
ActionForm to the
business tierdoing so would couple the business methods to the
Servlet API and make it more difficult to reuse the business tier
components. Instead, the data within the
ActionForm should be transferred to an object from
the domain model. A common approach is to create a DTO and populate
it with the data from the ActionForm.

You don't have to declare an
ActionForm for every HTML form in your
application. The same ActionForm can be associated
with one or more action mappings. This means that they can be shared
across multiple HTML forms. For example, if you had a wizard
interface where a set of data was entered and posted across multiple
pages, you could use a single ActionForm to
capture all of this data, a few fields at a time.


7.2.1 ActionForms and Scope


ActionForms can have
two different levels of scope: request and session. If request scope
is used, the ActionForm is available only until
the end of the request/ response cycle. Once the response has been
returned to the client, the ActionForm and the
data within it are no longer accessible.

If you need to keep the form data around for longer than a single
request, you can configure an ActionForm to have
session scope. This might be necessary if your application captures
data across multiple pages, like a wizard dialog does. An
ActionForm that has been configured with session
scope will remain in the session until it's removed
or replaced with another object, or until the session times out. The
framework doesn't have a built-in facility for
automatically cleaning up session-scoped
ActionForm objects. As with any other object
placed into the HttpSession, it's
up to the application to routinely perform cleanup on the resources
stored there. This is slightly different from what happens with
objects placed into request scope, because once the request is
finished, they no longer can be referenced and so can be reclaimed by
the garbage collector.

Unless you need to hold the form data across multiple requests, you
should use request scope for your ActionForm
objects.


If you don't specify the scope
attribute for an action mapping, the
ActionForm will default to session scope. To be
safe, you should always explicitly specify the scope of the
ActionForm. To see how to specify the scope for an
action element, see "The Struts
Configuration DTD" in Chapter 4.

When the controller receives a request, it attempts to recycle an
ActionForm instance from either the request or the
session, depending on the scope that the
ActionForm has in the action
element. If no instance is found, a new instance is created.


7.2.2 The Lifecycle of an ActionForm


The section "Using the Struts
ActionForm" in Chapter 3
described the steps the framework takes when an
ActionForm is being used by an application. From
these steps, it's easy to get a picture of the
lifecycle of an ActionForm. Figure 7-4 illustrates the main steps taken by the
framework that have some effect on the ActionForm.


Figure 7-4. The lifecycle of an ActionForm

Figure 7-4 shows only the steps that are relevant
to an ActionForm, not all those that a request
goes through during request processing. Notice that when an
ActionForm detects one or more validation errors,
it performs a forward back to the resource identified in the
input attribute. The data that was sent in the
request is left in the ActionForm so that it can
be used to repopulate the HTML fields.


7.2.3 Creating an ActionForm


The
ActionForm class
provided by the Struts framework is abstract, and you need to create
subclasses of it to capture your application-specific form data.
Within your subclass, you should define a property for each field
that you want to capture from the HTML form. For example, suppose you
want to capture the email and password fields from a form, similar to
the one in Example 7-1. Example 7-2
illustrates the LoginForm for the
Storefront application that can be used to store and validate the
email and password fields.


Example 7-2. The LoginForm stores the email and password fields

package com.oreilly.struts.storefront.security;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.*;
public class LoginForm extends ActionForm {
private String email = null;
private String password = null;
public void setEmail(String email) {
this.email = email;
}
public String getEmail( ) {
return (this.email);
}
public String getPassword( ) {
return (this.password);
}
public void setPassword(String password) {
this.password = password;
}
/**
* Validate the properties that have been sent from the HTTP request,
* and return an ActionErrors object that encapsulates any
* validation errors that have been found. If no errors are found, return
* an empty ActionErrors object.
*/
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
ActionErrors errors = new ActionErrors( );
if( getEmail( ) == null || getEmail( ).length( ) < 1 ) {
errors.add("email", new ActionMessage("security.error.email.required"));
}
if( getPassword( ) == null || getPassword( ).length( ) < 1 ){
errors.add("password", new ActionMessage("security.error.password.required"));
}
return errors;
}
public void reset(ActionMapping mapping, HttpServletRequest request) {
/** Because this ActionForm should be request-scoped, do nothing here.
* The fields will be reset when a new instance is created. We could
* have just not overriden the parent reset( ) method, but we did so
* to provide an example of the reset( ) method signature.
*/
}
}

When the form is submitted, an instance of the
LoginForm will be created and populated from the
request parameters. The framework does this by matching each request
parameter name against the corresponding property name in the
ActionForm class.


The ActionForm is populated from request
parameters, not request attributes. If you are forwarding from one
action to another, you can't add a request attribute
and expect that the ActionForm will be populated
from it. Request parameters and request attributes are two separate
resources.


7.2.3.1 The validate( ) method

The RequestProcessor may call the
validate(
)
method for every request. Whether it's
called depends on two things. First, an ActionForm
must be configured for an action mapping. This means that the
name attribute for an action
element must correspond to the name attribute of
one of the form-bean elements in the configuration
file.

The second condition that must be met for the
RequestProcessor to invoke the validate(
)
method is that the validate attribute
in the action mapping must have a value of true.
The following fragment shows an action element
that uses the LoginForm from Example 7-2 and meets both requirements:

<action
path="/signin"
type="com.oreilly.struts.storefront.security.LoginAction"
scope="request"
name="loginForm"
validate="true"
input="/security/signin.jsp">
<forward name="Success" path="/index.jsp" redirect="true"/>
<forward name="Failure" path="/security/signin.jsp" redirect="true"/>
</action>

When the signin action is invoked, the framework
will populate an instance of a LoginForm using the
values it finds in the request. Because the
validate attribute has a value of
true, the validate() method in
the LoginForm will be called. Even if the
validate attribute is set to
false, the ActionForm still
will be populated from the request.


The validate() method in the base
ActionForm class simply returns null. If you want
to perform validation on the data submitted with the request,
you'll need to override the validate(
)
method in your ActionForm subclasses,
as in Example 7-2.

The validate() method may return an
ActionErrors object, depending on whether any
validation errors were detected; it also can return null if there are
no errors. The framework will check for both null and an empty
ActionErrors object. This saves you from having to
create an instance of ActionErrors when there are
no errors. The ActionError class and its parent
class, ActionMessage, are discussed later in this
chapter.


7.2.3.2 The reset( ) method

The
reset() method has
been a bane for much of the Struts user community at one time or
another. Exactly when the reset() method is
called and what should be done within it almost always are
misinterpreted. This doesn't mean that one
implementation is more correct than another, but many new Struts
developers pick up misconceptions about reset()
that they have a hard time shaking.

As Figure 7-4 showed, the reset(
)
method is called for each new request, regardless of the
scope of the ActionForm, before the
ActionForm is populated from the request. The
method was originally added to the ActionForm
class to facilitate resetting Boolean properties back to their
defaults. To understand why they need to be reset,
it's helpful to know how the browser and the HTML
form-submit operation process checkboxes.

When an HTML form contains checkboxes, only the values for the
checkboxes that are checked are sent in the request. Those that are
not checked are not included as a request parameter. The
reset() method was added to allow applications to
reset the Boolean properties in the ActionForm to
false--because false
wasn't included in the request, it was possible for
the Boolean values to be stuck in the true state.

The reset() method in the base
ActionForm contains no default behavior, as no
properties are defined in this abstract class. Applications that
extend the ActionForm class are allowed to
override this method and reset the ActionForm
properties to whatever state they want. This may include setting
Boolean properties to true or
false, setting String values to
null or some initialized value, or even instantiating instances of
other objects that the ActionForm holds on to. For
an ActionForm that has been configured with
request scope, the framework will create a new instance for each new
request; hence, there's not much need to reset the
values back to any default state. ActionForms that
are configured with session scope are different, however, and this is
where the reset() method comes in handy.


7.2.4 Declaring ActionForms in the Struts Configuration File


Once you have created a class that extends
ActionForm, you need to configure the class in the
Struts configuration file. The
first step is to add a new form-bean element to
the form-beans section of the file:

<form-beans> 
<form-bean
name="loginForm"
type="com.oreilly.struts.storefront.security.LoginForm"/>
</form-beans>

The value for the type attribute must be a fully
qualified Java class name that is a descendant of
ActionForm.

Once you have defined your form-bean, you can use
it in one or more action elements.
It's common to share one
ActionForm across several actions. For example,
suppose there was an admin application that managed the items in the
Storefront application. There would need to be an HTML form for
adding new items to the system. This might be the
createItem action. There also would need to be a
getItemDetail action to show the details of an
existing item. These HTML forms would look similar, but they might be
submitted to different actions. Still, because they contained the
same properties, both forms could use the same
ActionForm.

To use an ActionForm in an
action element, you need to specify a few
attributes for each action mapping that uses the
ActionForm. These attributes are
name, scope, and
validate:

<action
path="/signin"
input="/security/signin.jsp"
name="loginForm"
scope="request"
type="com.oreilly.struts.storefront.security.LoginAction"
validate="true">
<forward name="Success" path="/index.jsp" redirect="true"/>
<forward name="Failure" path="/security/signin.jsp" redirect="true"/>
</action>

For more information on the attributes of the
action element, see "The Struts
Configuration DTD" in Chapter 4.


7.2.5 Using an ActionForm in an Action


Once you have configured the
ActionForm for a particular
Action, you can insert values into it and retrieve
values from it within the execute() method, as
Example 7-3 illustrates.


Example 7-3. The ActionForm is available within the execute( ) method

public ActionForward execute( ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response )
throws Exception{
// Get the user's login name and password. They should already have
// been validated by the ActionForm.
String email = ((LoginForm)form).getEmail( );
String password = ((LoginForm)form).getPassword( );
// Log in through the security service.
IStorefrontService serviceImpl = getStorefrontService( );
UserView userView = serviceImpl.authenticate(email, password);
UserContainer existingContainer = null;
HttpSession session = request.getSession(false);
if ( session != null ){
existingContainer = getUserContainer(request);
session.invalidate( );
}else{
existingContainer = new UserContainer( );
}
// Create a new session for the user.
session = request.getSession(true);
existingContainer.setUserView(userView);
session.setAttribute(IConstants.USER_CONTAINER_KEY, existingContainer);
return mapping.findForward(IConstants.SUCCESS_KEY);
}


It's not mandatory that you use an
ActionForm to capture the data from an HTML form.
Even if you don't declare an
ActionForm for a form, the data is still available
from the request. However, your application will have to manually
handle the process of validation and error handling from the
Action class.


7.2.6 Declaring ActionForm Properties as Strings


All request parameters that are sent by the browser are strings. This
is true regardless of the type that the value will eventually map to
in Java. For example, dates, times, Booleans, and other values all
are strings when they are pulled out of the request, and they will be
converted into strings when they are written back out to the HTML
page. Therefore, all of the
ActionForm
properties where the input may be invalid should be of type
String, so that the data can be displayed back to
the user in its original form when an error occurs. For example, say
a user types in "12Z" for a
property expecting an Integer.
There's no way to store
"12Z" into an
int or Integer property, but
you can store it into a String until it can be
validated. This value can be used to render the input field with the
value, so the user can see his mistake. Even the most inexperienced
users have come to expect and look for this functionality.


ActionForms Are Not the Model


Many developers get confused when they learn about the
ActionForm class. Although it can hold state for
an application, the state that it holds should be limited and
constrained to the user input that is received from the client, and
the ActionForm should hold it only until it can be
validated and transferred to the business tier.

You've already seen why it's
important to separate the model from the presentation tier in an
application. Business objects can be persisted and should contain the
business logic for an application. They also should be reusable. This
set of criteria does not match up well when compared against
ActionForms. For one thing, the
ActionForm class is tied to the Struts framework
and explicitly to a web container, as it imports
javax.servlet packages. It would be very difficult
to port ActionForm classes to a different type of
framework, such as a Swing application.

ActionForms are designed exclusively to capture
the HTML data from a client, allow "presentation
validation" to occur, and provide a transport
vehicle for the data back to the more persistent business tier. They
also transport data from the business tier forward to the views.
Apart from these uses, you should keep the
ActionForms separate from your business
components.


7.2.7 Using ActionForms Across Multiple Pages


Many applications require wizard-like functionality where data is
captured across multiple pages.
ActionForms can be
used for this but the scope must be set to session
to ensure that the data collected from a previous page will be
present throughout the set of wizard pages.

When programming this type of functionality into your application,
you must be careful to validate only the fields that are ready to be
validated. For instance, if you go from page 1 to page 2, only the
properties populated from page 1 should be validated. You also must
be careful with the reset() method. You
don't want to reset fields that already have been
entered, but, you might want to reset the properties for the upcoming
page. This can be a little tricky, but keeping these thoughts in mind
will save time.


    / 181