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:
@@ -36,7 +36,7 @@ class CallableInterceptorChain {
|
||||
|
||||
private final List<CallableProcessingInterceptor> interceptors;
|
||||
|
||||
private int interceptorIndex = -1;
|
||||
private int preProcessIndex = -1;
|
||||
|
||||
|
||||
public CallableInterceptorChain(Collection<CallableProcessingInterceptor> interceptors) {
|
||||
@@ -44,21 +44,47 @@ class CallableInterceptorChain {
|
||||
}
|
||||
|
||||
public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception {
|
||||
for (int i = 0; i < this.interceptors.size(); i++) {
|
||||
this.interceptors.get(i).preProcess(request, task);
|
||||
this.interceptorIndex = i;
|
||||
for (CallableProcessingInterceptor interceptor : this.interceptors) {
|
||||
interceptor.preProcess(request, task);
|
||||
this.preProcessIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
public void applyPostProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
for (int i = this.interceptorIndex; i >= 0; i--) {
|
||||
public Object applyPostProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
|
||||
for (int i = this.preProcessIndex; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).postProcess(request, task, concurrentResult);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("CallableProcessingInterceptor.postProcess threw exception", ex);
|
||||
catch (Throwable t) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return concurrentResult;
|
||||
}
|
||||
|
||||
public Object triggerAfterTimeout(NativeWebRequest request, Callable<?> task) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
Object result = this.interceptors.get(i).afterTimeout(request, task);
|
||||
if (result != CallableProcessingInterceptor.RESULT_NONE) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return CallableProcessingInterceptor.RESULT_NONE;
|
||||
}
|
||||
|
||||
public void triggerAfterCompletion(NativeWebRequest request, Callable<?> task) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).afterCompletion(request, task);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.error("afterCompletion error", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,49 +19,80 @@ import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||
|
||||
/**
|
||||
* Intercepts concurrent request handling, where the concurrent result is
|
||||
* obtained by executing a {@link Callable} on behalf of the application with an
|
||||
* {@link AsyncTaskExecutor}.
|
||||
* <p>
|
||||
* A {@code CallableProcessingInterceptor} is invoked before and after the
|
||||
* invocation of the {@code Callable} task in the asynchronous thread.
|
||||
* obtained by executing a {@link Callable} on behalf of the application with
|
||||
* an {@link AsyncTaskExecutor}.
|
||||
*
|
||||
* <p>A {@code CallableProcessingInterceptor} may be registered as follows:
|
||||
* <pre>
|
||||
* CallableProcessingInterceptor interceptor = ... ;
|
||||
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
|
||||
* asyncManager.registerCallableInterceptor("key", interceptor);
|
||||
* </pre>
|
||||
* <p>A {@code CallableProcessingInterceptor} is invoked before and after the
|
||||
* invocation of the {@code Callable} task in the asynchronous thread, as well
|
||||
* as on timeout from a container thread, or after completing for any reason
|
||||
* including a timeout or network error.
|
||||
*
|
||||
* <p>To register an interceptor for every request, the above can be done through
|
||||
* a {@link WebRequestInterceptor} during pre-handling.
|
||||
* <p>As a general rule exceptions raised by interceptor methods will cause
|
||||
* async processing to resume by dispatching back to the container and using
|
||||
* the Exception instance as the concurrent result. Such exceptions will then
|
||||
* be processed through the {@code HandlerExceptionResolver} mechanism.
|
||||
*
|
||||
* <p>The {@link #afterTimeout(NativeWebRequest, Callable) afterTimeout} method
|
||||
* can select a value to be used to resume processing.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface CallableProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* Invoked from the asynchronous thread in which the {@code Callable} is
|
||||
* executed, before the {@code Callable} is invoked.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task that will produce a result
|
||||
*/
|
||||
void preProcess(NativeWebRequest request, Callable<?> task) throws Exception;
|
||||
public static final Object RESULT_NONE = new Object();
|
||||
|
||||
/**
|
||||
* Invoked from the asynchronous thread in which the {@code Callable} is
|
||||
* executed, after the {@code Callable} returned a result.
|
||||
* Invoked <em>after</em> the start of concurrent handling in the async
|
||||
* thread in which the {@code Callable} is executed and <em>before</em> the
|
||||
* actual invocation of the {@code Callable}.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task that produced the result
|
||||
* @param task the task for the current async request
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
<T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked <em>after</em> the {@code Callable} has produced a result in the
|
||||
* async thread in which the {@code Callable} is executed. This method may
|
||||
* be invoked later than {@code afterTimeout} or {@code afterCompletion}
|
||||
* depending on when the {@code Callable} finishes processing.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task for the current async request
|
||||
* @param concurrentResult the result of concurrent processing, which could
|
||||
* be a {@link Throwable} if the {@code Callable} raised an exception
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) throws Exception;
|
||||
<T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked from a container thread when the async request times out before
|
||||
* the {@code Callable} task completes. Implementations may return a value,
|
||||
* including an {@link Exception}, to use instead of the value the
|
||||
* {@link Callable} did not return in time.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task for the current async request
|
||||
* @return a concurrent result value; if the value is anything other than
|
||||
* {@link #RESULT_NONE}, concurrent processing is resumed and subsequent
|
||||
* interceptors are not invoked
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
<T> Object afterTimeout(NativeWebRequest request, Callable<T> task) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked from a container thread when async processing completes for any
|
||||
* reason including timeout or network error.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param task the task for the current async request
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
<T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.context.request.async;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Abstract adapter class for the {@link CallableProcessingInterceptor} interface,
|
||||
* for simplified implementation of individual methods.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class CallableProcessingInterceptorAdapter implements CallableProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation always returns
|
||||
* {@link CallableProcessingInterceptor#RESULT_NONE RESULT_NONE}.
|
||||
*/
|
||||
public <T> Object afterTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
|
||||
return RESULT_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,7 +66,7 @@ public final class DeferredResult<T> {
|
||||
/**
|
||||
* Create a DeferredResult with a timeout and a default result to use on timeout.
|
||||
* @param timeout timeout value in milliseconds; ignored if {@code null}
|
||||
* @param timeoutResult the result to use, possibly {@code null}
|
||||
* @param timeoutResult the result to use
|
||||
*/
|
||||
public DeferredResult(Long timeout, Object timeoutResult) {
|
||||
this.timeoutResult = timeoutResult;
|
||||
@@ -118,13 +118,7 @@ public final class DeferredResult<T> {
|
||||
}
|
||||
this.result = result;
|
||||
if (this.resultHandler != null) {
|
||||
try {
|
||||
this.resultHandler.handleResult(this.result);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.trace("DeferredResult not handled", t);
|
||||
return false;
|
||||
}
|
||||
this.resultHandler.handleResult(this.result);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -158,24 +152,19 @@ public final class DeferredResult<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the "expired" flag if and only if the result value was not already set.
|
||||
* @return {@code true} if expiration succeeded, {@code false} otherwise
|
||||
* Mark this instance expired so it may no longer be used.
|
||||
* @return the previous value of the expiration flag
|
||||
*/
|
||||
boolean expire() {
|
||||
synchronized (this) {
|
||||
if (!isSetOrExpired()) {
|
||||
this.expired = true;
|
||||
}
|
||||
boolean previous = this.expired;
|
||||
this.expired = true;
|
||||
return previous;
|
||||
}
|
||||
return this.expired;
|
||||
}
|
||||
|
||||
boolean hasTimeoutResult() {
|
||||
return this.timeoutResult != RESULT_NONE;
|
||||
}
|
||||
|
||||
boolean applyTimeoutResult() {
|
||||
return hasTimeoutResult() ? setResultInternal(this.timeoutResult) : false;
|
||||
return (this.timeoutResult != RESULT_NONE) ? setResultInternal(this.timeoutResult) : false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -35,35 +35,48 @@ class DeferredResultInterceptorChain {
|
||||
|
||||
private final List<DeferredResultProcessingInterceptor> interceptors;
|
||||
|
||||
private int preProcessingIndex = -1;
|
||||
|
||||
|
||||
public DeferredResultInterceptorChain(Collection<DeferredResultProcessingInterceptor> interceptors) {
|
||||
this.interceptors = new ArrayList<DeferredResultProcessingInterceptor>(interceptors);
|
||||
}
|
||||
|
||||
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> task) throws Exception {
|
||||
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception {
|
||||
for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
|
||||
interceptor.preProcess(request, task);
|
||||
interceptor.preProcess(request, deferredResult);
|
||||
this.preProcessingIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
public void applyPostProcess(NativeWebRequest request, DeferredResult<?> task, Object concurrentResult) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).postProcess(request, task, concurrentResult);
|
||||
public Object applyPostProcess(NativeWebRequest request, DeferredResult<?> deferredResult, Object concurrentResult) {
|
||||
try {
|
||||
for (int i = this.preProcessingIndex; i >= 0; i--) {
|
||||
this.interceptors.get(i).postProcess(request, deferredResult, concurrentResult);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("DeferredResultProcessingInterceptor.postProcess threw exception", ex);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
return t;
|
||||
}
|
||||
return concurrentResult;
|
||||
}
|
||||
|
||||
public void triggerAfterTimeout(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception {
|
||||
for (int i = this.preProcessingIndex; i >= 0; i--) {
|
||||
if (deferredResult.isSetOrExpired()) {
|
||||
return;
|
||||
}
|
||||
this.interceptors.get(i).afterTimeout(request, deferredResult);
|
||||
}
|
||||
}
|
||||
|
||||
public void triggerAfterExpiration(NativeWebRequest request, DeferredResult<?> task) {
|
||||
for (int i = this.interceptors.size()-1; i >= 0; i--) {
|
||||
public void triggerAfterCompletion(NativeWebRequest request, DeferredResult<?> deferredResult) {
|
||||
for (int i = this.preProcessingIndex; i >= 0; i--) {
|
||||
try {
|
||||
this.interceptors.get(i).afterExpiration(request, task);
|
||||
this.interceptors.get(i).afterCompletion(request, deferredResult);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("DeferredResultProcessingInterceptor.afterExpiration threw exception", ex);
|
||||
catch (Throwable t) {
|
||||
logger.error("afterCompletion error", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,26 +16,24 @@
|
||||
package org.springframework.web.context.request.async;
|
||||
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||
|
||||
/**
|
||||
* Intercepts concurrent request handling, where the concurrent result is
|
||||
* obtained by waiting for a {@link DeferredResult} to be set from a thread
|
||||
* chosen by the application (e.g. in response to some external event).
|
||||
*
|
||||
* <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start of
|
||||
* asynchronous processing and either when the {@code DeferredResult} is set or
|
||||
* when when the underlying request ends, whichever comes fist.
|
||||
* <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start
|
||||
* of async processing, after the {@code DeferredResult} is set as well as on
|
||||
* timeout, or or after completing for any reason including a timeout or network
|
||||
* error.
|
||||
*
|
||||
* <p>A {@code DeferredResultProcessingInterceptor} may be registered as follows:
|
||||
* <pre>
|
||||
* DeferredResultProcessingInterceptor interceptor = ... ;
|
||||
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
|
||||
* asyncManager.registerDeferredResultInterceptor("key", interceptor);
|
||||
* </pre>
|
||||
* <p>As a general rule exceptions raised by interceptor methods will cause
|
||||
* async processing to resume by dispatching back to the container and using
|
||||
* the Exception instance as the concurrent result. Such exceptions will then
|
||||
* be processed through the {@code HandlerExceptionResolver} mechanism.
|
||||
*
|
||||
* <p>To register an interceptor for every request, the above can be done through
|
||||
* a {@link WebRequestInterceptor} during pre-handling.
|
||||
* <p>The {@link #afterTimeout(NativeWebRequest, DeferredResult) afterTimeout}
|
||||
* method can set the {@code DeferredResult} in order to resume processing.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
@@ -43,40 +41,59 @@ import org.springframework.web.context.request.WebRequestInterceptor;
|
||||
public interface DeferredResultProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* Invoked before the start of concurrent handling using a
|
||||
* {@link DeferredResult}. The invocation occurs in the thread that
|
||||
* initiated concurrent handling.
|
||||
* Invoked immediately after the start of concurrent handling, in the same
|
||||
* thread that started it. This method may be used to detect the start of
|
||||
* concurrent processing with the given {@code DeferredResult}.
|
||||
*
|
||||
* <p>The {@code DeferredResult} may have already been set, for example at
|
||||
* the time of its creation or by another thread.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult instance
|
||||
* @param deferredResult the DeferredResult for the current request
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
void preProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception;
|
||||
<T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked when a {@link DeferredResult} is set via
|
||||
* {@link DeferredResult#setResult(Object) setResult}, or
|
||||
* {@link DeferredResult#setErrorResult(Object) setErrorResult}, or after
|
||||
* a timeout if a {@code DeferredResult} was created with a constructor
|
||||
* accepting a default timeout result.
|
||||
* <p>
|
||||
* If the request ends before the {@code DeferredResult} is set, then
|
||||
* {@link #afterExpiration(NativeWebRequest, DeferredResult)} is called.
|
||||
* Invoked after a {@code DeferredResult} has been set, via
|
||||
* {@link DeferredResult#setResult(Object)} or
|
||||
* {@link DeferredResult#setErrorResult(Object)}, and is also ready to
|
||||
* handle the concurrent result.
|
||||
*
|
||||
* <p>This method may also be invoked after a timeout when the
|
||||
* {@code DeferredResult} was created with a constructor accepting a default
|
||||
* timeout result.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult that has been set
|
||||
* @param deferredResult the DeferredResult for the current request
|
||||
* @param concurrentResult the result to which the {@code DeferredResult}
|
||||
* was set
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
void postProcess(NativeWebRequest request, DeferredResult<?> deferredResult,
|
||||
Object concurrentResult) throws Exception;
|
||||
<T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult, Object concurrentResult) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked when a {@link DeferredResult} was never set before the request
|
||||
* completed due to a timeout or network error.
|
||||
* Invoked from a container thread when an async request times out before
|
||||
* the {@code DeferredResult} has been set. Implementations may invoke
|
||||
* {@link DeferredResult#setResult(Object) setResult} or
|
||||
* {@link DeferredResult#setErrorResult(Object) to resume processing.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult that has been set
|
||||
* @param deferredResult the DeferredResult for the current request; if the
|
||||
* {@code DeferredResult} is set, then concurrent processing is resumed and
|
||||
* subsequent interceptors are not invoked
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
void afterExpiration(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception;
|
||||
<T> void afterTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked from a container thread when an async request completed for any
|
||||
* reason including timeout and network error. This method is useful for
|
||||
* detecting that a {@code DeferredResult} instance is no longer usable.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param deferredResult the DeferredResult for the current request
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
<T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.context.request.async;
|
||||
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Abstract adapter class for the {@link DeferredResultProcessingInterceptor}
|
||||
* interface for simplified implementation of individual methods.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class DeferredResultProcessingInterceptorAdapter implements DeferredResultProcessingInterceptor {
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult,
|
||||
Object concurrentResult) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void afterTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty.
|
||||
*/
|
||||
public <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import javax.servlet.AsyncListener;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
||||
@@ -50,7 +49,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
||||
|
||||
private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
|
||||
|
||||
private Runnable timeoutHandler = new DefaultTimeoutHandler();
|
||||
private Runnable timeoutHandler;
|
||||
|
||||
private final List<Runnable> completionHandlers = new ArrayList<Runnable>();
|
||||
|
||||
@@ -74,15 +73,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>If not set, by default a timeout is handled by returning
|
||||
* SERVICE_UNAVAILABLE (503).
|
||||
*/
|
||||
public void setTimeoutHandler(Runnable timeoutHandler) {
|
||||
if (timeoutHandler != null) {
|
||||
this.timeoutHandler = timeoutHandler;
|
||||
}
|
||||
this.timeoutHandler = timeoutHandler;
|
||||
}
|
||||
|
||||
public void addCompletionHandler(Runnable runnable) {
|
||||
@@ -135,31 +127,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
||||
}
|
||||
|
||||
public void onTimeout(AsyncEvent event) throws IOException {
|
||||
this.timeoutHandler.run();
|
||||
if (this.timeoutHandler != null) {
|
||||
this.timeoutHandler.run();
|
||||
}
|
||||
}
|
||||
|
||||
public void onComplete(AsyncEvent event) throws IOException {
|
||||
for (Runnable runnable : this.completionHandlers) {
|
||||
runnable.run();
|
||||
for (Runnable handler : this.completionHandlers) {
|
||||
handler.run();
|
||||
}
|
||||
this.asyncContext = null;
|
||||
this.asyncCompleted.set(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a SERVICE_UNAVAILABLE (503).
|
||||
*/
|
||||
private class DefaultTimeoutHandler implements Runnable {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
getResponse().sendError(HttpStatus.SERVICE_UNAVAILABLE.value());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,8 +44,6 @@ import org.springframework.web.util.UrlPathHelper;
|
||||
* result can be accessed via {@link #getConcurrentResult()} or its presence
|
||||
* detected via {@link #hasConcurrentResult()}.
|
||||
*
|
||||
* <p>TODO .. Servlet 3 config
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*
|
||||
@@ -61,13 +59,13 @@ public final class WebAsyncManager {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(WebAsyncManager.class);
|
||||
|
||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
|
||||
private AsyncWebRequest asyncWebRequest;
|
||||
|
||||
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());
|
||||
|
||||
private Runnable timeoutHandler;
|
||||
|
||||
private Object concurrentResult = RESULT_NONE;
|
||||
|
||||
private Object[] concurrentResultContext;
|
||||
@@ -78,8 +76,6 @@ public final class WebAsyncManager {
|
||||
private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
|
||||
new LinkedHashMap<Object, DeferredResultProcessingInterceptor>();
|
||||
|
||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
|
||||
/**
|
||||
* Package private constructor.
|
||||
@@ -119,15 +115,6 @@ public final class WebAsyncManager {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the handler to use when concurrent handling times out. If not set, by
|
||||
* default a timeout is handled by returning SERVICE_UNAVAILABLE (503).
|
||||
* @param timeoutHandler the handler
|
||||
*/
|
||||
public void setTimeoutHandler(Runnable timeoutHandler) {
|
||||
this.timeoutHandler = timeoutHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the selected handler for the current request chose to handle the
|
||||
* request asynchronously. A return value of "true" indicates concurrent
|
||||
@@ -226,42 +213,65 @@ public final class WebAsyncManager {
|
||||
*/
|
||||
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
|
||||
Assert.notNull(callable, "Callable must not be null");
|
||||
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
|
||||
|
||||
final CallableInterceptorChain chain = new CallableInterceptorChain(this.callableInterceptors.values());
|
||||
|
||||
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
|
||||
public void run() {
|
||||
logger.debug("Processing timeout");
|
||||
Object result = chain.triggerAfterTimeout(asyncWebRequest, callable);
|
||||
if (result != CallableProcessingInterceptor.RESULT_NONE) {
|
||||
setConcurrentResultAndDispatch(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||
public void run() {
|
||||
chain.triggerAfterCompletion(asyncWebRequest, callable);
|
||||
}
|
||||
});
|
||||
|
||||
startAsyncProcessing(processingContext);
|
||||
|
||||
this.taskExecutor.submit(new Runnable() {
|
||||
|
||||
public void run() {
|
||||
|
||||
CallableInterceptorChain chain =
|
||||
new CallableInterceptorChain(callableInterceptors.values());
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
chain.applyPreProcess(asyncWebRequest, callable);
|
||||
concurrentResult = callable.call();
|
||||
result = callable.call();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
concurrentResult = t;
|
||||
result = t;
|
||||
}
|
||||
finally {
|
||||
chain.applyPostProcess(asyncWebRequest, callable, concurrentResult);
|
||||
result = chain.applyPostProcess(asyncWebRequest, callable, result);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Concurrent result value [" + concurrentResult + "]");
|
||||
}
|
||||
|
||||
if (asyncWebRequest.isAsyncComplete()) {
|
||||
logger.error("Could not complete processing due to a timeout or network error");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Dispatching request to continue processing");
|
||||
asyncWebRequest.dispatch();
|
||||
setConcurrentResultAndDispatch(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setConcurrentResultAndDispatch(Object result) {
|
||||
synchronized (WebAsyncManager.this) {
|
||||
if (hasConcurrentResult()) {
|
||||
return;
|
||||
}
|
||||
concurrentResult = result;
|
||||
}
|
||||
|
||||
if (asyncWebRequest.isAsyncComplete()) {
|
||||
logger.error("Could not complete async processing due to timeout or network error");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Concurrent result value [" + concurrentResult + "]");
|
||||
logger.debug("Dispatching request to resume processing");
|
||||
|
||||
asyncWebRequest.dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link AsyncTask} to configure the task executor as well as
|
||||
* the timeout value of the {@code AsyncWebRequest} before delegating to
|
||||
@@ -273,6 +283,7 @@ public final class WebAsyncManager {
|
||||
*/
|
||||
public void startCallableProcessing(AsyncTask<?> asyncTask, Object... processingContext) {
|
||||
Assert.notNull(asyncTask, "AsyncTask must not be null");
|
||||
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
|
||||
|
||||
Long timeout = asyncTask.getTimeout();
|
||||
if (timeout != null) {
|
||||
@@ -302,53 +313,54 @@ public final class WebAsyncManager {
|
||||
* @see #getConcurrentResult()
|
||||
* @see #getConcurrentResultContext()
|
||||
*/
|
||||
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult,
|
||||
Object... processingContext) throws Exception {
|
||||
public void startDeferredResultProcessing(
|
||||
final DeferredResult<?> deferredResult, Object... processingContext) {
|
||||
|
||||
Assert.notNull(deferredResult, "DeferredResult must not be null");
|
||||
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
|
||||
|
||||
Long timeout = deferredResult.getTimeoutMilliseconds();
|
||||
if (timeout != null) {
|
||||
this.asyncWebRequest.setTimeout(timeout);
|
||||
}
|
||||
|
||||
if (deferredResult.hasTimeoutResult()) {
|
||||
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
|
||||
public void run() {
|
||||
deferredResult.applyTimeoutResult();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final DeferredResultInterceptorChain chain =
|
||||
new DeferredResultInterceptorChain(this.deferredResultInterceptors.values());
|
||||
|
||||
chain.applyPreProcess(this.asyncWebRequest, deferredResult);
|
||||
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
|
||||
public void run() {
|
||||
if (!deferredResult.applyTimeoutResult()) {
|
||||
try {
|
||||
chain.triggerAfterTimeout(asyncWebRequest, deferredResult);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
setConcurrentResultAndDispatch(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||
public void run() {
|
||||
if (deferredResult.expire()) {
|
||||
chain.triggerAfterExpiration(asyncWebRequest, deferredResult);
|
||||
}
|
||||
deferredResult.expire();
|
||||
chain.triggerAfterCompletion(asyncWebRequest, deferredResult);
|
||||
}
|
||||
});
|
||||
|
||||
startAsyncProcessing(processingContext);
|
||||
|
||||
deferredResult.setResultHandler(new DeferredResultHandler() {
|
||||
|
||||
public void handleResult(Object result) {
|
||||
concurrentResult = result;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Deferred result value [" + concurrentResult + "]");
|
||||
try {
|
||||
chain.applyPreProcess(this.asyncWebRequest, deferredResult);
|
||||
deferredResult.setResultHandler(new DeferredResultHandler() {
|
||||
public void handleResult(Object result) {
|
||||
result = chain.applyPostProcess(asyncWebRequest, deferredResult, result);
|
||||
setConcurrentResultAndDispatch(result);
|
||||
}
|
||||
|
||||
chain.applyPostProcess(asyncWebRequest, deferredResult, result);
|
||||
|
||||
logger.debug("Dispatching request to complete processing");
|
||||
asyncWebRequest.dispatch();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
setConcurrentResultAndDispatch(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void startAsyncProcessing(Object[] processingContext) {
|
||||
@@ -356,15 +368,10 @@ public final class WebAsyncManager {
|
||||
clearConcurrentResult();
|
||||
this.concurrentResultContext = processingContext;
|
||||
|
||||
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
|
||||
this.asyncWebRequest.startAsync();
|
||||
|
||||
if (this.timeoutHandler != null) {
|
||||
this.asyncWebRequest.setTimeoutHandler(this.timeoutHandler);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
|
||||
HttpServletRequest request = this.asyncWebRequest.getNativeRequest(HttpServletRequest.class);
|
||||
String requestUri = urlPathHelper.getRequestUri(request);
|
||||
logger.debug("Concurrent handling starting for " + request.getMethod() + " [" + requestUri + "]");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user