Add interceptors for async processing
This change introduces two new interceptors with callback methods for concurrent request handling. These interfaces are CallableProcessingInterceptor and DeferredResultProcessingInterceptor. Unlike a HandlerInterceptor, and its AsyncHandlerInterceptor sub-type, which intercepts the invocation of a handler in he main request processing thread, the two new interfaces are aimed at intercepting the asynchronous execution of a Callable or a DeferredResult. This allows for the registration of thread initialization logic in the case of Callable executed with an AsyncTaskExecutor, or for centralized tracking of the completion and/or expiration of a DeferredResult.
This commit is contained in:
@@ -41,6 +41,11 @@ public interface AsyncWebRequest extends NativeWebRequest {
|
||||
*/
|
||||
void setTimeoutHandler(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Add a Runnable to be invoked when request processing completes.
|
||||
*/
|
||||
void addCompletionHandler(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Mark the start of asynchronous request processing so that when the main
|
||||
* processing thread exits, the response remains open for further processing
|
||||
@@ -62,17 +67,6 @@ public interface AsyncWebRequest extends NativeWebRequest {
|
||||
*/
|
||||
void dispatch();
|
||||
|
||||
/**
|
||||
* Whether the request was dispatched to the container in order to resume
|
||||
* processing after concurrent execution in an application thread.
|
||||
*/
|
||||
boolean isDispatched();
|
||||
|
||||
/**
|
||||
* Add a Runnable to be invoked when request processing completes.
|
||||
*/
|
||||
void addCompletionHandler(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Whether asynchronous processing has completed.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Assists with the invocation of {@link CallableProcessingInterceptor}'s.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
class CallableInterceptorChain {
|
||||
|
||||
private static Log logger = LogFactory.getLog(CallableInterceptorChain.class);
|
||||
|
||||
private final List<CallableProcessingInterceptor> interceptors;
|
||||
|
||||
private int interceptorIndex = -1;
|
||||
|
||||
|
||||
public CallableInterceptorChain(Collection<CallableProcessingInterceptor> interceptors) {
|
||||
this.interceptors = new ArrayList<CallableProcessingInterceptor>(interceptors);
|
||||
}
|
||||
|
||||
public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception {
|
||||
for (int i = 0; i < this.interceptors.size(); i++) {
|
||||
this.interceptors.get(i).preProcess(request, task);
|
||||
this.interceptorIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
public void applyPostProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
for (int i = this.interceptorIndex; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).postProcess(request, task, concurrentResult);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("CallableProcessingInterceptor.postProcess threw exception", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||
|
||||
/**
|
||||
* Intercepts concurrent request handling, where the concurrent result is
|
||||
* obtained by executing a {@link Callable} on behalf of the application with an
|
||||
* {@link AsyncTaskExecutor}.
|
||||
* <p>
|
||||
* A {@code CallableProcessingInterceptor} is invoked before and after the
|
||||
* invocation of the {@code Callable} task in the asynchronous thread.
|
||||
*
|
||||
* <p>A {@code CallableProcessingInterceptor} may be registered as follows:
|
||||
* <pre>
|
||||
* CallableProcessingInterceptor interceptor = ... ;
|
||||
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
|
||||
* asyncManager.registerCallableInterceptor("key", interceptor);
|
||||
* </pre>
|
||||
*
|
||||
* <p>To register an interceptor for every request, the above can be done through
|
||||
* a {@link WebRequestInterceptor} during pre-handling.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface CallableProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* Invoked from the asynchronous thread in which the {@code Callable} is
|
||||
* executed, before the {@code Callable} is invoked.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task that will produce a result
|
||||
*/
|
||||
void preProcess(NativeWebRequest request, Callable<?> task) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked from the asynchronous thread in which the {@code Callable} is
|
||||
* executed, after the {@code Callable} returned a result.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task that produced the result
|
||||
* @param concurrentResult the result of concurrent processing, which could
|
||||
* be a {@link Throwable} if the {@code Callable} raised an exception
|
||||
*/
|
||||
void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) throws Exception;
|
||||
|
||||
}
|
||||
@@ -25,9 +25,9 @@ import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* {@code DeferredResult} provides an alternative to using a {@link Callable}
|
||||
* for asynchronous request processing. While a Callable is executed concurrently
|
||||
* on behalf of the application, with a DeferredResult the application can produce
|
||||
* the result from a thread of its choice.
|
||||
* for asynchronous request processing. While a {@code Callable} is executed
|
||||
* concurrently on behalf of the application, with a {@code DeferredResult} the
|
||||
* application can produce the result from a thread of its choice.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
@@ -45,8 +45,6 @@ public final class DeferredResult<T> {
|
||||
|
||||
private DeferredResultHandler resultHandler;
|
||||
|
||||
private Object result = RESULT_NONE;
|
||||
|
||||
private final AtomicBoolean expired = new AtomicBoolean(false);
|
||||
|
||||
private final Object lock = new Object();
|
||||
@@ -79,7 +77,6 @@ public final class DeferredResult<T> {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the configured timeout value in milliseconds.
|
||||
*/
|
||||
@@ -88,8 +85,12 @@ public final class DeferredResult<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a handler to handle the result when set. Normally applications do not
|
||||
* use this method at runtime but may do so during testing.
|
||||
* Set a handler to handle the result when set. There can be only handler
|
||||
* for a {@code DeferredResult}. At runtime it will be set by the framework.
|
||||
* However applications may set it when unit testing.
|
||||
*
|
||||
* <p>If you need to be called back when a {@code DeferredResult} is set or
|
||||
* expires, register a {@link DeferredResultProcessingInterceptor} instead.
|
||||
*/
|
||||
public void setResultHandler(DeferredResultHandler resultHandler) {
|
||||
this.resultHandler = resultHandler;
|
||||
@@ -122,14 +123,14 @@ public final class DeferredResult<T> {
|
||||
}
|
||||
|
||||
private boolean processResult(Object result) {
|
||||
|
||||
synchronized (this.lock) {
|
||||
|
||||
if (isSetOrExpired()) {
|
||||
boolean wasExpired = getAndSetExpired();
|
||||
if (wasExpired) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.result = result;
|
||||
|
||||
if (!awaitResultHandler()) {
|
||||
throw new IllegalStateException("DeferredResultHandler not set");
|
||||
}
|
||||
@@ -156,15 +157,24 @@ public final class DeferredResult<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the DeferredResult can no longer be set either because the async
|
||||
* request expired or because it was already set.
|
||||
* Return {@code true} if this DeferredResult is no longer usable either
|
||||
* because it was previously set or because the underlying request ended
|
||||
* before it could be set.
|
||||
* <p>
|
||||
* The result may have been set with a call to {@link #setResult(Object)},
|
||||
* or {@link #setErrorResult(Object)}, or following a timeout, assuming a
|
||||
* timeout result was provided to the constructor. The request may before
|
||||
* the result set due to a timeout or network error.
|
||||
*/
|
||||
public boolean isSetOrExpired() {
|
||||
return (this.expired.get() || (this.result != RESULT_NONE));
|
||||
return this.expired.get();
|
||||
}
|
||||
|
||||
void setExpired() {
|
||||
this.expired.set(true);
|
||||
/**
|
||||
* Atomically set the expired flag and return its previous value.
|
||||
*/
|
||||
boolean getAndSetExpired() {
|
||||
return this.expired.getAndSet(true);
|
||||
}
|
||||
|
||||
boolean hasTimeoutResult() {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Assists with the invocation of {@link DeferredResultProcessingInterceptor}'s.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
class DeferredResultInterceptorChain {
|
||||
|
||||
private static Log logger = LogFactory.getLog(DeferredResultInterceptorChain.class);
|
||||
|
||||
private final List<DeferredResultProcessingInterceptor> interceptors;
|
||||
|
||||
|
||||
public DeferredResultInterceptorChain(Collection<DeferredResultProcessingInterceptor> interceptors) {
|
||||
this.interceptors = new ArrayList<DeferredResultProcessingInterceptor>(interceptors);
|
||||
}
|
||||
|
||||
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> task) throws Exception {
|
||||
for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
|
||||
interceptor.preProcess(request, task);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyPostProcess(NativeWebRequest request, DeferredResult<?> task, Object concurrentResult) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).postProcess(request, task, concurrentResult);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("DeferredResultProcessingInterceptor.postProcess threw exception", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void triggerAfterExpiration(NativeWebRequest request, DeferredResult<?> task) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).afterExpiration(request, task);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("DeferredResultProcessingInterceptor.afterExpiration threw exception", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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;
|
||||
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||
|
||||
/**
|
||||
* Intercepts concurrent request handling, where the concurrent result is
|
||||
* obtained by waiting for a {@link DeferredResult} to be set from a thread
|
||||
* chosen by the application (e.g. in response to some external event).
|
||||
*
|
||||
* <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start of
|
||||
* asynchronous processing and either when the {@code DeferredResult} is set or
|
||||
* when when the underlying request ends, whichever comes fist.
|
||||
*
|
||||
* <p>A {@code DeferredResultProcessingInterceptor} may be registered as follows:
|
||||
* <pre>
|
||||
* DeferredResultProcessingInterceptor interceptor = ... ;
|
||||
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
|
||||
* asyncManager.registerDeferredResultInterceptor("key", interceptor);
|
||||
* </pre>
|
||||
*
|
||||
* <p>To register an interceptor for every request, the above can be done through
|
||||
* a {@link WebRequestInterceptor} during pre-handling.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface DeferredResultProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* Invoked before the start of concurrent handling using a
|
||||
* {@link DeferredResult}. The invocation occurs in the thread that
|
||||
* initiated concurrent handling.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult instance
|
||||
*/
|
||||
void preProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked when a {@link DeferredResult} is set either with a normal value
|
||||
* or with a {@link DeferredResult#DeferredResult(Long, Object) timeout
|
||||
* result}. The invocation occurs in the thread that set the result.
|
||||
* <p>
|
||||
* If the request ends before the {@code DeferredResult} is set, then
|
||||
* {@link #afterExpiration(NativeWebRequest, DeferredResult)} is called.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult that has been set
|
||||
* @param concurrentResult the result to which the {@code DeferredResult}
|
||||
* was set
|
||||
*/
|
||||
void postProcess(NativeWebRequest request, DeferredResult<?> deferredResult,
|
||||
Object concurrentResult) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* Invoked when a {@link DeferredResult} expires before a result has been
|
||||
* set possibly due to a timeout or a network error. This invocation occurs
|
||||
* in the thread where the timeout or network error notification is
|
||||
* processed.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult that has been set
|
||||
*/
|
||||
void afterExpiration(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception;
|
||||
|
||||
}
|
||||
@@ -49,10 +49,6 @@ public class NoSupportAsyncWebRequest extends ServletWebRequest implements Async
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isDispatched() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not supported
|
||||
|
||||
public void startAsync() {
|
||||
|
||||
@@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.AsyncEvent;
|
||||
import javax.servlet.AsyncListener;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -94,10 +93,6 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
||||
return ((this.asyncContext != null) && getRequest().isAsyncStarted());
|
||||
}
|
||||
|
||||
public boolean isDispatched() {
|
||||
return (DispatcherType.ASYNC.equals(getRequest().getDispatcherType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether async request processing has completed.
|
||||
* <p>It is important to avoid use of request and response objects after async
|
||||
|
||||
@@ -15,10 +15,7 @@
|
||||
*/
|
||||
package org.springframework.web.context.request.async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@@ -73,7 +70,11 @@ public final class WebAsyncManager {
|
||||
|
||||
private Object[] concurrentResultContext;
|
||||
|
||||
private final Map<Object, WebAsyncThreadInitializer> threadInitializers = new LinkedHashMap<Object, WebAsyncThreadInitializer>();
|
||||
private final Map<Object, CallableProcessingInterceptor> callableInterceptors =
|
||||
new LinkedHashMap<Object, CallableProcessingInterceptor>();
|
||||
|
||||
private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
|
||||
new LinkedHashMap<Object, DeferredResultProcessingInterceptor>();
|
||||
|
||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
@@ -87,11 +88,13 @@ public final class WebAsyncManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link AsyncWebRequest} to use. This property may be
|
||||
* set more than once during a single request to accurately reflect the
|
||||
* current state of the request (e.g. following a forward, request/response
|
||||
* wrapping, etc). However, it should not be set while concurrent handling is
|
||||
* in progress, i.e. while {@link #isConcurrentHandlingStarted()} is {@code true}.
|
||||
* Configure the {@link AsyncWebRequest} to use. This property may be set
|
||||
* more than once during a single request to accurately reflect the current
|
||||
* state of the request (e.g. following a forward, request/response
|
||||
* wrapping, etc). However, it should not be set while concurrent handling
|
||||
* is in progress, i.e. while {@link #isConcurrentHandlingStarted()} is
|
||||
* {@code true}.
|
||||
*
|
||||
* @param asyncWebRequest the web request to use
|
||||
*/
|
||||
public void setAsyncWebRequest(final AsyncWebRequest asyncWebRequest) {
|
||||
@@ -127,17 +130,18 @@ public final class WebAsyncManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the request has been dispatched to process the result of
|
||||
* concurrent handling.
|
||||
* Whether a result value exists as a result of concurrent handling.
|
||||
*/
|
||||
public boolean hasConcurrentResult() {
|
||||
return ((this.concurrentResult != RESULT_NONE) && this.asyncWebRequest.isDispatched());
|
||||
return (this.concurrentResult != RESULT_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the result from concurrent handling.
|
||||
*
|
||||
* @return an Object, possibly an {@code Exception} or {@code Throwable} if
|
||||
* concurrent handling raised one.
|
||||
* concurrent handling raised one.
|
||||
* @see #clearConcurrentResult()
|
||||
*/
|
||||
public Object getConcurrentResult() {
|
||||
return this.concurrentResult;
|
||||
@@ -146,11 +150,46 @@ public final class WebAsyncManager {
|
||||
/**
|
||||
* Provides access to additional processing context saved at the start of
|
||||
* concurrent handling.
|
||||
*
|
||||
* @see #clearConcurrentResult()
|
||||
*/
|
||||
public Object[] getConcurrentResultContext() {
|
||||
return this.concurrentResultContext;
|
||||
}
|
||||
|
||||
public CallableProcessingInterceptor getCallableInterceptor(Object key) {
|
||||
return this.callableInterceptors.get(key);
|
||||
}
|
||||
|
||||
public DeferredResultProcessingInterceptor getDeferredResultInterceptor(Object key) {
|
||||
return this.deferredResultInterceptors.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link CallableProcessingInterceptor} that will be applied
|
||||
* when concurrent request handling with a {@link Callable} starts.
|
||||
*
|
||||
* @param key a unique the key under which to register the interceptor
|
||||
* @param interceptor the interceptor to register
|
||||
*/
|
||||
public void registerCallableInterceptor(Object key, CallableProcessingInterceptor interceptor) {
|
||||
Assert.notNull(interceptor, "interceptor is required");
|
||||
this.callableInterceptors.put(key, interceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link DeferredResultProcessingInterceptor} that will be
|
||||
* applied when concurrent request handling with a {@link DeferredResult}
|
||||
* starts.
|
||||
*
|
||||
* @param key a unique the key under which to register the interceptor
|
||||
* @param interceptor the interceptor to register
|
||||
*/
|
||||
public void registerDeferredResultInterceptor(Object key, DeferredResultProcessingInterceptor interceptor) {
|
||||
Assert.notNull(interceptor, "interceptor is required");
|
||||
this.deferredResultInterceptors.put(key, interceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear {@linkplain #getConcurrentResult() concurrentResult} and
|
||||
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.
|
||||
@@ -169,7 +208,7 @@ 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()}
|
||||
* via {@link #getConcurrentResultContext()}
|
||||
*
|
||||
* @see #getConcurrentResult()
|
||||
* @see #getConcurrentResultContext()
|
||||
@@ -182,23 +221,19 @@ public final class WebAsyncManager {
|
||||
this.taskExecutor.submit(new Runnable() {
|
||||
|
||||
public void run() {
|
||||
List<WebAsyncThreadInitializer> initializers =
|
||||
new ArrayList<WebAsyncThreadInitializer>(threadInitializers.values());
|
||||
|
||||
CallableInterceptorChain chain =
|
||||
new CallableInterceptorChain(callableInterceptors.values());
|
||||
|
||||
try {
|
||||
for (WebAsyncThreadInitializer initializer : initializers) {
|
||||
initializer.initialize();
|
||||
}
|
||||
chain.applyPreProcess(asyncWebRequest, callable);
|
||||
concurrentResult = callable.call();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
concurrentResult = t;
|
||||
}
|
||||
finally {
|
||||
Collections.reverse(initializers);
|
||||
for (WebAsyncThreadInitializer initializer : initializers) {
|
||||
initializer.reset();
|
||||
}
|
||||
chain.applyPostProcess(asyncWebRequest, callable, concurrentResult);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -220,9 +255,10 @@ public final class WebAsyncManager {
|
||||
* Use the given {@link AsyncTask} to configure the task executor as well as
|
||||
* the timeout value of the {@code AsyncWebRequest} before delegating to
|
||||
* {@link #startCallableProcessing(Callable, Object...)}.
|
||||
*
|
||||
* @param asyncTask an asyncTask containing the target {@code Callable}
|
||||
* @param processingContext additional context to save that can be accessed
|
||||
* via {@link #getConcurrentResultContext()}
|
||||
* via {@link #getConcurrentResultContext()}
|
||||
*/
|
||||
public void startCallableProcessing(AsyncTask asyncTask, Object... processingContext) {
|
||||
Assert.notNull(asyncTask, "AsyncTask must not be null");
|
||||
@@ -241,21 +277,23 @@ public final class WebAsyncManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start concurrent request processing and initialize the given {@link DeferredResult}
|
||||
* with a {@link DeferredResultHandler} that saves the result and dispatches
|
||||
* the request to resume processing of that result.
|
||||
* The {@code AsyncWebRequest} is also updated with a completion handler that
|
||||
* expires the {@code DeferredResult} and a timeout handler assuming the
|
||||
* {@code DeferredResult} has a default timeout result.
|
||||
* Start concurrent request processing and initialize the given
|
||||
* {@link DeferredResult} with a {@link DeferredResultHandler} that saves
|
||||
* the result and dispatches the request to resume processing of that
|
||||
* result. The {@code AsyncWebRequest} is also updated with a completion
|
||||
* handler that expires the {@code DeferredResult} and a timeout handler
|
||||
* assuming the {@code DeferredResult} has a default timeout result.
|
||||
*
|
||||
* @param deferredResult the DeferredResult instance to initialize
|
||||
* @param processingContext additional context to save that can be accessed
|
||||
* via {@link #getConcurrentResultContext()}
|
||||
* via {@link #getConcurrentResultContext()}
|
||||
*
|
||||
* @see #getConcurrentResult()
|
||||
* @see #getConcurrentResultContext()
|
||||
*/
|
||||
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult, Object... processingContext) {
|
||||
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult,
|
||||
Object... processingContext) throws Exception {
|
||||
|
||||
Assert.notNull(deferredResult, "DeferredResult must not be null");
|
||||
|
||||
Long timeout = deferredResult.getTimeoutMilliseconds();
|
||||
@@ -263,12 +301,6 @@ public final class WebAsyncManager {
|
||||
this.asyncWebRequest.setTimeout(timeout);
|
||||
}
|
||||
|
||||
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||
public void run() {
|
||||
deferredResult.setExpired();
|
||||
}
|
||||
});
|
||||
|
||||
if (deferredResult.hasTimeoutResult()) {
|
||||
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
|
||||
public void run() {
|
||||
@@ -277,6 +309,19 @@ public final class WebAsyncManager {
|
||||
});
|
||||
}
|
||||
|
||||
final DeferredResultInterceptorChain chain =
|
||||
new DeferredResultInterceptorChain(this.deferredResultInterceptors.values());
|
||||
|
||||
chain.applyPreProcess(this.asyncWebRequest, deferredResult);
|
||||
|
||||
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||
public void run() {
|
||||
if (!deferredResult.getAndSetExpired()) {
|
||||
chain.triggerAfterExpiration(asyncWebRequest, deferredResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
startAsyncProcessing(processingContext);
|
||||
|
||||
deferredResult.setResultHandler(new DeferredResultHandler() {
|
||||
@@ -287,8 +332,7 @@ public final class WebAsyncManager {
|
||||
logger.debug("Deferred result value [" + concurrentResult + "]");
|
||||
}
|
||||
|
||||
Assert.state(!asyncWebRequest.isAsyncComplete(),
|
||||
"Cannot handle DeferredResult [ " + deferredResult + " ] due to a timeout or network error");
|
||||
chain.applyPostProcess(asyncWebRequest, deferredResult, result);
|
||||
|
||||
logger.debug("Dispatching request to complete processing");
|
||||
asyncWebRequest.dispatch();
|
||||
@@ -298,12 +342,12 @@ public final class WebAsyncManager {
|
||||
|
||||
private void startAsyncProcessing(Object[] processingContext) {
|
||||
|
||||
clearConcurrentResult();
|
||||
this.concurrentResultContext = processingContext;
|
||||
|
||||
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
|
||||
this.asyncWebRequest.startAsync();
|
||||
|
||||
this.concurrentResult = null;
|
||||
this.concurrentResultContext = processingContext;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
|
||||
String requestUri = urlPathHelper.getRequestUri(request);
|
||||
@@ -311,43 +355,4 @@ public final class WebAsyncManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an {@link WebAsyncThreadInitializer} for the current request. It may
|
||||
* later be accessed and applied via {@link #initializeAsyncThread(String)}
|
||||
* and will also be used to initialize and reset threads for concurrent handler execution.
|
||||
* @param key a unique the key under which to keep the initializer
|
||||
* @param initializer the initializer instance
|
||||
*/
|
||||
public void registerAsyncThreadInitializer(Object key, WebAsyncThreadInitializer initializer) {
|
||||
Assert.notNull(initializer, "WebAsyncThreadInitializer must not be null");
|
||||
this.threadInitializers.put(key, initializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the {@linkplain WebAsyncThreadInitializer#initialize() initialize()}
|
||||
* method of the named {@link WebAsyncThreadInitializer}.
|
||||
* @param key the key under which the initializer was registered
|
||||
* @return whether an initializer was found and applied
|
||||
*/
|
||||
public boolean initializeAsyncThread(Object key) {
|
||||
WebAsyncThreadInitializer initializer = this.threadInitializers.get(key);
|
||||
if (initializer != null) {
|
||||
initializer.initialize();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize and reset thread-bound variables.
|
||||
*/
|
||||
public interface WebAsyncThreadInitializer {
|
||||
|
||||
void initialize();
|
||||
|
||||
void reset();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,10 +64,11 @@ public abstract class WebAsyncUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an AsyncWebRequest instance.
|
||||
* <p>By default an instance of {@link StandardServletAsyncWebRequest} is created
|
||||
* if running in Servlet 3.0 (or higher) environment or as a fallback option an
|
||||
* instance of {@link NoSupportAsyncWebRequest} is returned.
|
||||
* Create an AsyncWebRequest instance. By default an instance of
|
||||
* {@link StandardServletAsyncWebRequest} is created if running in Servlet
|
||||
* 3.0 (or higher) environment or as a fallback, an instance of
|
||||
* {@link NoSupportAsyncWebRequest} is returned.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param response the current response
|
||||
* @return an AsyncWebRequest instance, never {@code null}
|
||||
|
||||
Reference in New Issue
Block a user