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

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

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

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

Chuck Cavaness

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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








3.3 Struts Controller Components


The controller component in an
MVC application has several
responsibilities, including receiving input from a client, invoking a
business operation, and coordinating the view to return to the
client. Of course, the controller may perform many other functions,
but these are a few of the primary ones.

For an application built using the JSP Model 2 approach, the
controller is implemented by a Java servlet. This servlet is the
centralized point of control for the web application. The controller
servlet maps user actions into business operations and then helps to
select the view to return to the client based on the request and
other state information. As a reminder, Figure 3-5
shows the figure used in Chapter 1 to
illustrate how this works.


Figure 3-5. The Struts framework uses a servlet as a controller

In the Struts framework, however, the controller responsibilities are
implemented by several different components, one of which is an
instance of the org.apache.struts.action.ActionServlet
class.


3.3.1 The Struts ActionServlet


The ActionServlet extends the
javax.servlet.http.HttpServlet
class and is responsible for packaging and routing HTTP traffic to
the appropriate handler in the framework. The
ActionServlet
class is not abstract and therefore can be used as a concrete
controller by your applications. Prior to Version 1.1 of the Struts
framework, the ActionServlet was solely
responsible for receiving the request and processing it by calling
the appropriate handler. In Version 1.1, a new class, called
org.apache.struts.action.RequestProcessor, was introduced to process the request
for the controller. The main reason for decoupling the request
processing from the ActionServlet is to provide
you with the flexibility to subclass the
RequestProcessor with your own version and modify
how the request is processed.

For the banking application example, we will keep it simple and use
the default ActionServlet and
RequestProcessor classes provided by the
framework. For brevity in this chapter, we will refer to these two
components simply as "the
controller." Chapter 5
describes in detail how these classes can be extended to modify the
default controller behavior and explains the roles and
responsibilities of each component.

Like any other Java servlet, the Struts
ActionServlet must be configured in the deployment
descriptor for the web application. We won't go into
detail about the deployment descriptor here,
thoughit's covered in Chapter 4.

Once the controller receives a client request, it delegates the
handling of the request to a
helper class. This helper knows how to
execute the business operation associated with the requested action.
In the Struts framework, this helper class is a descendant of the
org.apache.struts.action.Action class.


3.3.2 Struts Action Classes


An org.apache.struts.action.Action class in the
Struts framework is an extension of the controller component. It acts
as a bridge between a client-side user action and a business
operation. The Action class decouples the client
request from the business model. This decoupling allows for more than
a one-to-one mapping between the user request and an
Action. The Action class also
can perform other functions, such as authorization, logging, and
session validation, before invoking the business operation.


Is the Action Part of the Controller or Model?



The various articles, tutorials, and other resources available on the
Struts framework disagree about whether the Action
class is part of the controller or the model. The argument for it
being part of the controller is that it isn't part
of the "real" business logic. If
Struts were replaced with an alternative framework, chances are the
Action class would be replaced with something
else. Therefore, it really isn't part of the model
domain, but rather is tightly coupled to the Struts controller. It
doesn't make sense to put business logic into the
Action, because other types of clients
can't easily reuse it.

Another reason to consider the Struts Action class
part of the controller is that it has access to the
ActionServlet, and therefore all of the controller
resources, which the model domain shouldn't know
about. Hypothetically, the Action
class's behavior could have been left in the
servlet, and the servlet would call the appropriate method on itself.
If this were the case, there would be no doubt about whether this was
controller or model functionality.

With all of that said, the Action class may invoke
operations on the business model, and many developers end up trying
to insert too much of their business logic into the
Action classes. Eventually, the line becomes
blurry. Perhaps this is why some developers consider it part of the
model. However, this book will take the approach that the
Action class is part of the controller.

The Struts Action class contains several methods,
but the most important is the
execute()
method. Here is the method signature:

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

The execute() method is called by the controller
when a request is received from a client. The controller creates an
instance of the Action class if one
doesn't already exist. The Struts framework will
create only a single instance of each Action class
in your application. Because there is only one instance for all
users, you must ensure that all of your Action
classes operate properly in a multithreaded environment, just as you
would do when developing a servlet. Figure 3-6
illustrates how the execute() method is invoked
by the controller components.


