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:
Rossen Stoyanchev 2012-10-26 17:55:28 -04:00
parent 06e34f05a6
commit f036ed639f
23 changed files with 860 additions and 333 deletions

View File

@ -34,7 +34,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.NativeWebRequest; 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.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.WebApplicationContextUtils; 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. * 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; private final SessionFactory sessionFactory;
@ -329,17 +329,19 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
this.sessionHolder = sessionHolder; this.sessionHolder = sessionHolder;
} }
public void preProcess(NativeWebRequest request, Callable<?> task) { @Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
initializeThread(); initializeThread();
} }
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
}
private void initializeThread() { private void initializeThread() {
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
} }
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
}
} }
} }

View File

@ -29,7 +29,7 @@ import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.AsyncWebRequestInterceptor; import org.springframework.web.context.request.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest; 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.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils; 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. * 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; private final SessionHolder sessionHolder;
@ -284,16 +284,18 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
this.sessionHolder = sessionHolder; this.sessionHolder = sessionHolder;
} }
public void preProcess(NativeWebRequest request, Callable<?> task) { @Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
initializeThread(); initializeThread();
} }
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
private void initializeThread() { private void initializeThread() {
TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder); TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder);
} }
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
} }
} }

View File

@ -34,7 +34,7 @@ import org.springframework.orm.hibernate4.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.NativeWebRequest; 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.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.WebApplicationContextUtils; 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. * 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; private final SessionFactory sessionFactory;
@ -226,16 +226,18 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
this.sessionHolder = sessionHolder; this.sessionHolder = sessionHolder;
} }
public void preProcess(NativeWebRequest request, Callable<?> task) { @Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
initializeThread(); initializeThread();
} }
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
}
private void initializeThread() { private void initializeThread() {
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
} }
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
}
} }
} }

View File

@ -33,7 +33,7 @@ import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.AsyncWebRequestInterceptor; import org.springframework.web.context.request.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest; 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.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils; 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. * 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; private final SessionHolder sessionHolder;
@ -223,17 +223,19 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
this.sessionHolder = sessionHolder; this.sessionHolder = sessionHolder;
} }
public void preProcess(NativeWebRequest request, Callable<?> task) { @Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
initializeThread(); initializeThread();
} }
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
private void initializeThread() { private void initializeThread() {
TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder); TransactionSynchronizationManager.bindResource(getSessionFactory(), this.sessionHolder);
} }
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.util.StringUtils;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.NativeWebRequest; 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.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.WebApplicationContextUtils; 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. * 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; private final EntityManagerFactory emFactory;
@ -254,17 +254,19 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter {
this.emHolder = emHolder; this.emHolder = emHolder;
} }
public void preProcess(NativeWebRequest request, Callable<?> task) { @Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
initializeThread(); initializeThread();
} }
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(this.emFactory);
}
private void initializeThread() { private void initializeThread() {
TransactionSynchronizationManager.bindResource(this.emFactory, this.emHolder); TransactionSynchronizationManager.bindResource(this.emFactory, this.emHolder);
} }
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(this.emFactory);
}
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest; 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.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils; 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. * 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; private final EntityManagerHolder emHolder;
@ -172,17 +172,19 @@ public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAcce
this.emHolder = emHolder; this.emHolder = emHolder;
} }
public void preProcess(NativeWebRequest request, Callable<?> task) { @Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
initializeThread(); initializeThread();
} }
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
}
private void initializeThread() { private void initializeThread() {
TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), this.emHolder); TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), this.emHolder);
} }
public void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
}
} }
} }

View File

