Author: remy.maucherat(a)jboss.com
Date: 2009-09-15 12:41:32 -0400 (Tue, 15 Sep 2009)
New Revision: 1169
Modified:
trunk/java/javax/servlet/AsyncContext.java
trunk/java/javax/servlet/AsyncEvent.java
trunk/java/javax/servlet/AsyncListener.java
trunk/java/javax/servlet/ServletRequest.java
trunk/java/org/apache/catalina/connector/Request.java
trunk/java/org/apache/catalina/core/StandardWrapperValve.java
Log:
- Update to latest spec.
Modified: trunk/java/javax/servlet/AsyncContext.java
===================================================================
--- trunk/java/javax/servlet/AsyncContext.java 2009-09-10 16:34:29 UTC (rev 1168)
+++ trunk/java/javax/servlet/AsyncContext.java 2009-09-15 16:41:32 UTC (rev 1169)
@@ -38,7 +38,7 @@
/**
* Class representing the execution context for an asynchronous operation
- * that was started on a ServletRequest.
+ * that was initiated on a ServletRequest.
*
* <p>An AsyncContext is created and initialized by a call to
* {@link ServletRequest#startAsync()} or
@@ -46,6 +46,20 @@
* Repeated invocations of these methods will return the same AsyncContext
* instance, reinitialized as appropriate.
*
+ * <p>In the event that an asynchronous operation has timed out, the
+ * container must run through these steps:
+ * <ol>
+ * <li>Invoke, at their {@link AsyncListener#onTimeout onTimeout} method, all
+ * {@link AsyncListener} instances registered with the ServletRequest
+ * on which the asynchronous operation was initiated.</li>
+ * <li>If none of the listeners called {@link #complete} or any of the
+ * {@link #dispatch} methods, perform an error dispatch with a status code
+ * equal to
<tt>HttpServletResponse.SC_INTERNAL_SERVER_ERROR</tt>.</li>
+ * <li>If no matching error page was found, or the error page did not call
+ * {@link #complete} or any of the {@link #dispatch} methods, call
+ * {@link #complete}.</li>
+ * </ol>
+ *
* @since Servlet 3.0
*/
public interface AsyncContext {
@@ -181,6 +195,24 @@
* {@link ServletRequest#startAsync(ServletRequest, ServletResponse)}
* are called.
*
+ * <p>Any errors or exceptions that may occur during the execution
+ * of this method must be caught and handled by the container, as
+ * follows:
+ * <ol>
+ * <li>Invoke, at their {@link AsyncListener#onError onError} method, all
+ * {@link AsyncListener} instances registered with the ServletRequest
+ * for which this AsyncContext was created, and make the caught
+ * <tt>Throwable</tt> available via {@link
AsyncEvent#getThrowable}.</li>
+ * <li>If none of the listeners called {@link #complete} or any of the
+ * {@link #dispatch} methods, perform an error dispatch with a status code
+ * equal to <tt>HttpServletResponse.SC_INTERNAL_SERVER_ERROR</tt>, and
+ * make the above <tt>Throwable</tt> available as the value of the
+ * <tt>RequestDispatcher.ERROR_EXCEPTION</tt> request
attribute.</li>
+ * <li>If no matching error page was found, or the error page did not call
+ * {@link #complete} or any of the {@link #dispatch} methods, call
+ * {@link #complete}.</li>
+ * </ol>
+ *
* @exception IllegalStateException if {@link #complete} has already
* been called
*
@@ -207,7 +239,8 @@
* attributes will always reflect the original path elements, even under
* repeated dispatches.
*
- * <p>See {@link #dispatch()} for additional details.
+ * <p>See {@link #dispatch()} for additional details, including error
+ * handling.
*
* @param path the path of the dispatch target, scoped to the
* ServletContext from which this AsyncContext was initialized
@@ -237,7 +270,8 @@
* attributes will always reflect the original path elements, even under
* repeated dispatches.
*
- * <p>See {@link #dispatch()} for additional details.
+ * <p>See {@link #dispatch()} for additional details, including error
+ * handling.
*
* @param context the ServletContext of the dispatch target
* @param path the path of the dispatch target, scoped to the given
@@ -256,9 +290,10 @@
* that was used to initialze this AsyncContext, closing the response
* that was used to initialize this AsyncContext.
*
- * <p>Any listeners of type {@link AsyncListener} that were added to the
- * request that was used to initialize this AsyncContext will have their
- * {@link AsyncListener#onComplete(AsyncEvent)} method invoked.
+ * <p>Any listeners of type {@link AsyncListener} that were registered
+ * with the ServletRequest for which this AsyncContext was created will
+ * be invoked at their {@link AsyncListener#onComplete(AsyncEvent)
+ * onComplete} method.
*
* <p>It is legal to call this method any time after a call to
* {@link ServletRequest#startAsync()} or
Modified: trunk/java/javax/servlet/AsyncEvent.java
===================================================================
--- trunk/java/javax/servlet/AsyncEvent.java 2009-09-10 16:34:29 UTC (rev 1168)
+++ trunk/java/javax/servlet/AsyncEvent.java 2009-09-15 16:41:32 UTC (rev 1169)
@@ -38,47 +38,126 @@
package javax.servlet;
/**
- * Event that gets fired when a ServletRequest on which asynchronous
- * processing was started has finished processing through a call to
- * {@link AsyncContext#complete} or has timed out.
+ * Event that gets fired when the asynchronous operation initiated on a
+ * ServletRequest (via a call to {@link ServletRequest#startAsync} or
+ * {@link ServletRequest#startAsync(ServletRequest, ServletResponse)})
+ * has completed, timed out, or produced an error.
*
* @since Servlet 3.0
*/
public class AsyncEvent {
+ private AsyncContext context;
private ServletRequest request;
private ServletResponse response;
+ private Throwable throwable;
+
/**
- * Constructs an AsyncEvent from the given request and response objects.
+ * Constructs an AsyncEvent from the given AsyncContext.
*
- * @param request the ServletRequest
- * @param response the ServletResponse
+ * @param context the AsyncContex to be delivered with this AsyncEvent
*/
- public AsyncEvent(ServletRequest request, ServletResponse response) {
+ public AsyncEvent(AsyncContext context) {
+ this(context, null, null, null);
+ }
+
+ /**
+ * Constructs an AsyncEvent from the given AsyncContext, ServletRequest,
+ * and ServletResponse.
+ *
+ * @param context the AsyncContex to be delivered with this AsyncEvent
+ * @param request the ServletRequest to be delivered with this AsyncEvent
+ * @param response the ServletResponse to be delivered with this
+ * AsyncEvent
+ */
+ public AsyncEvent(AsyncContext context, ServletRequest request,
+ ServletResponse response) {
+ this(context, request, response, null);
+ }
+
+ /**
+ * Constructs an AsyncEvent from the given AsyncContext and Throwable.
+ *
+ * @param context the AsyncContex to be delivered with this AsyncEvent
+ * @param throwable the Throwable to be delivered with this AsyncEvent
+ */
+ public AsyncEvent(AsyncContext context, Throwable throwable) {
+ this(context, null, null, throwable);
+ }
+
+ /**
+ * Constructs an AsyncEvent from the given AsyncContext, ServletRequest,
+ * ServletResponse, and Throwable.
+ *
+ * @param context the AsyncContex to be delivered with this AsyncEvent
+ * @param request the ServletRequest to be delivered with this AsyncEvent
+ * @param response the ServletResponse to be delivered with this
+ * AsyncEvent
+ * @param throwable the Throwable to be delivered with this AsyncEvent
+ */
+ public AsyncEvent(AsyncContext context, ServletRequest request,
+ ServletResponse response, Throwable throwable) {
+ this.context = context;
this.request = request;
this.response = response;
+ this.throwable = throwable;
}
+ /**
+ * Gets the AsyncContext from this AsyncEvent.
+ *
+ * @return the AsyncContext that was used to initialize this AsyncEvent
+ */
+ public AsyncContext getAsyncContext() {
+ return context;
+ }
/**
- * Gets the ServletRequest from this event
+ * Gets the ServletRequest from this AsyncEvent.
*
- * @return the ServletRequest
+ * <p>If the AsyncListener to which this AsyncEvent is being delivered
+ * was added using {@link ServletRequest#addAsyncListener(AsyncListener,
+ * ServletRequest, ServletResponse)}, the returned ServletRequest
+ * will be the same as the one supplied to the above method.
+ * If the AsyncListener was added via
+ * {@link ServletRequest#addAsyncListener(AsyncListener)}, this method
+ * must return null.
+ *
+ * @return the ServletRequest that was used to initialize this AsyncEvent,
+ * or null if this AsyncEvent was initialized without any ServletRequest
*/
- public ServletRequest getRequest() {
+ public ServletRequest getSuppliedRequest() {
return request;
}
-
/**
- * Gets the ServletResponse from this event
+ * Gets the ServletResponse from this AsyncEvent.
*
- * @return the ServletResponse
+ * <p>If the AsyncListener to which this AsyncEvent is being delivered
+ * was added using {@link ServletRequest#addAsyncListener(AsyncListener,
+ * ServletRequest, ServletResponse)}, the returned ServletResponse
+ * will be the same as the one supplied to the above method.
+ * If the AsyncListener was added via
+ * {@link ServletRequest#addAsyncListener(AsyncListener)}, this method
+ * must return null.
+ *
+ * @return the ServletResponse that was used to initialize this AsyncEvent,
+ * or null if this AsyncEvent was initialized without any ServletResponse
*/
- public ServletResponse getResponse() {
+ public ServletResponse getSuppliedResponse() {
return response;
}
+ /**
+ * Gets the Throwable from this AsyncEvent.
+ *
+ * @return the Throwable that was used to initialize this AsyncEvent,
+ * or null if this AsyncEvent was initialized without any Throwable
+ */
+ public Throwable getThrowable() {
+ return throwable;
+ }
+
}
Modified: trunk/java/javax/servlet/AsyncListener.java
===================================================================
--- trunk/java/javax/servlet/AsyncListener.java 2009-09-10 16:34:29 UTC (rev 1168)
+++ trunk/java/javax/servlet/AsyncListener.java 2009-09-15 16:41:32 UTC (rev 1169)
@@ -40,10 +40,9 @@
import java.util.EventListener;
/**
- * Listener that will be notified when an asynchronous operation that was
- * started on a ServletRequest by a call to {@link ServletRequest#startAsync}
- * or {@link ServletRequest#startAsync(ServletRequest, ServletResponse)}
- * completes or times out.
+ * Listener that will be notified in the event that an asynchronous
+ * operation initiated on a ServletRequest to which the listener had been
+ * added has completed, timed out, or resulted in an error.
*
* @since Servlet 3.0
*/
@@ -51,35 +50,79 @@
/**
* Notifies this AsyncListener that an asynchronous operation
- * started on the ServletRequest with which this AsyncListener was
- * registered has been completed.
+ * has been completed.
*
- * @param event the AsyncEvent containing the request and response
- * objects that were used when this AsyncListener was registered via a
- * call to {@link ServletRequest#addAsyncListener(AsyncListener)}
- * or {@link ServletRequest#addAsyncListener(AsyncListener,
- * ServletRequest, ServletResponse)}
+ * <p>The {@link AsyncContext} corresponding to the asynchronous
+ * operation that has been completed may be obtained by calling
+ * {@link AsyncEvent#getAsyncContext getAsyncContext} on the given
+ * <tt>event</tt>.
*
- * @throws IOException if an I/O exception occurred during the processing
- * of the given AsyncEvent
+ * <p>In addition, if this AsyncListener had been registered via a call
+ * to {@link ServletRequest#addAsyncListener(AsyncListener,
+ * ServletRequest, ServletResponse)}, the supplied ServletRequest and
+ * ServletResponse objects may be retrieved by calling
+ * {@link AsyncEvent#getSuppliedRequest getSuppliedRequest} and
+ * {@link AsyncEvent#getSuppliedResponse getSuppliedResponse},
+ * respectively, on the given <tt>event</tt>.
+ *
+ * @param event the AsyncEvent indicating that an asynchronous
+ * operation has been completed
+ *
+ * @throws IOException if an I/O related error has occurred during the
+ * processing of the given AsyncEvent
*/
public void onComplete(AsyncEvent event) throws IOException;
/**
* Notifies this AsyncListener that an asynchronous operation
- * started on the ServletRequest with which this AsyncListener was
- * registered has timed out.
+ * has timed out.
*
- * @param event the AsyncEvent containing the request and response
- * objects that were used when this AsyncListener was registered via a
- * call to {@link ServletRequest#addAsyncListener(AsyncListener)}
- * or {@link ServletRequest#addAsyncListener(AsyncListener,
- * ServletRequest, ServletResponse)}
+ * <p>The {@link AsyncContext} corresponding to the asynchronous
+ * operation that has timed out may be obtained by calling
+ * {@link AsyncEvent#getAsyncContext getAsyncContext} on the given
+ * <tt>event</tt>.
*
- * @throws IOException if an I/O exception occurred during the processing
- * of the given AsyncEvent
+ * <p>In addition, if this AsyncListener had been registered via a call
+ * to {@link ServletRequest#addAsyncListener(AsyncListener,
+ * ServletRequest, ServletResponse)}, the supplied ServletRequest and
+ * ServletResponse objects may be retrieved by calling
+ * {@link AsyncEvent#getSuppliedRequest getSuppliedRequest} and
+ * {@link AsyncEvent#getSuppliedResponse getSuppliedResponse},
+ * respectively, on the given <tt>event</tt>.
+ *
+ * @param event the AsyncEvent indicating that an asynchronous
+ * operation has timed out
+ *
+ * @throws IOException if an I/O related error has occurred during the
+ * processing of the given AsyncEvent
*/
public void onTimeout(AsyncEvent event) throws IOException;
+
+ /**
+ * Notifies this AsyncListener that an asynchronous operation
+ * has failed to complete.
+ *
+ * <p>The {@link AsyncContext} corresponding to the asynchronous
+ * operation that failed to complete may be obtained by calling
+ * {@link AsyncEvent#getAsyncContext getAsyncContext} on the given
+ * <tt>event</tt>.
+ *
+ * <p>In addition, if this AsyncListener had been registered via a call
+ * to {@link ServletRequest#addAsyncListener(AsyncListener,
+ * ServletRequest, ServletResponse)}, the supplied ServletRequest and
+ * ServletResponse objects may be retrieved by calling
+ * {@link AsyncEvent#getSuppliedRequest getSuppliedRequest} and
+ * {@link AsyncEvent#getSuppliedResponse getSuppliedResponse},
+ * respectively, on the given <tt>event</tt>.
+ *
+ * @param event the AsyncEvent indicating that an asynchronous
+ * operation has failed to complete
+ *
+ * @throws IOException if an I/O related error has occurred during the
+ * processing of the given AsyncEvent
+ */
+ public void onError(AsyncEvent event) throws IOException;
+
}
Modified: trunk/java/javax/servlet/ServletRequest.java
===================================================================
--- trunk/java/javax/servlet/ServletRequest.java 2009-09-10 16:34:29 UTC (rev 1168)
+++ trunk/java/javax/servlet/ServletRequest.java 2009-09-15 16:41:32 UTC (rev 1169)
@@ -608,19 +608,17 @@
* and ServletResponse objects and the timeout as returned by
* {@link #getAsyncTimeout}.
*
- * <p>This will delay committal of the associated response until
- * {@link AsyncContext#complete} is called on the returned
- * {@link AsyncContext}, or the AsyncContext times out.
+ * <p>Calling this method will cause committal of the associated
+ * response to be delayed until {@link AsyncContext#complete} is
+ * called on the returned {@link AsyncContext}, or the asynchronous
+ * operation has timed out.
*
- * <p>The timer for async timeouts will not start until the
+ * <p>The timer for asynchronous timeouts will not start until the
* container-initiated dispatch that called <tt>startAsync</tt>
* has returned to the container.
*
- * <p>If a timeout occurs and none of the
- * {@link AsyncListener#onTimeout(AsyncEvent)} handlers call
- * {@link AsyncContext#complete} or one of the
- * {@link AsyncContext#dispatch} methods, the container must call
- * {@link AsyncContext#complete}.
+ * <p>See {@link AsyncContext} for a description of how timeouts of
+ * asynchronous operations are handled by the container.
*
* <p>Calling {@link AsyncContext#hasOriginalRequestAndResponse()} on
* the returned AsyncContext will return <code>true</code>. Any filters
@@ -654,25 +652,24 @@
* {@link AsyncContext} with the given request and response objects
* and the timeout as returned by {@link #getAsyncTimeout}.
*
- * <p>The ServletRequest and ServletResponse parameters must be either
- * the same objects as were passed to the calling servlet's service
- * (or calling filter's doFilter) method, or be subclasses of the
- * {@link ServletRequestWrapper} or {@link ServletResponseWrapper}
- * classes that wrap them.
+ * <p>The ServletRequest and ServletResponse arguments must be
+ * the same instances, or instances of {@link ServletRequestWrapper} and
+ * {@link ServletResponseWrapper} that wrap them, that were passed to the
+ * {@link Servlet#service service} method of the Servlet or the
+ * {@link Filter#doFilter doFilter} method of the Filter, respectively,
+ * in whose scope this method is being called.
*
- * <p>This will delay committal of the response until
- * {@link AsyncContext#complete} is called on the returned
- * {@link AsyncContext}, or the AsyncContext times out.
+ * <p>Calling this method will cause committal of the associated
+ * response to be delayed until {@link AsyncContext#complete} is
+ * called on the returned {@link AsyncContext}, or the asynchronous
+ * operation has timed out.
*
- * <p>The timer for async timeouts will not start until the
+ * <p>The timer for asynchronous timeouts will not start until the
* container-initiated dispatch that called <tt>startAsync</tt>
* has returned to the container.
*
- * <p>If a timeout occurs and none of the
- * {@link AsyncListener#onTimeout(AsyncEvent)} handlers call
- * {@link AsyncContext#complete} or one of the
- * {@link AsyncContext#dispatch} methods, the container must call
- * {@link AsyncContext#complete}.
+ * <p>See {@link AsyncContext} for a description of how timeouts of
+ * asynchronous operations are handled by the container.
*
* <p>Calling {@link AsyncContext#hasOriginalRequestAndResponse()} on
* the returned AsyncContext will return <code>false</code>,
@@ -776,16 +773,11 @@
/**
- * Registers the given {@link AsyncListener} with this request for
- * asynchronous complete and timeout events.
+ * Registers the given {@link AsyncListener} with this request.
*
- * <p>If {@link #startAsync} or
- * {@link #startAsync(ServletRequest,ServletResponse)} is called on this
- * request, an {@link AsyncEvent} will be sent to this AsyncListener as
- * soon as the asynchronous operation has completed or timed out.
- * The AsyncEvent will contain the ServletRequest and ServletResponse
- * objects that were used to initialize the {@link AsyncContext}
- * returned by the call to startAsync.
+ * <p>If this request is put into asynchronous mode, the given
+ * AsyncListener will receive an {@link AsyncEvent} when the
+ * asynchronous operation completes, times out, or fails to complete.
*
* <p>AsyncListener instances will be notified in the order
* in which they were added to this request.
@@ -799,26 +791,24 @@
/**
* Registers the given {@link AsyncListener} with this request for
- * asynchronous complete and timeout events.
+ * asynchronous complete, timeout, and error events.
*
- * <p>If {@link #startAsync} or
- * {@link #startAsync(ServletRequest,ServletResponse)} is called on this
- * request, an {@link AsyncEvent} will be sent to this AsyncListener as
- * soon as the asynchronous operation has completed or timed out.
- * The AsyncEvent will contain the given ServletRequest and
- * ServletResponse objects.
+ * <p>If this request is put into asynchronous mode, the given
+ * AsyncListener will receive an {@link AsyncEvent} when the
+ * asynchronous operation completes, times out, or fails to complete.
*
* <p>AsyncListener instances will be notified in the order
* in which they were added to this request.
*
- * <p>The specified request and response objects, which will be included
- * in the AsyncEvent that will be delivered to the given AsyncListener,
- * should not be read from or written to, respectively, at the time
- * when the AsyncEvent is delivered, because additional wrapping may have
- * occurred after this method was called. One of the main reasons for
- * allowing request and response objects to be passed to this method is
- * to allow the AsyncListener to release any resources associated with
- * them when the AsyncEvent is delivered.
+ * <p>The given ServletRequest and ServletResponse objects will
+ * be made available to the given AsyncListener via the
+ * {@link AsyncEvent#getSuppliedRequest getSuppliedRequest} and
+ * {@link AsyncEvent#getSuppliedResponse getSuppliedResponse} methods,
+ * respectively, of the {@link AsyncEvent} delivered to it. These objects
+ * should not be read from or written to, respectively, at the time the
+ * AsyncEvent is delivered, because additional wrapping may have
+ * occurred since the given AsyncListener was registered, but may be used
+ * in order to release any resources associated with them.
*
* @param listener the AsyncListener to be registered
* @param servletRequest the ServletRequest that will be included
@@ -844,14 +834,8 @@
* A timeout value of 0 or less indicates that the asynchronous
* operations will never time out.
*
- * <p>If neither {@link AsyncContext#complete} nor
- * {@link AsyncContext#dispatch} is called within the
- * specified timeout, any listeners of type {@link AsyncListener} that
- * were added to this request via a call to
- * {@link #addAsyncListener(AsyncListener)}
- * or {@link #addAsyncListener(AsyncListener, ServletRequest,
- * ServletResponse)} will have their
- * {@link AsyncListener#onTimeout(AsyncEvent)} method invoked.
+ * <p>See {@link AsyncContext} for a description of how timeouts of
+ * asynchronous operations are handled by the container.
*
* <p>This method raises an <code>IllegalStateException</code> if
* called after {@link #startAsync}, unless it is called within the
Modified: trunk/java/org/apache/catalina/connector/Request.java
===================================================================
--- trunk/java/org/apache/catalina/connector/Request.java 2009-09-10 16:34:29 UTC (rev
1168)
+++ trunk/java/org/apache/catalina/connector/Request.java 2009-09-15 16:41:32 UTC (rev
1169)
@@ -2974,7 +2974,7 @@
public void addAsyncListener(AsyncListener listener,
ServletRequest servletRequest, ServletResponse servletResponse) {
- AsyncEvent event = new AsyncEvent(servletRequest, servletResponse);
+ AsyncEvent event = new AsyncEvent(asyncContext, servletRequest,
servletResponse);
asyncListeners.put(event, listener);
}
Modified: trunk/java/org/apache/catalina/core/StandardWrapperValve.java
===================================================================
--- trunk/java/org/apache/catalina/core/StandardWrapperValve.java 2009-09-10 16:34:29 UTC
(rev 1168)
+++ trunk/java/org/apache/catalina/core/StandardWrapperValve.java 2009-09-15 16:41:32 UTC
(rev 1169)
@@ -406,6 +406,7 @@
|| event.getType() == EventType.TIMEOUT) {
// Invoke the listeners with onComplete or onTimeout
boolean timeout = (event.getType() == EventType.TIMEOUT) ? true : false;
+ boolean error = (event.getType() == EventType.ERROR) ? true : false;
Iterator<AsyncEvent> asyncEvents =
asyncContext.getAsyncListeners().keySet().iterator();
if (timeout && !asyncEvents.hasNext()) {
// FIXME: MUST do an ERROR dispatch to the original URI and MUST set
the response code to 500
@@ -416,6 +417,11 @@
try {
if (timeout) {
asyncListener.onTimeout(asyncEvent);
+ } else if (error) {
+ Throwable t = (Throwable)
request.getAttribute(Globals.EXCEPTION_ATTR);
+ AsyncEvent asyncEvent2 = new
AsyncEvent(asyncEvent.getAsyncContext(),
+ asyncEvent.getSuppliedRequest(),
asyncEvent.getSuppliedResponse(), t);
+ asyncListener.onError(asyncEvent2);
} else {
asyncListener.onComplete(asyncEvent);
}
@@ -455,6 +461,7 @@
container.getLogger().error(sm.getString("standardWrapper.async.dispatchError",
getContainer().getName()), e);
exception(request, response, e);
+ // TODO: the exception must be sent to error
}
// If there is no new startAsync, then close the response
if (!asyncContext.isReady()) {