Chapter 6. Leveraging Actions

Introduction

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 Actions? If it is, you aren’t alone. This condition can be acceptable, but you may not be leveraging Struts Actions to their full extent. Using the recipes in this chapter will enable you to get more reuse from your Actions 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 Actions 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 Actions.

6.1. Creating a Base Action

Problem

You want to implement and enforce a common feature or behavior across all of your Actions.

Solution

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.

Example 6-1. Abstract base action
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( ) {...}
}

Discussion

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 Actions 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.

See Also

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.

6.2. Relaying Actions

Problem

You want to link one action directly to another.

Solution

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>

Discussion

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>

See Also

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.

6.3. Returning the HTTP Response

Problem

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.

Solution

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.

Example 6-2. Writing the HTTP response in an action
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;
  }        

}

Discussion

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.

See Also

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.

6.4. Writing Thread-Safe Actions

Problem

Your Action classes must operate correctly in a multi-threaded environment.

Solution

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.

Example 6-3. Thread-safe action
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);
    }
}

Discussion

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.

See Also

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.

6.5. Forwarding Requests

Problem

You want to provide an action that forwards a request to any action, JSP page, servlet, or other resource of your web application.

Solution

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"/>

Discussion

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.

See Also

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.

6.6. Including the Response from a Servlet or JSP

Problem

You want to retrieve and include a partial HTTP response from a servlet or JSP, but you want control to go through Struts.

Solution

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"/>

Discussion

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"/>

See Also

Recipe 6.5 shows how to use a similar approach that provides the equivalent replacement for calls to the RequestDispatcher.forward().

6.7. Changing the Current Module

Problem

You want to allow the user to switch to a different module at runtime.

Solution

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.

Discussion

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!

Warning

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.

Table 6-1. Switch action parameters

Parameter

Description

Examples

prefix

The name of the module to switch to. This value should start with a leading /.. Use an empty string to denote the default module.

prefix=""prefix="/admin"

page

The module-relative URL of the JSP or Action to execute.

page="/main.jsp"page="/AddEmployee.do?id=5"

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=&amp;page=/default_module.jsp"/>
    <forward name="goToModule2" 
             path="/SwitchModule.do?prefix=/mod2&amp;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>

Warning

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 &amp; 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.

See Also

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.

6.8. Managing Related Operations from a Central Action

Problem

You want to use a single Action class to handle related operations instead of having to write separate Action classes for each operation.

Solution

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( ).

Example 6-4. Dispatch action for related operations
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 actions 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.

Example 6-5. JSP for submitting to 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:&nbsp;<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>

Discussion

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 DispatchActions 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 DispatchActions, 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.

See Also

Recipe 6.9 provides similar functionality as the DispatchAction without requiring the use of JavaScript on the JSP page.

6.9. Submitting a Form from Localized Form Controls

Problem

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.

Solution

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.

Example 6-6. LookupDispatchAction for related operations
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.

Example 6-7. JSP that submits to a LookupDispatchAction
<%@ 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:&nbsp;<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:&nbsp;<c:out value="${TestForm.map.name}"/><br />
    Dispatch Method:&nbsp;<b><c:out value="${dispatchedTo}"/></b>
  </c:if>
</body>
</html>

Discussion

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.

Tip

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.

See Also

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.

6.10. Dispatching to Related Operations with Action Mappings

Problem

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.

Solution

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( ).

Example 6-8. MappingDispatchAction for related operations
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.

Example 6-9. JSP that submits to a mapping dispatch action
<%@ 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:&nbsp;<html:text property="name"/>
    <p><html:submit/></p>
  </html:form>
  <p>
  Name:&nbsp;<c:out value="${AddForm.map.name}"/><br />
  Dispatch Method:&nbsp;<b><c:out value="${AddForm.map.dispatchedTo}"/>
  </b>
  <hr />
  <h3>Change Action</h3>
  <html:form method="get" action="/ChangeAction">
    Name:&nbsp;<html:text property="name"/>
    <p><html:submit/></p>
  </html:form>
  <p>
  Name:&nbsp;<c:out value="${ChangeForm.map.name}"/><br />
  Dispatch Method:&nbsp;<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:&nbsp;<c:out value="${RemoveForm.map.name}"/><br />
  Dispatch Method:&nbsp;<b><c:out value="${RemoveForm.map.dispatchedTo}"/>
  </b>
</body>
</html>

Discussion

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.

See Also

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.176.166