Add onTimeout/onCompletion callbacks to DeferredResult

Issue: SPR-9914
This commit is contained in:
Rossen Stoyanchev 2012-10-30 21:55:38 -04:00
parent 3a09644843
commit d701464517
21 changed files with 309 additions and 158 deletions

View File

@ -178,7 +178,7 @@ public class OpenSessionInViewTests {
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync();
replay(asyncWebRequest);
@ -494,7 +494,7 @@ public class OpenSessionInViewTests {
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject());
asyncWebRequest.addTimeoutHandler(EasyMock.<Runnable>anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();

View File

@ -20,7 +20,6 @@ import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
@ -49,8 +48,8 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.StaticWebApplicationContext;
/**
@ -155,7 +154,7 @@ public class OpenEntityManagerInViewTests extends TestCase {
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync();
replay(asyncWebRequest);
@ -346,7 +345,7 @@ public class OpenEntityManagerInViewTests extends TestCase {
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();

View File

@ -48,7 +48,7 @@ import org.springframework.web.servlet.ModelAndView;
@SuppressWarnings("serial")
final class TestDispatcherServlet extends DispatcherServlet {
private static final String KEY = TestDispatcherServlet.class.getName() + "-interceptor";
private static final String KEY = TestDispatcherServlet.class.getName() + ".interceptor";
/**
* Create a new instance with the given web application context.

View File

@ -28,7 +28,7 @@ import org.hamcrest.Matchers;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.web.context.request.async.AsyncTask;
import org.springframework.web.context.request.async.MvcAsyncTask;
import org.springframework.web.context.request.async.DeferredResult;
/**
@ -97,7 +97,7 @@ public class RequestResultMatchers {
/**
* Assert the result from asynchronous processing.
* This method can be used when a controller method returns {@link Callable}
* or {@link AsyncTask}. The value matched is the value returned from the
* or {@link MvcAsyncTask}. The value matched is the value returned from the
* {@code Callable} or the exception raised.
*/
public <T> ResultMatcher asyncResult(Object expectedResult) {

View File

@ -37,12 +37,12 @@ public interface AsyncWebRequest extends NativeWebRequest {
void setTimeout(Long timeout);
/**
* Set the handler to use when concurrent handling has timed out.
* Add a handler to invoke when concurrent handling has timed out.
*/
void setTimeoutHandler(Runnable runnable);
void addTimeoutHandler(Runnable runnable);
/**
* Add a Runnable to be invoked when request processing completes.
* Add a handle to invoke when request processing completes.
*/
void addCompletionHandler(Runnable runnable);

View File

@ -39,8 +39,8 @@ class CallableInterceptorChain {
private int preProcessIndex = -1;
public CallableInterceptorChain(Collection<CallableProcessingInterceptor> interceptors) {
this.interceptors = new ArrayList<CallableProcessingInterceptor>(interceptors);
public CallableInterceptorChain(List<CallableProcessingInterceptor> interceptors) {
this.interceptors = interceptors;
}
public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception {

View File

@ -20,6 +20,7 @@ import java.util.concurrent.Callable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.web.context.request.NativeWebRequest;
/**
* {@code DeferredResult} provides an alternative to using a {@link Callable}
@ -41,6 +42,10 @@ public final class DeferredResult<T> {
private final Object timeoutResult;
private Runnable timeoutCallback;
private Runnable completionCallback;
private DeferredResultHandler resultHandler;
private Object result = RESULT_NONE;
@ -56,7 +61,7 @@ public final class DeferredResult<T> {
}
/**
* Create a DeferredResult with a timeout.
* Create a DeferredResult with a timeout value.
* @param timeout timeout value in milliseconds
*/
public DeferredResult(long timeout) {
@ -64,7 +69,8 @@ public final class DeferredResult<T> {
}
/**
* Create a DeferredResult with a timeout and a default result to use on timeout.
* Create a DeferredResult with a timeout value and a default result to use
* in case of timeout.
* @param timeout timeout value in milliseconds; ignored if {@code null}
* @param timeoutResult the result to use
*/
@ -73,13 +79,48 @@ public final class DeferredResult<T> {
this.timeout = timeout;
}
/**
* Return {@code true} if this DeferredResult is no longer usable either
* because it was previously set or because the underlying request expired.
* <p>
* The result may have been set with a call to {@link #setResult(Object)},
* or {@link #setErrorResult(Object)}, or as a result of a timeout, if a
* timeout result was provided to the constructor. The request may also
* expire due to a timeout or network error.
*/
public boolean isSetOrExpired() {
return ((this.result != RESULT_NONE) || this.expired);
}
/**
* Return the configured timeout value in milliseconds.
*/
public Long getTimeoutMilliseconds() {
Long getTimeoutValue() {
return this.timeout;
}
/**
* Register code to invoke when the async request times out. This method is
* called from a container thread when an async request times out before the
* {@code DeferredResult} has been set. It may invoke
* {@link DeferredResult#setResult(Object) setResult} or
* {@link DeferredResult#setErrorResult(Object) setErrorResult} to resume
* processing.
*/
public void onTimeout(Runnable callback) {
this.timeoutCallback = callback;
}
/**
* Register code to invoke when the async request completes. This method is
* called from a container thread when an async request completed for any
* reason including timeout and network error. This method is useful for
* detecting that a {@code DeferredResult} instance is no longer usable.
*/
public void onCompletion(Runnable callback) {
this.completionCallback = callback;
}
/**
* Provide a handler to use to handle the result value.
* @param resultHandler the handler
@ -138,33 +179,29 @@ public final class DeferredResult<T> {
return setResultInternal(result);
}
/**
* Return {@code true} if this DeferredResult is no longer usable either
* because it was previously set or because the underlying request expired.
* <p>
* The result may have been set with a call to {@link #setResult(Object)},
* or {@link #setErrorResult(Object)}, or as a result of a timeout, if a
* timeout result was provided to the constructor. The request may also
* expire due to a timeout or network error.
*/
public boolean isSetOrExpired() {
return ((this.result != RESULT_NONE) || this.expired);
}
DeferredResultProcessingInterceptor getInterceptor() {
return new DeferredResultProcessingInterceptorAdapter() {
/**
* Mark this instance expired so it may no longer be used.
* @return the previous value of the expiration flag
*/
boolean expire() {
synchronized (this) {
boolean previous = this.expired;
this.expired = true;
return previous;
}
}
@Override
public <S> void afterTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) {
if (timeoutCallback != null) {
timeoutCallback.run();
}
if (DeferredResult.this.timeoutResult != RESULT_NONE) {
setResultInternal(timeoutResult);
}
}
boolean applyTimeoutResult() {
return (this.timeoutResult != RESULT_NONE) ? setResultInternal(this.timeoutResult) : false;
@Override
public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) {
synchronized (this) {
expired = true;
}
if (completionCallback != null) {
completionCallback.run();
}
}
};
}

View File

@ -15,8 +15,6 @@
*/
package org.springframework.web.context.request.async;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
@ -38,8 +36,8 @@ class DeferredResultInterceptorChain {
private int preProcessingIndex = -1;
public DeferredResultInterceptorChain(Collection<DeferredResultProcessingInterceptor> interceptors) {
this.interceptors = new ArrayList<DeferredResultProcessingInterceptor>(interceptors);
public DeferredResultInterceptorChain(List<DeferredResultProcessingInterceptor> interceptors) {
this.interceptors = interceptors;
}
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception {

View File

@ -75,7 +75,7 @@ public interface DeferredResultProcessingInterceptor {
* Invoked from a container thread when an async request times out before
* the {@code DeferredResult} has been set. Implementations may invoke
* {@link DeferredResult#setResult(Object) setResult} or
* {@link DeferredResult#setErrorResult(Object) to resume processing.
* {@link DeferredResult#setErrorResult(Object) setErrorResult} to resume processing.
*
* @param request the current request
* @param deferredResult the DeferredResult for the current request; if the

View File

@ -20,6 +20,7 @@ import java.util.concurrent.Callable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.util.Assert;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Holder for a {@link Callable}, a timeout value, and a task executor.
@ -27,7 +28,7 @@ import org.springframework.util.Assert;
* @author Rossen Stoyanchev
* @since 3.2
*/
public class AsyncTask<V> {
public class MvcAsyncTask<V> {
private final Callable<V> callable;
@ -37,40 +38,51 @@ public class AsyncTask<V> {
private final AsyncTaskExecutor executor;
private Callable<V> timeoutCallback;
private Runnable completionCallback;
private BeanFactory beanFactory;
/**
* Create an AsyncTask with a timeout value and a Callable.
* @param timeout timeout value in milliseconds
* Create an {@code MvcAsyncTask} wrapping the given {@link Callable}.
* @param callable the callable for concurrent handling
*/
public AsyncTask(long timeout, Callable<V> callable) {
this(timeout, null, null, callable);
Assert.notNull(timeout, "Timeout must not be null");
public MvcAsyncTask(Callable<V> callable) {
this(null, null, null, callable);
}
/**
* Create an AsyncTask with a timeout value, an executor name, and a Callable.
* Create an {@code MvcAsyncTask} with a timeout value and a {@link Callable}.
* @param timeout timeout value in milliseconds
* @param callable the callable for concurrent handling
*/
public MvcAsyncTask(long timeout, Callable<V> callable) {
this(timeout, null, null, callable);
}
/**
* Create an {@code MvcAsyncTask} with a timeout value, an executor name, and a {@link Callable}.
* @param timeout timeout value in milliseconds; ignored if {@code null}
* @param callable the callable for concurrent handling
*/
public AsyncTask(Long timeout, String executorName, Callable<V> callable) {
public MvcAsyncTask(Long timeout, String executorName, Callable<V> callable) {
this(timeout, null, executorName, callable);
Assert.notNull(executor, "Executor name must not be null");
}
/**
* Create an AsyncTask with a timeout value, an executor instance, and a Callable.
* Create an {@code MvcAsyncTask} with a timeout value, an executor instance, and a Callable.
* @param timeout timeout value in milliseconds; ignored if {@code null}
* @param callable the callable for concurrent handling
*/
public AsyncTask(Long timeout, AsyncTaskExecutor executor, Callable<V> callable) {
public MvcAsyncTask(Long timeout, AsyncTaskExecutor executor, Callable<V> callable) {
this(timeout, executor, null, callable);
Assert.notNull(executor, "Executor must not be null");
}
private AsyncTask(Long timeout, AsyncTaskExecutor executor, String executorName, Callable<V> callable) {
private MvcAsyncTask(Long timeout, AsyncTaskExecutor executor, String executorName, Callable<V> callable) {
Assert.notNull(callable, "Callable must not be null");
this.callable = callable;
this.timeout = timeout;
@ -111,11 +123,50 @@ public class AsyncTask<V> {
/**
* A {@link BeanFactory} to use to resolve an executor name. Applications are
* not expected to have to set this property when AsyncTask is used in a
* not expected to have to set this property when {@code MvcAsyncTask} is used in a
* Spring MVC controller.
*/
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/**
* Register code to invoke when the async request times out. This method is
* called from a container thread when an async request times out before the
* {@code Callable} has completed. The callback is executed in the same
* thread and therefore should return without blocking. It may return an
* alternative value to use, including an {@link Exception} or return
* {@link CallableProcessingInterceptor#RESULT_NONE RESULT_NONE}.
*/
public void onTimeout(Callable<V> callback) {
this.timeoutCallback = callback;
}
/**
* Register code to invoke when the async request completes. This method is
* called from a container thread when an async request completed for any
* reason including timeout and network error.
*/
public void onCompletion(Runnable callback) {
this.completionCallback = callback;
}
CallableProcessingInterceptor getInterceptor() {
return new CallableProcessingInterceptorAdapter() {
@Override
public <T> Object afterTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
return (timeoutCallback != null) ? timeoutCallback.call() : CallableProcessingInterceptor.RESULT_NONE;
}
@Override
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
if (completionCallback != null) {
completionCallback.run();
}
}
};
}
}

View File

@ -41,7 +41,7 @@ public class NoSupportAsyncWebRequest extends ServletWebRequest implements Async
// ignored
}
public void setTimeoutHandler(Runnable runnable) {
public void addTimeoutHandler(Runnable runnable) {
// ignored
}

View File

@ -49,7 +49,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
private Runnable timeoutHandler;
private final List<Runnable> timeoutHandlers = new ArrayList<Runnable>();
private final List<Runnable> completionHandlers = new ArrayList<Runnable>();
@ -73,8 +73,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
this.timeout = timeout;
}
public void setTimeoutHandler(Runnable timeoutHandler) {
this.timeoutHandler = timeoutHandler;
public void addTimeoutHandler(Runnable timeoutHandler) {
this.timeoutHandlers.add(timeoutHandler);
}
public void addCompletionHandler(Runnable runnable) {
@ -127,8 +127,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
}
public void onTimeout(AsyncEvent event) throws IOException {
if (this.timeoutHandler != null) {
this.timeoutHandler.run();
for (Runnable handler : this.timeoutHandlers) {
handler.run();
}
}

View File

@ -15,7 +15,9 @@
*/
package org.springframework.web.context.request.async;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@ -155,19 +157,27 @@ public final class WebAsyncManager {
return this.concurrentResultContext;
}
/**
* Get the {@link CallableProcessingInterceptor} registered under the given key.
* @param key the key
* @return the interceptor registered under that key or {@code null}
*/
public CallableProcessingInterceptor getCallableInterceptor(Object key) {
return this.callableInterceptors.get(key);
}
/**
* Get the {@link DeferredResultProcessingInterceptor} registered under the given key.
* @param key the key
* @return the interceptor registered under that key or {@code null}
*/
public DeferredResultProcessingInterceptor getDeferredResultInterceptor(Object key) {
return this.deferredResultInterceptors.get(key);
}
/**
* Register a {@link CallableProcessingInterceptor} that will be applied
* when concurrent request handling with a {@link Callable} starts.
*
* @param key a unique the key under which to register the interceptor
* Register a {@link CallableProcessingInterceptor} under the given key.
* @param key the key
* @param interceptor the interceptor to register
*/
public void registerCallableInterceptor(Object key, CallableProcessingInterceptor interceptor) {
@ -181,11 +191,8 @@ public final class WebAsyncManager {
}
/**
* Register a {@link DeferredResultProcessingInterceptor} that will be
* applied when concurrent request handling with a {@link DeferredResult}
* starts.
*
* @param key a unique the key under which to register the interceptor
* Register a {@link DeferredResultProcessingInterceptor} under the given key.
* @param key the key
* @param interceptor the interceptor to register
*/
public void registerDeferredResultInterceptor(Object key, DeferredResultProcessingInterceptor interceptor) {
@ -221,16 +228,47 @@ public final class WebAsyncManager {
* @see #getConcurrentResult()
* @see #getConcurrentResultContext()
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
Assert.notNull(callable, "Callable must not be null");
startCallableProcessing(new MvcAsyncTask(callable), processingContext);
}
/**
* Use the given {@link MvcAsyncTask} to configure the task executor as well as
* the timeout value of the {@code AsyncWebRequest} before delegating to
* {@link #startCallableProcessing(Callable, Object...)}.
*
* @param mvcAsyncTask an MvcAsyncTask containing the target {@code Callable}
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
*/
public void startCallableProcessing(final MvcAsyncTask<?> mvcAsyncTask, Object... processingContext) {
Assert.notNull(mvcAsyncTask, "MvcAsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
final CallableInterceptorChain chain = new CallableInterceptorChain(this.callableInterceptors.values());
final Callable<?> callable = mvcAsyncTask.getCallable();
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
Long timeout = mvcAsyncTask.getTimeout();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
AsyncTaskExecutor executor = mvcAsyncTask.getExecutor();
if (executor != null) {
this.taskExecutor = executor;
}
List<CallableProcessingInterceptor> interceptors = new ArrayList<CallableProcessingInterceptor>();
interceptors.add(mvcAsyncTask.getInterceptor());
interceptors.addAll(this.callableInterceptors.values());
final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors);
this.asyncWebRequest.addTimeoutHandler(new Runnable() {
public void run() {
logger.debug("Processing timeout");
Object result = chain.triggerAfterTimeout(asyncWebRequest, callable);
Object result = interceptorChain.triggerAfterTimeout(asyncWebRequest, callable);
if (result != CallableProcessingInterceptor.RESULT_NONE) {
setConcurrentResultAndDispatch(result);
}
@ -239,7 +277,7 @@ public final class WebAsyncManager {
this.asyncWebRequest.addCompletionHandler(new Runnable() {
public void run() {
chain.triggerAfterCompletion(asyncWebRequest, callable);
interceptorChain.triggerAfterCompletion(asyncWebRequest, callable);
}
});
@ -249,14 +287,14 @@ public final class WebAsyncManager {
public void run() {
Object result = null;
try {
chain.applyPreProcess(asyncWebRequest, callable);
interceptorChain.applyPreProcess(asyncWebRequest, callable);
result = callable.call();
}
catch (Throwable t) {
result = t;
}
finally {
result = chain.applyPostProcess(asyncWebRequest, callable, result);
result = interceptorChain.applyPostProcess(asyncWebRequest, callable, result);
}
setConcurrentResultAndDispatch(result);
}
@ -282,32 +320,6 @@ public final class WebAsyncManager {
asyncWebRequest.dispatch();
}
/**
* Use the given {@link AsyncTask} to configure the task executor as well as
* the timeout value of the {@code AsyncWebRequest} before delegating to
* {@link #startCallableProcessing(Callable, Object...)}.
*
* @param asyncTask an asyncTask containing the target {@code Callable}
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
*/
public void startCallableProcessing(AsyncTask<?> asyncTask, Object... processingContext) {
Assert.notNull(asyncTask, "AsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
Long timeout = asyncTask.getTimeout();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
AsyncTaskExecutor executor = asyncTask.getExecutor();
if (executor != null) {
this.taskExecutor = executor;
}
startCallableProcessing(asyncTask.getCallable(), processingContext);
}
/**
* Start concurrent request processing and initialize the given
* {@link DeferredResult} with a {@link DeferredResultHandler} that saves
@ -329,41 +341,41 @@ public final class WebAsyncManager {
Assert.notNull(deferredResult, "DeferredResult must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
Long timeout = deferredResult.getTimeoutMilliseconds();
Long timeout = deferredResult.getTimeoutValue();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
final DeferredResultInterceptorChain chain =
new DeferredResultInterceptorChain(this.deferredResultInterceptors.values());
List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<DeferredResultProcessingInterceptor>();
interceptors.add(deferredResult.getInterceptor());
interceptors.addAll(this.deferredResultInterceptors.values());
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);
this.asyncWebRequest.addTimeoutHandler(new Runnable() {
public void run() {
if (!deferredResult.applyTimeoutResult()) {
try {
chain.triggerAfterTimeout(asyncWebRequest, deferredResult);
}
catch (Throwable t) {
setConcurrentResultAndDispatch(t);
}
try {
interceptorChain.triggerAfterTimeout(asyncWebRequest, deferredResult);
}
catch (Throwable t) {
setConcurrentResultAndDispatch(t);
}
}
});
this.asyncWebRequest.addCompletionHandler(new Runnable() {
public void run() {
deferredResult.expire();
chain.triggerAfterCompletion(asyncWebRequest, deferredResult);
interceptorChain.triggerAfterCompletion(asyncWebRequest, deferredResult);
}
});
startAsyncProcessing(processingContext);
try {
chain.applyPreProcess(this.asyncWebRequest, deferredResult);
interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult);
deferredResult.setResultHandler(new DeferredResultHandler() {
public void handleResult(Object result) {
result = chain.applyPostProcess(asyncWebRequest, deferredResult, result);
result = interceptorChain.applyPostProcess(asyncWebRequest, deferredResult, result);
setConcurrentResultAndDispatch(result);
}
});

View File

@ -19,6 +19,7 @@ package org.springframework.web.context.request.async;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -80,27 +81,42 @@ public class DeferredResultTests {
}
@Test
public void setExpired() {
DeferredResult<String> result = new DeferredResult<String>();
assertFalse(result.isSetOrExpired());
public void onCompletion() throws Exception {
final StringBuilder sb = new StringBuilder();
DeferredResult<String> result = new DeferredResult<String>();
result.onCompletion(new Runnable() {
public void run() {
sb.append("completion event");
}
});
result.getInterceptor().afterCompletion(null, null);
result.expire();
assertTrue(result.isSetOrExpired());
assertFalse(result.setResult("hello"));
assertEquals("completion event", sb.toString());
}
@Test
public void applyTimeoutResult() {
public void onTimeout() throws Exception {
final StringBuilder sb = new StringBuilder();
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
handler.handleResult("timed out");
handler.handleResult("timeout result");
replay(handler);
DeferredResult<String> result = new DeferredResult<String>(null, "timed out");
DeferredResult<String> result = new DeferredResult<String>(null, "timeout result");
result.setResultHandler(handler);
result.onTimeout(new Runnable() {
public void run() {
sb.append("timeout event");
}
});
assertTrue(result.applyTimeoutResult());
assertFalse("Shouldn't be able to set result after timeout", result.setResult("hello"));
result.getInterceptor().afterTimeout(null, null);
assertEquals("timeout event", sb.toString());
assertFalse("Should not be able to set result a second time", result.setResult("hello"));
verify(handler);
}

View File

@ -128,7 +128,7 @@ public class StandardServletAsyncWebRequestTests {
timeoutHandler.run();
replay(timeoutHandler);
this.asyncRequest.setTimeoutHandler(timeoutHandler);
this.asyncRequest.addTimeoutHandler(timeoutHandler);
this.asyncRequest.onTimeout(new AsyncEvent(null));
verify(timeoutHandler);

View File

@ -208,13 +208,13 @@ public class WebAsyncManagerTests {
replay(executor);
this.asyncWebRequest.setTimeout(1000L);
this.asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject());
this.asyncWebRequest.addTimeoutHandler(EasyMock.<Runnable>anyObject());
this.asyncWebRequest.addCompletionHandler(EasyMock.<Runnable>anyObject());
this.asyncWebRequest.startAsync();
replay(this.asyncWebRequest);
@SuppressWarnings("unchecked")
AsyncTask<Object> asyncTask = new AsyncTask<Object>(1000L, executor, createMock(Callable.class));
MvcAsyncTask<Object> asyncTask = new MvcAsyncTask<Object>(1000L, executor, createMock(Callable.class));
this.asyncManager.startCallableProcessing(asyncTask);
verify(executor, this.asyncWebRequest);
@ -311,7 +311,7 @@ public class WebAsyncManagerTests {
}
private void setupDefaultAsyncScenario() {
this.asyncWebRequest.setTimeoutHandler((Runnable) notNull());
this.asyncWebRequest.addTimeoutHandler((Runnable) notNull());
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
this.asyncWebRequest.startAsync();
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);

View File

@ -16,7 +16,6 @@
package org.springframework.web.context.request.async;
import static org.springframework.web.context.request.async.CallableProcessingInterceptor.RESULT_NONE;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
@ -25,6 +24,8 @@ import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.web.context.request.async.CallableProcessingInterceptor.RESULT_NONE;
import java.util.concurrent.Callable;
@ -94,7 +95,27 @@ public class WebAsyncManagerTimeoutTests {
}
@Test
public void startCallableProcessingTimeoutAndResume() throws Exception {
public void startCallableProcessingTimeoutAndResumeThroughCallback() throws Exception {
StubCallable callable = new StubCallable();
MvcAsyncTask<Object> mvcAsyncTask = new MvcAsyncTask<Object>(callable);
mvcAsyncTask.onTimeout(new Callable<Object>() {
public Object call() throws Exception {
return 7;
}
});
this.asyncManager.startCallableProcessing(mvcAsyncTask);
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(7, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
}
@Test
public void startCallableProcessingTimeoutAndResumeThroughInterceptor() throws Exception {
StubCallable callable = new StubCallable();
@ -107,6 +128,7 @@ public class WebAsyncManagerTimeoutTests {
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(22, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
@ -128,6 +150,7 @@ public class WebAsyncManagerTimeoutTests {
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(exception, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
@ -161,25 +184,38 @@ public class WebAsyncManagerTimeoutTests {
public void startDeferredResultProcessingTimeoutAndResumeWithDefaultResult() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(null, 23);
DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() {
public <T> void afterTimeout(NativeWebRequest request, DeferredResult<T> result) throws Exception {
result.setErrorResult("should not get here");
}
};
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult);
AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(23, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
}
@Test
public void startDeferredResultProcessingTimeoutAndResumeWithInterceptor() throws Exception {
public void startDeferredResultProcessingTimeoutAndResumeThroughCallback() throws Exception {
final DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
deferredResult.onTimeout(new Runnable() {
public void run() {
deferredResult.setResult(23);
}
});
this.asyncManager.startDeferredResultProcessing(deferredResult);
AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(23, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
}
@Test
public void startDeferredResultProcessingTimeoutAndResumeThroughInterceptor() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
@ -195,6 +231,7 @@ public class WebAsyncManagerTimeoutTests {
AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(23, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
}
@ -217,6 +254,7 @@ public class WebAsyncManagerTimeoutTests {
AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(exception, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
}

View File

@ -905,7 +905,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
initContextHolders(request, localeContext, requestAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(this.getClass().getName(), createRequestBindingInterceptor(request));
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), getRequestBindingInterceptor(request));
try {
doService(request, response);
@ -988,7 +988,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
}
}
private CallableProcessingInterceptor createRequestBindingInterceptor(final HttpServletRequest request) {
private CallableProcessingInterceptor getRequestBindingInterceptor(final HttpServletRequest request) {
return new CallableProcessingInterceptorAdapter() {
@Override
public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) {

View File

@ -23,7 +23,7 @@ import java.util.concurrent.Callable;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert;
import org.springframework.web.context.request.async.AsyncTask;
import org.springframework.web.context.request.async.MvcAsyncTask;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
@ -50,7 +50,7 @@ public class AsyncSupportConfigurer {
/**
* Set the default {@link AsyncTaskExecutor} to use when a controller method
* returns a {@link Callable}. Controller methods can override this default on
* a per-request basis by returning an {@link AsyncTask}.
* a per-request basis by returning an {@link MvcAsyncTask}.
*
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used and it's
* highly recommended to change that default in production since the simple

View File

@ -19,13 +19,13 @@ package org.springframework.web.servlet.mvc.method.annotation;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.AsyncTask;
import org.springframework.web.context.request.async.MvcAsyncTask;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Handles return values of type {@link AsyncTask}.
* Handles return values of type {@link MvcAsyncTask}.
*
* @author Rossen Stoyanchev
* @since 3.2
@ -41,7 +41,7 @@ public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnVal
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return AsyncTask.class.isAssignableFrom(paramType);
return MvcAsyncTask.class.isAssignableFrom(paramType);
}
public void handleReturnValue(Object returnValue,
@ -52,9 +52,9 @@ public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnVal
return;
}
AsyncTask<?> asyncTask = (AsyncTask<?>) returnValue;
asyncTask.setBeanFactory(this.beanFactory);
WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(asyncTask, mavContainer);
MvcAsyncTask<?> mvcAsyncTask = (MvcAsyncTask<?>) returnValue;
mvcAsyncTask.setBeanFactory(this.beanFactory);
WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(mvcAsyncTask, mavContainer);
}
}

View File

@ -63,7 +63,7 @@ import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncTask;
import org.springframework.web.context.request.async.MvcAsyncTask;
import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
@ -350,7 +350,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
/**
* Set the default {@link AsyncTaskExecutor} to use when a controller method
* return a {@link Callable}. Controller methods can override this default on
* a per-request basis by returning an {@link AsyncTask}.
* a per-request basis by returning an {@link MvcAsyncTask}.
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
* It's recommended to change that default in production as the simple executor
* does not re-use threads.