Wait Page for Long Events

@WaitPage annotation

@WaitPage annotation is part of Stripes Stuff

Purpose

To show a wait page when action takes a long time to execute.

Configuration

  1. Add org.stripesstuff.plugin.waitpage.WaitPageInterceptor before net.sourceforge.stripes.controller.BeforeAfterMethodInterceptor in web.xml.
  2. Add a mapping for Stripes dispatcher for *.wait

    Why not put WaitPageInterceptor in Extension.Packages?

    Since WaitPageInterceptor skips EventHandling and ResolutionExecution stages in first request and ActionBeanResolution, HandlerResolution, BindingAndValidation, CustomValidation and ResolutionExecution stages in background request, WaitPageInterceptor needs to execute before BeforeAfterMethodInterceptor interceptor or your @Before methods could be executed too many times.

    A common configuration will look like this.

    web.xml
     
    <filter> 
    <display-name>Stripes Filter</display-name> 
    <filter-name>StripesFilter</filter-name> 
    <filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class> 
    <init-param> 
    <param-name>CoreInterceptor.Classes</param-name> 
    <param-value> 
    org.stripesstuff.plugin.waitpage.WaitPageInterceptor, 
    net.sourceforge.stripes.controller.BeforeAfterMethodInterceptor,
    net.sourceforge.stripes.controller.HttpCacheInterceptor 
    </param-value> 
    <!-- Optionally set a timeout for context (since StripesStuff 0.3) --> 
    <!-- 
    <param-name>WaitPageInterceptor.ContextTimeout</param-name> 
    <param-value> 
    300000 
    </param-value> 
    --> 
    </init-param> 
    </filter> 
    <servlet> 
    <servlet-name>StripesDispatcher</servlet-name> 
    <servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class> 
    <load-on-startup>1</load-on-startup> 
    </servlet> 
    <servlet-mapping> 
    <servlet-name>StripesDispatcher</servlet-name> 
    <url-pattern>*.wait</url-pattern> 
    </servlet-mapping> 
    

Simple example

SlowAction
 
public class SlowAction implements ActionBean { 

ActionBeanContext context; 
public ActionBeanContext getContext() {return context;} 
public void setContext(ActionBeanContext context) {this.context = context;} 


/** 
* Event's progression. 
*/ 
private int progress; 
/** 
* True after event completes. 
*/ 
private boolean complete; 


/** 
* Go to index page. 
* @return index page 
*/ 
@DefaultHandler 
public Resolution input() { 
return new ForwardResolution("/WEB-INF/pages/waitpage/index.jsp"); 
} 
/** 
* Execute a slow event. 
* @return index page 
*/ 
@HandlesEvent("slowEvent") 
@WaitPage(path="/WEB-INF/pages/waitpage/wait.jsp", delay=1000, refresh=1000) 
public Resolution slowEvent() { 
try { 
for (int i = 1; i <= 10; i++) { 
Thread.sleep(1000); 
progress = i*10; 
} 
} catch (InterruptedException e) { 
} 
complete = true; 
return new ForwardResolution("/WEB-INF/pages/waitpage/index.jsp"); 
} 
/** 
* Execute a slow event with an AJAX updater. 
* @return index page 
*/ 
@HandlesEvent("slowEventWithAjaxUpdater") 
@WaitPage(path="/WEB-INF/pages/waitpage/ajaxwait.jsp", delay=1000, refresh=1000, ajax="/WEB-INF/pages/waitpage/ajax.jsp") 
public Resolution slowEventWithAjaxUpdater() { 
try { 
for (int i = 1; i <= 10; i++) { 
Thread.sleep(1000); 
progress = i*10; 
} 
} catch (InterruptedException e) { 
} 
complete = true; 
return new ForwardResolution("/WEB-INF/pages/waitpage/index.jsp"); 
} 


public int getProgress() { 
return progress; 
} 
public boolean isComplete() { 
return complete; 
} 
} 
/WEB-INF/pages/waitpage/index.jsp
 
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %> 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 

<html> 
<head> 
</head> 

<body> 

<c:if test="${actionBean.complete}"> 
<div> 
Event completed. 
</div> 
</c:if> 

<s:form beanclass="org.stripesstuff.examples.waitpage.SlowAction" method="POST"> 
<div> 
<s:submit name="slowEvent">Slow event...</s:submit> 
</div> 
<div> 
<s:submit name="slowEventWithAjaxUpdater">Slow event with ajax updater...</s:submit> 
</div> 
</s:form> 

</body> 
</html> 

Simple wait page

/WEB-INF/pages/waitpage/wait.jsp
 
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %> 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 

<html> 
<head> 
<meta http-equiv="refresh" content="0"/> 
</head> 

<body> 
Progression: ${actionBean.progress} 
</body> 
</html> 

Wait page with an AJAX updater

/WEB-INF/pages/waitpage/ajaxwait.jsp
 
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %> 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 

<html> 
<head> 
<c:url var="javascriptUrl" value="/javascript/jquery-1.3.1.min.js"/> 
<script type="text/javascript" src="${javascriptUrl}"></script> 
<script type="text/javascript"> 
<!-- 
var count = 0; 
function updater() { 
var complete = false; 
var progress; 
count++; 
jQuery.get(window.location.href, {ajax: "true"}, function(content){ 
jQuery("span.progress").html(jQuery(content).filter("span.progress").html());
jQuery("span.complete").html(jQuery(content).filter("span.complete").html());
progress = jQuery(content).filter("span.progress").html(); 
complete = jQuery(content).filter("span.complete").html(); 
if (complete == "true") { 
window.location.reload(); 
} else { 
updater(); 
} 
}, "html"); 
} 
jQuery(function(){updater()}); 
--> 
</script> 
</head> 

<body> 
<div> 
Progression: <span class="progress">0</span> 
</div> 
<div> 
Complete: <span class="complete">false</span> 
</div> 
</body> 
</html> 
/WEB-INF/pages/waitpage/ajax.jsp
 
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %> 

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 

<span class="progress">${actionBean.progress}</span> 
<span class="complete">${actionBean.complete}</span> 

@WaitPage annotation attributes

Attribute

Value

Default

path

Location of wait page. If event takes a long time, user will be forwarded to wait page.

No default value. Attribute is required.

delay

Number of milliseconds that the event can take before user if forwarded to wait page.

0

refresh

Number of milliseconds between each refresh of wait page.

No negative refresh

If refresh is 0 or less, default value is used.

60 seconds (60000 milliseconds).

error

Page that user will be forwarded to if event throws an exception.

If event throws an exception and no error page is specified, the exception will be handled by stripes (or any exception handler registered by stripes).

Keep in mind that if event throws an exception, Stripes will handle the exception thrown in the background request. If no error page is specified, the exception will by handled a second time by Stripes when wait page refreshes.
This is done to insure that the exception thrown by event will be handled by Stripes at least once if user cancels page refresh.

No default value.

ajax

Page location for AJAX updater.

No default value.

Additional configuration (since StripesStuff 0.3)

Property

Value

Default

WaitPageInterceptor.ContextTimeout

Timeout, in milliseconds, used to automatically remove ActionBean instance from memory in case a user stop waiting in the wait page

Timeout is computed against the moment event completes

So no need to worry about events that takes more time to execute than the WaitPageInterceptor.ContextTimeout value

5 minutes (300000 milliseconds)

How to use @WaitPage

To use @WaitPage correctly, you must include some code in your action bean and your wait page.

  • Action bean event that takes a long time must be annotated with @WaitPage.
  • Your wait page must refresh itself. This can be done by doing one of the following:
    • Add a meta tag to page head <meta http-equiv="refresh" content="0"/>.
    • Have an AJAX updater in wait page that will refresh the page once event completes.

Progression

If your action bean records event progression, it can be shown in wait page.

Example

Action bean
 
private int progress; 

@WaitPage(path="wait.jsp", refresh=1000) 
public Resolution slowEvent() { 
progress = 0; 
Thread.sleep(500); 
progress = 25; 
Thread.sleep(500); 
progress = 50; 
Thread.sleep(500); 
progress = 75; 
Thread.sleep(500); 
progress = 100; 
return new ForwardResolution("index.jsp"); 
} 
Wait page
 
<html> 
<head> 
<meta http-equiv="refresh" content="0"/> 
</head> 
<body> 
Please wait for event to complete. Progress: ${actionBean.progress} 
</body> 
</html> 

How validation errors are handled

Validation errors requires no change to your code!

How messages are handled

Messages requires no change to your code!

Things to keep in mind when using @WaitPage

Use session to store attributes!

Request changes before event is executed and before resolution is executed. Session is the only reliable place to store attributes.

Request changes before event is executed

Request will change before event is executed since a response was sent before event is executed.
Request attributes set before event are not available in event.
Session will be available in event.

Request changes before resolution is executed

Request will change before resolution is executed since event was execute in a background request.
Request attributes set before resolution are not available in resolution.
Session will be available in resolution.

Form population in event's resolution requires action bean properties

Form tags values must be coming from action bean since no request parameter will be available in wait page or event's resolution.

Request headers are not available in event, wait page and event's resolution

Use session instead.

Request attributes are not available in event, wait page and event's resolution

Use session instead.

StripesStuff 0.2: Request parameters are not available in event, wait page and event's resolution

Use session instead.

Since StripesStuff 0.3, request parameters are available in the event but not in wait page and event's resolution.

  • @WaitPage will run the event in a separate request that will execute in the background.
  • All lifecycles stages that executes before event will be executed normally. @WaitPage have an effect only on EventHandling and ResolutionExecution stages.

Workflow

If a simple wait page is used

Wait page must refresh itself

Add a meta tag to page head <meta http-equiv="refresh" content="0"/>

  1. All stages before EventHandling executes exactly as if no @WaitPage annotation is present on event.
  2. A new request is created to execute the event in background. In the background request, all stages are skipped except EventHandling.
  3. A redirect resolution is returned to wait for event to complete.
  4. If event completes before delay, resolution returned by event is executed. Flow ends immediately.
  5. If delay expired and event didn't complete, wait page is returned.
  6. Wait page is refreshed until event completes.
  7. Event's resolution is executed.

If an AJAX updater is used

Wait page must refresh itself

If an AJAX updater is used, your page must have a way to known when to refresh itself. It can be done by putting an indicator in your action bean that will be flagged once event completes.

Action bean
 
public Resolution myEvent { 
Do stuff... 
completeIndicator = true; 
} 
  1. All stages before EventHandling executes exactly as if no @WaitPage annotation is present on event.
  2. A new request is created to execute the event in background. In the background request, all stages are skipped except EventHandling.
  3. A redirect resolution is returned to wait for event to complete.
  4. If event completes before delay, resolution returned by event is executed. Flow ends immediately.
  5. If delay expired and event didn't complete, wait page is returned.
  6. AJAX updater in wait page must make requests to the same URL as wait page would do for refresh, but a non-empty parameter named "ajax" must be added to request. The value of the parameter has no consequences.
  7. Once indicator is flagged, AJAX updater must refresh wait page.
  8. Event's resolution is executed.

Bugs

Please report any bugs to me at Christian.Poitras@ircm.qc.ca