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 e0e5e6fb0c9..ba3885330d0 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
@@ -26,6 +26,7 @@ import org.springframework.web.context.request.NativeWebRequest;
* Assists with the invocation of {@link CallableProcessingInterceptor}'s.
*
* @author Rossen Stoyanchev
+ * @author Rob Winch
* @since 3.2
*/
class CallableInterceptorChain {
@@ -41,6 +42,12 @@ class CallableInterceptorChain {
this.interceptors = interceptors;
}
+ public void applyBeforeConcurrentHandling(NativeWebRequest request, Callable> task) throws Exception {
+ for (CallableProcessingInterceptor interceptor : this.interceptors) {
+ interceptor.beforeConcurrentHandling(request, task);
+ }
+ }
+
public void applyPreProcess(NativeWebRequest request, Callable> task) throws Exception {
for (CallableProcessingInterceptor interceptor : this.interceptors) {
interceptor.preProcess(request, task);
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 f727b8f8c83..cc7f15e113b 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
@@ -39,6 +39,7 @@ import org.springframework.web.context.request.NativeWebRequest;
* can select a value to be used to resume processing.
*
* @author Rossen Stoyanchev
+ * @author Rob Winch
* @since 3.2
*/
public interface CallableProcessingInterceptor {
@@ -47,6 +48,24 @@ public interface CallableProcessingInterceptor {
static final Object RESPONSE_HANDLED = new Object();
+ /**
+ * Invoked before the start of concurrent handling in the original
+ * thread in which the {@code Callable} is submitted for concurrent handling.
+ *
+ *
+ * This is useful for capturing the state of the current thread just prior to
+ * invoking the {@link Callable}. Once the state is captured, it can then be
+ * transfered to the new {@link Thread} in
+ * {@link #preProcess(NativeWebRequest, Callable)}. Capturing the state of
+ * Spring Security's SecurityContextHolder and migrating it to the new Thread
+ * is a concrete example of where this is useful.
+ *
+ *
+ * @param request the current request
+ * @param task the task for the current async request
+ * @throws Exception in case of errors
+ */
+ void beforeConcurrentHandling(NativeWebRequest request, Callable task) throws Exception;
/**
* Invoked after the start of concurrent handling in the async
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
index 2d27f4711c3..264e44237ff 100644
--- 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
@@ -24,10 +24,17 @@ import org.springframework.web.context.request.NativeWebRequest;
* for simplified implementation of individual methods.
*
* @author Rossen Stoyanchev
+ * @author Rob Winch
* @since 3.2
*/
public abstract class CallableProcessingInterceptorAdapter implements CallableProcessingInterceptor {
+ /**
+ * This implementation is empty.
+ */
+ public void beforeConcurrentHandling(NativeWebRequest request, Callable task) throws Exception {
+ }
+
/**
* This implementation is empty.
*/
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 09763330c0f..ffe9bb81265 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
@@ -40,6 +40,12 @@ class DeferredResultInterceptorChain {
this.interceptors = interceptors;
}
+ public void applyBeforeConcurrentHandling(NativeWebRequest request, DeferredResult> deferredResult) throws Exception {
+ for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
+ interceptor.beforeConcurrentHandling(request, deferredResult);
+ }
+ }
+
public void applyPreProcess(NativeWebRequest request, DeferredResult> deferredResult) throws Exception {
for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
interceptor.preProcess(request, deferredResult);
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 281e478968b..3adcba96fcd 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
@@ -36,10 +36,22 @@ import org.springframework.web.context.request.NativeWebRequest;
* method can set the {@code DeferredResult} in order to resume processing.
*
* @author Rossen Stoyanchev
+ * @author Rob Winch
* @since 3.2
*/
public interface DeferredResultProcessingInterceptor {
+ /**
+ * Invoked immediately before the start of concurrent handling, in the same
+ * thread that started it. This method may be used to capture state just prior
+ * to the start of concurrent processing with the given {@code DeferredResult}.
+ *
+ * @param request the current request
+ * @param deferredResult the DeferredResult for the current request
+ * @throws Exception in case of errors
+ */
+ void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) throws Exception;
+
/**
* 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
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
index a6fa80d12d4..774761bdb65 100644
--- 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
@@ -22,10 +22,17 @@ import org.springframework.web.context.request.NativeWebRequest;
* interface for simplified implementation of individual methods.
*
* @author Rossen Stoyanchev
+ * @author Rob Winch
* @since 3.2
*/
public abstract class DeferredResultProcessingInterceptorAdapter implements DeferredResultProcessingInterceptor {
+ /**
+ * This implementation is empty.
+ */
+ public void beforeConcurrentHandling(NativeWebRequest request, DeferredResult deferredResult) throws Exception {
+ }
+
/**
* This implementation is empty.
*/
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 a4f0e00e3a3..1b5d9d597aa 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
@@ -249,12 +249,13 @@ public final class WebAsyncManager {
* @param callable a unit of work to be executed asynchronously
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
+ * @throws Exception If concurrent processing failed to start
*
* @see #getConcurrentResult()
* @see #getConcurrentResultContext()
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
- public void startCallableProcessing(final Callable> callable, Object... processingContext) {
+ public void startCallableProcessing(final Callable> callable, Object... processingContext) throws Exception {
Assert.notNull(callable, "Callable must not be null");
startCallableProcessing(new WebAsyncTask(callable), processingContext);
}
@@ -267,8 +268,9 @@ public final class WebAsyncManager {
* @param webAsyncTask an WebAsyncTask containing the target {@code Callable}
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
+ * @throws Exception If concurrent processing failed to start
*/
- public void startCallableProcessing(final WebAsyncTask> webAsyncTask, Object... processingContext) {
+ public void startCallableProcessing(final WebAsyncTask> webAsyncTask, Object... processingContext) throws Exception {
Assert.notNull(webAsyncTask, "WebAsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
@@ -306,6 +308,8 @@ public final class WebAsyncManager {
}
});
+ interceptorChain.applyBeforeConcurrentHandling(asyncWebRequest, callable);
+
startAsyncProcessing(processingContext);
this.taskExecutor.submit(new Runnable() {
@@ -356,12 +360,13 @@ public final class WebAsyncManager {
* @param deferredResult the DeferredResult instance to initialize
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
+ * @throws Exception If concurrent processing failed to start
*
* @see #getConcurrentResult()
* @see #getConcurrentResultContext()
*/
public void startDeferredResultProcessing(
- final DeferredResult> deferredResult, Object... processingContext) {
+ final DeferredResult> deferredResult, Object... processingContext) throws Exception {
Assert.notNull(deferredResult, "DeferredResult must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
@@ -395,6 +400,8 @@ public final class WebAsyncManager {
}
});
+ interceptorChain.applyBeforeConcurrentHandling(asyncWebRequest, deferredResult);
+
startAsyncProcessing(processingContext);
try {
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 a8436b651b0..1e5847b8309 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
@@ -66,7 +66,7 @@ public class WebAsyncManagerTests {
}
@Test
- public void startAsyncProcessingWithoutAsyncWebRequest() {
+ public void startAsyncProcessingWithoutAsyncWebRequest() throws Exception {
WebAsyncManager manager = WebAsyncUtils.getAsyncManager(new MockHttpServletRequest());
try {
@@ -118,6 +118,7 @@ public class WebAsyncManagerTests {
Callable