@ -16,7 +16,7 @@
package org.springframework.orm.hibernate3.support; 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.createMock;
import static org.easymock.EasyMock.createStrictMock; import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expect;
@ -39,6 +39,7 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
import javax.transaction.TransactionManager; import javax.transaction.TransactionManager;
import org.easymock.EasyMock;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.SessionFactory; 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.WebApplicationContext;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.AsyncWebRequest; 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.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.context.support.StaticWebApplicationContext;
@ -177,6 +178,8 @@ public class OpenSessionInViewTests {
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class); AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync(); asyncWebRequest.startAsync();
replay(asyncWebRequest); replay(asyncWebRequest);
@ -491,6 +494,8 @@ public class OpenSessionInViewTests {
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class); AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync(); asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes(); expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();
replay(asyncWebRequest); replay(asyncWebRequest);

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 asyncWebRequest = createStrictMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync(); asyncWebRequest.startAsync();
replay(asyncWebRequest); replay(asyncWebRequest);
@ -344,6 +346,8 @@ public class OpenEntityManagerInViewTests extends TestCase {
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class); AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync(); asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes(); expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();
replay(asyncWebRequest); replay(asyncWebRequest);

View File

@ -27,9 +27,9 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.NativeWebRequest; 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.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.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
@ -48,6 +48,8 @@ import org.springframework.web.servlet.ModelAndView;
@SuppressWarnings("serial") @SuppressWarnings("serial")
final class TestDispatcherServlet extends DispatcherServlet { final class TestDispatcherServlet extends DispatcherServlet {
private static final String KEY = TestDispatcherServlet.class.getName() + "-interceptor";
/** /**
* Create a new instance with the given web application context. * Create a new instance with the given web application context.
*/ */
@ -70,19 +72,15 @@ final class TestDispatcherServlet extends DispatcherServlet {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor("mockmvc", new CallableProcessingInterceptor() { asyncManager.registerCallableInterceptor(KEY, new CallableProcessingInterceptorAdapter() {
public void preProcess(NativeWebRequest request, Callable<?> task) throws Exception { } public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object value) throws Exception {
public void postProcess(NativeWebRequest request, Callable<?> task, Object value) throws Exception {
asyncResultLatch.countDown(); asyncResultLatch.countDown();
} }
}); });
asyncManager.registerDeferredResultInterceptor(KEY, new DeferredResultProcessingInterceptorAdapter() {
asyncManager.registerDeferredResultInterceptor("mockmvc", new DeferredResultProcessingInterceptor() { public <T> void postProcess(NativeWebRequest request, DeferredResult<T> result, Object value) throws Exception {
public void preProcess(NativeWebRequest request, DeferredResult<?> result) throws Exception { }
public void postProcess(NativeWebRequest request, DeferredResult<?> result, Object value) throws Exception {
asyncResultLatch.countDown(); asyncResultLatch.countDown();
} }
public void afterExpiration(NativeWebRequest request, DeferredResult<?> result) throws Exception { }
}); });
return asyncResultLatch; return asyncResultLatch;

View File

@ -36,7 +36,7 @@ class CallableInterceptorChain {
private final List<CallableProcessingInterceptor> interceptors; private final List<CallableProcessingInterceptor> interceptors;
private int interceptorIndex = -1; private int preProcessIndex = -1;
public CallableInterceptorChain(Collection<CallableProcessingInterceptor> interceptors) { public CallableInterceptorChain(Collection<CallableProcessingInterceptor> interceptors) {
@ -44,21 +44,47 @@ class CallableInterceptorChain {
} }
public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception { public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception {
for (int i = 0; i < this.interceptors.size(); i++) { for (CallableProcessingInterceptor interceptor : this.interceptors) {
this.interceptors.get(i).preProcess(request, task); interceptor.preProcess(request, task);
this.interceptorIndex = i; this.preProcessIndex++;
} }
} }
public void applyPostProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) { public Object applyPostProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
for (int i = this.interceptorIndex; i >= 0; i--) { for (int i = this.preProcessIndex; i >= 0; i--) {
try { try {
this.interceptors.get(i).postProcess(request, task, concurrentResult); this.interceptors.get(i).postProcess(request, task, concurrentResult);
} }
catch (Exception ex) { catch (Throwable t) {
logger.error("CallableProcessingInterceptor.postProcess threw exception", ex); 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);
} }
} }
} }
} }

View File

