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:
Rossen Stoyanchev
2012-10-26 17:55:28 -04:00
parent 06e34f05a6
commit f036ed639f
23 changed files with 860 additions and 333 deletions

View File

@@ -17,7 +17,6 @@
package org.springframework.web.context.request.async;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertFalse;
@@ -62,21 +61,6 @@ public class DeferredResultTests {
verify(handler);
}
@Test
public void setResultWithException() {
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
handler.handleResult("hello");
expectLastCall().andThrow(new IllegalStateException());
replay(handler);
DeferredResult<String> result = new DeferredResult<String>();
result.setResultHandler(handler);
assertFalse(result.setResult("hello"));
verify(handler);
}
@Test
public void isSetOrExpired() {
DeferredResultHandler handler = createMock(DeferredResultHandler.class);
@@ -105,12 +89,6 @@ public class DeferredResultTests {
assertFalse(result.setResult("hello"));
}
@Test
public void hasTimeout() {
assertFalse(new DeferredResult<String>().hasTimeoutResult());
assertTrue(new DeferredResult<String>(null, "timed out").hasTimeoutResult());
}
@Test
public void applyTimeoutResult() {
DeferredResultHandler handler = createMock(DeferredResultHandler.class);

View File

@@ -33,7 +33,6 @@ import javax.servlet.AsyncEvent;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockAsyncContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -120,7 +119,7 @@ public class StandardServletAsyncWebRequestTests {
@Test
public void onTimeoutDefaultBehavior() throws Exception {
this.asyncRequest.onTimeout(new AsyncEvent(null));
assertEquals(HttpStatus.SERVICE_UNAVAILABLE.value(), this.response.getStatus());
assertEquals(200, this.response.getStatus());
}
@Test

View File

@@ -19,6 +19,7 @@ package org.springframework.web.context.request.async;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.notNull;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
@@ -30,15 +31,15 @@ import static org.junit.Assert.fail;
import java.util.concurrent.Callable;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.mock.web.MockHttpServletRequest;
/**
* Test fixture with an {@link WebAsyncManager}.
* Test fixture with an {@link WebAsyncManager} with a mock AsyncWebRequest.
*
* @author Rossen Stoyanchev
*/
@@ -48,6 +49,7 @@ public class WebAsyncManagerTests {
private AsyncWebRequest asyncWebRequest;
@Before
public void setUp() {
this.asyncManager = WebAsyncUtils.getAsyncManager(new MockHttpServletRequest());
@@ -63,6 +65,27 @@ public class WebAsyncManagerTests {
reset(this.asyncWebRequest);
}
@Test
public void startAsyncProcessingWithoutAsyncWebRequest() {
WebAsyncManager manager = WebAsyncUtils.getAsyncManager(new MockHttpServletRequest());
try {
manager.startCallableProcessing(new StubCallable(1));
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertEquals(ex.getMessage(), "AsyncWebRequest must not be null");
}
try {
manager.startDeferredResultProcessing(new DeferredResult<String>());
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertEquals(ex.getMessage(), "AsyncWebRequest must not be null");
}
}
@Test
public void isConcurrentHandlingStarted() {
@@ -91,32 +114,102 @@ public class WebAsyncManagerTests {
@Test
public void startCallableProcessing() throws Exception {
Callable<Object> task = new StubCallable();
int concurrentResult = 21;
Callable<Object> task = new StubCallable(concurrentResult);
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, task);
interceptor.postProcess(this.asyncWebRequest, task, new Integer(1));
interceptor.postProcess(this.asyncWebRequest, task, new Integer(concurrentResult));
replay(interceptor);
this.asyncWebRequest.startAsync();
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
this.asyncWebRequest.dispatch();
replay(this.asyncWebRequest);
setupDefaultAsyncScenario();
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
this.asyncManager.startCallableProcessing(task);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(concurrentResult, this.asyncManager.getConcurrentResult());
verify(interceptor, this.asyncWebRequest);
}
@Test
public void startCallableProcessingAsyncTask() {
public void startCallableProcessingCallableException() throws Exception {
Exception concurrentResult = new Exception();
Callable<Object> task = new StubCallable(concurrentResult);
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, task);
interceptor.postProcess(this.asyncWebRequest, task, concurrentResult);
replay(interceptor);
setupDefaultAsyncScenario();
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
this.asyncManager.startCallableProcessing(task);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(concurrentResult, this.asyncManager.getConcurrentResult());
verify(interceptor, this.asyncWebRequest);
}
@Test
public void startCallableProcessingPreProcessException() throws Exception {
Callable<Object> task = new StubCallable(21);
Exception exception = new Exception();
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, task);
expectLastCall().andThrow(exception);
replay(interceptor);
setupDefaultAsyncScenario();
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
this.asyncManager.startCallableProcessing(task);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(exception, this.asyncManager.getConcurrentResult());
verify(interceptor, this.asyncWebRequest);
}
@Test
public void startCallableProcessingPostProcessException() throws Exception {
Callable<Object> task = new StubCallable(21);
Exception exception = new Exception();
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, task);
interceptor.postProcess(this.asyncWebRequest, task, 21);
expectLastCall().andThrow(exception);
replay(interceptor);
setupDefaultAsyncScenario();
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
this.asyncManager.startCallableProcessing(task);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(exception, this.asyncManager.getConcurrentResult());
verify(interceptor, this.asyncWebRequest);
}
@Test
public void startCallableProcessingWithAsyncTask() {
AsyncTaskExecutor executor = createMock(AsyncTaskExecutor.class);
expect(executor.submit((Runnable) notNull())).andReturn(null);
replay(executor);
this.asyncWebRequest.setTimeout(1000L);
this.asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject());
this.asyncWebRequest.addCompletionHandler(EasyMock.<Runnable>anyObject());
this.asyncWebRequest.startAsync();
replay(this.asyncWebRequest);
@@ -128,7 +221,7 @@ public class WebAsyncManagerTests {
}
@Test
public void startCallableProcessingNullCallable() {
public void startCallableProcessingNullInput() {
try {
this.asyncManager.startCallableProcessing((Callable<?>) null);
fail("Expected exception");
@@ -138,72 +231,108 @@ public class WebAsyncManagerTests {
}
}
@Test
public void startCallableProcessingNullRequest() {
WebAsyncManager manager = WebAsyncUtils.getAsyncManager(new MockHttpServletRequest());
try {
manager.startCallableProcessing(new StubCallable());
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertEquals(ex.getMessage(), "AsyncWebRequest must not be null");
}
}
@Test
public void startDeferredResultProcessing() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(1000L, 10);
this.asyncWebRequest.setTimeout(1000L);
this.asyncWebRequest.setTimeoutHandler((Runnable) notNull());
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
this.asyncWebRequest.startAsync();
replay(this.asyncWebRequest);
DeferredResult<String> deferredResult = new DeferredResult<String>(1000L);
String concurrentResult = "abc";
DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, deferredResult);
interceptor.postProcess(asyncWebRequest, deferredResult, concurrentResult);
replay(interceptor);
this.asyncWebRequest.setTimeout(1000L);
setupDefaultAsyncScenario();
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult);
verify(this.asyncWebRequest, interceptor);
reset(this.asyncWebRequest, interceptor);
deferredResult.setResult(concurrentResult);
this.asyncWebRequest.dispatch();
replay(this.asyncWebRequest);
interceptor.postProcess(asyncWebRequest, deferredResult, 25);
replay(interceptor);
deferredResult.setResult(25);
assertEquals(25, this.asyncManager.getConcurrentResult());
assertEquals(concurrentResult, this.asyncManager.getConcurrentResult());
verify(this.asyncWebRequest, interceptor);
}
@Test
public void setTimeoutHandler() throws Exception {
public void startDeferredResultProcessingPreProcessException() throws Exception {
Runnable timeoutHandler = new Runnable() { public void run() {} };
this.asyncManager.setTimeoutHandler(timeoutHandler);
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
Exception exception = new Exception();
DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, deferredResult);
expectLastCall().andThrow(exception);
replay(interceptor);
setupDefaultAsyncScenario();
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult);
deferredResult.setResult(25);
assertEquals(exception, this.asyncManager.getConcurrentResult());
verify(this.asyncWebRequest, interceptor);
}
@Test
public void startDeferredResultProcessingPostProcessException() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
Exception exception = new Exception();
DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, deferredResult);
interceptor.postProcess(this.asyncWebRequest, deferredResult, 25);
expectLastCall().andThrow(exception);
replay(interceptor);
setupDefaultAsyncScenario();
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult);
deferredResult.setResult(25);
assertEquals(exception, this.asyncManager.getConcurrentResult());
verify(this.asyncWebRequest, interceptor);
}
@Test
public void startDeferredResultProcessingNullInput() {
try {
this.asyncManager.startDeferredResultProcessing((DeferredResult<?>) null);
fail("Expected exception");
}
catch (IllegalArgumentException ex) {
assertEquals(ex.getMessage(), "DeferredResult must not be null");
}
}
private void setupDefaultAsyncScenario() {
this.asyncWebRequest.setTimeoutHandler((Runnable) notNull());
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
this.asyncWebRequest.startAsync();
this.asyncWebRequest.setTimeoutHandler(timeoutHandler);
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
this.asyncWebRequest.dispatch();
replay(this.asyncWebRequest);
this.asyncManager.startCallableProcessing(new StubCallable());
verify(this.asyncWebRequest);
}
private final class StubCallable implements Callable<Object> {
private Object value;
public StubCallable(Object value) {
this.value = value;
}
public Object call() throws Exception {
return 1;
if (this.value instanceof Exception) {
throw ((Exception) this.value);
}
return this.value;
}
}

View File

@@ -0,0 +1,230 @@
/*
* 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 static org.springframework.web.context.request.async.CallableProcessingInterceptor.RESULT_NONE;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.notNull;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.concurrent.Callable;
import javax.servlet.AsyncEvent;
import javax.servlet.DispatcherType;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.NativeWebRequest;
/**
* {@link WebAsyncManager} tests where container-triggered timeout/completion
* events are simulated.
*
* @author Rossen Stoyanchev
*/
public class WebAsyncManagerTimeoutTests {
private static final AsyncEvent ASYNC_EVENT = null;
private WebAsyncManager asyncManager;
private StandardServletAsyncWebRequest asyncWebRequest;
private MockHttpServletRequest servletRequest;
@Before
public void setUp() {
this.servletRequest = new MockHttpServletRequest();
this.servletRequest.setAsyncSupported(true);
this.asyncWebRequest = new StandardServletAsyncWebRequest(servletRequest, new MockHttpServletResponse());
AsyncTaskExecutor executor = createMock(AsyncTaskExecutor.class);
expect(executor.submit((Runnable) notNull())).andReturn(null);
replay(executor);
this.asyncManager = WebAsyncUtils.getAsyncManager(servletRequest);
this.asyncManager.setTaskExecutor(executor);
this.asyncManager.setAsyncWebRequest(this.asyncWebRequest);
}
@Test
public void startCallableProcessingTimeoutAndComplete() throws Exception {
StubCallable callable = new StubCallable();
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
expect(interceptor.afterTimeout(this.asyncWebRequest, callable)).andReturn(RESULT_NONE);
interceptor.afterCompletion(this.asyncWebRequest, callable);
replay(interceptor);
this.asyncManager.registerCallableInterceptor("interceptor", interceptor);
this.asyncManager.startCallableProcessing(callable);
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
this.asyncWebRequest.onComplete(ASYNC_EVENT);
assertFalse(this.asyncManager.hasConcurrentResult());
assertEquals(DispatcherType.REQUEST, this.servletRequest.getDispatcherType());
verify(interceptor);
}
@Test
public void startCallableProcessingTimeoutAndResume() throws Exception {
StubCallable callable = new StubCallable();
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
expect(interceptor.afterTimeout(this.asyncWebRequest, callable)).andReturn(22);
replay(interceptor);
this.asyncManager.registerCallableInterceptor("timeoutInterceptor", interceptor);
this.asyncManager.startCallableProcessing(callable);
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
assertEquals(22, this.asyncManager.getConcurrentResult());
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
verify(interceptor);
}
@Test
public void startCallableProcessingAfterTimeoutException() throws Exception {
StubCallable callable = new StubCallable();
Exception exception = new Exception();
CallableProcessingInterceptor interceptor = createStrictMock(CallableProcessingInterceptor.class);
expect(interceptor.afterTimeout(this.asyncWebRequest, callable)).andThrow(exception);
replay(interceptor);
this.asyncManager.registerCallableInterceptor("timeoutInterceptor", interceptor);
this.asyncManager.startCallableProcessing(callable);
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
assertEquals(exception, this.asyncManager.getConcurrentResult());
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
verify(interceptor);
}
@Test
public void startDeferredResultProcessingTimeoutAndComplete() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
DeferredResultProcessingInterceptor interceptor = createStrictMock(DeferredResultProcessingInterceptor.class);
interceptor.preProcess(this.asyncWebRequest, deferredResult);
interceptor.afterTimeout(this.asyncWebRequest, deferredResult);
interceptor.afterCompletion(this.asyncWebRequest, deferredResult);
replay(interceptor);
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult);
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
this.asyncWebRequest.onComplete(ASYNC_EVENT);
assertFalse(this.asyncManager.hasConcurrentResult());
assertEquals(DispatcherType.REQUEST, this.servletRequest.getDispatcherType());
verify(interceptor);
}
@Test
public void startDeferredResultProcessingTimeoutAndResumeWithDefaultResult() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(null, 23);
DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() {
public <T> void afterTimeout(NativeWebRequest request, DeferredResult<T> result) throws Exception {
result.setErrorResult("should not get here");
}
};
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult);
AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event);
assertEquals(23, this.asyncManager.getConcurrentResult());
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
}
@Test
public void startDeferredResultProcessingTimeoutAndResumeWithInterceptor() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() {
public <T> void afterTimeout(NativeWebRequest request, DeferredResult<T> result) throws Exception {
result.setErrorResult(23);
}
};
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult);
AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event);
assertEquals(23, this.asyncManager.getConcurrentResult());
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
}
@Test
public void startDeferredResultProcessingAfterTimeoutException() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
final Exception exception = new Exception();
DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() {
public <T> void afterTimeout(NativeWebRequest request, DeferredResult<T> result) throws Exception {
throw exception;
}
};
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult);
AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event);
assertEquals(exception, this.asyncManager.getConcurrentResult());
assertEquals(DispatcherType.ASYNC, this.servletRequest.getDispatcherType());
}
private final class StubCallable implements Callable<Object> {
public Object call() throws Exception {
return 21;
}
}
}