diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java index 858845dcdb..887a40e7dc 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java @@ -181,7 +181,7 @@ public final class AsyncExecutionChain { * the threading model, i.e. whether a TaskExecutor is used. * @see DeferredResult */ - public void startDeferredResultProcessing(final DeferredResult deferredResult) { + public void startDeferredResultProcessing(final DeferredResult deferredResult) { Assert.notNull(deferredResult, "DeferredResult is required"); startAsync(); deferredResult.init(new DeferredResultHandler() { @@ -193,13 +193,7 @@ public final class AsyncExecutionChain { new AsyncExecutionChainRunnable(asyncWebRequest, buildChain()).run(); } }); - if (deferredResult.canHandleTimeout()) { - this.asyncWebRequest.setTimeoutHandler(new Runnable() { - public void run() { - deferredResult.handleTimeout(); - } - }); - } + this.asyncWebRequest.setTimeoutHandler(deferredResult.getTimeoutHandler()); } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java index 5bcfdaabca..8a5eadccc2 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java @@ -47,68 +47,68 @@ import org.springframework.util.Assert; * @author Rossen Stoyanchev * @since 3.2 */ -public final class DeferredResult { +public final class DeferredResult { - private final static Object TIMEOUT_RESULT_NONE = new Object(); - - private Object result; - - private final Object timeoutResult; + private V result; private DeferredResultHandler resultHandler; - private final CountDownLatch readySignal = new CountDownLatch(1); + private final V timeoutValue; - private final ReentrantLock timeoutLock = new ReentrantLock(); + private final boolean timeoutValueSet; + + private boolean timeoutValueUsed; + + private final CountDownLatch initializationLatch = new CountDownLatch(1); + + private final ReentrantLock setLock = new ReentrantLock(); /** * Create a new instance. */ public DeferredResult() { - this(TIMEOUT_RESULT_NONE); + this.timeoutValue = null; + this.timeoutValueSet = false; } /** - * Create a new instance and also provide a default result to use if a - * timeout occurs before {@link #set(Object)} is called. + * Create a new instance also providing a default value to set if a timeout + * occurs before {@link #set(Object)} is called. */ - public DeferredResult(Object timeoutResult) { - this.timeoutResult = timeoutResult; - } - - boolean canHandleTimeout() { - return this.timeoutResult != TIMEOUT_RESULT_NONE; + public DeferredResult(V timeoutValue) { + this.timeoutValue = timeoutValue; + this.timeoutValueSet = true; } /** - * Complete async processing with the given result. If the DeferredResult is - * not yet fully initialized, this method will block and wait for that to + * Complete async processing with the given value. If the DeferredResult is + * not fully initialized yet, this method will block and wait for that to * 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(Object result) throws StaleAsyncWebRequestException { - if (this.timeoutLock.tryLock() && (this.result != this.timeoutResult)) { + public void set(V value) throws StaleAsyncWebRequestException { + if (this.setLock.tryLock() && (!this.timeoutValueUsed)) { try { - handle(result); + handle(value); } finally { - this.timeoutLock.unlock(); + this.setLock.unlock(); } } else { - // A timeout is in progress - throw new StaleAsyncWebRequestException("Async request already timed out"); + // A timeout is in progress or has already occurred + throw new StaleAsyncWebRequestException("Async request timed out"); } } /** - * A variant of {@link #set(Object)} that absorbs a potential, resulting + * 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(Object result) throws StaleAsyncWebRequestException { + public boolean trySet(V result) throws StaleAsyncWebRequestException { try { set(result); return true; @@ -119,29 +119,12 @@ public final class DeferredResult { return false; } - /** - * Invoked to complete async processing when a timeout occurs before - * {@link #set(Object)} is called. Or if {@link #set(Object)} is already in - * progress, this method blocks, waits for it to complete, and then returns. - */ - void handleTimeout() { - Assert.state(canHandleTimeout(), "Can't handle timeout"); - this.timeoutLock.lock(); - try { - if (this.result == null) { - handle(this.timeoutResult); - } - } - finally { - this.timeoutLock.unlock(); - } - } - - private void handle(Object result) throws StaleAsyncWebRequestException { + private void handle(V result) throws StaleAsyncWebRequestException { Assert.isNull(this.result, "A deferred result can be set once only"); this.result = result; + this.timeoutValueUsed = (this.timeoutValueSet && (this.result == this.timeoutValue)); try { - this.readySignal.await(10, TimeUnit.SECONDS); + this.initializationLatch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new IllegalStateException( @@ -153,9 +136,35 @@ public final class DeferredResult { this.resultHandler.handle(result); } + /** + * Return a handler to use to complete processing using the default timeout value + * provided via {@link #DeferredResult(Object)} or {@code null} if no timeout + * value was provided. + */ + Runnable getTimeoutHandler() { + if (!this.timeoutValueSet) { + return null; + } + return new Runnable() { + public void run() { useTimeoutValue(); } + }; + } + + private void useTimeoutValue() { + this.setLock.lock(); + try { + if (this.result == null) { + handle(this.timeoutValue); + this.timeoutValueUsed = true; + } + } finally { + this.setLock.unlock(); + } + } + void init(DeferredResultHandler handler) { this.resultHandler = handler; - this.readySignal.countDown(); + this.initializationLatch.countDown(); } diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/AsyncExecutionChainTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/AsyncExecutionChainTests.java index 0857ff9df0..2e7d83877d 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/AsyncExecutionChainTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/AsyncExecutionChainTests.java @@ -144,7 +144,7 @@ public class AsyncExecutionChainTests { this.chain.addDelegatingCallable(new IntegerIncrementingCallable()); this.chain.addDelegatingCallable(new IntegerIncrementingCallable()); - DeferredResult deferredResult = new DeferredResult(); + DeferredResult deferredResult = new DeferredResult(); this.chain.startDeferredResultProcessing(deferredResult); assertTrue(this.asyncWebRequest.isAsyncStarted()); @@ -159,7 +159,7 @@ public class AsyncExecutionChainTests { this.asyncWebRequest.startAsync(); this.asyncWebRequest.complete(); - DeferredResult deferredResult = new DeferredResult(); + DeferredResult deferredResult = new DeferredResult(); this.chain.startDeferredResultProcessing(deferredResult); deferredResult.set(1); } diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java index fc91196aad..667e084de6 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/DeferredResultTests.java @@ -20,8 +20,8 @@ import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.reset; import static org.easymock.EasyMock.verify; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import org.junit.Test; @@ -34,16 +34,10 @@ import org.springframework.web.context.request.async.DeferredResult.DeferredResu */ public class DeferredResultTests { - @Test - public void canHandleTimeout() { - assertFalse(new DeferredResult().canHandleTimeout()); - assertTrue(new DeferredResult("foo").canHandleTimeout()); - } - @Test public void set() { DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class); - DeferredResult deferredResult = new DeferredResult(); + DeferredResult deferredResult = new DeferredResult(); deferredResult.init(resultHandler); resultHandler.handle("foo"); @@ -54,35 +48,36 @@ public class DeferredResultTests { verify(resultHandler); } + @Test + public void getTimeoutHandler() { + assertNull(new DeferredResult().getTimeoutHandler()); + assertNotNull(new DeferredResult("foo").getTimeoutHandler()); + } + @Test public void handleTimeout() { DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class); - DeferredResult deferredResult = new DeferredResult("foo"); - deferredResult.init(resultHandler); - resultHandler.handle("foo"); replay(resultHandler); - deferredResult.handleTimeout(); + DeferredResult deferredResult = new DeferredResult("foo"); + deferredResult.init(resultHandler); + + deferredResult.getTimeoutHandler().run(); verify(resultHandler); } - @Test(expected=IllegalStateException.class) - public void handleTimeout_timeoutResultNone() { - new DeferredResult().handleTimeout(); - } - @Test - public void setAfterHandleTimeout() { + public void setAfterTimeoutValueUsed() { DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class); - DeferredResult deferredResult = new DeferredResult("foo"); - deferredResult.init(resultHandler); - resultHandler.handle("foo"); replay(resultHandler); - deferredResult.handleTimeout(); + DeferredResult deferredResult = new DeferredResult("foo"); + deferredResult.init(resultHandler); + + deferredResult.getTimeoutHandler().run(); verify(resultHandler); @@ -96,14 +91,13 @@ public class DeferredResultTests { } @Test - public void setBeforeHandleTimeout() { + public void setBeforeTimeoutValueUsed() { DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class); - DeferredResult deferredResult = new DeferredResult("foo"); - deferredResult.init(resultHandler); - resultHandler.handle("foo"); replay(resultHandler); + DeferredResult deferredResult = new DeferredResult("foo"); + deferredResult.init(resultHandler); deferredResult.set("foo"); verify(resultHandler); @@ -111,7 +105,7 @@ public class DeferredResultTests { reset(resultHandler); replay(resultHandler); - deferredResult.handleTimeout(); + deferredResult.getTimeoutHandler().run(); verify(resultHandler); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncMethodReturnValueHandler.java index 5152ac19fc..d7cd9d95d8 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncMethodReturnValueHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AsyncMethodReturnValueHandler.java @@ -63,7 +63,7 @@ public class AsyncMethodReturnValueHandler implements HandlerMethodReturnValueHa chain.startCallableChainProcessing(); } else if (DeferredResult.class.isAssignableFrom(paramType)) { - chain.startDeferredResultProcessing((DeferredResult) returnValue); + chain.startDeferredResultProcessing((DeferredResult) returnValue); } else { // should never happen.. diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index 09a15724e6..2d11eea675 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -22,6 +22,7 @@ Changes in version 3.2 M2 (2012-08-xx) * add @ExceptionResolver annotation to detect classes with @ExceptionHandler methods * move RSS/Atom message converter registration ahead of jackson/jaxb2 * handle BindException in DefaultHandlerExceptionResolver +* Parameterize DefaultResult type Changes in version 3.2 M1 (2012-05-28)