diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java index 7cc99cf9d2..ddc59cf61a 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java @@ -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 void preProcess(NativeWebRequest request, Callable task) { initializeThread(); } + @Override + public void postProcess(NativeWebRequest request, Callable 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); - } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java index d259a55bea..068424acd7 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java @@ -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 void preProcess(NativeWebRequest request, Callable task) { initializeThread(); } + @Override + public void postProcess(NativeWebRequest request, Callable 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()); - } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java index 4393d31f67..cbc5cd8e46 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java @@ -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 void preProcess(NativeWebRequest request, Callable task) { initializeThread(); } + @Override + public void postProcess(NativeWebRequest request, Callable 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); - } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java index 58faf09215..3b5677f83d 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java @@ -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 void preProcess(NativeWebRequest request, Callable task) { initializeThread(); } + @Override + public void postProcess(NativeWebRequest request, Callable 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()); - } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java index e4fe37f8b3..694b0a42fa 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java @@ -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 void preProcess(NativeWebRequest request, Callable task) { initializeThread(); } + @Override + public void postProcess(NativeWebRequest request, Callable 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); - } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java index 3930a1ae8c..8c9cf595ca 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java @@ -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 void preProcess(NativeWebRequest request, Callable task) { initializeThread(); } + @Override + public void postProcess(NativeWebRequest request, Callable 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()); - } } } diff --git a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java index 6eb9b8ae34..5cc6d78bb4 100644 --- a/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java @@ -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.anyObject()); + asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.startAsync(); expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes(); replay(asyncWebRequest); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java index 7e3d1a9f9b..28c56ea2b7 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java @@ -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); diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/TestDispatcherServlet.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/TestDispatcherServlet.java index eccdead211..795f32b35b 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/TestDispatcherServlet.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/TestDispatcherServlet.java @@ -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 void postProcess(NativeWebRequest request, Callable 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 void postProcess(NativeWebRequest request, DeferredResult result, Object value) throws Exception { asyncResultLatch.countDown(); } - public void afterExpiration(NativeWebRequest request, DeferredResult result) throws Exception { } }); return asyncResultLatch; diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/CallableInterceptorChain.java b/spring-web/src/main/java/org/springframework/web/context/request/async/CallableInterceptorChain.java index 120d224040..72ac05a0ed 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/CallableInterceptorChain.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/CallableInterceptorChain.java @@ -36,7 +36,7 @@ class CallableInterceptorChain { private final List interceptors; - private int interceptorIndex = -1; + private int preProcessIndex = -1; public CallableInterceptorChain(Collection 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); } } } - } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/CallableProcessingInterceptor.java b/spring-web/src/main/java/org/springframework/web/context/request/async/CallableProcessingInterceptor.java index 0b0c9accd2..10a3099cd4 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/CallableProcessingInterceptor.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/CallableProcessingInterceptor.java @@ -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}. - *

- * 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}. * - *

A {@code CallableProcessingInterceptor} may be registered as follows: - *

- * CallableProcessingInterceptor interceptor = ... ;
- * WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
- * asyncManager.registerCallableInterceptor("key", interceptor);
- * 
+ *

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. * - *