Figure 3-6. The execute( ) method is called by the controller

Although the execute() method is not abstract,
the default implementation returns null, so you will need to create
your own Action class implementation and override
this method.

There is some debate over how best to implement
Action classes
using Struts. Whether you create a different
Action class for each operation or put several
business operations in the same Action class is
subjective, and each approach has pros and cons.


In Chapter 5, we'll discuss an
action provided by the Struts framework called
org.apache.struts.actions.DispatchAction.
This action gives you the ability to create a single
Action class and implement several similar
operations, such as Create, Read, Update, and
Delete
(CRUD), within it. This has the effect of creating
a smaller number of Action classes, but it might
make maintenance a little more cumbersome.

For the banking application, we will create a unique
Action class for each action that the user can
perform:

  • Login

  • Logout

  • GetAccountInformation

  • GetAccountDetail


Each of the banking Action classes will extend the
Struts Action class and will override the
execute() method to carry out a specific
operation. In Chapter 5,
you'll learn that it's best to
create an
abstract base Action
class, which all of your other Action classes
extend. The application-specific base Action
extends the Struts Action class and provides you
with added flexibility and extensibility by allowing common
Action behavior to be handled by a single parent
class. For example, if you want to verify for each request that the
user's session has not timed out, you can put this
behavior in the abstract base Action before
calling the subclass. For the banking application, however, things
will be kept simple, and the actions will be direct descendants of
the Struts Action class.

The com.oreilly.struts.banking.action.LoginAction
class is shown in Example 3-3. It extends the Struts
Action class and is invoked by the controller when
a user attempts to log in to the banking application.


Example 3-3. The LoginAction used by the banking application

package com.oreilly.struts.banking.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
// Non Struts Imports
import com.oreilly.struts.banking.IConstants;
import com.oreilly.struts.banking.service.IAuthentication;
import com.oreilly.struts.banking.service.SecurityService;
import com.oreilly.struts.banking.service.InvalidLoginException;
import com.oreilly.struts.banking.view.UserView;
import com.oreilly.struts.banking.form.LoginForm;
/**
* This Action is called by the ActionServlet when a login attempt
* is made by the user. The ActionForm should be an instance of
* a LoginForm and contain the credentials needed by the SecurityService.
*/
public class LoginAction extends Action {
public ActionForward execute( ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response )
throws Exception {
// The ActionForward to return when completed
ActionForward forward = null;
UserView userView = null;
// Get the credentials from the LoginForm
String accessNbr = ((LoginForm)form).getAccessNumber( );
String pinNbr = ((LoginForm)form).getPinNumber( );
/*
* In a real application, you would typically get a reference
* to a security service through something like JNDI or a factory.
*/
IAuthentication service = new SecurityService( );
// Attempt to log in
userView = service.login(accessNbr, pinNbr);
// Since an exception wasn't thrown, login was successful
// Invalidate existing session if it exists
HttpSession session = request.getSession(false);
if(session != null) {
session.invalidate( );
}
// Create a new session for this user
session = request.getSession(true);
// Store the UserView into the session and return
session.setAttribute( IConstants.USER_VIEW_KEY, userView );
forward = mapping.findForward(IConstants.SUCCESS_KEY );
return forward;
}
}

The LoginAction in Example 3-3
gets the credentials from the ActionForm that was
passed in as an argument in the execute() method.


The ActionForm class will be discussed later in
this chapter in Section 3.5.

A SecurityService is then created, and the
security credentials are passed to the login()
method. If the login succeeds, a new HttpSession
is created for the user, and the UserView returned
from the login() method is put into the session.
If authentication fails, an InvalidLoginException
is thrown. Note that there is no try/catch block for the
InvalidLoginException in the execute(
)
method. This is because one of the new features of Struts
1.1 is its declarative exception-handling capabilities, which remove
much of the burden of exception handling from the developer. With the
declarative
exception handling in Struts, you
specify what exceptions can be thrown from the actions and what you
want the framework to do with them. You specify this information in
the Struts configuration file:

