001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.interceptor;
006:
007: import com.opensymphony.xwork.ActionContext;
008: import com.opensymphony.xwork.ActionInvocation;
009: import com.opensymphony.xwork.ActionProxy;
010: import com.opensymphony.xwork.config.entities.ResultConfig;
011: import com.opensymphony.xwork.interceptor.Interceptor;
012: import org.apache.commons.logging.Log;
013: import org.apache.commons.logging.LogFactory;
014:
015: import java.util.Collections;
016: import java.util.Map;
017:
018: /**
019: * <!-- START SNIPPET: description -->
020: *
021: * The ExecuteAndWaitInterceptor is great for running long-lived actions in the background while showing the user a nice
022: * progress meter. This also prevents the HTTP request from timing out when the action takes more than 5 or 10 minutes.
023: *
024: * <p/> Using this interceptor is pretty straight forward. Assuming that you are including webwork-default.xml, this
025: * interceptor is already configured but is not part of any of the default stacks. Because of the nature of this
026: * interceptor, it must be the <b>last</b> interceptor in the stack.
027: *
028: * <p/> This interceptor works on a per-session basis. That means that the same action name (myLongRunningAction, in the
029: * above example) cannot be run more than once at a time in a given session. On the initial request or any subsequent
030: * requests (before the action has completed), the <b>wait</b> result will be returned. <b>The wait result is
031: * responsible for issuing a subsequent request back to the action, giving the effect of a self-updating progress
032: * meter</b>.
033: *
034: * <p/> If no "wait" result is found, WebWork will automatically generate a wait result on the fly. This result is
035: * written in FreeMarker and cannot run unless FreeMarker is installed. If you don't wish to deploy with FreeMarker, you
036: * must provide your own wait result. This is generally a good thing to do anyway, as the default wait page is very
037: * plain.
038: *
039: * <p/>Whenever the wait result is returned, the <b>action that is currently running in the background will be placed on
040: * top of the stack</b>. This allows you to display progress data, such as a count, in the wait page. By making the wait
041: * page automatically reload the request to the action (which will be short-circuited by the interceptor), you can give
042: * the appearance of an automatic progress meter.
043: *
044: * <p/>This interceptor also supports using an initial wait delay. An initial delay is a time in milliseconds we let the
045: * server wait before the wait page is shown to the user. During the wait this interceptor will wake every 100 millis
046: * to check if the background process is done premature, thus if the job for some reason doesn't take to long the wait
047: * page is not shown to the user.
048: * <br/> This is useful for e.g. search actions that have a wide span of execution time. Using a delay time of 2000
049: * millis we ensure the user is presented fast search results immediately and for the slow results a wait page is used.
050: *
051: * <p/><b>Important</b>: Because the action will be running in a seperate thread, you can't use ActionContext because it
052: * is a ThreadLocal. This means if you need to access, for example, session data, you need to implement SessionAware
053: * rather than calling ActionContext.getSesion().
054: *
055: * <p/>The thread kicked off by this interceptor will be named in the form <b><u>actionName</u>BrackgroundProcess</b>.
056: * For example, the <i>search</i> action would run as a thread named <i>searchBackgroundProcess</i>.
057: *
058: * <!-- END SNIPPET: description -->
059: *
060: * <p/> <u>Interceptor parameters:</u>
061: *
062: * <!-- START SNIPPET: parameters -->
063: *
064: * <ul>
065: *
066: * <li>threadPriority (optional) - the priority to assign the thread. Default is <code>Thread.NORM_PRIORITY</code>.</li>
067: * <li>delay (optional) - an initial delay in millis to wait before the wait page is shown (returning <code>wait</code> as result code). Default is no initial delay.</li>
068: * <li>delaySleepInterval (optional) - only used with delay. Used for waking up at certain intervals to check if the background process is already done. Default is 100 millis.</li>
069: *
070: * </ul>
071: *
072: * <!-- END SNIPPET: parameters -->
073: *
074: * <p/> <u>Extending the interceptor:</u>
075: *
076: * <p/>
077: *
078: * <!-- START SNIPPET: extending -->
079: *
080: * If you wish to make special preparations before and/or after the invocation of the background thread, you can extend
081: * the BackgroundProcess class and implement the beforeInvocation() and afterInvocation() methods. This may be useful
082: * for obtaining and releasing resources that the background process will need to execute successfully. To use your
083: * background process extension, extend ExecuteAndWaitInterceptor and implement the getNewBackgroundProcess() method.
084: *
085: * <!-- END SNIPPET: extending -->
086: *
087: * <p/> <u>Example code:</u>
088: *
089: * <pre>
090: * <!-- START SNIPPET: example -->
091: * <action name="someAction" class="com.examples.SomeAction">
092: * <interceptor-ref name="completeStack"/>
093: * <interceptor-ref name="execAndWait"/>
094: * <result name="wait">longRunningAction-wait.jsp</result>
095: * <result name="success">longRunningAction-success.jsp</result>
096: * </action>
097: *
098: * <%@ taglib prefix="ww" uri="/webwork" %>
099: * <html>
100: * <head>
101: * <title>Please wait</title>
102: * <meta http-equiv="refresh" content="5;url=<ww:url includeParams="all" />"/>
103: * </head>
104: * <body>
105: * Please wait while we process your request.
106: * Click <a href="<ww:url includeParams="all" />"></a> if this page does not reload automatically.
107: * </body>
108: * </html>
109: * </pre>
110: *
111: * <p/> <u>Example code2:</u>
112: * This example will wait 2 second (2000 millis) before the wait page is shown to the user. Therefore
113: * if the long process didn't last long anyway the user isn't shown a wait page.
114: *
115: * <pre>
116: * <action name="someAction" class="com.examples.SomeAction">
117: * <interceptor-ref name="completeStack"/>
118: * <interceptor-ref name="execAndWait">
119: * <param name="delay">2000<param>
120: * <interceptor-ref>
121: * <result name="wait">longRunningAction-wait.jsp</result>
122: * <result name="success">longRunningAction-success.jsp</result>
123: * </action>
124: * </pre>
125: *
126: * <p/> <u>Example code3:</u>
127: * This example will wait 1 second (1000 millis) before the wait page is shown to the user.
128: * And at every 50 millis this interceptor will check if the background process is done, if so
129: * it will return before the 1 second has elapsed, and the user isn't shown a wait page.
130: *
131: * <pre>
132: * <action name="someAction" class="com.examples.SomeAction">
133: * <interceptor-ref name="completeStack"/>
134: * <interceptor-ref name="execAndWait">
135: * <param name="delay">1000<param>
136: * <param name="delaySleepInterval">50<param>
137: * <interceptor-ref>
138: * <result name="wait">longRunningAction-wait.jsp</result>
139: * <result name="success">longRunningAction-success.jsp</result>
140: * </action>
141: * </pre>
142: *
143: * <!-- END SNIPPET: example -->
144: *
145: * @author <a href="plightbo@gmail.com">Pat Lightbody</a>
146: * @author Rainer Hermanns
147: * @author Claus Ibsen
148: */
149: public class ExecuteAndWaitInterceptor implements Interceptor {
150:
151: private static final long serialVersionUID = -704630999325809993L;
152:
153: private static final Log LOG = LogFactory
154: .getLog(ExecuteAndWaitInterceptor.class);
155:
156: public static final String KEY = "__execWait";
157: public static final String WAIT = "wait";
158: protected int delay;
159: protected int delaySleepInterval = 100; // default sleep 100 millis before checking if background process is done
160:
161: private int threadPriority = Thread.NORM_PRIORITY;
162:
163: public void init() {
164: }
165:
166: protected BackgroundProcess getNewBackgroundProcess(String name,
167: ActionInvocation actionInvocation, int threadPriority) {
168: return new BackgroundProcess(name + "BackgroundThread",
169: actionInvocation, threadPriority);
170: }
171:
172: public String intercept(ActionInvocation actionInvocation)
173: throws Exception {
174: ActionProxy proxy = actionInvocation.getProxy();
175: String name = proxy.getActionName();
176: ActionContext context = actionInvocation.getInvocationContext();
177: Map session = context.getSession();
178:
179: synchronized (session) {
180: BackgroundProcess bp = (BackgroundProcess) session.get(KEY
181: + name);
182:
183: if (bp == null) {
184: bp = getNewBackgroundProcess(name, actionInvocation,
185: threadPriority);
186: session.put(KEY + name, bp);
187: performInitialDelay(bp); // first time let some time pass before showing wait page
188: }
189:
190: if (!bp.isDone()) {
191: actionInvocation.getStack().push(bp.getAction());
192: Map results = proxy.getConfig().getResults();
193: if (!results.containsKey(WAIT)) {
194: LOG
195: .warn("ExecuteAndWait interceptor has detected that no result named 'wait' is available. "
196: + "Defaulting to a plain built-in wait page. It is highly recommend you "
197: + "provide an action-specific or global result named '"
198: + WAIT
199: + "'! This requires FreeMarker support and won't work if you don't have it installed");
200: // no wait result? hmm -- let's try to do dynamically put it in for you!
201: ResultConfig rc = new ResultConfig(
202: WAIT,
203: "com.opensymphony.webwork.views.freemarker.FreemarkerResult",
204: Collections
205: .singletonMap("location",
206: "com/opensymphony/webwork/interceptor/wait.ftl"));
207: results.put(WAIT, rc);
208: }
209:
210: return WAIT;
211: } else {
212: session.remove(KEY + name);
213: actionInvocation.getStack().push(bp.getAction());
214:
215: // if an exception occured during action execution, throw it here
216: if (bp.getException() != null) {
217: throw bp.getException();
218: }
219:
220: return bp.getResult();
221: }
222: }
223: }
224:
225: public void destroy() {
226: }
227:
228: /**
229: * Performs the initial delay.
230: * <p/>
231: * When this interceptor is executed for the first time this methods handles any provided initial delay.
232: * An initial delay is a time in miliseconds we let the server wait before we continue.
233: * <br/> During the wait this interceptor will wake every 100 millis to check if the background
234: * process is done premature, thus if the job for some reason doesn't take to long the wait
235: * page is not shown to the user.
236: *
237: * @param bp the background process
238: * @throws InterruptedException is thrown by Thread.sleep
239: */
240: protected void performInitialDelay(BackgroundProcess bp)
241: throws InterruptedException {
242: if (delay <= 0 || delaySleepInterval <= 0) {
243: return;
244: }
245:
246: int steps = delay / delaySleepInterval;
247: if (LOG.isDebugEnabled()) {
248: LOG.debug("Delaying for " + delay + " millis. (using "
249: + steps + " steps)");
250: }
251: int step;
252: for (step = 0; step < steps && !bp.isDone(); step++) {
253: Thread.sleep(delaySleepInterval);
254: }
255: if (LOG.isDebugEnabled()) {
256: LOG.debug("Sleeping ended after " + step
257: + " steps and the background process is "
258: + (bp.isDone() ? " done" : " not done"));
259: }
260: }
261:
262: /**
263: * Sets the thread priority of the background process.
264: *
265: * @param threadPriority the priority from <code>Thread.XXX</code>
266: */
267: public void setThreadPriority(int threadPriority) {
268: this .threadPriority = threadPriority;
269: }
270:
271: /**
272: * Sets the initial delay in millis (msec).
273: *
274: * @param delay in millis. (0 for not used)
275: */
276: public void setDelay(int delay) {
277: this .delay = delay;
278: }
279:
280: /**
281: * Sets the sleep interval in millis (msec) when performing the initial delay.
282: *
283: * @param delaySleepInterval in millis (0 for not used)
284: */
285: public void setDelaySleepInterval(int delaySleepInterval) {
286: this.delaySleepInterval = delaySleepInterval;
287: }
288: }
|