To register an interceptor for every request, the above can be done through - * a {@link WebRequestInterceptor} during pre-handling. + *

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. + * + *

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 after the start of concurrent handling in the async + * thread in which the {@code Callable} is executed and before 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 + */ + void preProcess(NativeWebRequest request, Callable task) throws Exception; + + /** + * Invoked after 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; + void postProcess(NativeWebRequest request, Callable 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 + */ + Object afterTimeout(NativeWebRequest request, Callable 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 + */ + void afterCompletion(NativeWebRequest request, Callable task) throws Exception; } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/CallableProcessingInterceptorAdapter.java b/spring-web/src/main/java/org/springframework/web/context/request/async/CallableProcessingInterceptorAdapter.java new file mode 100644 index 0000000000..6f305edd73 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/CallableProcessingInterceptorAdapter.java @@ -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 void preProcess(NativeWebRequest request, Callable task) throws Exception { + } + + /** + * This implementation is empty. + */ + public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) throws Exception { + } + + /** + * This implementation always returns + * {@link CallableProcessingInterceptor#RESULT_NONE RESULT_NONE}. + */ + public Object afterTimeout(NativeWebRequest request, Callable task) throws Exception { + return RESULT_NONE; + } + + /** + * This implementation is empty. + */ + public void afterCompletion(NativeWebRequest request, Callable task) throws Exception { + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java index 365a753167..9b4cc499bd 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java @@ -66,7 +66,7 @@ public final class DeferredResult { /** * 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 { } 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 { } /** - * 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; } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultInterceptorChain.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultInterceptorChain.java index 600fe0bbde..f36659655e 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultInterceptorChain.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultInterceptorChain.java @@ -35,35 +35,48 @@ class DeferredResultInterceptorChain { private final List interceptors; + private int preProcessingIndex = -1; + public DeferredResultInterceptorChain(Collection interceptors) { this.interceptors = new ArrayList(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); } } } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java index 32750e2b8f..c50f2e2bfa 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java @@ -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). * - *

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. + *

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. * - *

A {@code DeferredResultProcessingInterceptor} may be registered as follows: - *

- * DeferredResultProcessingInterceptor interceptor = ... ;
- * WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
- * asyncManager.registerDeferredResultInterceptor("key", interceptor);
- * 
+ *

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. * - *

To register an interceptor for every request, the above can be done through - * a {@link WebRequestInterceptor} during pre-handling. + *

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}. + * + *

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; + void preProcess(NativeWebRequest request, DeferredResult 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. - *

- * 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. + * + *

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; + void postProcess(NativeWebRequest request, DeferredResult 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; + void afterTimeout(NativeWebRequest request, DeferredResult 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 + */ + void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) throws Exception; } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java new file mode 100644 index 0000000000..6120cc67af --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptorAdapter.java @@ -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 void preProcess(NativeWebRequest request, DeferredResult deferredResult) throws Exception { + } + + /** + * This implementation is empty. + */ + public void postProcess(NativeWebRequest request, DeferredResult deferredResult, + Object concurrentResult) throws Exception { + } + + /** + * This implementation is empty. + */ + public void afterTimeout(NativeWebRequest request, DeferredResult deferredResult) throws Exception { + } + + /** + * This implementation is empty. + */ + public void afterCompletion(NativeWebRequest request, DeferredResult deferredResult) throws Exception { + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java index 4049c11a66..7b90779314 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java @@ -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 completionHandlers = new ArrayList(); @@ -74,15 +73,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements this.timeout = timeout; } - /** - * {@inheritDoc} - *

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 - } - } - } - } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java index efa6e96041..53f269171a 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java @@ -44,8 +44,6 @@ import org.springframework.web.util.UrlPathHelper; * result can be accessed via {@link #getConcurrentResult()} or its presence * detected via {@link #hasConcurrentResult()}. * - *

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 deferredResultInterceptors = new LinkedHashMap(); - 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 + "]"); } diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java index 2ad5aba381..c4e70096df 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java @@ -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 result = new DeferredResult(); - 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().hasTimeoutResult()); - assertTrue(new DeferredResult(null, "timed out").hasTimeoutResult()); - } - @Test public void applyTimeoutResult() { DeferredResultHandler handler = createMock(DeferredResultHandler.class); diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java index e8ce85bae3..2e91abf993 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java @@ -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 diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java index fde9f63425..f433576646 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTests.java @@ -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()); + 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 task = new StubCallable(); + int concurrentResult = 21; + Callable 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 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 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 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.anyObject()); + this.asyncWebRequest.addCompletionHandler(EasyMock.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 deferredResult = new DeferredResult(1000L, 10); - - this.asyncWebRequest.setTimeout(1000L); - this.asyncWebRequest.setTimeoutHandler((Runnable) notNull()); - this.asyncWebRequest.addCompletionHandler((Runnable) notNull()); - this.asyncWebRequest.startAsync(); - replay(this.asyncWebRequest); + DeferredResult deferredResult = new DeferredResult(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 deferredResult = new DeferredResult(); + 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 deferredResult = new DeferredResult(); + 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 { + + 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; } } diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTimeoutTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTimeoutTests.java new file mode 100644 index 0000000000..cae90eac72 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTimeoutTests.java @@ -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 deferredResult = new DeferredResult(); + + 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 deferredResult = new DeferredResult(null, 23); + + DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() { + public void afterTimeout(NativeWebRequest request, DeferredResult 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 deferredResult = new DeferredResult(); + + DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() { + public void afterTimeout(NativeWebRequest request, DeferredResult 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 deferredResult = new DeferredResult(); + final Exception exception = new Exception(); + + DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() { + public void afterTimeout(NativeWebRequest request, DeferredResult 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 { + public Object call() throws Exception { + return 21; + } + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java index f3cd6758a4..dc3652b6f6 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java @@ -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 void preProcess(NativeWebRequest webRequest, Callable task) { initContextHolders(request, buildLocaleContext(request), new ServletRequestAttributes(request)); } - - public void postProcess(NativeWebRequest webRequest, Callable task, Object concurrentResult) { + @Override + public void postProcess(NativeWebRequest webRequest, Callable task, Object concurrentResult) { resetContextHolders(request, null, null); } };