Wait Page for Long Events
@WaitPage annotation
@WaitPage annotation is part of Stripes Stuff
- Web site & download: http://sourceforge.net/projects/stripes-stuff/
- View source: http://stripes-stuff.svn.sourceforge.net/viewvc/stripes-stuff/
Purpose
To show a wait page when action takes a long time to execute.
Configuration
- Add
org.stripesstuff.plugin.waitpage.WaitPageInterceptor
beforenet.sourceforge.stripes.controller.BeforeAfterMethodInterceptor
in web.xml. Add a mapping for Stripes dispatcher for
*.wait
Why not put WaitPageInterceptor in Extension.Packages?
Since
WaitPageInterceptor
skipsEventHandling
andResolutionExecution
stages in first request andActionBeanResolution
,HandlerResolution
,BindingAndValidation
,CustomValidation
andResolutionExecution
stages in background request,WaitPageInterceptor
needs to execute beforeBeforeAfterMethodInterceptor
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
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; } }
<%@ 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
<%@ 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
<%@ 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>
<%@ 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. | 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.
- Add a meta tag to page head
Progression
If your action bean records event progression, it can be shown in wait page.
Example
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"); }
<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
andResolutionExecution
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"/>
- All stages before
EventHandling
executes exactly as if no @WaitPage annotation is present on event. - A new request is created to execute the event in background. In the background request, all stages are skipped except
EventHandling
. - A redirect resolution is returned to wait for event to complete.
- If event completes before delay, resolution returned by event is executed. Flow ends immediately.
- If delay expired and event didn't complete, wait page is returned.
- Wait page is refreshed until event completes.
- 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.
public Resolution myEvent { Do stuff... completeIndicator = true; }
- All stages before
EventHandling
executes exactly as if no @WaitPage annotation is present on event. - A new request is created to execute the event in background. In the background request, all stages are skipped except
EventHandling
. - A redirect resolution is returned to wait for event to complete.
- If event completes before delay, resolution returned by event is executed. Flow ends immediately.
- If delay expired and event didn't complete, wait page is returned.
- 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.
- Once indicator is flagged, AJAX updater must refresh wait page.
- Event's resolution is executed.
Bugs
Please report any bugs to me at Christian.Poitras@ircm.qc.ca