Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Struts Wiki" for change 
notification.

The following page has been changed by BaqHaidri:
http://wiki.apache.org/struts/StrutsPleaseWait

The comment on the change is:
Baq Haidri's Struts please wait solution.

New page:
=== A plug & play solution to add a 'Please Wait' page to any Struts action ===

A while back I was tasked with providing 'please wait' functionality to the 
[http://panther.appliedbiosystems.com PANTHER] website. The requirements were 
that the solution be easily added to any action that the programmer felt needed 
it.  Conversely, it should be just as easily be removed, if the background 
action were in some way sped up or changed.  We already decided that using 
Javascript/CSS/hiding layers/etc. was not the right solution for us.  I thought 
to myself, given Struts' remarkable page flow flexibility and general 
extensibility, there's got to be a way to do this elegantly.  I couldn't really 
find anything on the web that showed a satisfactory solution.   When I finally 
came up with a solution, I was surprised that the Struts package didn't already 
come with a feature like it.  I wondered to myself if there was something deep 
down terribly wrong with my method because I couldn't find anything similar 
published on the web.  I still wonder that but as of yet, I ha
 ven't found any problems with it and the solution was pushed to production.  
It's up to all of you in the user community to provide feedback and 
improvements. 

The entire solution itself involves five elements:

 1.  An addition of {{{parameter=processWithWait}}} in any action-mapping in 
the struts-config.xml file for an action that would take a long time.
 2.  An extension of both the {{{processActionPerform}}} and the 
{{{processValidate}}} methods in the Struts {{{RequestProcessor}}}.
 3.  A simple Java bean, which I call {{{ForwardActionBean}}}.
 4.  A jsp, pleaseWait.jsp, which represents the 'Please Wait' page itself.
 5.  The addition of a global forwards in the struts-config.xml that points to 
the pleaseWait.jsp, and of course, tell Struts that you're using another 
{{{RequestProcessor}}}.

Here's a sample action mapping in struts-config.xml that takes a long time to 
process and needs a please wait page:

''1. The struts-config.xml Action Mapping''
{{{
<action path="/doLongAction"
            type="com.takesforever.webapp.BakeATurkeyAction"
            scope="request"
            input="/turkeyForm.jsp"
            name="TurkeyForm"
            parameter="processWithWait"
            validate="true">
      <forward name="failure" path="/error.jsp" />
      <forward name="success" path="/turkeyDone.jsp" />
    </action>
}}}

Ok, so now you have this parameter, {{{parameter="processWithWait"}}}.  What is 
Struts going to do with it?  As you may know, all incoming client requests go 
through the Struts {{{ActionServlet}}} then over to the Struts 
{{{RequestProcessor}}} class.  Depending on your configuration, this class then 
forwards to the appropriate Struts Action class for your actual business 
processing.  

That's all nice and good.  But we don't want Struts to go right to our action 
just yet.  We want it to serve up our 'Please Wait' page first and then forward 
to our action.  That way, our user has a nice Please Wait page with some 
animation that they can swear at while our computers chug away in the 
background.  Here's where our overridden 
{{{RequestProcessor.processActionPerform}}} method comes in.  Now before you 
take a look at the lines and lines of code below (mostly comments), it's good 
to understand the following flow:

 1. {{{RequestProcessor's}}} {{{processActionPerform}}} receives a request.
 2. It checks the action mapping to see if it's a long transaction.  If not, 
call {{{super.processActionPerform}}} and it's business as usual. 
 3. The {{{ActionMapping}}} specifies a long transaction.  Check a session 
attribute to make sure this isn't the SECOND time we've received this same 
request (this will make more sense later).  It isn't.
 4. We create a {{{ForwardActionBean}}} class.  If this is a GET request, we 
store away the entire request string, form parameters and all, into this bean.  
We store the bean in the user's session.  If it is a POST request, we store 
away the request in the bean and we store the bean away into the user's 
session.  We ALSO clone the current form (already validated) and store that 
away into the user's session as well.
 5. After doing this bookkeeping, we forward off the please wait jsp page.
 6. The please wait jsp contains a META tag which basically ''re-requests the 
same request'' that is specified by the {{{ForwardActionBean}}} which is stored 
away in the user's session.  The client browser now basically "hangs" on this 
please wait page.
 7. {{{RequestProcessor's}}} {{{processActionPerform}}} receives a request for 
a long transaction.
 8. It checks the same session attribute it checked in step 2.  Since the 
session attribute is NOT null, this means we've seen this request before and 
we're receiving it a SECOND time.  
 9. We set this session attribute to null.
 10.  If it's a GET request, we simply call {{{super.processActionPerform}}} 
with the current form.  If it's a POST request, we simply call 
{{{super.processActionPerform}}} with the cloned form that we've stored away in 
the session.  (NB: our overridden processValidate method makes sure that we 
revalidate the cloned form for the POST requests NOT the new form.  Depending 
on the browser you're using, this new form is usually empty for roundtrip POST 
requests.)

''2. Here is my overriden Struts {{{RequestProcessor}}}.''

{{{
package com.takesforever.webapp;

import org.apache.struts.action.*;

/**
 * LongWaitRequestProcessor overrides the original RequestProcessor that
 * comes packaged with Struts.  It allows for the handling of long transactions,
 * and overrides the processActionPerform method to do this.
 * To use this functionality, the following element must be added to the
 * struts-config.xml:
 * <controller
 *  contentType="text/html;charset=UTF-8"
 *  debug="3"
 *  locale="true"
 *  nocache="true"
 *  processorClass="com.takesforever.webapp.LongWaitRequestProcessor"
 * />
 * Then the following two parameters must be added to your specific action
 * mapping:
 * parameter="processWithWait"
 * @author Baq Haidri
 * @version 1.0
 */
public class LongWaitRequestProcessor extends RequestProcessor {

  /**
   * This method has been overridden to handle the case where forms are 
submitted via
   * the POST request.  In a POST request, form parameters are not available 
directly
   * from the request URI, so they cannot be saved to the ForwardActionBean 
like in
   * a GET request.  However, the POST data is saved into the Struts 
ActionForms.
   * This data can be stored away in the session.  We must guard against Struts
   * resetting the form data between the requests, so the form bean is cloned, 
then
   * stored.
   * @param request
   * @param response
   * @param form
   * @param mapping
   * @return
   * @throws java.io.IOException
   * @throws javax.servlet.ServletException
   */
  protected boolean processValidate(javax.servlet.http.HttpServletRequest 
request,
                                    javax.servlet.http.HttpServletResponse 
response,
                                    ActionForm form,
                                    ActionMapping mapping)
      throws java.io.IOException, javax.servlet.ServletException {
    if (mapping.getParameter() == null) {
      return super.processValidate(request, response, form, mapping);
    } else if (mapping.getParameter().equals("processWithWait")) {
      javax.servlet.http.HttpSession session = request.getSession(true);
      if (session.getAttribute("action_path_key") == null) {
        return super.processValidate(request,response, form, mapping);
      } else {
        // Retrieve the saved form, it's not null, that means the previous 
request was
        // sent via POST and we'll need this form because the current one has 
had
        // all its values reset to null
        ActionForm nonResetForm = 
(ActionForm)session.getAttribute("current_form_key");
        if (nonResetForm != null) {
          session.setAttribute(mapping.getName(), nonResetForm);
          //System.out.println("*** Calling super.processValidate on 
nonResetForm ***");
          return super.processValidate(request, response, nonResetForm, 
mapping);
        } else {
          return super.processValidate(request, response, form, mapping);
        }
      }
    } else {
      return super.processValidate(request, response, form, mapping);
    }
  }


  protected ActionForward 
processActionPerform(javax.servlet.http.HttpServletRequest request,
                                               
javax.servlet.http.HttpServletResponse response,
                                               Action action,
                                               ActionForm form,
                                               ActionMapping mapping) throws
      java.io.IOException, javax.servlet.ServletException {

    // Get the parameter from the action mapping to find out whether or not this
    // action requires long transaction support.  If it doesn't continue 
processing
    // as usual.  If it does, then process with a wait page.
    if (mapping.getParameter() == null) {
      //System.out.println("*** There is no wait parameter given, process as 
usual ***");
      return super.processActionPerform(request, response, action, form, 
mapping);
    } else if (mapping.getParameter().equals("processWithWait")) {
      //System.out.println("*** Parameter present, process with wait page ***");
      javax.servlet.http.HttpSession session = request.getSession(true);
      // If the action_path_key attribute is null, then it is the first time 
this
      // long transaction Action is being accessed.  This means we send down a
      // wait page with a meta-refresh tag that calls on the action again.
      if (session.getAttribute("action_path_key") == null) {
        //System.out.println("*** First time the action has been requested 
***");
        //System.out.println("*** forward user to wait page, have wait page 
***");
        ForwardActionBean b = new ForwardActionBean();
        if (request.getQueryString() != null)
          b.setActionPath(request.getRequestURI()+"?"+request.getQueryString());
        else
          b.setActionPath(request.getRequestURI());
        //System.out.println("*** re-request the following " + 
b.getActionPath() +" ***");
        session.setAttribute("action_path_key", b);
        // Save the current state of the form into the session because the
        // next time we arrive here, the form will have been reset.
        // This will be used only for requests that were sent via the POST 
method.
        if (request.getMethod().equalsIgnoreCase("POST")) {
          //System.out.println("*** Request sent via the POST method ***");
          ActionForm nonResetForm = null;
          try {
            nonResetForm = (ActionForm) 
org.apache.commons.beanutils.BeanUtils.cloneBean(form);
            session.setAttribute("current_form_key", nonResetForm);
          } catch (Exception e) {
            System.out.println("*** Error cloning the form for processWithWait 
***");
          }
        }
        return mapping.findForward("pleaseWait");
      // The action has been called on a second time, from the wait
      // page itself.  This time we actually execute the action.  We reset
      // the action path key so that the next time this action is requested, the
      // wait page is NOT served.
      } else {
        //System.out.println("*** Second time the action has been requested 
***");
        //System.out.println("*** forward user to actual action ***");
        session.setAttribute("action_path_key", null);
        // Retrieve the form, it's not null, that means the previous request was
        // sent via POST and we'll need this form because the current one has 
had
        // all its values reset to null
        ActionForm nonResetForm = 
(ActionForm)session.getAttribute("current_form_key");
        if (nonResetForm != null) {
          session.setAttribute("current_form_key", null);
          return super.processActionPerform(request, response, action, 
nonResetForm, mapping);
        } else {
          return super.processActionPerform(request, response, action, form, 
mapping);
        }
      }
    // A parameter has been set in the mapping but it is not the value we are
    // looking for
    } else {
      return super.processActionPerform(request, response, action, form, 
mapping);
    }

  }
}
}}}

''3. Here's what the {{{ForwardActionBean}}} looks like''

{{{
package com.takesforever.webapp;

/**
 * This class is a utility class that goes along with the
 * LongWaitRequestProcessor and helps in processing requests that require
 * long transaction support.
 * @author Baq Haidri
 * @version 1.0
 */
public class ForwardActionBean {

  private String actionPath;

  public void setActionPath(String v) {
    this.actionPath = v;
  }

  public String getActionPath() {
    return this.actionPath;
  }
}
}}}

''4. Here's what the pleaseWait.jsp page looks like''

{{{
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>

<html:html>
<head>
  <!-- The following two lines are required, they instruct the browser to not 
cache the wait page. -->
  <!-- This way we can better guarantee that when the user hits the 'Back' 
button, they don't get the wait page instead of the form. -->
  <META http-equiv="CACHE-CONTROL" content="NO-CACHE">  <!-- For HTTP 1.1 -->
  <META http-equiv="PRAGMA" content="NO-CACHE">         <!-- For HTTP 1.0 -->
  <META http-equiv="refresh" content="0; URL=<bean:write name="action_path_key" 
property="actionPath"/>">
  <link href="/css/pleasewait.css" type=text/css rel=stylesheet>
  <title>Please wait...</title>
</head>

<body onload="javascript:window.focus();">
  <table cellpadding="10">
         <tr>
           <td align="left"><h1>Please wait...</h1></td>
         </tr>
  </table>
</body>

</html:html>
}}}

''5. And here's an excerpt of a struts-config.xml file''

{{{
<!-- ========== Controller Configuration ================================ -->
  <controller
    contentType="text/html;charset=UTF-8"
    debug="3"
    locale="true"
    nocache="true"
    processorClass="com.takesforever.webapp.LongWaitRequestProcessor"
  />

  <global-forwards>
    <forward name="pleaseWait"   path="/pleaseWait.jsp"/>
  </global-forwards>
}}}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to