Initial cut of Servlet 3.0 based async support

From a programming model perspective, @RequestMapping methods now
support two new return value types:

* java.util.concurrent.Callable - used by Spring MVC to obtain the
return value asynchronously in a separate thread managed transparently
by Spring MVC on behalf of the application.
* org.springframework.web.context.request.async.DeferredResult - used
by the application to produce the return value asynchronously in a
separate thread of its own choosing.

The high-level idea is that whatever value a controller normally
returns, it can now provide it asynchronously, through a Callable or
through a DeferredResult, with all remaining processing --
@ResponseBody, view resolution, etc, working just the same but
completed outside the main request thread.

From an SPI perspective, there are several new types:

* AsyncExecutionChain - the central class for managing async request
processing through a sequence of Callable instances each representing
work required to complete request processing asynchronously.
* AsyncWebRequest - provides methods for starting, completing, and
configuring async request processing.
* StandardServletAsyncWebRequest - Servlet 3 based implementation.
* AsyncExecutionChainRunnable - the Runnable used for async request
execution.

All spring-web and spring-webmvc Filter implementations have been
updated to participate in async request execution.
The open-session-in-view Filter and interceptors implementations in
spring-orm will be updated in a separate pull request.

Issue: SPR-8517
This commit is contained in:
Rossen Stoyanchev
2012-04-12 23:20:35 -04:00
parent fdded0768e
commit 3642b0f365
27 changed files with 2257 additions and 363 deletions

View File

@@ -0,0 +1,246 @@
/*
* 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.addDelegatingCallable(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());
this.chain.setAsyncWebRequest(null);
assertFalse(this.chain.isAsyncStarted());
}
@Test
public void startCallableChainProcessing() throws Exception {
this.chain.addDelegatingCallable(new IntegerIncrementingCallable());
this.chain.addDelegatingCallable(new IntegerIncrementingCallable());
this.chain.setCallable(new Callable<Object>() {
public Object call() throws Exception {
return 1;
}
});
this.chain.startCallableChainProcessing();
assertEquals(3, this.resultSavingCallable.result);
}
@Test
public void startCallableChainProcessing_staleRequest() {
this.chain.setCallable(new Callable<Object>() {
public Object call() throws Exception {
return 1;
}
});
this.asyncWebRequest.startAsync();
this.asyncWebRequest.complete();
this.chain.startCallableChainProcessing();
Exception ex = this.resultSavingCallable.exception;
assertNotNull(ex);
assertEquals(StaleAsyncWebRequestException.class, ex.getClass());
}
@Test
public void startCallableChainProcessing_requiredCallable() {
try {
this.chain.startCallableChainProcessing();
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), containsString("The callable field is required"));
}
}
@Test
public void startCallableChainProcessing_requiredAsyncWebRequest() {
this.chain.setAsyncWebRequest(null);
try {
this.chain.startCallableChainProcessing();
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), containsString("AsyncWebRequest is required"));
}
}
@Test
public void startDeferredValueProcessing() throws Exception {
this.chain.addDelegatingCallable(new IntegerIncrementingCallable());
this.chain.addDelegatingCallable(new IntegerIncrementingCallable());
DeferredResult deferredValue = new DeferredResult();
this.chain.startDeferredResultProcessing(deferredValue);
assertTrue(this.asyncWebRequest.isAsyncStarted());
deferredValue.set(1);
assertEquals(3, this.resultSavingCallable.result);
}
@Test(expected=StaleAsyncWebRequestException.class)
public void startDeferredValueProcessing_staleRequest() throws Exception {
this.asyncWebRequest.startAsync();
this.asyncWebRequest.complete();
DeferredResult deferredValue = new DeferredResult();
this.chain.startDeferredResultProcessing(deferredValue);
deferredValue.set(1);
}
@Test
public void startDeferredValueProcessing_requiredDeferredValue() {
try {
this.chain.startDeferredResultProcessing(null);
fail("Expected exception");
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), containsString("A DeferredValue 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 startAsync() {
this.asyncStarted = true;
}
public boolean isAsyncStarted() {
return this.asyncStarted;
}
public void setTimeout(Long timeout) { }
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 = getNextCallable().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) getNextCallable().call() + 1);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.setNextCallable(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

@@ -0,0 +1,165 @@
/*
* 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.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
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.MockHttpServletResponse;
/**
* A test fixture with a {@link StandardServletAsyncWebRequest}.
*
* @author Rossen Stoyanchev
*/
public class StandardServletAsyncWebRequestTests {
private StandardServletAsyncWebRequest asyncRequest;
private HttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setup() {
this.request = EasyMock.createMock(HttpServletRequest.class);
this.response = new MockHttpServletResponse();
this.asyncRequest = new StandardServletAsyncWebRequest(this.request, this.response);
this.asyncRequest.setTimeout(60*1000L);
}
@Test
public void isAsyncStarted() throws Exception {
assertEquals(false, this.asyncRequest.isAsyncStarted());
startAsync();
reset(this.request);
expect(this.request.isAsyncStarted()).andReturn(true);
replay(this.request);
assertTrue(this.asyncRequest.isAsyncStarted());
}
@Test
public void isAsyncStarted_stale() throws Exception {
this.asyncRequest.onComplete(new AsyncEvent(null));
try {
this.asyncRequest.isAsyncStarted();
fail("expected exception");
}
catch (IllegalStateException ex) {
assertStaleRequestMessage(ex);
}
}
@Test
public void startAsync() throws Exception {
AsyncContext asyncContext = EasyMock.createMock(AsyncContext.class);
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);
}
@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);
this.asyncRequest.onComplete(new AsyncEvent(null));
try {
this.asyncRequest.startAsync();
fail("expected exception");
}
catch (IllegalStateException ex) {
assertStaleRequestMessage(ex);
}
}
@Test
public void complete_stale() throws Exception {
this.asyncRequest.onComplete(new AsyncEvent(null));
try {
this.asyncRequest.complete();
fail("expected exception");
}
catch (IllegalStateException ex) {
assertStaleRequestMessage(ex);
}
}
@Test
public void sendError() throws Exception {
this.asyncRequest.sendError(HttpStatus.INTERNAL_SERVER_ERROR, "error");
assertEquals(500, this.response.getStatus());
}
@Test
public void sendError_requestAlreadyCompleted() throws Exception {
this.asyncRequest.onComplete(new AsyncEvent(null));
this.asyncRequest.sendError(HttpStatus.INTERNAL_SERVER_ERROR, "error");
assertEquals(200, this.response.getStatus());
}
private void assertStaleRequestMessage(IllegalStateException ex) {
assertEquals("Cannot use async request after completion", ex.getMessage());
}
}