@ -19,49 +19,80 @@ import java.util.concurrent.Callable;
import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
/** /**
* Intercepts concurrent request handling, where the concurrent result is * Intercepts concurrent request handling, where the concurrent result is
* obtained by executing a {@link Callable} on behalf of the application with an * obtained by executing a {@link Callable} on behalf of the application with
* {@link AsyncTaskExecutor}. * an {@link AsyncTaskExecutor}.
* <p>
* A {@code CallableProcessingInterceptor} is invoked before and after the
* invocation of the {@code Callable} task in the asynchronous thread.
* *
* <p>A {@code CallableProcessingInterceptor} may be registered as follows: * <p>A {@code CallableProcessingInterceptor} is invoked before and after the
* <pre> * invocation of the {@code Callable} task in the asynchronous thread, as well
* CallableProcessingInterceptor interceptor = ... ; * as on timeout from a container thread, or after completing for any reason
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); * including a timeout or network error.
* asyncManager.registerCallableInterceptor("key", interceptor);
* </pre>
* *
* <p>To register an interceptor for every request, the above can be done through * <p>As a general rule exceptions raised by interceptor methods will cause
* a {@link WebRequestInterceptor} during pre-handling. * 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 * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
*/ */
public interface CallableProcessingInterceptor { public interface CallableProcessingInterceptor {
/** public static final Object RESULT_NONE = new Object();
* 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;
/** /**
* Invoked from the asynchronous thread in which the {@code Callable} is * Invoked <em>after</em> the start of concurrent handling in the async
* executed, after the {@code Callable} returned a result. * 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 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 * @param concurrentResult the result of concurrent processing, which could
* be a {@link Throwable} if the {@code Callable} raised an exception * 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;
} }

View File

@ -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 {
}
}

View File

@ -66,7 +66,7 @@ public final class DeferredResult<T> {
/** /**
* Create a DeferredResult with a timeout and a default result to use on timeout. * Create a DeferredResult with a timeout and a default result to use on timeout.
* @param timeout timeout value in milliseconds; ignored if {@code null} * @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) { public DeferredResult(Long timeout, Object timeoutResult) {
this.timeoutResult = timeoutResult; this.timeoutResult = timeoutResult;
@ -118,13 +118,7 @@ public final class DeferredResult<T> {
} }
this.result = result; this.result = result;
if (this.resultHandler != null) { if (this.resultHandler != null) {
try { this.resultHandler.handleResult(this.result);
this.resultHandler.handleResult(this.result);
}
catch (Throwable t) {
logger.trace("DeferredResult not handled", t);
return false;
}
} }
} }
return true; 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. * Mark this instance expired so it may no longer be used.
* @return {@code true} if expiration succeeded, {@code false} otherwise * @return the previous value of the expiration flag
*/ */
boolean expire() { boolean expire() {
synchronized (this) { synchronized (this) {
if (!isSetOrExpired()) { boolean previous = this.expired;
this.expired = true; this.expired = true;
} return previous;
} }
return this.expired;
}
boolean hasTimeoutResult() {
return this.timeoutResult != RESULT_NONE;
} }
boolean applyTimeoutResult() { boolean applyTimeoutResult() {
return hasTimeoutResult() ? setResultInternal(this.timeoutResult) : false; return (this.timeoutResult != RESULT_NONE) ? setResultInternal(this.timeoutResult) : false;
} }

View File

@ -35,35 +35,48 @@ class DeferredResultInterceptorChain {
private final List<DeferredResultProcessingInterceptor> interceptors; private final List<DeferredResultProcessingInterceptor> interceptors;
private int preProcessingIndex = -1;
public DeferredResultInterceptorChain(Collection<DeferredResultProcessingInterceptor> interceptors) { public DeferredResultInterceptorChain(Collection<DeferredResultProcessingInterceptor> interceptors) {
this.interceptors = new ArrayList<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) { for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
interceptor.preProcess(request, task); interceptor.preProcess(request, deferredResult);
this.preProcessingIndex++;
} }
} }
public void applyPostProcess(NativeWebRequest request, DeferredResult<?> task, Object concurrentResult) { public Object applyPostProcess(NativeWebRequest request, DeferredResult<?> deferredResult, Object concurrentResult) {
for (int i = this.interceptors.size()-1; i >= 0; i--) { try {
try { for (int i = this.preProcessingIndex; i >= 0; i--) {
this.interceptors.get(i).postProcess(request, task, concurrentResult); 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) { public void triggerAfterCompletion(NativeWebRequest request, DeferredResult<?> deferredResult) {
for (int i = this.interceptors.size()-1; i >= 0; i--) { for (int i = this.preProcessingIndex; i >= 0; i--) {
try { try {
this.interceptors.get(i).afterExpiration(request, task); this.interceptors.get(i).afterCompletion(request, deferredResult);
} }
catch (Exception ex) { catch (Throwable t) {
logger.error("DeferredResultProcessingInterceptor.afterExpiration threw exception", ex); logger.error("afterCompletion error", t);
} }
} }
} }

View File

@ -16,26 +16,24 @@
package org.springframework.web.context.request.async; package org.springframework.web.context.request.async;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
/** /**
* Intercepts concurrent request handling, where the concurrent result is * Intercepts concurrent request handling, where the concurrent result is
* obtained by waiting for a {@link DeferredResult} to be set from a thread * 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). * chosen by the application (e.g. in response to some external event).
* *
* <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start of * <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start
* asynchronous processing and either when the {@code DeferredResult} is set or * of async processing, after the {@code DeferredResult} is set as well as on
* when when the underlying request ends, whichever comes fist. * timeout, or or after completing for any reason including a timeout or network
* error.
* *
* <p>A {@code DeferredResultProcessingInterceptor} may be registered as follows: * <p>As a general rule exceptions raised by interceptor methods will cause
* <pre> * async processing to resume by dispatching back to the container and using
* DeferredResultProcessingInterceptor interceptor = ... ; * the Exception instance as the concurrent result. Such exceptions will then
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); * be processed through the {@code HandlerExceptionResolver} mechanism.
* asyncManager.registerDeferredResultInterceptor("key", interceptor);
* </pre>
* *
* <p>To register an interceptor for every request, the above can be done through * <p>The {@link #afterTimeout(NativeWebRequest, DeferredResult) afterTimeout}
* a {@link WebRequestInterceptor} during pre-handling. * method can set the {@code DeferredResult} in order to resume processing.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
@ -43,40 +41,59 @@ import org.springframework.web.context.request.WebRequestInterceptor;
public interface DeferredResultProcessingInterceptor { public interface DeferredResultProcessingInterceptor {
/** /**
* Invoked before the start of concurrent handling using a * Invoked immediately after the start of concurrent handling, in the same
* {@link DeferredResult}. The invocation occurs in the thread that * thread that started it. This method may be used to detect the start of
* initiated concurrent handling. * 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 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 * Invoked after a {@code DeferredResult} has been set, via
* {@link DeferredResult#setResult(Object) setResult}, or * {@link DeferredResult#setResult(Object)} or
* {@link DeferredResult#setErrorResult(Object) setErrorResult}, or after * {@link DeferredResult#setErrorResult(Object)}, and is also ready to
* a timeout if a {@code DeferredResult} was created with a constructor * handle the concurrent result.
* accepting a default timeout result. *
* <p> * <p>This method may also be invoked after a timeout when the
* If the request ends before the {@code DeferredResult} is set, then * {@code DeferredResult} was created with a constructor accepting a default
* {@link #afterExpiration(NativeWebRequest, DeferredResult)} is called. * timeout result.
* *
* @param request the current request * @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} * @param concurrentResult the result to which the {@code DeferredResult}
* was set * @throws Exception in case of errors
*/ */
void postProcess(NativeWebRequest request, DeferredResult<?> deferredResult, <T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult, Object concurrentResult) throws Exception;
Object concurrentResult) throws Exception;
/** /**
* Invoked when a {@link DeferredResult} was never set before the request * Invoked from a container thread when an async request times out before
* completed due to a timeout or network error. * 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 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;
} }

View File

@ -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 {
}
}

View File

@ -27,7 +27,6 @@ import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
@ -50,7 +49,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
private AtomicBoolean asyncCompleted = new AtomicBoolean(false); private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
private Runnable timeoutHandler = new DefaultTimeoutHandler(); private Runnable timeoutHandler;
private final List<Runnable> completionHandlers = new ArrayList<Runnable>(); private final List<Runnable> completionHandlers = new ArrayList<Runnable>();
@ -74,15 +73,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
this.timeout = timeout; this.timeout = timeout;
} }
/**
* {@inheritDoc}
* <p>If not set, by default a timeout is handled by returning
* SERVICE_UNAVAILABLE (503).
*/
public void setTimeoutHandler(Runnable timeoutHandler) { public void setTimeoutHandler(Runnable timeoutHandler) {
if (timeoutHandler != null) { this.timeoutHandler = timeoutHandler;
this.timeoutHandler = timeoutHandler;
}
} }
public void addCompletionHandler(Runnable runnable) { public void addCompletionHandler(Runnable runnable) {
@ -135,31 +127,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
} }
public void onTimeout(AsyncEvent event) throws IOException { public void onTimeout(AsyncEvent event) throws IOException {
this.timeoutHandler.run(); if (this.timeoutHandler != null) {
this.timeoutHandler.run();
}
} }
public void onComplete(AsyncEvent event) throws IOException { public void onComplete(AsyncEvent event) throws IOException {
for (Runnable runnable : this.completionHandlers) { for (Runnable handler : this.completionHandlers) {
runnable.run(); handler.run();
} }
this.asyncContext = null; this.asyncContext = null;
this.asyncCompleted.set(true); 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
}
}
}
} }

View File

@ -44,8 +44,6 @@ import org.springframework.web.util.UrlPathHelper;
* result can be accessed via {@link #getConcurrentResult()} or its presence * result can be accessed via {@link #getConcurrentResult()} or its presence
* detected via {@link #hasConcurrentResult()}. * detected via {@link #hasConcurrentResult()}.
* *
* <p>TODO .. Servlet 3 config
*
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
* *
@ -61,13 +59,13 @@ public final class WebAsyncManager {
private static final Log logger = LogFactory.getLog(WebAsyncManager.class); private static final Log logger = LogFactory.getLog(WebAsyncManager.class);
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
private AsyncWebRequest asyncWebRequest; private AsyncWebRequest asyncWebRequest;
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName()); private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());
private Runnable timeoutHandler;
private Object concurrentResult = RESULT_NONE; private Object concurrentResult = RESULT_NONE;
private Object[] concurrentResultContext; private Object[] concurrentResultContext;
@ -78,8 +76,6 @@ public final class WebAsyncManager {
private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors = private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
new LinkedHashMap<Object, DeferredResultProcessingInterceptor>(); new LinkedHashMap<Object, DeferredResultProcessingInterceptor>();
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
/** /**
* Package private constructor. * Package private constructor.
@ -119,15 +115,6 @@ public final class WebAsyncManager {
this.taskExecutor = taskExecutor; 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 * Whether the selected handler for the current request chose to handle the
* request asynchronously. A return value of "true" indicates concurrent * 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) { public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
Assert.notNull(callable, "Callable must not be null"); 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); startAsyncProcessing(processingContext);
this.taskExecutor.submit(new Runnable() { this.taskExecutor.submit(new Runnable() {
public void run() { public void run() {
Object result = null;
CallableInterceptorChain chain =
new CallableInterceptorChain(callableInterceptors.values());
try { try {
chain.applyPreProcess(asyncWebRequest, callable); chain.applyPreProcess(asyncWebRequest, callable);
concurrentResult = callable.call(); result = callable.call();
} }
catch (Throwable t) { catch (Throwable t) {
concurrentResult = t; result = t;
} }
finally { finally {
chain.applyPostProcess(asyncWebRequest, callable, concurrentResult); result = chain.applyPostProcess(asyncWebRequest, callable, result);
} }
setConcurrentResultAndDispatch(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();
} }
}); });
} }
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 * Use the given {@link AsyncTask} to configure the task executor as well as
* the timeout value of the {@code AsyncWebRequest} before delegating to * 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) { public void startCallableProcessing(AsyncTask<?> asyncTask, Object... processingContext) {
Assert.notNull(asyncTask, "AsyncTask must not be null"); Assert.notNull(asyncTask, "AsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
Long timeout = asyncTask.getTimeout(); Long timeout = asyncTask.getTimeout();
if (timeout != null) { if (timeout != null) {
@ -302,53 +313,54 @@ public final class WebAsyncManager {
* @see #getConcurrentResult() * @see #getConcurrentResult()
* @see #getConcurrentResultContext() * @see #getConcurrentResultContext()
*/ */
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult, public void startDeferredResultProcessing(
Object... processingContext) throws Exception { final DeferredResult<?> deferredResult, Object... processingContext) {
Assert.notNull(deferredResult, "DeferredResult must not be null"); Assert.notNull(deferredResult, "DeferredResult must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
Long timeout = deferredResult.getTimeoutMilliseconds(); Long timeout = deferredResult.getTimeoutMilliseconds();
if (timeout != null) { if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout); this.asyncWebRequest.setTimeout(timeout);
} }
if (deferredResult.hasTimeoutResult()) {
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
public void run() {
deferredResult.applyTimeoutResult();
}
});
}
final DeferredResultInterceptorChain chain = final DeferredResultInterceptorChain chain =
new DeferredResultInterceptorChain(this.deferredResultInterceptors.values()); 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() { this.asyncWebRequest.addCompletionHandler(new Runnable() {
public void run() { public void run() {
if (deferredResult.expire()) { deferredResult.expire();
chain.triggerAfterExpiration(asyncWebRequest, deferredResult); chain.triggerAfterCompletion(asyncWebRequest, deferredResult);
}
} }
}); });
startAsyncProcessing(processingContext); startAsyncProcessing(processingContext);
deferredResult.setResultHandler(new DeferredResultHandler() { try {
chain.applyPreProcess(this.asyncWebRequest, deferredResult);
public void handleResult(Object result) { deferredResult.setResultHandler(new DeferredResultHandler() {
concurrentResult = result; public void handleResult(Object result) {
if (logger.isDebugEnabled()) { result = chain.applyPostProcess(asyncWebRequest, deferredResult, result);
logger.debug("Deferred result value [" + concurrentResult + "]"); setConcurrentResultAndDispatch(result);
} }
});
chain.applyPostProcess(asyncWebRequest, deferredResult, result); }
catch (Throwable t) {
logger.debug("Dispatching request to complete processing"); setConcurrentResultAndDispatch(t);
asyncWebRequest.dispatch(); }
}
});
} }
private void startAsyncProcessing(Object[] processingContext) { private void startAsyncProcessing(Object[] processingContext) {
@ -356,15 +368,10 @@ public final class WebAsyncManager {
clearConcurrentResult(); clearConcurrentResult();
this.concurrentResultContext = processingContext; this.concurrentResultContext = processingContext;
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
this.asyncWebRequest.startAsync(); this.asyncWebRequest.startAsync();
if (this.timeoutHandler != null) {
this.asyncWebRequest.setTimeoutHandler(this.timeoutHandler);
}
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class); HttpServletRequest request = this.asyncWebRequest.getNativeRequest(HttpServletRequest.class);
String requestUri = urlPathHelper.getRequestUri(request); String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Concurrent handling starting for " + request.getMethod() + " [" + requestUri + "]"); logger.debug("Concurrent handling starting for " + request.getMethod() + " [" + requestUri + "]");
} }

