Chapter 21: Struts Cookbook

 < Day Day Up > 



This chapter contains a wealth of advanced material that will enable you to get the most out of Struts. Think of it as a cookbook for recipes that will help you solve common problems in your Web application development with Struts.

Action Helper Methods

The Action class contains many helper methods which enable you to add advanced functionality to your Struts applications. Some of the functionality we'll cover in this section includes:

  • Making sure that a form is not submitted twice (see the section "Action saveToken and isTokenValid").

  • Display dynamic messages that are i18n-enabled (see the section "Action saveMessages and getResources").

  • Allow users to cancel an operation (see the section "Action isCancelled").

  • Allow users to change their locale (see the section "Action getLocale and setLocale").

Action saveToken and isTokenValid

Have you ever wanted to make sure that a user does not submit a form twice? I have. Perhaps you have even implemented your own routine that does this. The idea behind saveToken and isTokenValid is to make sure that a form has not been submitted twice. It is nice to know that this functionality is built into Struts and that it is handled by many of its internal operations.

Struts provides transaction tokens to ensure that a form is not submitted twice. A transaction token has nothing to do with Extended Attribute (XA) transactions, Enterprise JavaBeans (EJB), or Java Transaction Services (JTS). A transaction token is a unique string that is generated. The token is submitted with the html:form tag. Several classes and one custom tag are involved in this bit of choreography.

To use a transaction token, follow these steps:

  1. Before you load the JavaServer Pages (JSP) page that has the html:form tag on it, call saveToken inside an action.

  2. When the user submits the form, call isTokenValid and handle the form only if the token is valid.

The first step is to call saveToken inside an action. To do this, you have to make sure an action is called before the JSP page loads.

Let's say that before you started using transaction tokens you had a form that was associated with an action using the action mapping's forward attribute, as follows:

   <global-forwards>       <forward name="input" path="/input.do" />       ...   </global-forwards>   <action-mappings>         <action path="/input" forward="/input.jsp" /> </action-mappings> 

Then any JSP page that links to the input form would link to it like this:

 <html:link forward="input"> Input </html:link> 

Therefore, no JSP links directly to input.jsp. If this is the case, it is easy to start using transaction tokens.

Now let's say that you want to make sure that the user cannot hit the back button in the browser and submit the form twice. To do this, you must change the action mapping associated with the input form to map to an action that will call the saveToken method of Action:

   <action-mappings>         <action             path="/input"             type="masteringStruts.InputAction"             parameter="loadAddForm">          <forward name="success" path="/input.jsp"/>        </action>         <action             path="/inputSubmit"             type="masteringStruts.InputAction"             name="inputForm"             scope="request"             validate="true"             input="/input.jsp">          <forward name="success" path="/success.jsp"/>          <forward name="resubmit" path="/resubmit.jsp"/>        </action>   </action-mappings> 

Action's saveToken method generates and saves a transaction token and puts it in session scope under the key Globals.TRANSACTION_TOKEN_KEY. Think of a transaction token as a unique string.

Notice that the action mapping for input sets the parameter to loadAddForm. The action will use the parameter to load the form.