<global-exceptions>
<exception
key="global.error.invalidlogin"
path="/login.jsp"
scope="request"
type="com.oreilly.struts.banking.service.InvalidLoginException"/>
</global-exceptions>

This fragment from the banking configuration file tells the framework
that if an InvalidLoginException is thrown by any
action, it should forward the request to the
login.jsp resource and build an error message
using the key global.error.invalidlogin from the
resource bundle. You also have the ability to override the default
exception-handling behavior with whatever functionality you need it
to perform. Exception handling is covered at length in Chapter 10.


3.3.3 Mapping the Actions


At this point, you might be asking yourself, "How
does the controller know which Action instance to
invoke when it receives a request?" It determines
this by inspecting the request information and using a set of
action mappings.

Action mappings are part of the Struts configuration information that
is configured in a special XML file. This configuration information
is loaded into memory at startup and made available to the framework
at runtime. Each action element is represented in
memory by an instance of the org.apache.struts.action.
ActionMapping
class. The
ActionMapping
object contains a path attribute that is matched
against a portion of the URI of the incoming request.
We'll talk more about action mappings and the Struts
configuration file in Chapter 4.

The following XML fragment illustrates the login
action mapping from the configuration file used by the banking
application:

<action
path="/login"
type="com.oreilly.struts.banking.action.LoginAction"
scope="request"
name="loginForm"
validate="true"
input="/login.jsp">
<forward name="Success" path="/action/getaccountinformation" redirect="true"/>
<forward name="Failure" path="/login.jsp" redirect="true"/>
</action>

The login action mapping shown here maps the path
"/login" to the
Action class
com.oreilly.struts.banking.LoginAction. Whenever
the controller receives a request where the path in the URI contains
the string "/login", the
execute() method of the
LoginAction instance will be invoked. The Struts
framework also uses the mappings to identify the resource to forward
the user to once the action has completed. We'll
talk more about configuring action mappings in Chapter 4.


3.3.4 Determining the Next View


So far, we've discussed how the controller receives
the request and determines the correct Action
instance to invoke. What hasn't been discussed is
how or what determines the view to return to the client.

If you looked closely at the execute() method
signature in the Action class from the previous
section, you might have noticed that the return type for the method
is an
org.apache.struts.action.ActionForward
class. The ActionForward class represents a
destination to which the controller may send control once an
Action has completed. Instead of specifying an
actual JSP page in the code, you can declaratively associate an
action forward mapping with the JSP and then use that
ActionForward throughout your application.

The action
forwards are specified in the configuration file, similar to action
mappings. They can be specified at an Action
level, as this forward is for the logout action
mapping:

<action
path="/logout"
type="com.oreilly.struts.banking.action.LogoutAction"
scope="request">
<forward name="Success" path="/login.jsp" redirect="true"/>
</action>

The logout action declares a
forward element named
"Success", which forwards to the
"/login.jsp" resource. The
redirect attribute is specified and set to
"true". Now, instead of performing
a forward using a RequestDispatcher, the request
will be redirected.

The action forward mappings also can be specified in a global
section, independent of any specific action mapping. In the previous
case, only the logout action mapping could
reference the action forward named
"Success", However, all action
mappings can reference forwards declared in the
global-forwards section. Here is an example
global-forwards section from the
banking configuration file:

<global-forwards>
<forward name="SystemFailure" path="/systemerror.jsp" />
<forward name="SessionTimeOut" path="/sessiontimeout.jsp" />
</global-forwards>

The forwards defined in this section are more general and
don't apply to specific action mappings. Notice that
every forward must have a name and
path, but the redirect
attribute is optional. If you don't specify a
redirect attribute, its default value of
"false" is used, and thus the
framework will perform a forward. The path
attribute in an action forward also can specify another Struts
Action. You'll see an example of
how to do this in Chapter 5.

Now that you understand from a high level how the Struts controller
components operate, it's time to look at the next
piece of the MVC puzzlethe model.


    / 181