Make DeferredResult more usable and testable
DeferredResult now has a setErrorResult method that can be set to an Exception or an error object, error view, etc. The new isSetOrExpired() method can be used to check pro-actively if the DeferredResult is still usable or not. The setDeferredResultHandler method is now public so tests may use it. Issue: SPR-9690, SPR-9689
This commit is contained in:
@@ -52,9 +52,9 @@ public interface AsyncWebRequest extends NativeWebRequest {
|
|||||||
void startAsync();
|
void startAsync();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the request is in asynchronous mode after a call to {@link #startAsync()}.
|
* Whether the request is in async mode following a call to {@link #startAsync()}.
|
||||||
* Returns "false" if asynchronous processing never started, has completed, or the
|
* Returns "false" if asynchronous processing never started, has completed,
|
||||||
* request was dispatched for further processing.
|
* or the request was dispatched for further processing.
|
||||||
*/
|
*/
|
||||||
boolean isAsyncStarted();
|
boolean isAsyncStarted();
|
||||||
|
|
||||||
@@ -65,13 +65,13 @@ public interface AsyncWebRequest extends NativeWebRequest {
|
|||||||
void dispatch();
|
void dispatch();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the request was dispatched to the container.
|
* Whether the request was dispatched to the container in order to resume
|
||||||
|
* processing after concurrent execution in an application thread.
|
||||||
*/
|
*/
|
||||||
boolean isDispatched();
|
boolean isDispatched();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether asynchronous processing has completed in which case the request
|
* Whether asynchronous processing has completed.
|
||||||
* response should no longer be used.
|
|
||||||
*/
|
*/
|
||||||
boolean isAsyncComplete();
|
boolean isAsyncComplete();
|
||||||
|
|
||||||
|
|||||||
@@ -13,136 +13,121 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.web.context.request.async;
|
package org.springframework.web.context.request.async;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.core.task.AsyncTaskExecutor;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DeferredResult provides an alternative to using a Callable for async request
|
* {@code DeferredResult} provides an alternative to returning a {@link Callable}
|
||||||
* processing. With a Callable the framework manages a thread on behalf of the
|
* for asynchronous request processing. While with a Callable, a thread is used
|
||||||
* application through an {@link AsyncTaskExecutor}. With a DeferredResult the
|
* to execute it on behalf of the application, with a DeferredResult the application
|
||||||
* application sets the result in a thread of its choice.
|
* sets the result whenever it needs to from a thread of its choice.
|
||||||
*
|
|
||||||
* <p>The following sequence describes the intended use scenario:
|
|
||||||
* <ol>
|
|
||||||
* <li>thread-1: framework calls application method
|
|
||||||
* <li>thread-1: application method returns a DeferredResult
|
|
||||||
* <li>thread-1: framework initializes DeferredResult
|
|
||||||
* <li>thread-2: application calls {@link #set(Object)}
|
|
||||||
* <li>thread-2: framework completes async processing with given result
|
|
||||||
* </ol>
|
|
||||||
*
|
|
||||||
* <p>If the application calls {@link #set(Object)} in thread-2 before the
|
|
||||||
* DeferredResult is initialized by the framework in thread-1, then thread-2
|
|
||||||
* will block and wait for the initialization to complete. Therefore an
|
|
||||||
* application should never create and set the DeferredResult in the same
|
|
||||||
* thread because the initialization will never complete.</p>
|
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
public final class DeferredResult<V> {
|
public final class DeferredResult<T> {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(DeferredResult.class);
|
private static final Log logger = LogFactory.getLog(DeferredResult.class);
|
||||||
|
|
||||||
private V result;
|
private static final Object RESULT_NONE = new Object();
|
||||||
|
|
||||||
|
private Object result = RESULT_NONE;
|
||||||
|
|
||||||
|
private final Object timeoutResult;
|
||||||
|
|
||||||
|
private final AtomicBoolean expired = new AtomicBoolean(false);
|
||||||
|
|
||||||
private DeferredResultHandler resultHandler;
|
private DeferredResultHandler resultHandler;
|
||||||
|
|
||||||
private final V timeoutValue;
|
private final Object lock = new Object();
|
||||||
|
|
||||||
private final boolean timeoutValueSet;
|
private final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
private boolean timeoutValueUsed;
|
|
||||||
|
|
||||||
private final CountDownLatch initializationLatch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
private final ReentrantLock setLock = new ReentrantLock();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance.
|
* Create a DeferredResult instance.
|
||||||
*/
|
*/
|
||||||
public DeferredResult() {
|
public DeferredResult() {
|
||||||
this.timeoutValue = null;
|
this(RESULT_NONE);
|
||||||
this.timeoutValueSet = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance also providing a default value to set if a timeout
|
* Create a DeferredResult with a default result to use in case of a timeout.
|
||||||
* occurs before {@link #set(Object)} is called.
|
* @param timeoutResult the result to use
|
||||||
*/
|
*/
|
||||||
public DeferredResult(V timeoutValue) {
|
public DeferredResult(Object timeoutResult) {
|
||||||
this.timeoutValue = timeoutValue;
|
this.timeoutResult = timeoutResult;
|
||||||
this.timeoutValueSet = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete async processing with the given value. If the DeferredResult is
|
* Set a handler to handle the result when set. Normally applications do not
|
||||||
* not fully initialized yet, this method will block and wait for that to
|
* use this method at runtime but may do so during testing.
|
||||||
* occur before proceeding. See the class level javadoc for more details.
|
|
||||||
*
|
|
||||||
* @throws StaleAsyncWebRequestException if the underlying async request
|
|
||||||
* has already timed out or ended due to a network error.
|
|
||||||
*/
|
*/
|
||||||
public void set(V value) throws StaleAsyncWebRequestException {
|
public void setResultHandler(DeferredResultHandler resultHandler) {
|
||||||
if (this.setLock.tryLock() && (!this.timeoutValueUsed)) {
|
this.resultHandler = resultHandler;
|
||||||
|
this.latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the result value and pass it on for handling.
|
||||||
|
* @param result the result value
|
||||||
|
* @return "true" if the result was set and passed on for handling;
|
||||||
|
* "false" if the result was already set or the async request expired.
|
||||||
|
* @see #isSetOrExpired()
|
||||||
|
*/
|
||||||
|
public boolean setResult(T result) {
|
||||||
|
return processResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an error result value and pass it on for handling. If the result is an
|
||||||
|
* {@link Exception} or {@link Throwable}, it will be processed as though the
|
||||||
|
* controller raised the exception. Otherwise it will be processed as if the
|
||||||
|
* controller returned the given result.
|
||||||
|
* @param result the error result value
|
||||||
|
* @return "true" if the result was set to the error value and passed on for handling;
|
||||||
|
* "false" if the result was already set or the async request expired.
|
||||||
|
* @see #isSetOrExpired()
|
||||||
|
*/
|
||||||
|
public boolean setErrorResult(Object result) {
|
||||||
|
return processResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processResult(Object result) {
|
||||||
|
synchronized (this.lock) {
|
||||||
|
|
||||||
|
if (isSetOrExpired()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.result = result;
|
||||||
|
|
||||||
|
if (!awaitResultHandler()) {
|
||||||
|
throw new IllegalStateException("DeferredResultHandler not set");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
handle(value);
|
this.resultHandler.handleResult(result);
|
||||||
}
|
}
|
||||||
finally {
|
catch (Throwable t) {
|
||||||
this.setLock.unlock();
|
logger.trace("DeferredResult not handled", t);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
// A timeout is in progress or has already occurred
|
|
||||||
throw new StaleAsyncWebRequestException("Async request timed out");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An alternative to {@link #set(Object)} that absorbs a potential
|
|
||||||
* {@link StaleAsyncWebRequestException}.
|
|
||||||
* @return {@code false} if the outcome was a {@code StaleAsyncWebRequestException}
|
|
||||||
*/
|
|
||||||
public boolean trySet(V result) throws StaleAsyncWebRequestException {
|
|
||||||
try {
|
|
||||||
set(result);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (StaleAsyncWebRequestException ex) {
|
|
||||||
// absorb
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handle(V result) throws StaleAsyncWebRequestException {
|
private boolean awaitResultHandler() {
|
||||||
Assert.isNull(this.result, "A deferred result can be set once only");
|
|
||||||
this.result = result;
|
|
||||||
this.timeoutValueUsed = (this.timeoutValueSet && (this.result == this.timeoutValue));
|
|
||||||
if (!await()) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Gave up on waiting for DeferredResult to be initialized. " +
|
|
||||||
"Are you perhaps creating and setting a DeferredResult in the same thread? " +
|
|
||||||
"The DeferredResult must be fully initialized before you can set it. " +
|
|
||||||
"See the class javadoc for more details");
|
|
||||||
}
|
|
||||||
if (this.timeoutValueUsed) {
|
|
||||||
logger.debug("Using default timeout value");
|
|
||||||
}
|
|
||||||
this.resultHandler.handle(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean await() {
|
|
||||||
try {
|
try {
|
||||||
return this.initializationLatch.await(10, TimeUnit.SECONDS);
|
return this.latch.await(5, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
return false;
|
return false;
|
||||||
@@ -150,43 +135,32 @@ public final class DeferredResult<V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a handler to use to complete processing using the default timeout value
|
* Whether the DeferredResult can no longer be set either because the async
|
||||||
* provided via {@link #DeferredResult(Object)} or {@code null} if no timeout
|
* request expired or because it was already set.
|
||||||
* value was provided.
|
|
||||||
*/
|
*/
|
||||||
Runnable getTimeoutHandler() {
|
public boolean isSetOrExpired() {
|
||||||
if (!this.timeoutValueSet) {
|
return (this.expired.get() || (this.result != RESULT_NONE));
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Runnable() {
|
|
||||||
public void run() { useTimeoutValue(); }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void useTimeoutValue() {
|
void setExpired() {
|
||||||
this.setLock.lock();
|
this.expired.set(true);
|
||||||
try {
|
|
||||||
if (this.result == null) {
|
|
||||||
handle(this.timeoutValue);
|
|
||||||
this.timeoutValueUsed = true;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.setLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(DeferredResultHandler handler) {
|
boolean hasTimeoutResult() {
|
||||||
this.resultHandler = handler;
|
return this.timeoutResult != RESULT_NONE;
|
||||||
this.initializationLatch.countDown();
|
}
|
||||||
|
|
||||||
|
boolean applyTimeoutResult() {
|
||||||
|
return hasTimeoutResult() ? processResult(this.timeoutResult) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completes processing when {@link DeferredResult#set(Object)} is called.
|
* Handles a DeferredResult value when set.
|
||||||
*/
|
*/
|
||||||
interface DeferredResultHandler {
|
public interface DeferredResultHandler {
|
||||||
|
|
||||||
void handle(Object result) throws StaleAsyncWebRequestException;
|
void handleResult(Object result);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raised if a stale AsyncWebRequest is detected during async request processing.
|
|
||||||
*
|
|
||||||
* @author Rossen Stoyanchev
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class StaleAsyncWebRequestException extends RuntimeException {
|
|
||||||
|
|
||||||
public StaleAsyncWebRequestException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -55,6 +55,12 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
|||||||
|
|
||||||
private final List<Runnable> completionHandlers = new ArrayList<Runnable>();
|
private final List<Runnable> completionHandlers = new ArrayList<Runnable>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance for the given request/response pair.
|
||||||
|
* @param request current HTTP request
|
||||||
|
* @param response current HTTP response
|
||||||
|
*/
|
||||||
public StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
|
public StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||||
super(request, response);
|
super(request, response);
|
||||||
}
|
}
|
||||||
@@ -94,7 +100,6 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
|||||||
return this.asyncCompleted.get();
|
return this.asyncCompleted.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void startAsync() {
|
public void startAsync() {
|
||||||
Assert.state(getRequest().isAsyncSupported(),
|
Assert.state(getRequest().isAsyncSupported(),
|
||||||
"Async support must be enabled on a servlet and for all filters involved " +
|
"Async support must be enabled on a servlet and for all filters involved " +
|
||||||
|
|||||||
@@ -269,24 +269,35 @@ public final class WebAsyncManager {
|
|||||||
|
|
||||||
startAsyncProcessing(processingContext);
|
startAsyncProcessing(processingContext);
|
||||||
|
|
||||||
deferredResult.init(new DeferredResultHandler() {
|
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
deferredResult.setExpired();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
public void handle(Object result) {
|
if (deferredResult.hasTimeoutResult()) {
|
||||||
|
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
deferredResult.applyTimeoutResult();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deferredResult.setResultHandler(new DeferredResultHandler() {
|
||||||
|
|
||||||
|
public void handleResult(Object result) {
|
||||||
concurrentResult = result;
|
concurrentResult = result;
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Deferred result value [" + concurrentResult + "]");
|
logger.debug("Deferred result value [" + concurrentResult + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asyncWebRequest.isAsyncComplete()) {
|
Assert.state(!asyncWebRequest.isAsyncComplete(),
|
||||||
throw new StaleAsyncWebRequestException("Could not complete processing due to a timeout or network error");
|
"Cannot handle DeferredResult [ " + deferredResult + " ] due to a timeout or network error");
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Dispatching request to complete processing");
|
logger.debug("Dispatching request to complete processing");
|
||||||
asyncWebRequest.dispatch();
|
asyncWebRequest.dispatch();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.asyncWebRequest.setTimeoutHandler(deferredResult.getTimeoutHandler());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startAsyncProcessing(Object... context) {
|
private void startAsyncProcessing(Object... context) {
|
||||||
|
|||||||
@@ -17,16 +17,13 @@
|
|||||||
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.reset;
|
|
||||||
import static org.easymock.EasyMock.verify;
|
import static org.easymock.EasyMock.verify;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.web.context.request.async.DeferredResult;
|
|
||||||
import org.springframework.web.context.request.async.StaleAsyncWebRequestException;
|
|
||||||
import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler;
|
import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,79 +34,96 @@ import org.springframework.web.context.request.async.DeferredResult.DeferredResu
|
|||||||
public class DeferredResultTests {
|
public class DeferredResultTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void set() {
|
public void setResult() {
|
||||||
DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class);
|
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
|
||||||
DeferredResult<String> deferredResult = new DeferredResult<String>();
|
handler.handleResult("hello");
|
||||||
deferredResult.init(resultHandler);
|
replay(handler);
|
||||||
|
|
||||||
resultHandler.handle("foo");
|
DeferredResult<String> result = new DeferredResult<String>();
|
||||||
replay(resultHandler);
|
result.setResultHandler(handler);
|
||||||
|
|
||||||
deferredResult.set("foo");
|
assertTrue(result.setResult("hello"));
|
||||||
|
|
||||||
verify(resultHandler);
|
verify(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getTimeoutHandler() {
|
public void setResultTwice() {
|
||||||
assertNull(new DeferredResult<String>().getTimeoutHandler());
|
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
|
||||||
assertNotNull(new DeferredResult<String>("foo").getTimeoutHandler());
|
handler.handleResult("hello");
|
||||||
|
replay(handler);
|
||||||
|
|
||||||
|
DeferredResult<String> result = new DeferredResult<String>();
|
||||||
|
result.setResultHandler(handler);
|
||||||
|
|
||||||
|
assertTrue(result.setResult("hello"));
|
||||||
|
assertFalse(result.setResult("hi"));
|
||||||
|
|
||||||
|
verify(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleTimeout() {
|
public void setResultWithException() {
|
||||||
DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class);
|
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
|
||||||
resultHandler.handle("foo");
|
handler.handleResult("hello");
|
||||||
replay(resultHandler);
|
expectLastCall().andThrow(new IllegalStateException());
|
||||||
|
replay(handler);
|
||||||
|
|
||||||
DeferredResult<String> deferredResult = new DeferredResult<String>("foo");
|
DeferredResult<String> result = new DeferredResult<String>();
|
||||||
deferredResult.init(resultHandler);
|
result.setResultHandler(handler);
|
||||||
|
|
||||||
deferredResult.getTimeoutHandler().run();
|
assertFalse(result.setResult("hello"));
|
||||||
|
|
||||||
verify(resultHandler);
|
verify(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setAfterTimeoutValueUsed() {
|
public void isSetOrExpired() {
|
||||||
DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class);
|
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
|
||||||
resultHandler.handle("foo");
|
handler.handleResult("hello");
|
||||||
replay(resultHandler);
|
replay(handler);
|
||||||
|
|
||||||
DeferredResult<String> deferredResult = new DeferredResult<String>("foo");
|
DeferredResult<String> result = new DeferredResult<String>();
|
||||||
deferredResult.init(resultHandler);
|
result.setResultHandler(handler);
|
||||||
|
|
||||||
deferredResult.getTimeoutHandler().run();
|
assertFalse(result.isSetOrExpired());
|
||||||
|
|
||||||
verify(resultHandler);
|
result.setResult("hello");
|
||||||
|
|
||||||
try {
|
assertTrue(result.isSetOrExpired());
|
||||||
deferredResult.set("foo");
|
|
||||||
fail("Expected exception");
|
verify(handler);
|
||||||
}
|
|
||||||
catch (StaleAsyncWebRequestException ex) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setBeforeTimeoutValueUsed() {
|
public void setExpired() {
|
||||||
DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class);
|
DeferredResult<String> result = new DeferredResult<String>();
|
||||||
resultHandler.handle("foo");
|
assertFalse(result.isSetOrExpired());
|
||||||
replay(resultHandler);
|
|
||||||
|
|
||||||
DeferredResult<String> deferredResult = new DeferredResult<String>("foo");
|
result.setExpired();
|
||||||
deferredResult.init(resultHandler);
|
assertTrue(result.isSetOrExpired());
|
||||||
deferredResult.set("foo");
|
assertFalse(result.setResult("hello"));
|
||||||
|
}
|
||||||
|
|
||||||
verify(resultHandler);
|
@Test
|
||||||
|
public void hasTimeout() {
|
||||||
|
assertFalse(new DeferredResult<String>().hasTimeoutResult());
|
||||||
|
assertTrue(new DeferredResult<String>("timed out").hasTimeoutResult());
|
||||||
|
}
|
||||||
|
|
||||||
reset(resultHandler);
|
@Test
|
||||||
replay(resultHandler);
|
public void applyTimeoutResult() {
|
||||||
|
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
|
||||||
|
handler.handleResult("timed out");
|
||||||
|
replay(handler);
|
||||||
|
|
||||||
deferredResult.getTimeoutHandler().run();
|
DeferredResult<String> result = new DeferredResult<String>("timed out");
|
||||||
|
result.setResultHandler(handler);
|
||||||
|
|
||||||
verify(resultHandler);
|
assertTrue(result.applyTimeoutResult());
|
||||||
|
assertFalse("Shouldn't be able to set result after timeout", result.setResult("hello"));
|
||||||
|
|
||||||
|
verify(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,17 +141,17 @@ public class WebAsyncManagerTests {
|
|||||||
|
|
||||||
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
|
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
|
||||||
|
|
||||||
deferredResult.set(25);
|
deferredResult.setResult(25);
|
||||||
assertEquals(25, this.asyncManager.getConcurrentResult());
|
assertEquals(25, this.asyncManager.getConcurrentResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=StaleAsyncWebRequestException.class)
|
@Test
|
||||||
public void startDeferredResultProcessing_staleRequest() throws Exception {
|
public void startDeferredResultProcessingStaleRequest() throws Exception {
|
||||||
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
|
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
|
||||||
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
||||||
|
|
||||||
this.stubAsyncWebRequest.setAsyncComplete(true);
|
this.stubAsyncWebRequest.setAsyncComplete(true);
|
||||||
deferredResult.set(1);
|
assertFalse(deferredResult.setResult(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -705,7 +705,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Found concurrent result value [" + result + "]");
|
logger.debug("Found concurrent result value [" + result + "]");
|
||||||
}
|
}
|
||||||
requestMappingMethod = requestMappingMethod.wrapConcurrentProcessingResult(result);
|
requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
|
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
|||||||
* from an async operation essentially either applying return value handling or
|
* from an async operation essentially either applying return value handling or
|
||||||
* raising an exception if the end result is an Exception.
|
* raising an exception if the end result is an Exception.
|
||||||
*/
|
*/
|
||||||
ServletInvocableHandlerMethod wrapConcurrentProcessingResult(final Object result) {
|
ServletInvocableHandlerMethod wrapConcurrentResult(final Object result) {
|
||||||
|
|
||||||
return new CallableHandlerMethod(new Callable<Object>() {
|
return new CallableHandlerMethod(new Callable<Object>() {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user