Actions control the flow of data and navigation in a Struts
application. Each
Action
acts like a decision point in a flow chart.
It’s the place where you can decide what data to
create, what data to save, and where to send users based on their
input. A Struts application doesn’t require that
every request be routed through an Action
, but
doing so gives you a level of control that will allow your
application to adapt as requirements and new features are changed and
added.
Do you feel like your application is exploding with hundreds of
custom Action
s? If it is, you
aren’t alone. This condition can be acceptable, but
you may not be leveraging Struts Action
s to their
full extent. Using the recipes in this chapter will enable you to get
more reuse from your Action
s and reduce the number
of custom classes that you have to maintain.
A number of the recipes in this chapter utilize
Action
subclasses included with Struts. These
pre-built actions are found in the
org.apache.struts.actions
package. Some of these
actions are designed to be extended, and others are used as is. The
DispatchAction
and its provided subclasses,
LookupDispatchAction
and
MappingDispatchAction
, fall into this first
category. These actions reduce the amount of code you have to write
by replacing multiple related Action
s with a
single Action
that supports multiple operations.
You use two other pre-built actions, ForwardAction
and IncludeAction
, directly—no subclassing
required. These actions perform the same functions as the
javax.servlet.RequestDispatcher
provided by the
Java Servlet API. The ForwardAction
forwards an
HTTP request to another resource such as a servlet, JSP, or static
HTML page. The IncludeAction
includes content from
another resource, such as a servlet or JSP, which generates a partial
HTTP response.
Struts provides a SwitchAction
used to change the
current Struts module and a LocaleAction
used to
change the current locale. This chapter covers the
SwitchAction
(however, the
LocaleAction
is discussed in Recipe 12.4) and some programming techniques and
guidelines specifically related to working with
Action
s.
You want to implement and enforce a common feature or behavior across
all of your Action
s.
Implement an abstract base class that incorporates the behavior in
its execute( )
method. As shown by the class in
Example 6-1, the execute( )
method calls abstract methods that are implemented by subclasses.
package com.oreilly.strutsckbk.ch06; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public abstract class BaseAction extends Action { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { executeBefore( ); // call the abstract method ActionForward forward = executeAction( mapping, form, request, response ); executeAfter( ); return forward; } protected abstract ActionForward executeAction( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception; private void executeBefore( ) { //Real stuff goes here } private void executeAfter( ) { //Real stuff goes here } protected CommonServices getCommonServices( ) {...} }
For some applications, you may want to apply global business rules any time a user accesses the application, submits a form, or clicks on a link. Here are some common examples of these rules:
Users must be logged in.
Users can only access the page if they have been granted permission.
You want to track every time users hit a page.
The user’s first name and last name should be displayed at the top of every page.
You can enforce these across-the-board features using an abstract
base Action
class that your custom
Action
s extend. The Solution above shows such a
base Action
. BaseAction
extends
org.apache.struts.action.Action
and implements the
standard execute( )
method. The execute()
method calls the private method executeBefore(
)
and then the abstract method executeAction()
. executeAction( )
returns an
ActionForward
, which is stored in a local
variable. Next, the private method executeAfter( )
is called. The execute( )
method ends by returning
the ActionForward
referenced by the local
variable.
Your concrete actions extend this BaseAction
. Each
concrete action implements the executeAction( )
method as if it were the execute( )
method
inherited from org.apache.struts.action.Action
.
If you are familiar with design patterns, you will probably recognize the Solution as the Template Method from Design Patterns of Object-Oriented Software by Erich Gamma, et al. (Addison-Wesley). The Template Method pattern is characterized by a base abstract class that defines the steps of an algorithm. In the Solution, the steps are comprised of a pre-execution step, the execution step, and a post-execution step.
If you want subclasses to be able to override the common behavior in
the pre- and post-execution steps, make the methods
protected
; otherwise, they should be
private
. The signature of the
executeAction( )
method is identical to the
execute( )
method; however, this is not required.
In general, you’ll want to pass through at least the
set of parameters available to the execute( )
method. In addition, you may want to pass additional references to
objects that are created in the base class, such as a user object
representing the current user.
Base actions can provide access to global objects and services that
concrete actions will need. For example, you might want to provide a
getUser( )
method that returns a user object for
the current session. Chapter 11 provides more
detail on this specific case.
Chuck Cavaness discusses the use of a base Action
class in Programming Jakarta Struts
(O’Reilly). The timeless book Design
Patterns of Object-Oriented Software by Erich Gamma, et
al. (Addison-Wesley) is the bible of design patterns. In its pages is
an assortment of low-level design approaches applicable to any
object-oriented application.
You want to link one action directly to another.
Use a local or global forward that specifies its destination URL as another action:
<action path="/FirstAction" type="com.foo.FirstAction"> <forward name="success" path="/SecondAction.do"/> </action> <action path="/SecondAction" type="com.foo.SecondAction"> <forward name="success" path="/second_page.jsp"/> </action>
The actions configured in your struts-config.xml are commonly used as the target action of forms on a JSP page. These actions subsequently forward requests to JSP pages for rendering of the view. However, nothing prohibits an action from forwarding the request to another action. This technique is referred to as action relaying.
As you can see from the Solution, setting up a relay from one action to another is easy. The motivation for doing so, however, isn’t as apparent, particularly if you have just started developing your Struts application. By relaying actions, you can hook together application features that you had not originally intended.
Consider a point-of-sale web application for a retail business.
Salespersons use the application to make sales and to track and
maintain data about customers and their purchases. The application
supports two main workflows: one for making purchases and one for
updating customer data. The developer on this project has created an
action with the path /UpdateCustomer.do
that
allows retrieval and editing of customer information. After an
initial release of the application, the developer is given a new
requirement: Every time a customer makes a purchase, the salesperson
should make updates to the customer data. Though the developer
hadn’t planned for this workflow, the steps in the
flow can be easily linked using the technique shown in the Solution.
The action that completes the purchase has the path
/SavePurchase.do
. The desired workflow can be set
up as follows:
<action path="/SavePurchase" type="com.foo.SavePurchaseAction"> <forward name="success" path="/UpdateCustomer.do"/> </action>
The Struts User’s mailing list has some good discussions on the implications of hooking actions together. Ted Husted discusses this in more detail in a thread archived at http://www.mail-archive.com/[email protected]/msg96565.html.
You want to create a response in the Action
and
send that response to the client instead of forwarding to another
action or JSP page.
Use the standard methods provided by the
HttpServletResponse
object to write the response.
Then return null
instead of an
ActionForward
from the execute()
method of the Action
. Example 6-2 shows an Action
that
creates and returns a simple response.
package com.oreilly.strutsckbk.ch06; import java.io.PrintWriter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class ResponseWriterAction extends Action { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { response.setContentType("text/html"); PrintWriter out = response.getWriter( ); out.write("<html><head></head><body>Hello World!</body></html>"); return null; } }
The typical Action
returns an
ActionForward
from its execute(
)
method. The returned ActionForward
is
evaluated and processed by the Struts request processor. The returned
forward specifies the path to a resource, like a JSP page, that
generates the actual HTTP response. However, you can generate the
response in the Action
. If you do so, you must
return null
from the
Action
’s execute()
method. This tells the Struts request processor no
ActionForward
will follow; therefore, the response
should be returned to the client.
Many scenarios exist in which you may want to write the response. Applications that dynamically generate non-HTML content from binary data can use this approach. These applications can write binary content, such as images and PDF documents, directly to the response and can set the content type in the HTTP header to the appropriate MIME type for handling by the browser.
Though the Action
may return the response, the
Action
should delegate the writing of the response
to a custom class not tied to the Struts API.
The traditional Java mechanism for creating an HTTP response is the
servlet. If you are writing the response in your
Action
, you may want to consider using a servlet
to do this instead. Your Action
would forward the
request to that servlet using an ActionForward
.
The ForwardAction
described in Recipe 6.5 shows a technique for wrapping access to
servlets with Struts actions.
Your Action
classes must operate correctly in a
multi-threaded environment.
In your custom Action
, never use instance
variables to carry per-request state; only use local variables. If
you want to have local methods called from the execute()
method, pass values to these methods as arguments instead
of using instance variables. The Action
class in
Example 6-3 demonstrates this approach.
package com.oreilly.strutsckbk.ch06; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class ThreadSafeAction extends Action { // This variable is not thread-safe private SecurityUtil securityUtil = new SecurityUtil.instance( ); public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // the 'user' variable is thread-safe User user = (User) request.getSession( ).getAttribute("user"); // pass the user to the private method doSomething(user); // ... return mapping.findForward("success"); } private void doSomething(User user) throws Exception { // authenticate the user securityUtil.authenticate(user); } }
A Struts Action
is subject to the same threading
issues as a servlet. Internally, Struts maintains and reuses each
Action
instance to service requests. Your
Action
’s execute(
)
method will probably be called by multiple concurrent
threads. You might be tempted to synchronize the execute()
method, but avoid this temptation! Synchronizing the
execute( )
method will degrade performance and you
should avoid it. If you need to synchronize behavior in your
application, perform the synchronization in the service layer of your
application.
If you use a base Action
, as described in Recipe 6-1, you need to follow these guidelines. A
base Action
should refrain from using client-state
instance variables like any other well-behaved
Action
class.
The Struts User’s guide addresses this specific
issue, and other Action
class design guidelines,
in the section at http://struts.apache.org/userGuide/building_controller.html#action_design_guide.
A recent JavaWorld article,
“Writing thread-safe Servlets” by
Phillip Bridgham, discusses this issue with respect to servlets and
can be found at http://www.javaworld.com/javaworld/jw-07-2004/jw-0712-threadsafe.html.
You can find discussions in the archives of the Struts user and
developer mailing lists.
You want to provide an action that forwards a request to any action, JSP page, servlet, or other resource of your web application.
Use a forwarding action. Specifying the module-relative path to the
resource as the value of the forward
attribute is
the most convenient way:
<action path="/ForwardTest" forward="/forward_test.jsp"/>
Alternatively, if you use a custom
RequestProcessor
, which overrides the
processForwardConfig( )
method, you must use the
Struts built-in ForwardAction
, specifying the
context-relative path for the value of the
parameter
attribute.
<action path="/ForwardActionTest" type="org.apache.struts.actions.ForwardAction" parameter="/forward_test.jsp"/>
Struts provides two mechanisms for creating an action that forwards
directly to a specified resource. You can use the
forward
attribute or the
ForwardAction
. In most situations, the forward
attribute will work just fine. The value of the attribute is treated
as a module-relative path. At request time, the
RequestProcessor
checks the action mapping
specifies a forward attribute. If so, it converts the
forward
attribute value to a context-relative path
by adding the module prefix to the front of the path. The request
processor then hands the request and response to the
RequestDispatcher.forward( )
method and
immediately returns.
The ForwardAction
is a Struts prebuilt action
class included with the Struts distribution. This
Action
forwards the request to the path specified
as the value of the parameter
attribute in an
action
mapping. The parameter
attribute may contain an HTTP query string for passing request
parameters.
<action path="/SendToLegacyServlet" type="org.apache.struts.actions.ForwardAction" parameter="/LegacyServlet?foo=bar"/>
Unlike handling the forward
attribute, when you
use the ForwardAction
, the
RequestProcessor
routes the request through all
steps of the process. This difference is relevant if you are using a
custom RequestProcessor
, which overrides the
processForwardConfig( )
method, for custom
processing of action forwards. If so, you should use the
ForwardAction
to preserve your custom processing.
Whether using the forward
attribute or
ForwardAction
, your action acts as a bridge
between requests. Since the action performs a forward and not a
redirect, request data is maintained.
If you are prototyping a Struts application, a forwarding action can
save you significant time and effort as your application develops. A
forwarding action allows you to define your action paths—the
URIs that link the application together—prior to coding the
actual custom Action
classes. Suppose your web
designers have created a prototype comprised of static HTML pages and
simple JSP pages. Instead of linking directly from page to page,
create a forwarding action in your
struts-config.xml that routes the request to the
path of the prototype page.
You can use the action URI for hyperlinks and HTML form actions
instead of the prototype JSP page name. As you create the real
functionality of the application, reconfigure the
action
element to use your own custom
Action
class. You’ll need to make
changes to the JSPs, but you will already have established the
workflow. This is a great development approach, particularly if you
want to have a working prototype available.
This practice allows your application to take advantage of
application-wide services provided by Struts. These services may be
Struts-provided, like a role-based access, or they may be custom
behaviors that you implement by extending the Struts
RequestProcessor
. Forwarding actions are useful
for creating proxies to resources, such as traditional servlets. If
you are adding Struts to an existing servlet-based application, you
can use a forwarding action to route requests to the servlet. Once
you have the proxy in place, you can replace the servlet with a JSP
without breaking the application.
If you need to link to a servlet that is used to include a portion of the HTTP response, Recipe 6.6 provides a similar proxy ability as a forwarding action.
The Struts documentation on the ForwardAction
can
be found at http://jakarta.apache.org/struts/api/org/apache/struts/actions/ForwardAction.html.
The difference between the forward attribute and the
ForwardAction
has been discussed on the
Struts-users mailing list. Searching the archives for
“Forward attribute vs. Forward
Action” will return several threads of
interest.
You want to retrieve and include a partial HTTP response from a servlet or JSP, but you want control to go through Struts.
Employ an including action. Specifying the module-relative path to
the resource as the value of the include
attribute
is the most convenient way:
<action path="/IncludeContent" include="/LegacyIncludeServlet"/>
Alternatively, if you use a custom
RequestProcessor
, which overrides the
processForwardConfig( )
method, you must use the
Struts built-in IncludeAction
, specifying the
context-relative path for the value of the
parameter
attribute:
<action path="/IncludeContent" type="org.apache.struts.actions.IncludeAction" parameter="/LegacyIncludeServlet"/>
This recipe addresses a problem similar to that in Recipe 6.6. The solution is similar as well; you can use
the include
attribute of the action element, or
you can use the Struts-provided IncludeAction
. The
IncludeAction
uses the value specified for the
parameter
attribute to indicate the resource whose
response is to be included.
You may have legacy code that includes content, using
RequestDispatcher.include( )
or
jsp:include
, from another servlet or JSP. You can
replace direct references to the included resources with an including
action defined in your
struts-config.xml
file.
Like the ForwardAction
, you only need to use the
IncludeAction
if you are using a custom
RequestProcessor
, which overrides the
processForwardConfig( )
method, to handle
requests. Using an including action ensures that the request is
routed through your application’s control layer
provided by Struts’s
ActionServlet
and
RequestProcessor
. An including action essentially
decorates the legacy resource with Struts functionality. You use this
action as you would the original resource. In other words, you would
change JSP tags that look like this:
<jsp:include page="/LegacyIncludeServlet"/>
to this:
<jsp:include page="/IncludeContent.do"/>
Recipe 6.5 shows how to use a similar
approach that provides the equivalent replacement for calls to the
RequestDispatcher.forward()
.
You want to allow the user to switch to a different module at runtime.
In the struts-config.xml file of the module that
you will be switching (referred to as the
“source” module), create an
action
that uses a type of
org.apache.struts.actions.SwitchAction
:
<action path="/ChangeModuleTest" type="org.apache.struts.actions.SwitchAction"/>
To use the action, pass request parameters that indicate the module
and page within the module to switch to. The module is specified as
the value of the prefix
request parameter. The
page is specified as the value for the page
request parameter and defines the module-relative location of the
resource to access.
<html:link page="/ChangeModuleTest.do?prefix=moduleName&page=/ SomeAction.do"> Change Module </html:link>
Since the page value is a location, if you are linking to an
action
, you must include the action extension
(e.g., .do
) as part of the parameter value.
SwitchAction
changes the
application’s current module to another module and
forwards control to a specified module-relative URL. Like
ForwardAction
and
IncludeAction
, the SwitchAction
doesn’t require subclassing.
A module is an in-memory, application-relative
context maintained by Struts. Modules partition a web application
context into subcontexts. A module is defined by
creating a separate Struts configuration XML file for the module.
This file is referenced using an initialization parameter for the
Struts ActionServlet
in the web
application’s web.xml file. The
name of the initialization parameter specifies the
module’s prefix. The following servlet definition
creates three modules: the default module, /mod1
,
and /mod2
:
<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> <init-param> <param-name>config/mod1</param-name> <param-value>/WEB-INF/struts-config-mod1.xml</param-value> </init-param> <init-param> <param-name>config/mod2</param-name> <param-value>/WEB-INF/struts-config-mod2.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
You might assume that you can switch from one module to another by using the module prefix on a URL of a link or action. For example, say that you have created a module for administration features called admin. You want to create a link from your application’s main page, in the default module, to the main page of the admin module. You might be inclined to create the link using something like this:
<html:link action="/admin/Main.do">Admin Module</html:link>
If you were to code this, you would find that the link would not work!
To switch to an action in another module, the request must go through
the Struts controller layer. If you issue a request directly to a JSP
page in another module, it won’t have access to
module-specific Struts objects, such as message resources, plug-ins,
and global forwards. The SwitchAction
ensures that
the request goes through the controller.
Since you can’t link directly to JSP in another
module, create an action
in the Struts
configuration file for the module you are linking from that uses the
SwitchAction
type. Then reference the path of that
action in the link, passing the module prefix and page as request
parameters:
<html:link page="/SwitchModule.do?prefix=admin&page=/Main.do"> Admin Module </html:link>
The SwitchAction
determines the module and the URL
of the page or action to forward to based on the
prefix
and page
request
parameters. Table 6-1 describes these parameters
and provides some examples.
Parameter |
Description |
Examples |
|
The name of the module to switch to. This value should start with a
leading |
|
|
The module-relative URL of the JSP or Action to execute. |
|
Use global forwards to predefine module links in the Struts configuration file. If the module prefix or page to execute should change, you only need to change the global forward:
<global-forwards> <forward name="goToDefaultModule" contextRelative="true" path="/default_module.jsp"/> <forward name="goToDefaultModuleViaAction" path="/SwitchModule.do?prefix=&page=/default_module.jsp"/> <forward name="goToModule2" path="/SwitchModule.do?prefix=/mod2&page=/module2.jsp"/> </global-forwards>
Setting contextRelative="true
"
indicates that you will be switching to the default module. If you
are switching to a module other than the default module, the action
referenced by the path
must be a
SwitchAction
. To create a link that switches to
the module, specify the value of the html:link
tag’s forward
attribute as the
name of the global forward:
<html:link forward="goToModule2"> Module2 </html:link>
When specifying request parameters on a path attribute in a Struts
configuration XML file, the ampersand character
(&
) can’t be used literally.
Instead, use the &
character entity. The
resulting attribute value will be parsed correctly when
it’s processed and read by Struts.
Struts 1.2 has added additional support for modules that makes it
easier to create links between modules. You can use the
html:link
tag to create a link to an
action
in a different module without having to use
the SwitchAction
or a global forward. When used
with the action
attribute of the
html:link
tag, the module
attribute specifies the prefix name of the module containing the
action mapping specified by the action
attribute.
Use the empty string (“”) to denote
the default module. The module
attribute is only
valid when used with the action
attribute:
<html:link module="/mod2" action="module2Action.do"> Module2 </html:link>
If you need to link to a JSP page in another module, you must pass
the request through the controller using the
SwitchAction
.
If you are unfamiliar with Struts modules, Recipe 2.5 provides complete information. The Struts User Guide has describes the module switching. The specific section can be found at http://struts.apache.org/userGuide/configuration.html#module_config-switching.
You want to use a single Action
class to handle
related operations instead of having to write separate
Action
classes for each operation.
Extend the
DispatchAction
with your own class. Provide methods for each operation that you wish
to be accessible as an Action
. Each method should
have the same signature as the execute( )
method
except for the method name. The Action
class shown
in Example 6-4 provides three related operations in
one class: create( )
, update()
, and delete( )
.
package com.oreilly.strutsckbk.ch06; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.actions.DispatchAction; public class MyDispatchAction extends DispatchAction { public ActionForward create( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // create data request.setAttribute("dispatchedTo","create"); return mapping.findForward("success"); } public ActionForward update( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // update data request.setAttribute("dispatchedTo","update"); return mapping.findForward("success"); } public ActionForward delete( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // delete data request.setAttribute("dispatchedTo","delete"); return mapping.findForward("success"); } }
In the action
s that use your
DispatchAction
, specify the request parameter
whose value will be the method to call:
<action path="/DispatchActionTest" name="TestForm" scope="request" type="com.oreilly.strutsckbk.ch06.MyDispatchAction" parameter="methodToCall"> <forward name="success" path="/dispatch_test.jsp"/> </action>
On a form that submits to this action, use JavaScript to set the
parameter for the method to call. The name of the request parameter
must match the value of the parameter
attribute
from the action mapping. The value of the request parameter is the
name of the method. To dispatch to a method from a hyperlink, set the
method parameter on the URL. Example 6-5
(dispatch_test.jsp) shows the different ways
that you can specify the method parameter for a
DispatchAction
.
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %> <html> <head> <title>Struts Cookbook - Chapter 6 : Dispatch Action Test</title> </head> <body bgcolor="white"> <h2>Dispatch Action Test</h2> <html:form method="get" action="/DispatchActionTest"> Name: <html:text property="name"/> <input type="hidden" name="methodToCall"> <script> function set(target) { document.forms[0].methodToCall.value=target; } </script> <p> <html:submit onclick="set('create'),">New</html:submit> <html:submit onclick="set('update'),">Edit</html:submit> <html:link href="javascript:set('touch'),document.forms[0].submit( );"> <html:img border="0" srcKey="image.touch"/> </html:link> </p> </html:form> <html:link page="/DispatchActionTest.do?methodToCall=delete"> Remove</html:link> </body> </html>
The DispatchAction
enables a single custom class
to process multiple requests for similar operations. Performing
create/read/update/delete(CRUD) operations for the same business
object exemplifies the classic use case for the
DispatchAction
. Traditionally, four custom
Action
classes would be written, one for each
operation. Since the operations are for the same business object, a
significant amount of code would be duplicated.
The DispatchAction
allows you to use a more
natural programming approach. Instead of creating separate classes,
you create one class with methods corresponding to the desired
operations. A DispatchAction
is created by
subclassing
org.apache.struts.actions.DispatchAction
. You
don’t override the execute( )
method as you would for a normal custom Action
.
Instead, you implement methods corresponding to the operations you
wish to support. Common behavior can be implemented by private
methods that you call from the main dispatch operations. The method
signature of each operation must be the same as the signature as the
execute method:
public ActionForward someOperation( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // custom code return mapping.findForward("success"); }
The execute( )
method of the base
DispatchAction
calls a given method of your
subclass using the value of a specific HTTP request parameter.
The parameter
attribute of the
action
element in the
struts-config.xml file dictates the name of that
request parameter:
<action path="/DispatchActionTest" type="com.foo.MyDispatchAction" parameter="methodToCall"> <forward name="success" path="/some_page.jsp"/> </action>
If the action is called on form submission, use a hidden field with
the value set using JavaScript. You’ll want to use
the HTML <input type="hidden
"
name=
"methodToCall
">
tag instead of the Struts <html:hidden
property=
"methodToCall
"/>
.
Using the Struts tag forces you to create an artificial property in
your ActionForm
to hold the
methodToCall
parameter.
If you want to use an image to perform the submission instead of a
button, wrap the image in a link. Set the href
attribute of the link so it sets the
methodToCall
property appropriately and
submits the form:
<script> function set(target) { document.forms[0].methodToCall.value=target; } </script> <html:link href="javascript:set('create'),document.forms[0].submit( );"> <html:img border="0" srcKey="image.create"/> </html:link>
Finally, all DispatchAction
s support the ability
to define default behavior if the name of the method to call
can’t be resolved; that is, no matching method is
defined in the DispatchAction
subclass. The
default behavior is implemented by overriding the protected method
named unspecified( )
. This method, like other
custom operational methods of DispatchAction
s,
takes the same arguments as the execute( )
method.
Use the unspecified( )
method if the
DispatchAction
has a primary default operation;
then you won’t have to use an additional request
parameter to access this primary flow. If you don’t
provide an implementation of unspecified( )
, then
a ServletException
will be thrown if an unknown
method is specified.
Recipe 6.9 provides similar functionality as
the DispatchAction
without requiring the use of
JavaScript on the JSP page.
You want to use a single Action
class to process
related operations where each button, which has a localized value, on
a form corresponds to a specific method in the
Action
class.
Extend the Struts pre-built
LookupDispatchAction
with your own class. Provide methods for each operation you wish to
be called. Each method should have the same signature as the
execute( )
method. Implement the
getKeyMethodMap()
method, mapping the button label resource bundle key to
the corresponding method name to call. The Action
class shown in Example 6-6 provides three related
operations in one class: create( )
,
update( )
, and delete( )
. The
getKeyMethodMap( )
method returns a map where the
map key is a MessageResources
key for the button
label, and the map value is the corresponding method name.
package com.oreilly.strutsckbk.ch06; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.actions.LookupDispatchAction; public class MyLookupDispatchAction extends LookupDispatchAction { public MyLookupDispatchAction( ) { keyMethodMap = new HashMap( ); keyMethodMap.put("button.add", "create"); keyMethodMap.put("button.edit", "update"); keyMethodMap.put("button.remove", "delete"); } protected Map getKeyMethodMap( ) { return keyMethodMap; } public ActionForward create( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // create data request.setAttribute("dispatchedTo","create"); return mapping.findForward("success"); } public ActionForward update( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // update data request.setAttribute("dispatchedTo","update"); return mapping.findForward("success"); } public ActionForward delete( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // delete data request.setAttribute("dispatchedTo","delete"); return mapping.findForward("success"); } }
In the actions that use your LookupDispatchAction
,
specify the request parameter whose value will be the
button’s value. When your custom
Action
receives the request, the base
LookupDispatchAction
performs a reverse lookup on
the MessageResources
, retrieving the matching key
for the value from the bundle:
<action path="/LookupDispatchActionTest" name="TestForm" type="com.oreilly.strutsckbk.ch06.MyLookupDispatchAction" parameter="methodToCall"> <forward name="success" path="/lookup_dispatch_test.jsp"/> </action>
On a form that submits to this action, use the
bean:message
tag as the body for each submit
button rendered using html:submit
. The property
attribute value on the html:submit
tag must match
the parameter
attribute value for the
corresponding action in the struts-config.xml
file. Struts will look up the method to call from your
LookupDispatchAction
implementation, using the map
returned in the getKeyMethodMap( )
method. The JSP
page in Example 6-7
(lookup_dispatch_test.jsp) specifies three
submit buttons corresponding to the three operations provided by the
class in Example 6-6.
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %> <html> <head> <title>Struts Cookbook - Chapter 6 : Lookup Dispatch Action Test</title> </head> <body bgcolor="white"> <h2>Lookup Dispatch Action Test</h2> <html:form method="get" action="/LookupDispatchActionTest"> Name: <html:text property="name"/> <p> <html:submit property="methodToCall"> <bean:message key="button.add"/> </html:submit> <html:submit property="methodToCall"> <bean:message key="button.edit"/> </html:submit> <html:submit property="methodToCall"> <bean:message key="button.remove"/> </html:submit> </p> </html:form> <hr /> <c:if test="${not empty TestForm.map.name}"> Name: <c:out value="${TestForm.map.name}"/><br /> Dispatch Method: <b><c:out value="${dispatchedTo}"/></b> </c:if> </body> </html>
The
LookupDispatchAction
, included with Struts,
performs the same function as the DispatchAction
that it extends. It is designed to allow for a single
Action
class to service related operations
triggered by clicking submit buttons on a form. Unlike the
DispatchAction
, the
LookupDispatchAction
doesn’t
require the use of JavaScript to set a request parameter. Instead,
you add a method to your subclass of
LookupDispatchAction
that returns a map of
key/value pairs. The key
matches the
MessageResources
key for the corresponding button
label, and the value
represents the name
of the corresponding method to call.
In the Solution, the relevant key/value pairs values defined in the
MessageResources
properties file are shown here:
button.add = Add Me button.edit = Change Me button.remove = Remove Me
Each property value—that is, the right-side of the
pair—is retrieved using the bean:message
tag:
<html:submit property="methodToCall"> <bean:message key="button.add"/> </html:submit>
When you click this button, the URL will look something like:
http://localhost/jsc-ch06/LookupDispatchActionTest.do? name=Bill&methodToCall=Add+Me
Struts will use the value of the
methodToCall
request parameter to look up
the key for the value from the MessageResources
.
For this example, it will retrieve the key
“button.add”. Struts then uses the
Map
returned in your class’s
getKeyMethodMap( )
method to determine the method
to call. For the example, this will be the create()
method. The method is discovered and invoked using Java
reflection.
The performance of reflection operations has improved in JDK 1.4. In
particular, reflective method invocation, used by the
DispatchAction
(and subclasses), and object instantiation have been rewritten. These
operations perform several times faster than in previous releases of
the JDK. For Struts applications the overhead of using reflection is
minimal.
Using the LookupDispatchAction
can seem a little
tricky at first. Once you understand how it works, it can reduce
substantially the number of classes you have to maintain. It ties
nicely into Struts support for internationalization. If you are
localizing button labels using Struts bean:message
tags, you have the necessary presentation hooks in place to leverage
the LookupDispatchAction
.
If you are using images as submit buttons instead of text-based
buttons, use the DispatchAction
shown in Recipe 6-8 and set the
methodToCall
request parameter using
JavaScript. For an alternative approach when using image buttons,
check out Michael McGrady’s page on the Struts Wiki
at http://wiki.apache.org/struts/StrutsCatalogMultipleImageButtonsWithNoJavaScript.
You want to use a single Action
class to process
related operations yet allow the form type, validation rules, and
other action
attributes to vary for each
operation.
Extend the Struts pre-built
MappingDispatchAction
(only available in Struts 1.2 or later) with your own subclass.
Provide methods for each operation you wish to be callable. Each
method should have the same signature as the execute()
method. The class shown in Example 6-8
provides three related operations: create( )
,
update( )
, and delete( )
.
package com.oreilly.strutsckbk.ch06; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.beanutils.PropertyUtils; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.actions.MappingDispatchAction; public class MyMappingDispatchAction extends MappingDispatchAction { public ActionForward create( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { PropertyUtils.setSimpleProperty(form, "dispatchedTo", "create"); return mapping.findForward("success"); } public ActionForward update( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { PropertyUtils.setSimpleProperty(form, "dispatchedTo", "update"); return mapping.findForward("success"); } public ActionForward delete( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { PropertyUtils.setSimpleProperty(form, "dispatchedTo", "delete"); return mapping.findForward("success"); } }
In the actions that use your
MappingDispatchAction
, specify the value of the
method to call as the value of the parameter
attribute. Unlike the DispatchAction
and
LookupDispatchAction
, a different action form,
identified by the name
attribute, can be specified
for each action mapping:
<action path="/AddAction" name="AddForm" type="com.oreilly.strutsckbk.ch06.MyMappingDispatchAction" parameter="create"> <forward name="success" path="/mapping_dispatch_test.jsp"/> </action> <action path="/ChangeAction" name="ChangeForm" type="com.oreilly.strutsckbk.ch06.MyMappingDispatchAction" parameter="update"> <forward name="success" path="/mapping_dispatch_test.jsp"/> </action> <action path="/RemoveAction" name="RemoveForm" type="com.oreilly.strutsckbk.ch06.MyMappingDispatchAction" parameter="delete"> <forward name="success" path="/mapping_dispatch_test.jsp"/> </action>
Reference the path
of the action to use as the
value for the action
attribute on the
html:form
tag. The JSP in Example 6-9
(mapping_dispatch_test.jsp) contains three
separate forms, each corresponding to the three actions defined in
the struts-config.xml file.
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %> <html> <head> <title>Struts Cookbook - Chapter 6 : Mapping Dispatch Action Test</title> </head> <body bgcolor="white"> <h2>Mapping Dispatch Action Test</h2> <hr /> <h3>Add Action</h3> <html:form method="get" action="/AddAction"> Name: <html:text property="name"/> <p><html:submit/></p> </html:form> <p> Name: <c:out value="${AddForm.map.name}"/><br /> Dispatch Method: <b><c:out value="${AddForm.map.dispatchedTo}"/> </b> <hr /> <h3>Change Action</h3> <html:form method="get" action="/ChangeAction"> Name: <html:text property="name"/> <p><html:submit/></p> </html:form> <p> Name: <c:out value="${ChangeForm.map.name}"/><br /> Dispatch Method: <b><c:out value="${ChangeForm.map.dispatchedTo}"/> </b> <hr /> <h3>Remove Action</h3> <html:link page="/RemoveAction.do?name=Test">Remove Me</html:link> <p> Name: <c:out value="${RemoveForm.map.name}"/><br /> Dispatch Method: <b><c:out value="${RemoveForm.map.dispatchedTo}"/> </b> </body> </html>
The
MappingDispatchAction
, included with Struts 1.2,
extends the DispatchAction
. Whereas the
operational methods implemented in a
DispatchAction
or
LookupDispatchAction
are tied to a request
parameter, the method to call in a
MappingDispatchAction
is tied to an
action
definition in the
struts-config.xml file. The
parameter
attribute of the
action
element identifies the method to call.
The MappingDispatchAction
allows you to group
together related methods. However, the ActionForm
can vary per method. This allows for greater flexibility when
dispatching related operations. You get the benefits of grouping the
related operations, yet you aren’t required to use a
common form if it doesn’t make sense. Consider a
concrete Action
for performing the traditional
CRUD functionality. Though the create( )
and
update( )
operations most likely use the same
ActionForm
, the read( )
and
delete( )
operations only require a single form
property representing the object’s primary
identifier or key.
Because the form can vary per method, the validation rules can vary
since these rules are related to the form type. The
MappingDispatchAction
provides a more elegant
solution than the other DispatchAction
types
because it doesn’t rely on JavaScript or the use of
reverse MessageResource
lookups. However, this
action is new to Struts and is available with Struts Version 1.2 or
later.
If you are using Struts 1.1 and want to use a
DispatchAction
for related operations, Recipes
Section 6.8 and Section 6.9 show
how this can be accomplished. A short tutorial on using the
MappingDispatchAction
can be found in the weblog
entry at http://frustratedprogrammer.blogspot.com/2004/07/struts-121-mappingdispatchaction.html.
You should also read over the JavaDocs for the
MappingDispatchAction
. These can be
found at http://struts.apache.org/api/org/apache/struts/actions/MappingDispatchAction.html.
3.137.176.166