View File

@@ -16,16 +16,23 @@
package org.springframework.web.filter;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.notNull;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.same;
import static org.easymock.EasyMock.verify;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import junit.framework.TestCase;
import org.easymock.MockControl;
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;
/**
* @author Rick Evans
@@ -39,28 +46,22 @@ public class CharacterEncodingFilterTests extends TestCase {
public void testForceAlwaysSetsEncoding() throws Exception {
MockControl mockRequest = MockControl.createControl(HttpServletRequest.class);
HttpServletRequest request = (HttpServletRequest) mockRequest.getMock();
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
request.setCharacterEncoding(ENCODING);
mockRequest.setVoidCallable();
request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setReturnValue(null);
expect(request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).andReturn(null);
request.setAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX, Boolean.TRUE);
mockRequest.setVoidCallable();
request.removeAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setVoidCallable();
mockRequest.replay();
replay(request);
MockControl mockResponse = MockControl.createControl(HttpServletResponse.class);
HttpServletResponse response = (HttpServletResponse) mockResponse.getMock();
HttpServletResponse response = createMock(HttpServletResponse.class);
response.setCharacterEncoding(ENCODING);
mockResponse.setVoidCallable();
mockResponse.replay();
replay(response);
MockControl mockFilter = MockControl.createControl(FilterChain.class);
FilterChain filterChain = (FilterChain) mockFilter.getMock();
FilterChain filterChain = createMock(FilterChain.class);
filterChain.doFilter(request, response);
mockFilter.replay();
replay(filterChain);
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setForceEncoding(true);
@@ -68,32 +69,27 @@ public class CharacterEncodingFilterTests extends TestCase {
filter.init(new MockFilterConfig(FILTER_NAME));
filter.doFilter(request, response, filterChain);
mockRequest.verify();
mockResponse.verify();
mockFilter.verify();
verify(request);
verify(response);
verify(filterChain);
}
public void testEncodingIfEmptyAndNotForced() throws Exception {
MockControl mockRequest = MockControl.createControl(HttpServletRequest.class);
HttpServletRequest request = (HttpServletRequest) mockRequest.getMock();
request.getCharacterEncoding();
mockRequest.setReturnValue(null);
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
expect(request.getCharacterEncoding()).andReturn(null);
request.setCharacterEncoding(ENCODING);
mockRequest.setVoidCallable();
request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setReturnValue(null);
expect(request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).andReturn(null);
request.setAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX, Boolean.TRUE);
mockRequest.setVoidCallable();
request.removeAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setVoidCallable();
mockRequest.replay();
replay(request);
MockHttpServletResponse response = new MockHttpServletResponse();
MockControl mockFilter = MockControl.createControl(FilterChain.class);
FilterChain filterChain = (FilterChain) mockFilter.getMock();
FilterChain filterChain = createMock(FilterChain.class);
filterChain.doFilter(request, response);
mockFilter.replay();
replay(filterChain);
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setForceEncoding(false);
@@ -101,60 +97,51 @@ public class CharacterEncodingFilterTests extends TestCase {
filter.init(new MockFilterConfig(FILTER_NAME));
filter.doFilter(request, response, filterChain);
mockRequest.verify();
mockFilter.verify();
verify(request);
verify(filterChain);
}
public void testDoesNowtIfEncodingIsNotEmptyAndNotForced() throws Exception {
MockControl mockRequest = MockControl.createControl(HttpServletRequest.class);
HttpServletRequest request = (HttpServletRequest) mockRequest.getMock();
request.getCharacterEncoding();
mockRequest.setReturnValue(ENCODING);
request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setReturnValue(null);
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
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);
mockRequest.setVoidCallable();
request.removeAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setVoidCallable();
mockRequest.replay();
replay(request);
MockHttpServletResponse response = new MockHttpServletResponse();
MockControl mockFilter = MockControl.createControl(FilterChain.class);
FilterChain filterChain = (FilterChain) mockFilter.getMock();
FilterChain filterChain = createMock(FilterChain.class);
filterChain.doFilter(request, response);
mockFilter.replay();
replay(filterChain);
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding(ENCODING);
filter.init(new MockFilterConfig(FILTER_NAME));
filter.doFilter(request, response, filterChain);
mockRequest.verify();
mockFilter.verify();
verify(request);
verify(filterChain);
}
public void testWithBeanInitialization() throws Exception {
MockControl mockRequest = MockControl.createControl(HttpServletRequest.class);
HttpServletRequest request = (HttpServletRequest) mockRequest.getMock();
request.getCharacterEncoding();
mockRequest.setReturnValue(null);
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
expect(request.getCharacterEncoding()).andReturn(null);
request.setCharacterEncoding(ENCODING);
mockRequest.setVoidCallable();
request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setReturnValue(null);
expect(request.getAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).andReturn(null);
request.setAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX, Boolean.TRUE);
mockRequest.setVoidCallable();
request.removeAttribute(FILTER_NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setVoidCallable();
mockRequest.replay();
replay(request);
MockHttpServletResponse response = new MockHttpServletResponse();
MockControl mockFilter = MockControl.createControl(FilterChain.class);
FilterChain filterChain = (FilterChain) mockFilter.getMock();
FilterChain filterChain = createMock(FilterChain.class);
filterChain.doFilter(request, response);
mockFilter.replay();
replay(filterChain);
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding(ENCODING);
@@ -162,38 +149,33 @@ public class CharacterEncodingFilterTests extends TestCase {
filter.setServletContext(new MockServletContext());
filter.doFilter(request, response, filterChain);
mockRequest.verify();
mockFilter.verify();
verify(request);
verify(filterChain);
}
public void testWithIncompleteInitialization() throws Exception {
MockControl mockRequest = MockControl.createControl(HttpServletRequest.class);
HttpServletRequest request = (HttpServletRequest) mockRequest.getMock();
request.getCharacterEncoding();
mockRequest.setReturnValue(null);
HttpServletRequest request = createMock(HttpServletRequest.class);
expect(request.getAttribute(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE)).andReturn(null);
request.setAttribute(same(AsyncExecutionChain.CALLABLE_CHAIN_ATTRIBUTE), notNull());
expect(request.getCharacterEncoding()).andReturn(null);
request.setCharacterEncoding(ENCODING);
mockRequest.setVoidCallable();
request.getAttribute(CharacterEncodingFilter.class.getName() + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setReturnValue(null);
expect(request.getAttribute(CharacterEncodingFilter.class.getName() + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX)).andReturn(null);
request.setAttribute(CharacterEncodingFilter.class.getName() + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX, Boolean.TRUE);
mockRequest.setVoidCallable();
request.removeAttribute(CharacterEncodingFilter.class.getName() + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX);
mockRequest.setVoidCallable();
mockRequest.replay();
replay(request);
MockHttpServletResponse response = new MockHttpServletResponse();
MockControl mockFilter = MockControl.createControl(FilterChain.class);
FilterChain filterChain = (FilterChain) mockFilter.getMock();
FilterChain filterChain = createMock(FilterChain.class);
filterChain.doFilter(request, response);
mockFilter.replay();
replay(filterChain);
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding(ENCODING);
filter.doFilter(request, response, filterChain);
mockRequest.verify();
mockFilter.verify();
verify(request);
verify(filterChain);
}
}