View File

@ -17,7 +17,6 @@
package org.springframework.web.context.request.async; package org.springframework.web.context.request.async;
import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify; import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -62,21 +61,6 @@ public class DeferredResultTests {
verify(handler); 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 @Test
public void isSetOrExpired() { public void isSetOrExpired() {
DeferredResultHandler handler = createMock(DeferredResultHandler.class); DeferredResultHandler handler = createMock(DeferredResultHandler.class);
@ -105,12 +89,6 @@ public class DeferredResultTests {
assertFalse(result.setResult("hello")); assertFalse(result.setResult("hello"));
} }
@Test
public void hasTimeout() {
assertFalse(new DeferredResult<String>().hasTimeoutResult());
assertTrue(new DeferredResult<String>(null, "timed out").hasTimeoutResult());
}
@Test @Test
public void applyTimeoutResult() { public void applyTimeoutResult() {
DeferredResultHandler handler = createMock(DeferredResultHandler.class); DeferredResultHandler handler = createMock(DeferredResultHandler.class);

View File

@ -33,7 +33,6 @@ import javax.servlet.AsyncEvent;
import org.easymock.EasyMock; import org.easymock.EasyMock;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockAsyncContext; import org.springframework.mock.web.MockAsyncContext;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
@ -120,7 +119,7 @@ public class StandardServletAsyncWebRequestTests {
@Test @Test
public void onTimeoutDefaultBehavior() throws Exception { public void onTimeoutDefaultBehavior() throws Exception {
this.asyncRequest.onTimeout(new AsyncEvent(null)); this.asyncRequest.onTimeout(new AsyncEvent(null));
assertEquals(HttpStatus.SERVICE_UNAVAILABLE.value(), this.response.getStatus()); assertEquals(200, this.response.getStatus());
} }
@Test @Test

View File

@ -19,6 +19,7 @@ package org.springframework.web.context.request.async;
import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock; import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.notNull; import static org.easymock.EasyMock.notNull;
import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset; import static org.easymock.EasyMock.reset;
@ -30,15 +31,15 @@ import static org.junit.Assert.fail;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.easymock.EasyMock;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.mock.web.MockHttpServletRequest; 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 * @author Rossen Stoyanchev
*/ */
@ -48,6 +49,7 @@ public class WebAsyncManagerTests {
private AsyncWebRequest asyncWebRequest; private AsyncWebRequest asyncWebRequest;
@Before @Before
public void setUp() { public void setUp() {
this.asyncManager = WebAsyncUtils.getAsyncManager(new MockHttpServletRequest()); this.asyncManager = WebAsyncUtils.getAsyncManager(new MockHttpServletRequest());
@ -63,6 +65,27 @@ public class WebAsyncManagerTests {
reset(this.asyncWebRequest); 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 @Test
public void isConcurrentHandlingStarted() { public void isConcurrentHandlingStarted() {
@ -91,32 +114,102 @@ public class WebAsyncManagerTests {
@Test @Test
public void startCallableProcessing() throws Exception { public void startCallableProcessing() throws Exception {
Callable<Object> task = new StubCallable(); int concurrentResult = 21;
Callable<Object> task = new StubCallable(concurrentResult);
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class); CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, task); interceptor.preProcess(this.asyncWebRequest, task);
interceptor.postProcess(this.asyncWebRequest, task, new Integer(1)); interceptor.postProcess(this.asyncWebRequest, task, new Integer(concurrentResult));
replay(interceptor); replay(interceptor);
this.asyncWebRequest.startAsync(); setupDefaultAsyncScenario();
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
this.asyncWebRequest.dispatch();
replay(this.asyncWebRequest);
this.asyncManager.registerCallableInterceptor("interceptor", interceptor); this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
this.asyncManager.startCallableProcessing(task); this.asyncManager.startCallableProcessing(task);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(concurrentResult, this.asyncManager.getConcurrentResult());
verify(interceptor, this.asyncWebRequest); verify(interceptor, this.asyncWebRequest);
} }
@Test @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); AsyncTaskExecutor executor = createMock(AsyncTaskExecutor.class);
expect(executor.submit((Runnable) notNull())).andReturn(null); expect(executor.submit((Runnable) notNull())).andReturn(null);
replay(executor); replay(executor);
this.asyncWebRequest.setTimeout(1000L); this.asyncWebRequest.setTimeout(1000L);
this.asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject());
this.asyncWebRequest.addCompletionHandler(EasyMock.<Runnable>anyObject());
this.asyncWebRequest.startAsync(); this.asyncWebRequest.startAsync();
replay(this.asyncWebRequest); replay(this.asyncWebRequest);
@ -128,7 +221,7 @@ public class WebAsyncManagerTests {
} }
@Test @Test
public void startCallableProcessingNullCallable() { public void startCallableProcessingNullInput() {
try { try {
this.asyncManager.startCallableProcessing((Callable<?>) null); this.asyncManager.startCallableProcessing((Callable<?>) null);
fail("Expected exception"); 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 @Test
public void startDeferredResultProcessing() throws Exception { public void startDeferredResultProcessing() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(1000L, 10); DeferredResult<String> deferredResult = new DeferredResult<String>(1000L);
String concurrentResult = "abc";
this.asyncWebRequest.setTimeout(1000L);
this.asyncWebRequest.setTimeoutHandler((Runnable) notNull());
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
this.asyncWebRequest.startAsync();
replay(this.asyncWebRequest);
DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class); DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, deferredResult); interceptor.preProcess(this.asyncWebRequest, deferredResult);
interceptor.postProcess(asyncWebRequest, deferredResult, concurrentResult);
replay(interceptor); replay(interceptor);
this.asyncWebRequest.setTimeout(1000L);
setupDefaultAsyncScenario();
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor); this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult); this.asyncManager.startDeferredResultProcessing(deferredResult);
verify(this.asyncWebRequest, interceptor); deferredResult.setResult(concurrentResult);
reset(this.asyncWebRequest, interceptor);
this.asyncWebRequest.dispatch(); assertEquals(concurrentResult, this.asyncManager.getConcurrentResult());
replay(this.asyncWebRequest);
interceptor.postProcess(asyncWebRequest, deferredResult, 25);
replay(interceptor);
deferredResult.setResult(25);
assertEquals(25, this.asyncManager.getConcurrentResult());
verify(this.asyncWebRequest, interceptor); verify(this.asyncWebRequest, interceptor);
} }
@Test @Test
public void setTimeoutHandler() throws Exception { public void startDeferredResultProcessingPreProcessException() throws Exception {
Runnable timeoutHandler = new Runnable() { public void run() {} }; DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
this.asyncManager.setTimeoutHandler(timeoutHandler); 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.startAsync();
this.asyncWebRequest.setTimeoutHandler(timeoutHandler);
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false); expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
this.asyncWebRequest.dispatch(); this.asyncWebRequest.dispatch();
replay(this.asyncWebRequest); replay(this.asyncWebRequest);
this.asyncManager.startCallableProcessing(new StubCallable());
verify(this.asyncWebRequest);
} }
private final class StubCallable implements Callable<Object> { private final class StubCallable implements Callable<Object> {
private Object value;
public StubCallable(Object value) {
this.value = value;
}
public Object call() throws Exception { public Object call() throws Exception {
return 1; if (this.value instanceof Exception) {
throw ((Exception) this.value);
}
return this.value;
} }
} }

View File

@ -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;
}
}
}

View File

@ -50,6 +50,7 @@ import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.async.CallableProcessingInterceptor; 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.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.ServletRequestHandledEvent; import org.springframework.web.context.support.ServletRequestHandledEvent;
@ -988,14 +989,13 @@ public abstract class FrameworkServlet extends HttpServletBean {
} }
private CallableProcessingInterceptor createRequestBindingInterceptor(final HttpServletRequest request) { private CallableProcessingInterceptor createRequestBindingInterceptor(final HttpServletRequest request) {
return new CallableProcessingInterceptorAdapter() {
return new CallableProcessingInterceptor() { @Override
public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) {
public void preProcess(NativeWebRequest webRequest, Callable<?> task) {
initContextHolders(request, buildLocaleContext(request), new ServletRequestAttributes(request)); initContextHolders(request, buildLocaleContext(request), new ServletRequestAttributes(request));
} }
@Override
public void postProcess(NativeWebRequest webRequest, Callable<?> task, Object concurrentResult) { public <T> void postProcess(NativeWebRequest webRequest, Callable<T> task, Object concurrentResult) {
resetContextHolders(request, null, null); resetContextHolders(request, null, null);
} }
}; };