@WaitPage annotation
@WaitPage annotation is part of Stripes Stuff
Purpose
To show a wait page when action takes a long time to execute.
Configuration
- Add org.stripesstuff.plugin.waitpage.WaitPageInterceptor before net.sourceforge.stripes.controller.BeforeAfterMethodInterceptor in web.xml.
- 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.
<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>
</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: %>
<%@ taglib prefix="s" uri="http: %>
<!DOCTYPE HTML PUBLIC "- "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: %>
<%@ taglib prefix="s" uri="http: %>
<!DOCTYPE HTML PUBLIC "- "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: %>
<%@ taglib prefix="s" uri="http: %>
<!DOCTYPE HTML PUBLIC "- "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: %>
<%@ taglib prefix="s" uri="http: %>
<!DOCTYPE html PUBLIC "- "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. |
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
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 and parameters are not available in event, wait page and event's resolution Request headers and parameters will not be available in event, wait page and event's resolution. Attributes should be available in event, wait page and event's resolution, but it is not reliable. Use session instead. |
- @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"/> |
- 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