We already have an InputAction for this form that processes the form once it is validated. Thus, we need to modify the InputAction so that it can handle loading the form by calling saveToken:

 public class InputAction extends Action {     public ActionForward execute(         ActionMapping mapping,         ActionForm form,         HttpServletRequest request,         HttpServletResponse response)         throws Exception {         if ("loadAddForm".equals(mapping.getParameter())) {             return loadAddUserForm(mapping, form, request, response);         } else {             return add(mapping, form, request, response);         }     } public ActionForward loadAddUserForm(     ActionMapping mapping,     ActionForm form,     HttpServletRequest request,     HttpServletResponse response)     throws Exception {         saveToken(request);         return mapping.findForward("success");    } ... 

If you have been following along, you realize that we want to pass the parameter back to the action when the form is submitted. And you may think you need to add your own hidden field to the form. Actually, the html:form tag does this bit of magic for us; in fact, here is a code snippet from html:form:

          String token =               (String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);          if (token != null) {              results.append("<input type=\"hidden\" name=\"");              results.append(Constants.TOKEN_KEY);              results.append("\" value=\"");              results.append(token);              if (this.isXhtml() ) {                  results.append("\" />");              } else {                  results.append("\">");              }         } 

Now, as you'll recall from Step 2, when the user submits the form call isTokenValid, the code should handle the form only if the token is valid. Now that we know the form will have the transaction token, we can check to see whether it exists by using isTokenValid. When the user submits the valid form, the add method is called on the InputAction:

 public class InputAction extends Action {     public ActionForward execute(         ActionMapping mapping,         ActionForm form,         HttpServletRequest request,         HttpServletResponse response)         throws Exception {         if ("loadAddForm".equals(mapping.getParameter())) {             return loadAddUserForm(mapping, form, request, response);         } else {             return add(mapping, form, request, response);         }     } ...     public ActionForward add(         ActionMapping mapping,         ActionForm form,         HttpServletRequest request,         HttpServletResponse response)         throws Exception {         ...         if ( isTokenValid(request, true)){             InputForm inputForm = (InputForm) form;             // Do something useful with this form!             return mapping.findForward("success");         } else{             // Invalid token! User tried to submit form twice.             return mapping.findForward("resubmit");         }     } 

The way the add method is implemented, it will not allow a user to submit a form, then hit the Backspace key and submit the same form again. If the token is valid, the add method will do something useful with the form and then forward to success. If the token is not valid, the user is forwarded to the "resubmit" forward.

In RC1, saveToken and isTokenValid methods were implemented in the Action class. They have been refactored, and now all they do is delegate to methods of the same name in TokenProcessor (in the Struts util package). Thus, you can use these methods in your own Web components (e.g., the Tile controller, a custom RequestProcessor, JSP custom tags).

The RequestUtils computeParameters method optionally adds a transaction token to its list of parameters. The computeParameters method is used by the html:link, bean:include, html:rewrite, and logic:redirect tags. Many tags are transaction token aware.

Take a look at the JavaDocs for TokenProcessor to learn more and to see some variations of these methods. You can check the source code for TokenProcessor to gain a good understanding of how things really work.

Action isCancelled

It is often nice to give your user the ability to cancel an operation. Struts provides a special input field called html:cancel:

 <html:form action="inputSubmit">  <bean:message key="inputForm.userName"/>  <html:text property='userName'/> <br />  <bean:message key="inputForm.password"/>  <html:text property='password'/> <br />  ...  <html:cancel/> </html:form> 

You can use this input field to cancel a form submission. Then you just use the isCancelled method in the action handler to see whether the action was canceled:

 public class InputAction extends Action {     public ActionForward execute(         ActionMapping mapping,         ActionForm form,         HttpServletRequest request,         HttpServletResponse response)         throws Exception {         if ("loadAddForm".equals(mapping.getParameter())) {             return loadAddUserForm(mapping, form, request, response);         } else {             return add(mapping, form, request, response);         }     } ...     public ActionForward add(         ActionMapping mapping,         ActionForm form,         HttpServletRequest request,         HttpServletResponse response)         throws Exception {         if (isCancelled(request)) {             System.out.println("this submission has been cancelled");             return mapping.findForward("home");         } else if ( isTokenValid(request, true)){             InputForm inputForm = (InputForm) form;             System.out.println(inputForm.getUserName());             return mapping.findForward("success");         } else{             System.out.println("Can't resubmit the same form twice");             return mapping.findForward("resubmit");         }     } 

This action handler checks to see whether the operation was canceled. If it was, the action forwards the user to the home page.

Action getLocale and setLocale

You can dynamically set the locale for the user's session. Essentially, you let users decide what locale they want, using a form or link. Think of an automated teller machine (ATM); the first thing it often asks you is what language you speak, and you press the appropriate button. Well, you could do the same thing for your site.

To do this magic, you have an action tied to a form or link that reads the locale information from the request, creates a java.util.Locale, and stores the locale in session scope under the key Globals.LOCALE_KEY using the setLocale method:

 public class SetLocaleAction extends Action {   public ActionForward execute(              ActionMapping mapping,              ActionForm form,              HttpServletRequest request,              HttpServletResponse response) throws Exception {     String language = request.getParameter("language");        String country = request.getParameter("country");     Locale locale = new Locale(language, country);     setLocale(request,locale);     if (getLocale(request).getLanguage().equals("en")){         System.out.println("the language is set to English");         // Do something special for those who are logged in         // who speak English. Perhaps some features         // are different or disabled for English.         ...     }     ...     return actionMapping.findForward("success");   } } 

Notice that you can also get the locale and operate on it accordingly if needed. All of the tags (such as bean:message) respect this locale for the entire user session.

See Chapter 9, "Internationalizing Your Struts Applications," for more details on how to support the internationalization (i18n) features with resource bundles and bean:message.

Action saveMessages and getResources

Back in the primordial ooze of Struts pre-1.1, people liked ActionErrors. They started using them for tasks for which they were never intended. People started using ActionErrors as a general-purpose way to display i18n-enabled dynamic messages. Then Struts 1.1 added the concept of ActionMessages. If you recall from our ActionErrors discussion in Chapter 10, "Managing Errors," ActionErrors subclass ActionMessages. Now you can use ActionMessages to display dynamic i18n-enabled messages without feeling like a hack.

Working with ActionMessages is nearly identical to working with ActionErrors. Here is an example of adding ActionMessages that you want to be displayed inside an Actions Execute method:

   public ActionForward execute(       ActionMapping mapping,       ActionForm form,       HttpServletRequest request,       HttpServletResponse response)       throws Exception {       ActionMessages messages = new ActionMessages();       ActionMessage message = new ActionMessage("inputForm.greet");       messages.add(ActionMessages.GLOBAL_MESSAGE, message);       ...       saveMessages(request,messages);       ... 

Then to display the messages in the JSP, you use the html:messages tag as follows:

 <ul> <font color='green' > <html:messages  message="true">   <li><%= message %></li> </html:messages> </font> </ul> 

The html:messages tag will iterate over all of the messages. Notice that the message attribute of html:messages is set to true. This forces html:messages to get the messages from Globals.MESSAGE_KEY in request scope. If you do not set the message attribute, the html:messages tag will display the errors instead (Globals.ERROR_KEY).

What if you want to display an arbitrary number of messages-and it could vary by locale? In that case, you use getResources to get the number of messages for that locale:

    public ActionForward execute(        ActionMapping mapping,        ActionForm form,        HttpServletRequest request,        HttpServletResponse response)        throws Exception {        ActionMessages messages = new ActionMessages();        ActionMessage message = new ActionMessage("inputForm.greet");        messages.add(ActionMessages.GLOBAL_MESSAGE, message);        String num =            this.getResources(request).getMessage("inputForm.messageNum");        int messageCount = Integer.parseInt(num);        for(int index=0; index<messageCount; index++){            String messageKey="inputForm.message" + index;            message = new ActionMessage(messageKey);            messages.add(ActionMessages.GLOBAL_MESSAGE, message);        }        saveMessages(request,messages);        System.out.println(Globals.MESSAGE_KEY);        if (messages ==             request.getAttribute(Globals.MESSAGE_KEY)){           System.out.println("its there can't you see it");        }       ... 

The previous code corresponds to the following resource bundle entries:

 inputForm.greet=Hello Welcome to Trivera Technologies inputForm.messageNum=2 inputForm.message0=Please be sure to fill out your phone ... inputForm.message1=Pick a user name and password you can remember 

The inputForm.messageNum method specifies the number of messages for the inputForm. The for loop iterates up to the count of messages, grabbing each message for inputForm-that is, inputForm.message0, inputForm.message1, and so on.

These examples cause the messages in the resource bundle to display in an unordered list at the top of the input JSP page.

The RequestUtils class has a method called getActionMessages() that is used by logic:messagePresent and html:messages. The getActionMessages method allows you to retrieve the messages saved by Action.saveMessages and Action.saveErrors. The messages look like the one shown in Figure 21.1.

click to expand
Figure 21.1: The getActionMessages method allows you to retrieve messages.



 < Day Day Up > 



Professional Jakarta Struts
Professional Jakarta Struts (Programmer to Programmer)
ISBN: 0764544373
EAN: 2147483647
Year: 2003
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net