Polish (major) MVC async processing interceptors
New afterTimeout and afterCompletion callbacks afterTimeout can provide a concurrent result to be used instead of the one that could not be set or returned on time Interceptor exceptions cause async processing to resume treating the exception as the concurrent result Adapter classes for convenient implementation of the interfaces Issue: SPR-9914
This commit is contained in:
parent
06e34f05a6
commit
f036ed639f
|
@ -34,7 +34,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||
|
@ -318,7 +318,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||
/**
|
||||
* Bind and unbind the Hibernate {@code Session} to the current thread.
|
||||
*/
|
||||
private static class SessionBindingCallableInterceptor implements CallableProcessingInterceptor {
|
||||
private static class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter {
|
||||
|
||||
private final SessionFactory sessionFactory;
|
||||
|
||||
|
@ -329,17 +329,19 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||
this.sessionHolder = sessionHolder;
|
||||
}
|
||||
|
||||
public void preProcess(NativeWebRequest request, Callable<?> task) {
|
||||
@Override
|
||||
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
|
||||
initializeThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
|
||||
}
|
||||
|
||||
private void initializeThread() {
|
||||
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
|
||||
}
|
||||
|
||||
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.springframework.ui.ModelMap;
|
|||
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
|
||||
|
@ -276,7 +276,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
|
|||
/**
|
||||
* Bind and unbind the Hibernate {@code Session} to the current thread.
|
||||
*/
|
||||
private class SessionBindingCallableInterceptor implements CallableProcessingInterceptor {
|
||||
private class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter {
|
||||
|
||||
private final SessionHolder sessionHolder;
|
||||
|
||||
|
@ -284,16 +284,18 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
|
|||
this.sessionHolder = sessionHolder;
|
||||
}
|
||||
|
||||
public void preProcess(NativeWebRequest request, Callable<?> task) {
|
||||
@Override
|
||||
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
|
||||
initializeThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(getSessionFactory());
|
||||
}
|
||||
|
||||
private void initializeThread() {
|
||||
TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder);
|
||||
}
|
||||
|
||||
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(getSessionFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.springframework.orm.hibernate4.SessionHolder;
|
|||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||
|
@ -215,7 +215,7 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||
/**
|
||||
* Bind and unbind the Hibernate {@code Session} to the current thread.
|
||||
*/
|
||||
private static class SessionBindingCallableInterceptor implements CallableProcessingInterceptor {
|
||||
private static class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter {
|
||||
|
||||
private final SessionFactory sessionFactory;
|
||||
|
||||
|
@ -226,16 +226,18 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||
this.sessionHolder = sessionHolder;
|
||||
}
|
||||
|
||||
public void preProcess(NativeWebRequest request, Callable<?> task) {
|
||||
@Override
|
||||
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
|
||||
initializeThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
|
||||
}
|
||||
|
||||
private void initializeThread() {
|
||||
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
|
||||
}
|
||||
|
||||
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import org.springframework.ui.ModelMap;
|
|||
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
|
||||
|
@ -215,7 +215,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
|
|||
/**
|
||||
* Bind and unbind the Hibernate {@code Session} to the current thread.
|
||||
*/
|
||||
private class SessionBindingCallableInterceptor implements CallableProcessingInterceptor {
|
||||
private class SessionBindingCallableInterceptor extends CallableProcessingInterceptorAdapter {
|
||||
|
||||
private final SessionHolder sessionHolder;
|
||||
|
||||
|
@ -223,17 +223,19 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
|
|||
this.sessionHolder = sessionHolder;
|
||||
}
|
||||
|
||||
public void preProcess(NativeWebRequest request, Callable<?> task) {
|
||||
@Override
|
||||
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
|
||||
initializeThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(getSessionFactory());
|
||||
}
|
||||
|
||||
private void initializeThread() {
|
||||
TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder);
|
||||
}
|
||||
|
||||
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(getSessionFactory());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -34,7 +34,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
|||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||
|
@ -242,7 +242,7 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter {
|
|||
/**
|
||||
* Bind and unbind the {@code EntityManager} to the current thread.
|
||||
*/
|
||||
private static class EntityManagerBindingCallableInterceptor implements CallableProcessingInterceptor {
|
||||
private static class EntityManagerBindingCallableInterceptor extends CallableProcessingInterceptorAdapter {
|
||||
|
||||
private final EntityManagerFactory emFactory;
|
||||
|
||||
|
@ -254,17 +254,19 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter {
|
|||
this.emHolder = emHolder;
|
||||
}
|
||||
|
||||
public void preProcess(NativeWebRequest request, Callable<?> task) {
|
||||
@Override
|
||||
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
|
||||
initializeThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(this.emFactory);
|
||||
}
|
||||
|
||||
private void initializeThread() {
|
||||
TransactionSynchronizationManager.bindResource(this.emFactory, this.emHolder);
|
||||
}
|
||||
|
||||
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(this.emFactory);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -31,7 +31,7 @@ import org.springframework.ui.ModelMap;
|
|||
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
|
||||
|
@ -163,7 +163,7 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce
|
|||
/**
|
||||
* Bind and unbind the Hibernate {@code Session} to the current thread.
|
||||
*/
|
||||
private class EntityManagerBindingCallableInterceptor implements CallableProcessingInterceptor {
|
||||
private class EntityManagerBindingCallableInterceptor extends CallableProcessingInterceptorAdapter {
|
||||
|
||||
private final EntityManagerHolder emHolder;
|
||||
|
||||
|
@ -172,17 +172,19 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce
|
|||
this.emHolder = emHolder;
|
||||
}
|
||||
|
||||
public void preProcess(NativeWebRequest request, Callable<?> task) {
|
||||
@Override
|
||||
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
|
||||
initializeThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
|
||||
}
|
||||
|
||||
private void initializeThread() {
|
||||
TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), this.emHolder);
|
||||
}
|
||||
|
||||
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package org.springframework.orm.hibernate3.support;
|
||||
|
||||
import static org.easymock.EasyMock.anyObject;
|
||||
import static org.easymock.EasyMock.*;
|
||||
import static org.easymock.EasyMock.createMock;
|
||||
import static org.easymock.EasyMock.createStrictMock;
|
||||
import static org.easymock.EasyMock.expect;
|
||||
|
@ -39,6 +39,7 @@ import javax.servlet.ServletRequest;
|
|||
import javax.servlet.ServletResponse;
|
||||
import javax.transaction.TransactionManager;
|
||||
|
||||
import org.easymock.EasyMock;
|
||||
import org.hibernate.FlushMode;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
@ -63,8 +64,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;
|
||||
|
||||
|
||||
|
@ -177,6 +178,8 @@ public class OpenSessionInViewTests {
|
|||
|
||||
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
|
||||
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
|
||||
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
|
||||
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
|
||||
asyncWebRequest.startAsync();
|
||||
replay(asyncWebRequest);
|
||||
|
||||
|
@ -491,6 +494,8 @@ public class OpenSessionInViewTests {
|
|||
|
||||
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
|
||||
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
|
||||
asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject());
|
||||
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
|
||||
asyncWebRequest.startAsync();
|
||||
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();
|
||||
replay(asyncWebRequest);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -155,6 +155,8 @@ public class OpenEntityManagerInViewTests extends TestCase {
|
|||
|
||||
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
|
||||
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
|
||||
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
|
||||
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
|
||||
asyncWebRequest.startAsync();
|
||||
replay(asyncWebRequest);
|
||||
|
||||
|
@ -344,6 +346,8 @@ public class OpenEntityManagerInViewTests extends TestCase {
|
|||
|
||||
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
|
||||
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
|
||||
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
|
||||
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
|
||||
asyncWebRequest.startAsync();
|
||||
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();
|
||||
replay(asyncWebRequest);
|
||||
|
|
|
@ -27,9 +27,9 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter;
|
||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
@ -48,6 +48,8 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
@SuppressWarnings("serial")
|
||||
final class TestDispatcherServlet extends DispatcherServlet {
|
||||
|
||||
private static final String KEY = TestDispatcherServlet.class.getName() + "-interceptor";
|
||||
|
||||
/**
|
||||
* Create a new instance with the given web application context.
|
||||
*/
|
||||
|
@ -70,19 +72,15 @@ final class TestDispatcherServlet extends DispatcherServlet {
|
|||
|
||||
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
|
||||
|
||||
asyncManager.registerCallableInterceptor("mockmvc", new CallableProcessingInterceptor() {
|
||||
public void preProcess(NativeWebRequest request, Callable<?> task) throws Exception { }
|
||||
public void postProcess(NativeWebRequest request, Callable<?> task, Object value) throws Exception {
|
||||
asyncManager.registerCallableInterceptor(KEY, new CallableProcessingInterceptorAdapter() {
|
||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object value) throws Exception {
|
||||
asyncResultLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
asyncManager.registerDeferredResultInterceptor("mockmvc", new DeferredResultProcessingInterceptor() {
|
||||
public void preProcess(NativeWebRequest request, DeferredResult<?> result) throws Exception { }
|
||||
public void postProcess(NativeWebRequest request, DeferredResult<?> result, Object value) throws Exception {
|
||||
asyncManager.registerDeferredResultInterceptor(KEY, new DeferredResultProcessingInterceptorAdapter() {
|
||||
public <T> void postProcess(NativeWebRequest request, DeferredResult<T> result, Object value) throws Exception {
|
||||
asyncResultLatch.countDown();
|
||||
}
|
||||
public void afterExpiration(NativeWebRequest request, DeferredResult<?> result) throws Exception { }
|
||||
});
|
||||
|
||||
return asyncResultLatch;
|
||||
|
|
|
@ -36,7 +36,7 @@ class CallableInterceptorChain {
|
|||
|
||||
private final List<CallableProcessingInterceptor> interceptors;
|
||||
|
||||
private int interceptorIndex = -1;
|
||||
private int preProcessIndex = -1;
|
||||
|
||||
|
||||
public CallableInterceptorChain(Collection<CallableProcessingInterceptor> interceptors) {
|
||||
|
@ -44,21 +44,47 @@ class CallableInterceptorChain {
|
|||
}
|
||||
|
||||
public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception {
|
||||
for (int i = 0; i < this.interceptors.size(); i++) {
|
||||
this.interceptors.get(i).preProcess(request, task);
|
||||
this.interceptorIndex = i;
|
||||
for (CallableProcessingInterceptor interceptor : this.interceptors) {
|
||||
interceptor.preProcess(request, task);
|
||||
this.preProcessIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
public void applyPostProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
for (int i = this.interceptorIndex; i >= 0; i--) {
|
||||
public Object applyPostProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
for (int i = this.preProcessIndex; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).postProcess(request, task, concurrentResult);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("CallableProcessingInterceptor.postProcess threw exception", ex);
|
||||
catch (Throwable t) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return concurrentResult;
|
||||
}
|
||||
|
||||
public Object triggerAfterTimeout(NativeWebRequest request, Callable<?> task) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
Object result = this.interceptors.get(i).afterTimeout(request, task);
|
||||
if (result != CallableProcessingInterceptor.RESULT_NONE) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return CallableProcessingInterceptor.RESULT_NONE;
|
||||
}
|
||||
|
||||
public void triggerAfterCompletion(NativeWebRequest request, Callable<?> task) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).afterCompletion(request, task);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.error("afterCompletion error", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,49 +19,80 @@ import java.util.concurrent.Callable;
|
|||
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||
|
||||
/**
|
||||
* Intercepts concurrent request handling, where the concurrent result is
|
||||
* obtained by executing a {@link Callable} on behalf of the application with an
|
||||
* {@link AsyncTaskExecutor}.
|
||||
* <p>
|
||||
* A {@code CallableProcessingInterceptor} is invoked before and after the
|
||||
* invocation of the {@code Callable} task in the asynchronous thread.
|
||||
* obtained by executing a {@link Callable} on behalf of the application with
|
||||
* an {@link AsyncTaskExecutor}.
|
||||
*
|
||||
* <p>A {@code CallableProcessingInterceptor} may be registered as follows:
|
||||
* <pre>
|
||||
* CallableProcessingInterceptor interceptor = ... ;
|
||||
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
|
||||
* asyncManager.registerCallableInterceptor("key", interceptor);
|
||||
* </pre>
|
||||
* <p>A {@code CallableProcessingInterceptor} is invoked before and after the
|
||||
* invocation of the {@code Callable} task in the asynchronous thread, as well
|
||||
* as on timeout from a container thread, or after completing for any reason
|
||||
* including a timeout or network error.
|
||||
*
|
||||
* <p>To register an interceptor for every request, the above can be done through
|
||||
* a {@link WebRequestInterceptor} during pre-handling.
|
||||
* <p>As a general rule exceptions raised by interceptor methods will cause
|
||||
* async processing to resume by dispatching back to the container and using
|
||||
* the Exception instance as the concurrent result. Such exceptions will then
|
||||
* be processed through the {@code HandlerExceptionResolver} mechanism.
|
||||
*
|
||||
* <p>The {@link #afterTimeout(NativeWebRequest, Callable) afterTimeout} method
|
||||
* can select a value to be used to resume processing.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface CallableProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* Invoked from the asynchronous thread in which the {@code Callable} is
|
||||
* executed, before the {@code Callable} is invoked.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task that will produce a result
|
||||
*/
|
||||
void preProcess(NativeWebRequest request, Callable<?> task) throws Exception;
|
||||
public static final Object RESULT_NONE = new Object();
|
||||
|
||||
/**
|
||||
* Invoked from the asynchronous thread in which the {@code Callable} is
|
||||
* executed, after the {@code Callable} returned a result.
|
||||
* Invoked <em>after</em> the start of concurrent handling in the async
|
||||
* thread in which the {@code Callable} is executed and <em>before</em> the
|
||||
* actual invocation of the {@code Callable}.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task that produced the result
|
||||
* @param task the task for the current async request
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
<T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked <em>after</em> the {@code Callable} has produced a result in the
|
||||
* async thread in which the {@code Callable} is executed. This method may
|
||||
* be invoked later than {@code afterTimeout} or {@code afterCompletion}
|
||||
* depending on when the {@code Callable} finishes processing.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task for the current async request
|
||||
* @param concurrentResult the result of concurrent processing, which could
|
||||
* be a {@link Throwable} if the {@code Callable} raised an exception
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) throws Exception;
|
||||
<T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked from a container thread when the async request times out before
|
||||
* the {@code Callable} task completes. Implementations may return a value,
|
||||
* including an {@link Exception}, to use instead of the value the
|
||||
* {@link Callable} did not return in time.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task for the current async request
|
||||
* @return a concurrent result value; if the value is anything other than
|
||||
* {@link #RESULT_NONE}, concurrent processing is resumed and subsequent
|
||||
* interceptors are not invoked
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
<T> Object afterTimeout(NativeWebRequest request, Callable<T> task) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked from a container thread when async processing completes for any
|
||||
* reason including timeout or network error.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task for the current async request
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
<T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.context.request.async;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Abstract adapter class for the {@link CallableProcessingInterceptor} interface,
|
||||
* for simplified implementation of individual methods.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class CallableProcessingInterceptorAdapter implements CallableProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation always returns
|
||||
* {@link CallableProcessingInterceptor#RESULT_NONE RESULT_NONE}.
|
||||
*/
|
||||
public <T> Object afterTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
|
||||
return RESULT_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
|
||||
}
|
||||
|
||||
}
|
|
@ -66,7 +66,7 @@ public final class DeferredResult<T> {
|
|||
/**
|
||||
* Create a DeferredResult with a timeout and a default result to use on timeout.
|
||||
* @param timeout timeout value in milliseconds; ignored if {@code null}
|
||||
* @param timeoutResult the result to use, possibly {@code null}
|
||||
* @param timeoutResult the result to use
|
||||
*/
|
||||
public DeferredResult(Long timeout, Object timeoutResult) {
|
||||
this.timeoutResult = timeoutResult;
|
||||
|
@ -118,13 +118,7 @@ public final class DeferredResult<T> {
|
|||
}
|
||||
this.result = result;
|
||||
if (this.resultHandler != null) {
|
||||
try {
|
||||
this.resultHandler.handleResult(this.result);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.trace("DeferredResult not handled", t);
|
||||
return false;
|
||||
}
|
||||
this.resultHandler.handleResult(this.result);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -158,24 +152,19 @@ public final class DeferredResult<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the "expired" flag if and only if the result value was not already set.
|
||||
* @return {@code true} if expiration succeeded, {@code false} otherwise
|
||||
* Mark this instance expired so it may no longer be used.
|
||||
* @return the previous value of the expiration flag
|
||||
*/
|
||||
boolean expire() {
|
||||
synchronized (this) {
|
||||
if (!isSetOrExpired()) {
|
||||
this.expired = true;
|
||||
}
|
||||
boolean previous = this.expired;
|
||||
this.expired = true;
|
||||
return previous;
|
||||
}
|
||||
return this.expired;
|
||||
}
|
||||
|
||||
boolean hasTimeoutResult() {
|
||||
return this.timeoutResult != RESULT_NONE;
|
||||
}
|
||||
|
||||
boolean applyTimeoutResult() {
|
||||
return hasTimeoutResult() ? setResultInternal(this.timeoutResult) : false;
|
||||
return (this.timeoutResult != RESULT_NONE) ? setResultInternal(this.timeoutResult) : false;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -35,35 +35,48 @@ class DeferredResultInterceptorChain {
|
|||
|
||||
private final List<DeferredResultProcessingInterceptor> interceptors;
|
||||
|
||||
private int preProcessingIndex = -1;
|
||||
|
||||
|
||||
public DeferredResultInterceptorChain(Collection<DeferredResultProcessingInterceptor> interceptors) {
|
||||
this.interceptors = new ArrayList<DeferredResultProcessingInterceptor>(interceptors);
|
||||
}
|
||||
|
||||
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> task) throws Exception {
|
||||
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception {
|
||||
for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
|
||||
interceptor.preProcess(request, task);
|
||||
interceptor.preProcess(request, deferredResult);
|
||||
this.preProcessingIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
public void applyPostProcess(NativeWebRequest request, DeferredResult<?> task, Object concurrentResult) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).postProcess(request, task, concurrentResult);
|
||||
public Object applyPostProcess(NativeWebRequest request, DeferredResult<?> deferredResult, Object concurrentResult) {
|
||||
try {
|
||||
for (int i = this.preProcessingIndex; i >= 0; i--) {
|
||||
this.interceptors.get(i).postProcess(request, deferredResult, concurrentResult);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("DeferredResultProcessingInterceptor.postProcess threw exception", ex);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
return t;
|
||||
}
|
||||
return concurrentResult;
|
||||
}
|
||||
|
||||
public void triggerAfterTimeout(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception {
|
||||
for (int i = this.preProcessingIndex; i >= 0; i--) {
|
||||
if (deferredResult.isSetOrExpired()) {
|
||||
return;
|
||||
}
|
||||
this.interceptors.get(i).afterTimeout(request, deferredResult);
|
||||
}
|
||||
}
|
||||
|
||||
public void triggerAfterExpiration(NativeWebRequest request, DeferredResult<?> task) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
public void triggerAfterCompletion(NativeWebRequest request, DeferredResult<?> deferredResult) {
|
||||
for (int i = this.preProcessingIndex; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).afterExpiration(request, task);
|
||||
this.interceptors.get(i).afterCompletion(request, deferredResult);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("DeferredResultProcessingInterceptor.afterExpiration threw exception", ex);
|
||||
catch (Throwable t) {
|
||||
logger.error("afterCompletion error", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,26 +16,24 @@
|
|||
package org.springframework.web.context.request.async;
|
||||
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||
|
||||
/**
|
||||
* Intercepts concurrent request handling, where the concurrent result is
|
||||
* obtained by waiting for a {@link DeferredResult} to be set from a thread
|
||||
* chosen by the application (e.g. in response to some external event).
|
||||
*
|
||||
* <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start of
|
||||
* asynchronous processing and either when the {@code DeferredResult} is set or
|
||||
* when when the underlying request ends, whichever comes fist.
|
||||
* <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start
|
||||
* of async processing, after the {@code DeferredResult} is set as well as on
|
||||
* timeout, or or after completing for any reason including a timeout or network
|
||||
* error.
|
||||
*
|
||||
* <p>A {@code DeferredResultProcessingInterceptor} may be registered as follows:
|
||||
* <pre>
|
||||
* DeferredResultProcessingInterceptor interceptor = ... ;
|
||||
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
|
||||
* asyncManager.registerDeferredResultInterceptor("key", interceptor);
|
||||
* </pre>
|
||||
* <p>As a general rule exceptions raised by interceptor methods will cause
|
||||
* async processing to resume by dispatching back to the container and using
|
||||
* the Exception instance as the concurrent result. Such exceptions will then
|
||||
* be processed through the {@code HandlerExceptionResolver} mechanism.
|
||||
*
|
||||
* <p>To register an interceptor for every request, the above can be done through
|
||||
* a {@link WebRequestInterceptor} during pre-handling.
|
||||
* <p>The {@link #afterTimeout(NativeWebRequest, DeferredResult) afterTimeout}
|
||||
* method can set the {@code DeferredResult} in order to resume processing.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
|
@ -43,40 +41,59 @@ import org.springframework.web.context.request.WebRequestInterceptor;
|
|||
public interface DeferredResultProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* Invoked before the start of concurrent handling using a
|
||||
* {@link DeferredResult}. The invocation occurs in the thread that
|
||||
* initiated concurrent handling.
|
||||
* Invoked immediately after the start of concurrent handling, in the same
|
||||
* thread that started it. This method may be used to detect the start of
|
||||
* concurrent processing with the given {@code DeferredResult}.
|
||||
*
|
||||
* <p>The {@code DeferredResult} may have already been set, for example at
|
||||
* the time of its creation or by another thread.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult instance
|
||||
* @param deferredResult the DeferredResult for the current request
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
void preProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception;
|
||||
<T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked when a {@link DeferredResult} is set via
|
||||
* {@link DeferredResult#setResult(Object) setResult}, or
|
||||
* {@link DeferredResult#setErrorResult(Object) setErrorResult}, or after
|
||||
* a timeout if a {@code DeferredResult} was created with a constructor
|
||||
* accepting a default timeout result.
|
||||
* <p>
|
||||
* If the request ends before the {@code DeferredResult} is set, then
|
||||
* {@link #afterExpiration(NativeWebRequest, DeferredResult)} is called.
|
||||
* Invoked after a {@code DeferredResult} has been set, via
|
||||
* {@link DeferredResult#setResult(Object)} or
|
||||
* {@link DeferredResult#setErrorResult(Object)}, and is also ready to
|
||||
* handle the concurrent result.
|
||||
*
|
||||
* <p>This method may also be invoked after a timeout when the
|
||||
* {@code DeferredResult} was created with a constructor accepting a default
|
||||
* timeout result.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult that has been set
|
||||
* @param deferredResult the DeferredResult for the current request
|
||||
* @param concurrentResult the result to which the {@code DeferredResult}
|
||||
* was set
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
void postProcess(NativeWebRequest request, DeferredResult<?> deferredResult,
|
||||
Object concurrentResult) throws Exception;
|
||||
<T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult, Object concurrentResult) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked when a {@link DeferredResult} was never set before the request
|
||||
* completed due to a timeout or network error.
|
||||
* 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.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult that has been set
|
||||
* @param deferredResult the DeferredResult for the current request; if the
|
||||
* {@code DeferredResult} is set, then concurrent processing is resumed and
|
||||
* subsequent interceptors are not invoked
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
void afterExpiration(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception;
|
||||
<T> void afterTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked 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.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult for the current request
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
<T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.context.request.async;
|
||||
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Abstract adapter class for the {@link DeferredResultProcessingInterceptor}
|
||||
* interface for simplified implementation of individual methods.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class DeferredResultProcessingInterceptorAdapter implements DeferredResultProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult,
|
||||
Object concurrentResult) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void afterTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
|
||||
}
|
||||
|
||||
}
|
|
@ -27,7 +27,6 @@ import javax.servlet.AsyncListener;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
||||
|
@ -50,7 +49,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
|||
|
||||
private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
|
||||
|
||||
private Runnable timeoutHandler = new DefaultTimeoutHandler();
|
||||
private Runnable timeoutHandler;
|
||||
|
||||
private final List<Runnable> completionHandlers = new ArrayList<Runnable>();
|
||||
|
||||
|
@ -74,15 +73,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
|||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>If not set, by default a timeout is handled by returning
|
||||
* SERVICE_UNAVAILABLE (503).
|
||||
*/
|
||||
public void setTimeoutHandler(Runnable timeoutHandler) {
|
||||
if (timeoutHandler != null) {
|
||||
this.timeoutHandler = timeoutHandler;
|
||||
}
|
||||
this.timeoutHandler = timeoutHandler;
|
||||
}
|
||||
|
||||
public void addCompletionHandler(Runnable runnable) {
|
||||
|
@ -135,31 +127,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
|||
}
|
||||
|
||||
public void onTimeout(AsyncEvent event) throws IOException {
|
||||
this.timeoutHandler.run();
|
||||
if (this.timeoutHandler != null) {
|
||||
this.timeoutHandler.run();
|
||||
}
|
||||
}
|
||||
|
||||
public void onComplete(AsyncEvent event) throws IOException {
|
||||
for (Runnable runnable : this.completionHandlers) {
|
||||
runnable.run();
|
||||
for (Runnable handler : this.completionHandlers) {
|
||||
handler.run();
|
||||
}
|
||||
this.asyncContext = null;
|
||||
this.asyncCompleted.set(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a SERVICE_UNAVAILABLE (503).
|
||||
*/
|
||||
private class DefaultTimeoutHandler implements Runnable {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
getResponse().sendError(HttpStatus.SERVICE_UNAVAILABLE.value());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -44,8 +44,6 @@ import org.springframework.web.util.UrlPathHelper;
|
|||
* result can be accessed via {@link #getConcurrentResult()} or its presence
|
||||
* detected via {@link #hasConcurrentResult()}.
|
||||
*
|
||||
* <p>TODO .. Servlet 3 config
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*
|
||||
|
@ -61,13 +59,13 @@ public final class WebAsyncManager {
|
|||
|
||||
private static final Log logger = LogFactory.getLog(WebAsyncManager.class);
|
||||
|
||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
|
||||
private AsyncWebRequest asyncWebRequest;
|
||||
|
||||
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());
|
||||
|
||||
private Runnable timeoutHandler;
|
||||
|
||||
private Object concurrentResult = RESULT_NONE;
|
||||
|
||||
private Object[] concurrentResultContext;
|
||||
|
@ -78,8 +76,6 @@ public final class WebAsyncManager {
|
|||
private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
|
||||
new LinkedHashMap<Object, DeferredResultProcessingInterceptor>();
|
||||
|
||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
|
||||
/**
|
||||
* Package private constructor.
|
||||
|
@ -119,15 +115,6 @@ public final class WebAsyncManager {
|
|||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the handler to use when concurrent handling times out. If not set, by
|
||||
* default a timeout is handled by returning SERVICE_UNAVAILABLE (503).
|
||||
* @param timeoutHandler the handler
|
||||
*/
|
||||
public void setTimeoutHandler(Runnable timeoutHandler) {
|
||||
this.timeoutHandler = timeoutHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the selected handler for the current request chose to handle the
|
||||
* request asynchronously. A return value of "true" indicates concurrent
|
||||
|
@ -226,42 +213,65 @@ public final class WebAsyncManager {
|
|||
*/
|
||||
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
|
||||
Assert.notNull(callable, "Callable must not be null");
|
||||
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
|
||||
|
||||
final CallableInterceptorChain chain = new CallableInterceptorChain(this.callableInterceptors.values());
|
||||
|
||||
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
|
||||
public void run() {
|
||||
logger.debug("Processing timeout");
|
||||
Object result = chain.triggerAfterTimeout(asyncWebRequest, callable);
|
||||
if (result != CallableProcessingInterceptor.RESULT_NONE) {
|
||||
setConcurrentResultAndDispatch(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||
public void run() {
|
||||
chain.triggerAfterCompletion(asyncWebRequest, callable);
|
||||
}
|
||||
});
|
||||
|
||||
startAsyncProcessing(processingContext);
|
||||
|
||||
this.taskExecutor.submit(new Runnable() {
|
||||
|
||||
public void run() {
|
||||
|
||||
CallableInterceptorChain chain =
|
||||
new CallableInterceptorChain(callableInterceptors.values());
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
chain.applyPreProcess(asyncWebRequest, callable);
|
||||
concurrentResult = callable.call();
|
||||
result = callable.call();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
concurrentResult = t;
|
||||
result = t;
|
||||
}
|
||||
finally {
|
||||
chain.applyPostProcess(asyncWebRequest, callable, concurrentResult);
|
||||
result = chain.applyPostProcess(asyncWebRequest, callable, result);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Concurrent result value [" + concurrentResult + "]");
|
||||
}
|
||||
|
||||
if (asyncWebRequest.isAsyncComplete()) {
|
||||
logger.error("Could not complete processing due to a timeout or network error");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Dispatching request to continue processing");
|
||||
asyncWebRequest.dispatch();
|
||||
setConcurrentResultAndDispatch(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setConcurrentResultAndDispatch(Object result) {
|
||||
synchronized (WebAsyncManager.this) {
|
||||
if (hasConcurrentResult()) {
|
||||
return;
|
||||
}
|
||||
concurrentResult = result;
|
||||
}
|
||||
|
||||
if (asyncWebRequest.isAsyncComplete()) {
|
||||
logger.error("Could not complete async processing due to timeout or network error");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Concurrent result value [" + concurrentResult + "]");
|
||||
logger.debug("Dispatching request to resume processing");
|
||||
|
||||
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
|
||||
|
@ -273,6 +283,7 @@ public final class WebAsyncManager {
|
|||
*/
|
||||
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) {
|
||||
|
@ -302,53 +313,54 @@ public final class WebAsyncManager {
|
|||
* @see #getConcurrentResult()
|
||||
* @see #getConcurrentResultContext()
|
||||
*/
|
||||
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult,
|
||||
Object... processingContext) throws Exception {
|
||||
public void startDeferredResultProcessing(
|
||||
final DeferredResult<?> deferredResult, Object... processingContext) {
|
||||
|
||||
Assert.notNull(deferredResult, "DeferredResult must not be null");
|
||||
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
|
||||
|
||||
Long timeout = deferredResult.getTimeoutMilliseconds();
|
||||
if (timeout != null) {
|
||||
this.asyncWebRequest.setTimeout(timeout);
|
||||
}
|
||||
|
||||
if (deferredResult.hasTimeoutResult()) {
|
||||
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
|
||||
public void run() {
|
||||
deferredResult.applyTimeoutResult();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final DeferredResultInterceptorChain chain =
|
||||
new DeferredResultInterceptorChain(this.deferredResultInterceptors.values());
|
||||
|
||||
chain.applyPreProcess(this.asyncWebRequest, deferredResult);
|
||||
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
|
||||
public void run() {
|
||||
if (!deferredResult.applyTimeoutResult()) {
|
||||
try {
|
||||
chain.triggerAfterTimeout(asyncWebRequest, deferredResult);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
setConcurrentResultAndDispatch(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||
public void run() {
|
||||
if (deferredResult.expire()) {
|
||||
chain.triggerAfterExpiration(asyncWebRequest, deferredResult);
|
||||
}
|
||||
deferredResult.expire();
|
||||
chain.triggerAfterCompletion(asyncWebRequest, deferredResult);
|
||||
}
|
||||
});
|
||||
|
||||
startAsyncProcessing(processingContext);
|
||||
|
||||
deferredResult.setResultHandler(new DeferredResultHandler() {
|
||||
|
||||
public void handleResult(Object result) {
|
||||
concurrentResult = result;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Deferred result value [" + concurrentResult + "]");
|
||||
try {
|
||||
chain.applyPreProcess(this.asyncWebRequest, deferredResult);
|
||||
deferredResult.setResultHandler(new DeferredResultHandler() {
|
||||
public void handleResult(Object result) {
|
||||
result = chain.applyPostProcess(asyncWebRequest, deferredResult, result);
|
||||
setConcurrentResultAndDispatch(result);
|
||||
}
|
||||
|
||||
chain.applyPostProcess(asyncWebRequest, deferredResult, result);
|
||||
|
||||
logger.debug("Dispatching request to complete processing");
|
||||
asyncWebRequest.dispatch();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
setConcurrentResultAndDispatch(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void startAsyncProcessing(Object[] processingContext) {
|
||||
|
@ -356,15 +368,10 @@ public final class WebAsyncManager {
|
|||
clearConcurrentResult();
|
||||
this.concurrentResultContext = processingContext;
|
||||
|
||||
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
|
||||
this.asyncWebRequest.startAsync();
|
||||
|
||||
if (this.timeoutHandler != null) {
|
||||
this.asyncWebRequest.setTimeoutHandler(this.timeoutHandler);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
|
||||
HttpServletRequest request = this.asyncWebRequest.getNativeRequest(HttpServletRequest.class);
|
||||
String requestUri = urlPathHelper.getRequestUri(request);
|
||||
logger.debug("Concurrent handling starting for " + request.getMethod() + " [" + requestUri + "]");
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package org.springframework.web.context.request.async;
|
||||
|
||||
import static org.easymock.EasyMock.createMock;
|
||||
import static org.easymock.EasyMock.expectLastCall;
|
||||
import static org.easymock.EasyMock.replay;
|
||||
import static org.easymock.EasyMock.verify;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
@ -62,21 +61,6 @@ public class DeferredResultTests {
|
|||
verify(handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setResultWithException() {
|
||||
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
|
||||
handler.handleResult("hello");
|
||||
expectLastCall().andThrow(new IllegalStateException());
|
||||
replay(handler);
|
||||
|
||||
DeferredResult<String> result = new DeferredResult<String>();
|
||||
result.setResultHandler(handler);
|
||||
|
||||
assertFalse(result.setResult("hello"));
|
||||
|
||||
verify(handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSetOrExpired() {
|
||||
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
|
||||
|
@ -105,12 +89,6 @@ public class DeferredResultTests {
|
|||
assertFalse(result.setResult("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasTimeout() {
|
||||
assertFalse(new DeferredResult<String>().hasTimeoutResult());
|
||||
assertTrue(new DeferredResult<String>(null, "timed out").hasTimeoutResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyTimeoutResult() {
|
||||
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
|
||||
|
|
|
@ -33,7 +33,6 @@ import javax.servlet.AsyncEvent;
|
|||
import org.easymock.EasyMock;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.MockAsyncContext;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
@ -120,7 +119,7 @@ public class StandardServletAsyncWebRequestTests {
|
|||
@Test
|
||||
public void onTimeoutDefaultBehavior() throws Exception {
|
||||
this.asyncRequest.onTimeout(new AsyncEvent(null));
|
||||
assertEquals(HttpStatus.SERVICE_UNAVAILABLE.value(), this.response.getStatus());
|
||||
assertEquals(200, this.response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.web.context.request.async;
|
|||
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.notNull;
|
||||
import static org.easymock.EasyMock.replay;
|
||||
import static org.easymock.EasyMock.reset;
|
||||
|
@ -30,15 +31,15 @@ import static org.junit.Assert.fail;
|
|||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.easymock.EasyMock;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
|
||||
/**
|
||||
* Test fixture with an {@link WebAsyncManager}.
|
||||
* Test fixture with an {@link WebAsyncManager} with a mock AsyncWebRequest.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
|
@ -48,6 +49,7 @@ public class WebAsyncManagerTests {
|
|||
|
||||
private AsyncWebRequest asyncWebRequest;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.asyncManager = WebAsyncUtils.getAsyncManager(new MockHttpServletRequest());
|
||||
|
@ -63,6 +65,27 @@ public class WebAsyncManagerTests {
|
|||
reset(this.asyncWebRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startAsyncProcessingWithoutAsyncWebRequest() {
|
||||
WebAsyncManager manager = WebAsyncUtils.getAsyncManager(new MockHttpServletRequest());
|
||||
|
||||
try {
|
||||
manager.startCallableProcessing(new StubCallable(1));
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
assertEquals(ex.getMessage(), "AsyncWebRequest must not be null");
|
||||
}
|
||||
|
||||
try {
|
||||
manager.startDeferredResultProcessing(new DeferredResult<String>());
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
assertEquals(ex.getMessage(), "AsyncWebRequest must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isConcurrentHandlingStarted() {
|
||||
|
||||
|
@ -91,32 +114,102 @@ public class WebAsyncManagerTests {
|
|||
@Test
|
||||
public void startCallableProcessing() throws Exception {
|
||||
|
||||
Callable<Object> task = new StubCallable();
|
||||
int concurrentResult = 21;
|
||||
Callable<Object> task = new StubCallable(concurrentResult);
|
||||
|
||||
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
|
||||
interceptor.preProcess(this.asyncWebRequest, task);
|
||||
interceptor.postProcess(this.asyncWebRequest, task, new Integer(1));
|
||||
interceptor.postProcess(this.asyncWebRequest, task, new Integer(concurrentResult));
|
||||
replay(interceptor);
|
||||
|
||||
this.asyncWebRequest.startAsync();
|
||||
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
|
||||
this.asyncWebRequest.dispatch();
|
||||
replay(this.asyncWebRequest);
|
||||
setupDefaultAsyncScenario();
|
||||
|
||||
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startCallableProcessing(task);
|
||||
|
||||
assertTrue(this.asyncManager.hasConcurrentResult());
|
||||
assertEquals(concurrentResult, this.asyncManager.getConcurrentResult());
|
||||
|
||||
verify(interceptor, this.asyncWebRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableProcessingAsyncTask() {
|
||||
public void startCallableProcessingCallableException() throws Exception {
|
||||
|
||||
Exception concurrentResult = new Exception();
|
||||
Callable<Object> task = new StubCallable(concurrentResult);
|
||||
|
||||
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
|
||||
interceptor.preProcess(this.asyncWebRequest, task);
|
||||
interceptor.postProcess(this.asyncWebRequest, task, concurrentResult);
|
||||
replay(interceptor);
|
||||
|
||||
setupDefaultAsyncScenario();
|
||||
|
||||
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startCallableProcessing(task);
|
||||
|
||||
assertTrue(this.asyncManager.hasConcurrentResult());
|
||||
assertEquals(concurrentResult, this.asyncManager.getConcurrentResult());
|
||||
|
||||
verify(interceptor, this.asyncWebRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableProcessingPreProcessException() throws Exception {
|
||||
|
||||
Callable<Object> task = new StubCallable(21);
|
||||
Exception exception = new Exception();
|
||||
|
||||
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
|
||||
interceptor.preProcess(this.asyncWebRequest, task);
|
||||
expectLastCall().andThrow(exception);
|
||||
replay(interceptor);
|
||||
|
||||
setupDefaultAsyncScenario();
|
||||
|
||||
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startCallableProcessing(task);
|
||||
|
||||
assertTrue(this.asyncManager.hasConcurrentResult());
|
||||
assertEquals(exception, this.asyncManager.getConcurrentResult());
|
||||
|
||||
verify(interceptor, this.asyncWebRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableProcessingPostProcessException() throws Exception {
|
||||
|
||||
Callable<Object> task = new StubCallable(21);
|
||||
Exception exception = new Exception();
|
||||
|
||||
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
|
||||
interceptor.preProcess(this.asyncWebRequest, task);
|
||||
interceptor.postProcess(this.asyncWebRequest, task, 21);
|
||||
expectLastCall().andThrow(exception);
|
||||
replay(interceptor);
|
||||
|
||||
setupDefaultAsyncScenario();
|
||||
|
||||
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startCallableProcessing(task);
|
||||
|
||||
assertTrue(this.asyncManager.hasConcurrentResult());
|
||||
assertEquals(exception, this.asyncManager.getConcurrentResult());
|
||||
|
||||
verify(interceptor, this.asyncWebRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableProcessingWithAsyncTask() {
|
||||
|
||||
AsyncTaskExecutor executor = createMock(AsyncTaskExecutor.class);
|
||||
expect(executor.submit((Runnable) notNull())).andReturn(null);
|
||||
replay(executor);
|
||||
|
||||
this.asyncWebRequest.setTimeout(1000L);
|
||||
this.asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject());
|
||||
this.asyncWebRequest.addCompletionHandler(EasyMock.<Runnable>anyObject());
|
||||
this.asyncWebRequest.startAsync();
|
||||
replay(this.asyncWebRequest);
|
||||
|
||||
|
@ -128,7 +221,7 @@ public class WebAsyncManagerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void startCallableProcessingNullCallable() {
|
||||
public void startCallableProcessingNullInput() {
|
||||
try {
|
||||
this.asyncManager.startCallableProcessing((Callable<?>) null);
|
||||
fail("Expected exception");
|
||||
|
@ -138,72 +231,108 @@ public class WebAsyncManagerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableProcessingNullRequest() {
|
||||
WebAsyncManager manager = WebAsyncUtils.getAsyncManager(new MockHttpServletRequest());
|
||||
try {
|
||||
manager.startCallableProcessing(new StubCallable());
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
assertEquals(ex.getMessage(), "AsyncWebRequest must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startDeferredResultProcessing() throws Exception {
|
||||
|
||||
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(1000L, 10);
|
||||
|
||||
this.asyncWebRequest.setTimeout(1000L);
|
||||
this.asyncWebRequest.setTimeoutHandler((Runnable) notNull());
|
||||
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
|
||||
this.asyncWebRequest.startAsync();
|
||||
replay(this.asyncWebRequest);
|
||||
DeferredResult<String> deferredResult = new DeferredResult<String>(1000L);
|
||||
String concurrentResult = "abc";
|
||||
|
||||
DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class);
|
||||
interceptor.preProcess(this.asyncWebRequest, deferredResult);
|
||||
interceptor.postProcess(asyncWebRequest, deferredResult, concurrentResult);
|
||||
replay(interceptor);
|
||||
|
||||
this.asyncWebRequest.setTimeout(1000L);
|
||||
setupDefaultAsyncScenario();
|
||||
|
||||
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
||||
|
||||
verify(this.asyncWebRequest, interceptor);
|
||||
reset(this.asyncWebRequest, interceptor);
|
||||
deferredResult.setResult(concurrentResult);
|
||||
|
||||
this.asyncWebRequest.dispatch();
|
||||
replay(this.asyncWebRequest);
|
||||
|
||||
interceptor.postProcess(asyncWebRequest, deferredResult, 25);
|
||||
replay(interceptor);
|
||||
|
||||
deferredResult.setResult(25);
|
||||
|
||||
assertEquals(25, this.asyncManager.getConcurrentResult());
|
||||
assertEquals(concurrentResult, this.asyncManager.getConcurrentResult());
|
||||
verify(this.asyncWebRequest, interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTimeoutHandler() throws Exception {
|
||||
public void startDeferredResultProcessingPreProcessException() throws Exception {
|
||||
|
||||
Runnable timeoutHandler = new Runnable() { public void run() {} };
|
||||
this.asyncManager.setTimeoutHandler(timeoutHandler);
|
||||
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
|
||||
Exception exception = new Exception();
|
||||
|
||||
DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class);
|
||||
interceptor.preProcess(this.asyncWebRequest, deferredResult);
|
||||
expectLastCall().andThrow(exception);
|
||||
replay(interceptor);
|
||||
|
||||
setupDefaultAsyncScenario();
|
||||
|
||||
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
||||
|
||||
deferredResult.setResult(25);
|
||||
|
||||
assertEquals(exception, this.asyncManager.getConcurrentResult());
|
||||
verify(this.asyncWebRequest, interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startDeferredResultProcessingPostProcessException() throws Exception {
|
||||
|
||||
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
|
||||
Exception exception = new Exception();
|
||||
|
||||
DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class);
|
||||
interceptor.preProcess(this.asyncWebRequest, deferredResult);
|
||||
interceptor.postProcess(this.asyncWebRequest, deferredResult, 25);
|
||||
expectLastCall().andThrow(exception);
|
||||
replay(interceptor);
|
||||
|
||||
setupDefaultAsyncScenario();
|
||||
|
||||
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
||||
|
||||
deferredResult.setResult(25);
|
||||
|
||||
assertEquals(exception, this.asyncManager.getConcurrentResult());
|
||||
verify(this.asyncWebRequest, interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startDeferredResultProcessingNullInput() {
|
||||
try {
|
||||
this.asyncManager.startDeferredResultProcessing((DeferredResult<?>) null);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
assertEquals(ex.getMessage(), "DeferredResult must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDefaultAsyncScenario() {
|
||||
this.asyncWebRequest.setTimeoutHandler((Runnable) notNull());
|
||||
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
|
||||
this.asyncWebRequest.startAsync();
|
||||
this.asyncWebRequest.setTimeoutHandler(timeoutHandler);
|
||||
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
|
||||
this.asyncWebRequest.dispatch();
|
||||
replay(this.asyncWebRequest);
|
||||
|
||||
this.asyncManager.startCallableProcessing(new StubCallable());
|
||||
|
||||
verify(this.asyncWebRequest);
|
||||
}
|
||||
|
||||
|
||||
private final class StubCallable implements Callable<Object> {
|
||||
|
||||
private Object value;
|
||||
|
||||
public StubCallable(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Object call() throws Exception {
|
||||
return 1;
|
||||
if (this.value instanceof Exception) {
|
||||
throw ((Exception) this.value);
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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;
|
||||
import static org.easymock.EasyMock.notNull;
|
||||
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 java.util.concurrent.Callable;
|
||||
|
||||
import javax.servlet.AsyncEvent;
|
||||
import javax.servlet.DispatcherType;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* {@link WebAsyncManager} tests where container-triggered timeout/completion
|
||||
* events are simulated.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class WebAsyncManagerTimeoutTests {
|
||||
|
||||
private static final AsyncEvent ASYNC_EVENT = null;
|
||||
|
||||
private WebAsyncManager asyncManager;
|
||||
|
||||
private StandardServletAsyncWebRequest asyncWebRequest;
|
||||
|
||||
private MockHttpServletRequest servletRequest;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.servletRequest = new MockHttpServletRequest();
|
||||
this.servletRequest.setAsyncSupported(true);
|
||||
this.asyncWebRequest = new StandardServletAsyncWebRequest(servletRequest, new MockHttpServletResponse());
|
||||
|
||||
AsyncTaskExecutor executor = createMock(AsyncTaskExecutor.class);
|
||||
expect(executor.submit((Runnable) notNull())).andReturn(null);
|
||||
replay(executor);
|
||||
|
||||
this.asyncManager = WebAsyncUtils.getAsyncManager(servletRequest);
|
||||
this.asyncManager.setTaskExecutor(executor);
|
||||
this.asyncManager.setAsyncWebRequest(this.asyncWebRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableProcessingTimeoutAndComplete() throws Exception {
|
||||
|
||||
StubCallable callable = new StubCallable();
|
||||
|
||||
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
|
||||
expect(interceptor.afterTimeout(this.asyncWebRequest, callable)).andReturn(RESULT_NONE);
|
||||
interceptor.afterCompletion(this.asyncWebRequest, callable);
|
||||
replay(interceptor);
|
||||
|
||||
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startCallableProcessing(callable);
|
||||
|
||||
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
|
||||
this.asyncWebRequest.onComplete(ASYNC_EVENT);
|
||||
|
||||
assertFalse(this.asyncManager.hasConcurrentResult());
|
||||
assertEquals(DispatcherType.REQUEST, this.servletRequest.getDispatcherType());
|
||||
|
||||
verify(interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableProcessingTimeoutAndResume() throws Exception {
|
||||
|
||||
StubCallable callable = new StubCallable();
|
||||
|
||||
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
|
||||
expect(interceptor.afterTimeout(this.asyncWebRequest, callable)).andReturn(22);
|
||||
replay(interceptor);
|
||||
|
||||
this.asyncManager.registerCallableInterceptor("timeoutInterceptor", interceptor);
|
||||
this.asyncManager.startCallableProcessing(callable);
|
||||
|
||||
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
|
||||
|
||||
assertEquals(22, this.asyncManager.getConcurrentResult());
|
||||
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
|
||||
|
||||
verify(interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableProcessingAfterTimeoutException() throws Exception {
|
||||
|
||||
StubCallable callable = new StubCallable();
|
||||
Exception exception = new Exception();
|
||||
|
||||
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
|
||||
expect(interceptor.afterTimeout(this.asyncWebRequest, callable)).andThrow(exception);
|
||||
replay(interceptor);
|
||||
|
||||
this.asyncManager.registerCallableInterceptor("timeoutInterceptor", interceptor);
|
||||
this.asyncManager.startCallableProcessing(callable);
|
||||
|
||||
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
|
||||
|
||||
assertEquals(exception, this.asyncManager.getConcurrentResult());
|
||||
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
|
||||
|
||||
verify(interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startDeferredResultProcessingTimeoutAndComplete() throws Exception {
|
||||
|
||||
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
|
||||
|
||||
DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class);
|
||||
interceptor.preProcess(this.asyncWebRequest, deferredResult);
|
||||
interceptor.afterTimeout(this.asyncWebRequest, deferredResult);
|
||||
interceptor.afterCompletion(this.asyncWebRequest, deferredResult);
|
||||
replay(interceptor);
|
||||
|
||||
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
||||
|
||||
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
|
||||
this.asyncWebRequest.onComplete(ASYNC_EVENT);
|
||||
|
||||
assertFalse(this.asyncManager.hasConcurrentResult());
|
||||
assertEquals(DispatcherType.REQUEST, this.servletRequest.getDispatcherType());
|
||||
|
||||
verify(interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
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);
|
||||
|
||||
assertEquals(23, this.asyncManager.getConcurrentResult());
|
||||
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startDeferredResultProcessingTimeoutAndResumeWithInterceptor() throws Exception {
|
||||
|
||||
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
|
||||
|
||||
DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() {
|
||||
public <T> void afterTimeout(NativeWebRequest request, DeferredResult<T> result) throws Exception {
|
||||
result.setErrorResult(23);
|
||||
}
|
||||
};
|
||||
|
||||
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
||||
|
||||
AsyncEvent event = null;
|
||||
this.asyncWebRequest.onTimeout(event);
|
||||
|
||||
assertEquals(23, this.asyncManager.getConcurrentResult());
|
||||
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startDeferredResultProcessingAfterTimeoutException() throws Exception {
|
||||
|
||||
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
|
||||
final Exception exception = new Exception();
|
||||
|
||||
DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() {
|
||||
public <T> void afterTimeout(NativeWebRequest request, DeferredResult<T> result) throws Exception {
|
||||
throw exception;
|
||||
}
|
||||
};
|
||||
|
||||
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
|
||||
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
||||
|
||||
AsyncEvent event = null;
|
||||
this.asyncWebRequest.onTimeout(event);
|
||||
|
||||
assertEquals(exception, this.asyncManager.getConcurrentResult());
|
||||
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
|
||||
}
|
||||
|
||||
|
||||
private final class StubCallable implements Callable<Object> {
|
||||
public Object call() throws Exception {
|
||||
return 21;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -50,6 +50,7 @@ import org.springframework.web.context.request.RequestAttributes;
|
|||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
import org.springframework.web.context.support.ServletRequestHandledEvent;
|
||||
|
@ -988,14 +989,13 @@ public abstract class FrameworkServlet extends HttpServletBean {
|
|||
}
|
||||
|
||||
private CallableProcessingInterceptor createRequestBindingInterceptor(final HttpServletRequest request) {
|
||||
|
||||
return new CallableProcessingInterceptor() {
|
||||
|
||||
public void preProcess(NativeWebRequest webRequest, Callable<?> task) {
|
||||
return new CallableProcessingInterceptorAdapter() {
|
||||
@Override
|
||||
public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) {
|
||||
initContextHolders(request, buildLocaleContext(request), new ServletRequestAttributes(request));
|
||||
}
|
||||
|
||||
public void postProcess(NativeWebRequest webRequest, Callable<?> task, Object concurrentResult) {
|
||||
@Override
|
||||
public <T> void postProcess(NativeWebRequest webRequest, Callable<T> task, Object concurrentResult) {
|
||||
resetContextHolders(request, null, null);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue