Refactor Servlet 3 async support

As a result of the refactoring, the AsyncContext dispatch mechanism is
used much more centrally. Effectively every asynchronously processed
request involves one initial (container) thread, a second thread to
produce the handler return value asynchronously, and a third thread
as a result of a dispatch back to the container to resume processing
of the asynchronous resuilt.

Other updates include the addition of a MockAsyncContext and support
of related request method in the test packages of spring-web and
spring-webmvc. Also an upgrade of a Jetty test dependency required
to make tests pass.

Issue: SPR-9433
This commit is contained in:
Rossen Stoyanchev
2012-07-24 16:00:05 -04:00
parent 026ee846c7
commit 529e62921d
47 changed files with 1837 additions and 1741 deletions

View File

@@ -16,12 +16,16 @@
package org.springframework.http.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@@ -30,20 +34,16 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.util.FileCopyUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.util.FileCopyUtils;
public abstract class AbstractHttpRequestFactoryTestCase {
@@ -58,17 +58,23 @@ public abstract class AbstractHttpRequestFactoryTestCase {
int port = FreePortScanner.getFreePort();
jettyServer = new Server(port);
baseUrl = "http://localhost:" + port;
Context jettyContext = new Context(jettyServer, "/");
jettyContext.addServlet(new ServletHolder(new EchoServlet()), "/echo");
jettyContext.addServlet(new ServletHolder(new StatusServlet(200)), "/status/ok");
jettyContext.addServlet(new ServletHolder(new StatusServlet(404)), "/status/notfound");
jettyContext.addServlet(new ServletHolder(new MethodServlet("DELETE")), "/methods/delete");
jettyContext.addServlet(new ServletHolder(new MethodServlet("GET")), "/methods/get");
jettyContext.addServlet(new ServletHolder(new MethodServlet("HEAD")), "/methods/head");
jettyContext.addServlet(new ServletHolder(new MethodServlet("OPTIONS")), "/methods/options");
jettyContext.addServlet(new ServletHolder(new PostServlet()), "/methods/post");
jettyContext.addServlet(new ServletHolder(new MethodServlet("PUT")), "/methods/put");
jettyContext.addServlet(new ServletHolder(new MethodServlet("PATCH")), "/methods/patch");
ServletContextHandler handler = new ServletContextHandler();
handler.setContextPath("/");
handler.addServlet(new ServletHolder(new EchoServlet()), "/echo");
handler.addServlet(new ServletHolder(new EchoServlet()), "/echo");
handler.addServlet(new ServletHolder(new StatusServlet(200)), "/status/ok");
handler.addServlet(new ServletHolder(new StatusServlet(404)), "/status/notfound");
handler.addServlet(new ServletHolder(new MethodServlet("DELETE")), "/methods/delete");
handler.addServlet(new ServletHolder(new MethodServlet("GET")), "/methods/get");
handler.addServlet(new ServletHolder(new MethodServlet("HEAD")), "/methods/head");
handler.addServlet(new ServletHolder(new MethodServlet("OPTIONS")), "/methods/options");
handler.addServlet(new ServletHolder(new PostServlet()), "/methods/post");
handler.addServlet(new ServletHolder(new MethodServlet("PUT")), "/methods/put");
handler.addServlet(new ServletHolder(new MethodServlet("PATCH")), "/methods/patch");
jettyServer.setHandler(handler);
jettyServer.start();
}
@@ -179,6 +185,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
/**
* Servlet that sets a given status code.
*/
@SuppressWarnings("serial")
private static class StatusServlet extends GenericServlet {
private final int sc;
@@ -193,6 +200,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
}
}
@SuppressWarnings("serial")
private static class MethodServlet extends GenericServlet {
private final String method;
@@ -210,6 +218,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
}
}
@SuppressWarnings("serial")
private static class PostServlet extends MethodServlet {
private PostServlet() {
@@ -233,6 +242,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
}
}
@SuppressWarnings("serial")
private static class EchoServlet extends HttpServlet {
@Override

View File

@@ -0,0 +1,133 @@
/*
* 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.mock.web;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.web.util.WebUtils;
/**
* Mock implementation of the {@link AsyncContext} interface.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class MockAsyncContext implements AsyncContext {
private final ServletRequest request;
private final ServletResponse response;
private final MockHttpServletRequest mockRequest;
private final List<AsyncListener> listeners = new ArrayList<AsyncListener>();
private String dispatchPath;
private long timeout = 10 * 60 * 1000L;
public MockAsyncContext(ServletRequest request, ServletResponse response) {
this.request = request;
this.response = response;
this.mockRequest = WebUtils.getNativeRequest(request, MockHttpServletRequest.class);
}
public ServletRequest getRequest() {
return this.request;
}
public ServletResponse getResponse() {
return this.response;
}
public boolean hasOriginalRequestAndResponse() {
return false;
}
public String getDispatchPath() {
return this.dispatchPath;
}
public void dispatch() {
dispatch(null);
}
public void dispatch(String path) {
dispatch(null, path);
}
public void dispatch(ServletContext context, String path) {
this.dispatchPath = path;
if (this.mockRequest != null) {
this.mockRequest.setDispatcherType(DispatcherType.ASYNC);
this.mockRequest.setAsyncStarted(false);
}
}
public void complete() {
if (this.mockRequest != null) {
this.mockRequest.setAsyncStarted(false);
}
for (AsyncListener listener : this.listeners) {
try {
listener.onComplete(new AsyncEvent(this, this.request, this.response));
}
catch (IOException e) {
throw new IllegalStateException("AsyncListener failure", e);
}
}
}
public void start(Runnable run) {
}
public List<AsyncListener> getListeners() {
return this.listeners;
}
public void addListener(AsyncListener listener) {
this.listeners.add(listener);
}
public void addListener(AsyncListener listener, ServletRequest request, ServletResponse response) {
this.listeners.add(listener);
}
public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException {
return BeanUtils.instantiateClass(clazz);
}
public long getTimeout() {
return this.timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}

View File

@@ -56,8 +56,8 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
/**
* Mock implementation of the {@link javax.servlet.http.HttpServletRequest}
* interface. Supports the Servlet 2.5 API level; throws
* {@link UnsupportedOperationException} for all methods introduced in Servlet 3.0.
* interface. Supports the Servlet 2.5 API leve. Throws
* {@link UnsupportedOperationException} for some methods introduced in Servlet 3.0.
*
* <p>Used for testing the web framework; also useful for testing
* application controllers.
@@ -102,7 +102,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
public static final String DEFAULT_REMOTE_HOST = "localhost";
private static final String CONTENT_TYPE_HEADER = "Content-Type";
private static final String CHARSET_PREFIX = "charset=";
@@ -190,6 +190,13 @@ public class MockHttpServletRequest implements HttpServletRequest {
private boolean requestedSessionIdFromURL = false;
private boolean asyncSupported = false;
private boolean asyncStarted = false;
private MockAsyncContext asyncContext;
private DispatcherType dispatcherType = DispatcherType.REQUEST;
//---------------------------------------------------------------------
// Constructors
@@ -312,7 +319,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
this.characterEncoding = characterEncoding;
updateContentTypeHeader();
}
private void updateContentTypeHeader() {
if (this.contentType != null) {
StringBuilder sb = new StringBuilder(this.contentType);
@@ -679,7 +686,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
doAddHeaderValue(name, value, false);
}
@SuppressWarnings("rawtypes")
private void doAddHeaderValue(String name, Object value, boolean replace) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
@@ -898,33 +905,54 @@ public class MockHttpServletRequest implements HttpServletRequest {
//---------------------------------------------------------------------
public AsyncContext getAsyncContext() {
throw new UnsupportedOperationException();
return this.asyncContext;
}
public void setAsyncContext(MockAsyncContext asyncContext) {
this.asyncContext = asyncContext;
}
public DispatcherType getDispatcherType() {
throw new UnsupportedOperationException();
return this.dispatcherType;
}
public void setDispatcherType(DispatcherType dispatcherType) {
this.dispatcherType = dispatcherType;
}
public void setAsyncSupported(boolean asyncSupported) {
this.asyncSupported = asyncSupported;
}
public boolean isAsyncSupported() {
throw new UnsupportedOperationException();
return this.asyncSupported;
}
public AsyncContext startAsync() {
throw new UnsupportedOperationException();
return startAsync(this, null);
}
public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1) {
throw new UnsupportedOperationException();
public AsyncContext startAsync(ServletRequest request, ServletResponse response) {
if (!this.asyncSupported) {
throw new IllegalStateException("Async not supported");
}
this.asyncStarted = true;
this.asyncContext = new MockAsyncContext(request, response);
return this.asyncContext;
}
public void setAsyncStarted(boolean asyncStarted) {
this.asyncStarted = asyncStarted;
}
public boolean isAsyncStarted() {
throw new UnsupportedOperationException();
return this.asyncStarted;
}
public boolean authenticate(HttpServletResponse arg0) throws IOException, ServletException {
throw new UnsupportedOperationException();
}
public void addPart(Part part) {
parts.put(part.getName(), part);
}

View File

@@ -16,6 +16,14 @@
package org.springframework.web.client;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
@@ -25,6 +33,7 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@@ -38,14 +47,13 @@ import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
@@ -60,8 +68,6 @@ import org.springframework.util.FileCopyUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
/** @author Arjen Poutsma */
public class RestTemplateIntegrationTests {
@@ -80,21 +86,22 @@ public class RestTemplateIntegrationTests {
int port = FreePortScanner.getFreePort();
jettyServer = new Server(port);
baseUrl = "http://localhost:" + port;
Context jettyContext = new Context(jettyServer, "/");
ServletContextHandler handler = new ServletContextHandler();
byte[] bytes = helloWorld.getBytes("UTF-8");
contentType = new MediaType("text", "plain", Collections.singletonMap("charset", "utf-8"));
jettyContext.addServlet(new ServletHolder(new GetServlet(bytes, contentType)), "/get");
jettyContext.addServlet(new ServletHolder(new GetServlet(new byte[0], contentType)), "/get/nothing");
jettyContext.addServlet(new ServletHolder(new GetServlet(bytes, null)), "/get/nocontenttype");
jettyContext.addServlet(
contentType = new MediaType("text", "plain", Collections.singletonMap("charset", "UTF-8"));
handler.addServlet(new ServletHolder(new GetServlet(bytes, contentType)), "/get");
handler.addServlet(new ServletHolder(new GetServlet(new byte[0], contentType)), "/get/nothing");
handler.addServlet(new ServletHolder(new GetServlet(bytes, null)), "/get/nocontenttype");
handler.addServlet(
new ServletHolder(new PostServlet(helloWorld, baseUrl + "/post/1", bytes, contentType)),
"/post");
jettyContext.addServlet(new ServletHolder(new StatusCodeServlet(204)), "/status/nocontent");
jettyContext.addServlet(new ServletHolder(new StatusCodeServlet(304)), "/status/notmodified");
jettyContext.addServlet(new ServletHolder(new ErrorServlet(404)), "/status/notfound");
jettyContext.addServlet(new ServletHolder(new ErrorServlet(500)), "/status/server");
jettyContext.addServlet(new ServletHolder(new UriServlet()), "/uri/*");
jettyContext.addServlet(new ServletHolder(new MultipartServlet()), "/multipart");
handler.addServlet(new ServletHolder(new StatusCodeServlet(204)), "/status/nocontent");
handler.addServlet(new ServletHolder(new StatusCodeServlet(304)), "/status/notmodified");
handler.addServlet(new ServletHolder(new ErrorServlet(404)), "/status/notfound");
handler.addServlet(new ServletHolder(new ErrorServlet(500)), "/status/server");
handler.addServlet(new ServletHolder(new UriServlet()), "/uri/*");
handler.addServlet(new ServletHolder(new MultipartServlet()), "/multipart");
jettyServer.setHandler(handler);
jettyServer.start();
}
@@ -130,7 +137,7 @@ public class RestTemplateIntegrationTests {
String s = template.getForObject(baseUrl + "/get/nothing", String.class);
assertNull("Invalid content", s);
}
@Test
public void getNoContentTypeHeader() throws UnsupportedEncodingException {
byte[] bytes = template.getForObject(baseUrl + "/get/nocontenttype", byte[].class);
@@ -141,7 +148,7 @@ public class RestTemplateIntegrationTests {
public void getNoContent() {
String s = template.getForObject(baseUrl + "/status/nocontent", String.class);
assertNull("Invalid content", s);
ResponseEntity<String> entity = template.getForEntity(baseUrl + "/status/nocontent", String.class);
assertEquals("Invalid response code", HttpStatus.NO_CONTENT, entity.getStatusCode());
assertNull("Invalid content", entity.getBody());

View File

@@ -1,251 +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;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
/**
* Test fixture with an AsyncExecutionChain.
*
* @author Rossen Stoyanchev
*/
public class AsyncExecutionChainTests {
private AsyncExecutionChain chain;
private MockHttpServletRequest request;
private SimpleAsyncWebRequest asyncWebRequest;
private ResultSavingCallable resultSavingCallable;
@Before
public void setUp() {
this.request = new MockHttpServletRequest();
this.asyncWebRequest = new SimpleAsyncWebRequest(this.request, new MockHttpServletResponse());
this.resultSavingCallable = new ResultSavingCallable();
this.chain = AsyncExecutionChain.getForCurrentRequest(this.request);
this.chain.setTaskExecutor(new SyncTaskExecutor());
this.chain.setAsyncWebRequest(this.asyncWebRequest);
this.chain.push(this.resultSavingCallable);
}
@Test
public void getForCurrentRequest() throws Exception {
assertNotNull(this.chain);
assertSame(this.chain, AsyncExecutionChain.getForCurrentRequest(this.request));
assertSame(this.chain, this.request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE));
}
@Test
public void isAsyncStarted() {
assertFalse(this.chain.isAsyncStarted());
this.asyncWebRequest.startAsync();
assertTrue(this.chain.isAsyncStarted());
}
@Test(expected=IllegalStateException.class)
public void setAsyncWebRequestAfterAsyncStarted() {
this.asyncWebRequest.startAsync();
this.chain.setAsyncWebRequest(null);
}
@Test
public void startCallableChainProcessing() throws Exception {
this.chain.push(new IntegerIncrementingCallable());
this.chain.push(new IntegerIncrementingCallable());
this.chain.setLastCallable(new Callable<Object>() {
public Object call() throws Exception {
return 1;
}
});
this.chain.startCallableProcessing();
assertEquals(3, this.resultSavingCallable.result);
}
@Test
public void startCallableChainProcessing_staleRequest() {
this.chain.setLastCallable(new Callable<Object>() {
public Object call() throws Exception {
return 1;
}
});
this.asyncWebRequest.startAsync();
this.asyncWebRequest.complete();
this.chain.startCallableProcessing();
Exception ex = this.resultSavingCallable.exception;
assertNotNull(ex);
assertEquals(StaleAsyncWebRequestException.class, ex.getClass());
}
@Test
public void startCallableChainProcessing_requiredCallable() {
try {
this.chain.startCallableProcessing();
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertEquals(ex.getMessage(), "The last Callable was not set");
}
}
@Test
public void startCallableChainProcessing_requiredAsyncWebRequest() {
this.chain.setAsyncWebRequest(null);
try {
this.chain.startCallableProcessing();
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertEquals(ex.getMessage(), "AsyncWebRequest was not set");
}
}
@Test
public void startDeferredResultProcessing() throws Exception {
this.chain.push(new IntegerIncrementingCallable());
this.chain.push(new IntegerIncrementingCallable());
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
this.chain.startDeferredResultProcessing(deferredResult);
assertTrue(this.asyncWebRequest.isAsyncStarted());
deferredResult.set(1);
assertEquals(3, this.resultSavingCallable.result);
}
@Test(expected=StaleAsyncWebRequestException.class)
public void startDeferredResultProcessing_staleRequest() throws Exception {
this.asyncWebRequest.startAsync();
this.asyncWebRequest.complete();
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
this.chain.startDeferredResultProcessing(deferredResult);
deferredResult.set(1);
}
@Test
public void startDeferredResultProcessing_requiredDeferredResult() {
try {
this.chain.startDeferredResultProcessing(null);
fail("Expected exception");
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), containsString("DeferredResult is required"));
}
}
private static class SimpleAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest {
private boolean asyncStarted;
private boolean asyncCompleted;
public SimpleAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
super(request, response);
}
public void setTimeout(Long timeout) { }
public void setTimeoutHandler(Runnable runnable) { }
public void startAsync() {
this.asyncStarted = true;
}
public boolean isAsyncStarted() {
return this.asyncStarted;
}
public void complete() {
this.asyncStarted = false;
this.asyncCompleted = true;
}
public boolean isAsyncCompleted() {
return this.asyncCompleted;
}
public void sendError(HttpStatus status, String message) {
}
}
@SuppressWarnings("serial")
private static class SyncTaskExecutor extends SimpleAsyncTaskExecutor {
@Override
public void execute(Runnable task, long startTimeout) {
task.run();
}
}
private static class ResultSavingCallable extends AbstractDelegatingCallable {
Object result;
Exception exception;
public Object call() throws Exception {
try {
this.result = getNext().call();
}
catch (Exception ex) {
this.exception = ex;
throw ex;
}
return this.result;
}
}
private static class IntegerIncrementingCallable extends AbstractDelegatingCallable {
public Object call() throws Exception {
return ((Integer) getNext().call() + 1);
}
}
}

View File

@@ -25,6 +25,8 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
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;
/**

View File

@@ -1,71 +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;
import static org.easymock.EasyMock.*;
import java.util.concurrent.Callable;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
/**
* A test fixture with a {@link StaleAsyncRequestCheckingCallable}.
*
* @author Rossen Stoyanchev
*/
public class StaleAsyncRequestCheckingCallableTests {
private StaleAsyncRequestCheckingCallable callable;
private AsyncWebRequest asyncWebRequest;
@Before
public void setUp() {
this.asyncWebRequest = EasyMock.createMock(AsyncWebRequest.class);
this.callable = new StaleAsyncRequestCheckingCallable(asyncWebRequest);
this.callable.setNext(new Callable<Object>() {
public Object call() throws Exception {
return 1;
}
});
}
@Test
public void call_notStale() throws Exception {
expect(this.asyncWebRequest.isAsyncCompleted()).andReturn(false);
replay(this.asyncWebRequest);
this.callable.call();
verify(this.asyncWebRequest);
}
@Test(expected=StaleAsyncWebRequestException.class)
public void call_stale() throws Exception {
expect(this.asyncWebRequest.isAsyncCompleted()).andReturn(true);
replay(this.asyncWebRequest);
try {
this.callable.call();
}
finally {
verify(this.asyncWebRequest);
}
}
}

View File

@@ -17,25 +17,25 @@
package org.springframework.web.context.request.async;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.http.HttpServletRequest;
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;
/**
@@ -47,60 +47,55 @@ public class StandardServletAsyncWebRequestTests {
private StandardServletAsyncWebRequest asyncRequest;
private HttpServletRequest request;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setup() {
this.request = EasyMock.createMock(HttpServletRequest.class);
this.request = new MockHttpServletRequest();
this.request.setAsyncSupported(true);
this.response = new MockHttpServletResponse();
this.asyncRequest = new StandardServletAsyncWebRequest(this.request, this.response);
this.asyncRequest.setTimeout(60*1000L);
this.asyncRequest.setTimeout(44*1000L);
}
@Test
public void isAsyncStarted() throws Exception {
replay(this.request);
assertEquals("Should be \"false\" before startAsync()", false, this.asyncRequest.isAsyncStarted());
verify(this.request);
assertFalse(this.asyncRequest.isAsyncStarted());
startAsync();
reset(this.request);
expect(this.request.isAsyncStarted()).andReturn(true);
replay(this.request);
assertTrue("Should be \"true\" true startAsync()", this.asyncRequest.isAsyncStarted());
verify(this.request);
this.asyncRequest.onComplete(new AsyncEvent(null));
assertFalse("Should be \"false\" after complete()", this.asyncRequest.isAsyncStarted());
this.asyncRequest.startAsync();
assertTrue(this.asyncRequest.isAsyncStarted());
}
@Test
public void startAsync() throws Exception {
AsyncContext asyncContext = EasyMock.createMock(AsyncContext.class);
reset(this.request);
expect(this.request.isAsyncSupported()).andReturn(true);
expect(this.request.startAsync(this.request, this.response)).andStubReturn(asyncContext);
replay(this.request);
asyncContext.addListener(this.asyncRequest);
asyncContext.setTimeout(60*1000);
replay(asyncContext);
this.asyncRequest.startAsync();
verify(this.request);
MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext();
assertNotNull(asyncContext);
assertEquals("Timeout value not set", 44 * 1000, asyncContext.getTimeout());
assertEquals(1, asyncContext.getListeners().size());
assertSame(this.asyncRequest, asyncContext.getListeners().get(0));
}
@Test
public void startAsync_notSupported() throws Exception {
expect(this.request.isAsyncSupported()).andReturn(false);
replay(this.request);
public void startAsyncMultipleTimes() throws Exception {
this.asyncRequest.startAsync();
this.asyncRequest.startAsync();
this.asyncRequest.startAsync();
this.asyncRequest.startAsync(); // idempotent
MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext();
assertNotNull(asyncContext);
assertEquals(1, asyncContext.getListeners().size());
}
@Test
public void startAsyncNotSupported() throws Exception {
this.request.setAsyncSupported(false);
try {
this.asyncRequest.startAsync();
fail("expected exception");
@@ -111,60 +106,33 @@ public class StandardServletAsyncWebRequestTests {
}
@Test
public void startAsync_alreadyStarted() throws Exception {
startAsync();
reset(this.request);
expect(this.request.isAsyncSupported()).andReturn(true);
expect(this.request.isAsyncStarted()).andReturn(true);
replay(this.request);
try {
this.asyncRequest.startAsync();
fail("expected exception");
}
catch (IllegalStateException ex) {
assertEquals("Async processing already started", ex.getMessage());
}
verify(this.request);
}
@Test
public void startAsync_stale() throws Exception {
expect(this.request.isAsyncSupported()).andReturn(true);
replay(this.request);
public void startAsyncAfterCompleted() throws Exception {
this.asyncRequest.onComplete(new AsyncEvent(null));
try {
this.asyncRequest.startAsync();
fail("expected exception");
}
catch (IllegalStateException ex) {
assertEquals("Cannot use async request that has completed", ex.getMessage());
assertEquals("Async processing has already completed", ex.getMessage());
}
}
@Test
public void complete_stale() throws Exception {
this.asyncRequest.onComplete(new AsyncEvent(null));
this.asyncRequest.complete();
assertFalse(this.asyncRequest.isAsyncStarted());
assertTrue(this.asyncRequest.isAsyncCompleted());
public void onTimeoutDefaultBehavior() throws Exception {
this.asyncRequest.onTimeout(new AsyncEvent(null));
assertEquals(HttpStatus.SERVICE_UNAVAILABLE.value(), this.response.getStatus());
}
@Test
public void sendError() throws Exception {
this.asyncRequest.sendError(HttpStatus.INTERNAL_SERVER_ERROR, "error");
assertEquals(500, this.response.getStatus());
}
public void onTimeoutTimeoutHandler() throws Exception {
Runnable timeoutHandler = EasyMock.createMock(Runnable.class);
timeoutHandler.run();
replay(timeoutHandler);
@Test
public void sendError_stale() throws Exception {
this.asyncRequest.onComplete(new AsyncEvent(null));
this.asyncRequest.sendError(HttpStatus.INTERNAL_SERVER_ERROR, "error");
assertEquals(200, this.response.getStatus());
this.asyncRequest.setTimeoutHandler(timeoutHandler);
this.asyncRequest.onTimeout(new AsyncEvent(null));
verify(timeoutHandler);
}
}

View File

@@ -0,0 +1,222 @@
/*
* 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.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
/**
* Test fixture with an {@link WebAsyncManager}.
*
* @author Rossen Stoyanchev
*/
public class WebAsyncManagerTests {
private WebAsyncManager asyncManager;
private MockHttpServletRequest request;
private StubAsyncWebRequest stubAsyncWebRequest;
@Before
public void setUp() {
this.request = new MockHttpServletRequest();
this.stubAsyncWebRequest = new StubAsyncWebRequest(this.request, new MockHttpServletResponse());
this.asyncManager = AsyncWebUtils.getAsyncManager(this.request);
this.asyncManager.setTaskExecutor(new SyncTaskExecutor());
this.asyncManager.setAsyncWebRequest(this.stubAsyncWebRequest);
}
@Test
public void getForCurrentRequest() throws Exception {
assertNotNull(this.asyncManager);
assertSame(this.asyncManager, AsyncWebUtils.getAsyncManager(this.request));
assertSame(this.asyncManager, this.request.getAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE));
}
@Test
public void isConcurrentHandlingStarted() {
assertFalse(this.asyncManager.isConcurrentHandlingStarted());
this.stubAsyncWebRequest.startAsync();
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
}
@Test(expected=IllegalArgumentException.class)
public void setAsyncWebRequestAfterAsyncStarted() {
this.stubAsyncWebRequest.startAsync();
this.asyncManager.setAsyncWebRequest(null);
}
@Test
public void startCallableChainProcessing() throws Exception {
this.asyncManager.startCallableProcessing(new Callable<Object>() {
public Object call() throws Exception {
return 1;
}
});
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
assertTrue(this.stubAsyncWebRequest.isDispatched());
}
@Test
public void startCallableChainProcessingStaleRequest() {
this.stubAsyncWebRequest.setAsyncComplete(true);
this.asyncManager.startCallableProcessing(new Callable<Object>() {
public Object call() throws Exception {
return 1;
}
});
assertFalse(this.stubAsyncWebRequest.isDispatched());
}
@Test
public void startCallableChainProcessingCallableRequired() {
try {
this.asyncManager.startCallableProcessing(null);
fail("Expected exception");
}
catch (IllegalArgumentException ex) {
assertEquals(ex.getMessage(), "Callable is required");
}
}
@Test
public void startCallableChainProcessingAsyncWebRequestRequired() {
this.request.removeAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE);
this.asyncManager = AsyncWebUtils.getAsyncManager(this.request);
try {
this.asyncManager.startCallableProcessing(new Callable<Object>() {
public Object call() throws Exception {
return null;
}
});
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertEquals(ex.getMessage(), "AsyncWebRequest was not set");
}
}
@Test
public void startDeferredResultProcessing() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
this.asyncManager.startDeferredResultProcessing(deferredResult);
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
deferredResult.set(25);
assertEquals(25, this.asyncManager.getConcurrentResult());
}
@Test(expected=StaleAsyncWebRequestException.class)
public void startDeferredResultProcessing_staleRequest() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
this.asyncManager.startDeferredResultProcessing(deferredResult);
this.stubAsyncWebRequest.setAsyncComplete(true);
deferredResult.set(1);
}
@Test
public void startDeferredResultProcessingDeferredResultRequired() {
try {
this.asyncManager.startDeferredResultProcessing(null);
fail("Expected exception");
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), containsString("DeferredResult is required"));
}
}
private static class StubAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest {
private boolean asyncStarted;
private boolean dispatched;
private boolean asyncComplete;
public StubAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
super(request, response);
}
public void setTimeout(Long timeout) { }
public void setTimeoutHandler(Runnable runnable) { }
public void startAsync() {
this.asyncStarted = true;
}
public boolean isAsyncStarted() {
return this.asyncStarted;
}
public void dispatch() {
this.dispatched = true;
}
public boolean isDispatched() {
return dispatched;
}
public void setAsyncComplete(boolean asyncComplete) {
this.asyncComplete = asyncComplete;
}
public boolean isAsyncComplete() {
return this.asyncComplete;
}
public void addCompletionHandler(Runnable runnable) {
}
}
@SuppressWarnings("serial")
private static class SyncTaskExecutor extends SimpleAsyncTaskExecutor {
@Override
public void execute(Runnable task, long startTimeout) {
task.run();
}
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.web.filter;
import static org.easymock.EasyMock.createMock;
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.same;
@@ -32,7 +33,7 @@ import junit.framework.TestCase;
import org.springframework.mock.web.MockFilterConfig;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.request.async.AsyncWebUtils;
/**
* @author Rick Evans
@@ -47,8 +48,7 @@ public class CharacterEncodingFilterTests extends TestCase {
public void testForceAlwaysSetsEncoding() throws Exception {
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
addAsyncManagerExpectations(request);
request.setCharacterEncoding(ENCODING);
expect(request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).andReturn(null);
request.setAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX, Boolean.TRUE);
@@ -76,8 +76,7 @@ public class CharacterEncodingFilterTests extends TestCase {
public void testEncodingIfEmptyAndNotForced() throws Exception {
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
addAsyncManagerExpectations(request);
expect(request.getCharacterEncoding()).andReturn(null);
request.setCharacterEncoding(ENCODING);
expect(request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).andReturn(null);
@@ -103,8 +102,7 @@ public class CharacterEncodingFilterTests extends TestCase {
public void testDoesNowtIfEncodingIsNotEmptyAndNotForced() throws Exception {
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
addAsyncManagerExpectations(request);
expect(request.getCharacterEncoding()).andReturn(ENCODING);
expect(request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).andReturn(null);
request.setAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX, Boolean.TRUE);
@@ -128,8 +126,7 @@ public class CharacterEncodingFilterTests extends TestCase {
public void testWithBeanInitialization() throws Exception {
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
addAsyncManagerExpectations(request);
expect(request.getCharacterEncoding()).andReturn(null);
request.setCharacterEncoding(ENCODING);
expect(request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).andReturn(null);
@@ -155,8 +152,7 @@ public class CharacterEncodingFilterTests extends TestCase {
public void testWithIncompleteInitialization() throws Exception {
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
addAsyncManagerExpectations(request);
expect(request.getCharacterEncoding()).andReturn(null);
request.setCharacterEncoding(ENCODING);
expect(request.getAttribute(CharacterEncodingFilter.class.getName() + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).andReturn(null);
@@ -178,4 +174,11 @@ public class CharacterEncodingFilterTests extends TestCase {
verify(filterChain);
}
private void addAsyncManagerExpectations(HttpServletRequest request) {
expect(request.getAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE)).andReturn(null);
expectLastCall().anyTimes();
request.setAttribute(same(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE), notNull());
expectLastCall().anyTimes();
}
}