Browse Source

DeferredResult/ResponseBodyEmitter adapter mechanism

The DeferredResult~ and the ResponseBodyEmitterReturnValueHandler now
each expose an adapter mechanism for plugging in other async return
value types. As a result the ListenableFutureReturnValueHandler and
CompletionStageReturnValueHandler are no longer needed and are now
deprecated.

Issue: SPR-14046
pull/1005/head
Rossen Stoyanchev 9 years ago
parent
commit
971ccab038
  1. 4
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java
  2. 35
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java
  3. 123
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java
  4. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java
  5. 9
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
  6. 38
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java
  7. 74
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java
  8. 197
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultReturnValueHandlerTests.java
  9. 52
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandlerTests.java

4
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java

@ -34,7 +34,11 @@ import org.springframework.web.method.support.ModelAndViewContainer; @@ -34,7 +34,11 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*
* @author Sebastien Deleuze
* @since 4.2
*
* @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
* CompletionStage return values via an adapter mechanism.
*/
@Deprecated
@UsesJava8
public class CompletionStageReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {

35
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
/*
* Copyright 2002-2016 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.servlet.mvc.method.annotation;
import org.springframework.web.context.request.async.DeferredResult;
/**
* Contract to adapt a single-value async return value to {@code DeferredResult}.
*
* @author Rossen Stoyanchev
* @since 4.3
*/
public interface DeferredResultAdapter {
/**
* Create a {@code DeferredResult} for the given return value.
* @param returnValue the return value, never {@code null}
* @return the DeferredResult
*/
DeferredResult<?> adaptToDeferredResult(Object returnValue);
}

123
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -16,7 +16,17 @@ @@ -16,7 +16,17 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionStage;
import java.util.function.BiFunction;
import org.springframework.core.MethodParameter;
import org.springframework.lang.UsesJava8;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncUtils;
@ -24,21 +34,56 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl @@ -24,21 +34,56 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Handles return values of type {@link DeferredResult}.
* Handler for return values of type {@link DeferredResult}, {@link ListenableFuture},
* {@link CompletionStage} and any other async type with a {@link #getAdapterMap()
* registered adapter}.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
private final Map<Class<?>, DeferredResultAdapter> adapterMap;
public DeferredResultMethodReturnValueHandler() {
this.adapterMap = new HashMap<Class<?>, DeferredResultAdapter>(5);
this.adapterMap.put(DeferredResult.class, new SimpleDeferredResultAdapter());
this.adapterMap.put(ListenableFuture.class, new ListenableFutureAdapter());
if (ClassUtils.isPresent("java.util.concurrent.CompletionStage", getClass().getClassLoader())) {
this.adapterMap.put(CompletionStage.class, new CompletionStageAdapter());
}
}
/**
* Return the map with {@code DeferredResult} adapters.
* <p>By default the map contains adapters for {@code DeferredResult}, which
* simply downcasts, {@link ListenableFuture}, and {@link CompletionStage}.
* @return the map of adapters
*/
public Map<Class<?>, DeferredResultAdapter> getAdapterMap() {
return this.adapterMap;
}
private DeferredResultAdapter getAdapterFor(Class<?> type) {
for (Class<?> adapteeType : getAdapterMap().keySet()) {
if (adapteeType.isAssignableFrom(type)) {
return getAdapterMap().get(adapteeType);
}
}
return null;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return DeferredResult.class.isAssignableFrom(returnType.getParameterType());
return (getAdapterFor(returnType.getParameterType()) != null);
}
@Override
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
return (returnValue != null && returnValue instanceof DeferredResult);
return (returnValue != null && (getAdapterFor(returnValue.getClass()) != null));
}
@Override
@ -50,8 +95,74 @@ public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMetho @@ -50,8 +95,74 @@ public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMetho
return;
}
DeferredResult<?> deferredResult = (DeferredResult<?>) returnValue;
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
DeferredResultAdapter adapter = getAdapterFor(returnValue.getClass());
Assert.notNull(adapter);
DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue);
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
}
/**
* Adapter for {@code DeferredResult} return values.
*/
private static class SimpleDeferredResultAdapter implements DeferredResultAdapter {
@Override
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
Assert.isInstanceOf(DeferredResult.class, returnValue);
return (DeferredResult<?>) returnValue;
}
}
/**
* Adapter for {@code ListenableFuture} return values.
*/
private static class ListenableFutureAdapter implements DeferredResultAdapter {
@Override
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
Assert.isInstanceOf(ListenableFuture.class, returnValue);
final DeferredResult<Object> result = new DeferredResult<Object>();
((ListenableFuture<?>) returnValue).addCallback(new ListenableFutureCallback<Object>() {
@Override
public void onSuccess(Object value) {
result.setResult(value);
}
@Override
public void onFailure(Throwable ex) {
result.setErrorResult(ex);
}
});
return result;
}
}
/**
* Adapter for {@code CompletionStage} return values.
*/
@UsesJava8
private static class CompletionStageAdapter implements DeferredResultAdapter {
@Override
public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
Assert.isInstanceOf(CompletionStage.class, returnValue);
final DeferredResult<Object> result = new DeferredResult<Object>();
@SuppressWarnings("unchecked")
CompletionStage<?> future = (CompletionStage<?>) returnValue;
future.handle(new BiFunction<Object, Throwable, Object>() {
@Override
public Object apply(Object value, Throwable ex) {
if (ex != null) {
result.setErrorResult(ex);
}
else {
result.setResult(value);
}
return null;
}
});
return result;
}
}
}

6
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -31,7 +31,11 @@ import org.springframework.web.method.support.ModelAndViewContainer; @@ -31,7 +31,11 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*
* @author Rossen Stoyanchev
* @since 4.1
*
* @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
* ListenableFuture return values via an adapter mechanism.
*/
@Deprecated
public class ListenableFutureReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
@Override

9
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

@ -48,7 +48,6 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage @@ -48,7 +48,6 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.accept.ContentNegotiationManager;
@ -117,10 +116,6 @@ import org.springframework.web.util.WebUtils; @@ -117,10 +116,6 @@ import org.springframework.web.util.WebUtils;
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
private static final boolean completionStagePresent = ClassUtils.isPresent(
"java.util.concurrent.CompletionStage", RequestMappingHandlerAdapter.class.getClassLoader());
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private HandlerMethodArgumentResolverComposite argumentResolvers;
@ -677,10 +672,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter @@ -677,10 +672,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
handlers.add(new ListenableFutureReturnValueHandler());
if (completionStagePresent) {
handlers.add(new CompletionStageReturnValueHandler());
}
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));

38
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
/*
* Copyright 2002-2016 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.servlet.mvc.method.annotation;
import org.springframework.http.server.ServerHttpResponse;
/**
* Contract to adapt streaming async types to {@code ResponseBodyEmitter}.
* @author Rossen Stoyanchev
* @since 4.3
*/
public interface ResponseBodyEmitterAdapter {
/**
* Obtain a {@code ResponseBodyEmitter} for the given return value. If
* the return is the body {@code ResponseEntity} then the given
* {@code ServerHttpResponse} contains its status and headers.
* @param returnValue the return value, never {@code null}
* @param response the response
* @return the return value adapted to a {@code ResponseBodyEmitter}
*/
ResponseBodyEmitter adaptToEmitter(Object returnValue, ServerHttpResponse response);
}

74
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -18,8 +18,9 @@ package org.springframework.web.servlet.mvc.method.annotation; @@ -18,8 +18,9 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -44,8 +45,9 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl @@ -44,8 +45,9 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Supports return values of type {@link ResponseBodyEmitter} and also
* {@code ResponseEntity<ResponseBodyEmitter>}.
* Handler for return values of type {@link ResponseBodyEmitter} (and the
* {@code ResponseEntity<ResponseBodyEmitter>} sub-class) as well as any other
* async type with a {@link #getAdapterMap() registered adapter}.
*
* @author Rossen Stoyanchev
* @since 4.2
@ -54,36 +56,61 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod @@ -54,36 +56,61 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
private static final Log logger = LogFactory.getLog(ResponseBodyEmitterReturnValueHandler.class);
private final List<HttpMessageConverter<?>> messageConverters;
private final Map<Class<?>, ResponseBodyEmitterAdapter> adapterMap;
public ResponseBodyEmitterReturnValueHandler(List<HttpMessageConverter<?>> messageConverters) {
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.messageConverters = messageConverters;
this.adapterMap = new HashMap<Class<?>, ResponseBodyEmitterAdapter>(3);
this.adapterMap.put(ResponseBodyEmitter.class, new SimpleResponseBodyEmitterAdapter());
}
/**
* Return the map with {@code ResponseBodyEmitter} adapters.
* By default the map contains a single adapter {@code ResponseBodyEmitter}
* that simply downcasts the return value.
* @return the map of adapters
*/
public Map<Class<?>, ResponseBodyEmitterAdapter> getAdapterMap() {
return this.adapterMap;
}
private ResponseBodyEmitterAdapter getAdapterFor(Class<?> type) {
for (Class<?> adapteeType : getAdapterMap().keySet()) {
if (adapteeType.isAssignableFrom(type)) {
return getAdapterMap().get(adapteeType);
}
}
return null;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
if (ResponseBodyEmitter.class.isAssignableFrom(returnType.getParameterType())) {
return true;
Class<?> bodyType;
if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
bodyType = ResolvableType.forMethodParameter(returnType).getGeneric(0).resolve();
}
else if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
Class<?> bodyType = ResolvableType.forMethodParameter(returnType).getGeneric(0).resolve();
return (bodyType != null && ResponseBodyEmitter.class.isAssignableFrom(bodyType));
else {
bodyType = returnType.getParameterType();
}
return false;
return (getAdapterFor(bodyType) != null);
}
@Override
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
if (returnValue != null) {
if (returnValue instanceof ResponseBodyEmitter) {
return true;
Object adaptFrom = returnValue;
if (returnValue instanceof ResponseEntity) {
adaptFrom = ((ResponseEntity) returnValue).getBody();
}
else if (returnValue instanceof ResponseEntity) {
Object body = ((ResponseEntity) returnValue).getBody();
return (body != null && body instanceof ResponseBodyEmitter);
if (adaptFrom != null) {
return (getAdapterFor(adaptFrom.getClass()) != null);
}
}
return false;
@ -115,8 +142,9 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod @@ -115,8 +142,9 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
ServletRequest request = webRequest.getNativeRequest(ServletRequest.class);
ShallowEtagHeaderFilter.disableContentCaching(request);
Assert.isInstanceOf(ResponseBodyEmitter.class, returnValue);
ResponseBodyEmitter emitter = (ResponseBodyEmitter) returnValue;
ResponseBodyEmitterAdapter adapter = getAdapterFor(returnValue.getClass());
Assert.notNull(adapter);
ResponseBodyEmitter emitter = adapter.adaptToEmitter(returnValue, outputMessage);
emitter.extendResponse(outputMessage);
// Commit the response and wrap to ignore further header changes
@ -132,6 +160,18 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod @@ -132,6 +160,18 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
}
/**
* Adapter for {@code ResponseBodyEmitter} return values.
*/
private static class SimpleResponseBodyEmitterAdapter implements ResponseBodyEmitterAdapter {
@Override
public ResponseBodyEmitter adaptToEmitter(Object returnValue, ServerHttpResponse response) {
Assert.isInstanceOf(ResponseBodyEmitter.class, returnValue);
return (ResponseBodyEmitter) returnValue;
}
}
/**
* ResponseBodyEmitter.Handler that writes with HttpMessageConverter's.
*/

197
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultReturnValueHandlerTests.java

@ -0,0 +1,197 @@ @@ -0,0 +1,197 @@
/*
* Copyright 2002-2016 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.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.StandardServletAsyncWebRequest;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.method.support.ModelAndViewContainer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link DeferredResultMethodReturnValueHandler}.
* @author Rossen Stoyanchev
*/
public class DeferredResultReturnValueHandlerTests {
private DeferredResultMethodReturnValueHandler handler;
private MockHttpServletRequest request;
private NativeWebRequest webRequest;
@Before
public void setUp() throws Exception {
this.handler = new DeferredResultMethodReturnValueHandler();
this.request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
this.webRequest = new ServletWebRequest(this.request, response);
AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, response);
WebAsyncUtils.getAsyncManager(this.webRequest).setAsyncWebRequest(asyncWebRequest);
this.request.setAsyncSupported(true);
}
@Test
public void supportsReturnType() throws Exception {
assertTrue(this.handler.supportsReturnType(returnType("handleDeferredResult")));
assertTrue(this.handler.supportsReturnType(returnType("handleListenableFuture")));
assertTrue(this.handler.supportsReturnType(returnType("handleCompletableFuture")));
assertFalse(this.handler.supportsReturnType(returnType("handleString")));
}
@Test
public void deferredResult() throws Exception {
MethodParameter returnType = returnType("handleDeferredResult");
DeferredResult<String> deferredResult = new DeferredResult<>();
handleReturnValue(deferredResult, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
deferredResult.setResult("foo");
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertEquals("foo", WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
@Test
public void deferredResultWitError() throws Exception {
MethodParameter returnType = returnType("handleDeferredResult");
DeferredResult<String> deferredResult = new DeferredResult<>();
handleReturnValue(deferredResult, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
IllegalStateException ex = new IllegalStateException();
deferredResult.setErrorResult(ex);
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertSame(ex, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
@Test
public void listenableFuture() throws Exception {
MethodParameter returnType = returnType("handleListenableFuture");
SettableListenableFuture<String> future = new SettableListenableFuture<>();
handleReturnValue(future, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
future.set("foo");
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertEquals("foo", WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
@Test
public void listenableFutureWithError() throws Exception {
MethodParameter returnType = returnType("handleListenableFuture");
SettableListenableFuture<String> future = new SettableListenableFuture<>();
handleReturnValue(future, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
IllegalStateException ex = new IllegalStateException();
future.setException(ex);
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertSame(ex, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
@Test
public void completableFuture() throws Exception {
MethodParameter returnType = returnType("handleCompletableFuture");
SettableListenableFuture<String> future = new SettableListenableFuture<>();
handleReturnValue(future, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
future.set("foo");
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertEquals("foo", WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
@Test
public void completableFutureWithError() throws Exception {
MethodParameter returnType = returnType("handleCompletableFuture");
CompletableFuture<String> future = new CompletableFuture<>();
handleReturnValue(future, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
IllegalStateException ex = new IllegalStateException();
future.completeExceptionally(ex);
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertSame(ex, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
private void handleReturnValue(Object returnValue, MethodParameter returnType) throws Exception {
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
this.handler.handleReturnValue(returnValue, returnType, mavContainer, this.webRequest);
}
private MethodParameter returnType(String methodName) throws NoSuchMethodException {
Method method = TestController.class.getDeclaredMethod(methodName);
return new MethodParameter(method, -1);
}
@SuppressWarnings("unused")
private static class TestController {
private String handleString() {
return null;
}
private DeferredResult<String> handleDeferredResult() {
return null;
}
private ListenableFuture<String> handleListenableFuture() {
return null;
}
private CompletableFuture<String> handleCompletableFuture() {
return null;
}
}
}

52
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandlerTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -51,14 +51,12 @@ public class ResponseBodyEmitterReturnValueHandlerTests { @@ -51,14 +51,12 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
private ResponseBodyEmitterReturnValueHandler handler;
private ModelAndViewContainer mavContainer;
private NativeWebRequest webRequest;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private NativeWebRequest webRequest;
@Before
public void setUp() throws Exception {
@ -67,8 +65,6 @@ public class ResponseBodyEmitterReturnValueHandlerTests { @@ -67,8 +65,6 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter());
this.handler = new ResponseBodyEmitterReturnValueHandler(converters);
this.mavContainer = new ModelAndViewContainer();
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.webRequest = new ServletWebRequest(this.request, this.response);
@ -80,18 +76,18 @@ public class ResponseBodyEmitterReturnValueHandlerTests { @@ -80,18 +76,18 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
@Test
public void supportsReturnType() throws Exception {
assertTrue(this.handler.supportsReturnType(returnType(TestController.class, "handle")));
assertTrue(this.handler.supportsReturnType(returnType(TestController.class, "handleSse")));
assertTrue(this.handler.supportsReturnType(returnType(TestController.class, "handleResponseEntity")));
assertFalse(this.handler.supportsReturnType(returnType(TestController.class, "handleResponseEntityString")));
assertFalse(this.handler.supportsReturnType(returnType(TestController.class, "handleResponseEntityParameterized")));
assertTrue(this.handler.supportsReturnType(returnType("handle")));
assertTrue(this.handler.supportsReturnType(returnType("handleSse")));
assertTrue(this.handler.supportsReturnType(returnType("handleResponseEntity")));
assertFalse(this.handler.supportsReturnType(returnType("handleResponseEntityString")));
assertFalse(this.handler.supportsReturnType(returnType("handleResponseEntityParameterized")));
}
@Test
public void responseBodyEmitter() throws Exception {
MethodParameter returnType = returnType(TestController.class, "handle");
MethodParameter returnType = returnType("handle");
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
handleReturnValue(emitter, returnType);
assertTrue(this.request.isAsyncStarted());
assertEquals("", this.response.getContentAsString());
@ -133,8 +129,8 @@ public class ResponseBodyEmitterReturnValueHandlerTests { @@ -133,8 +129,8 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
emitter.onTimeout(mock(Runnable.class));
emitter.onCompletion(mock(Runnable.class));
MethodParameter returnType = returnType(TestController.class, "handle");
this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
MethodParameter returnType = returnType("handle");
handleReturnValue(emitter, returnType);
verify(asyncWebRequest).setTimeout(19000L);
verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class));
@ -144,9 +140,9 @@ public class ResponseBodyEmitterReturnValueHandlerTests { @@ -144,9 +140,9 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
@Test
public void sseEmitter() throws Exception {
MethodParameter returnType = returnType(TestController.class, "handleSse");
MethodParameter returnType = returnType("handleSse");
SseEmitter emitter = new SseEmitter();
this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
handleReturnValue(emitter, returnType);
assertTrue(this.request.isAsyncStarted());
assertEquals(200, this.response.getStatus());
@ -174,9 +170,9 @@ public class ResponseBodyEmitterReturnValueHandlerTests { @@ -174,9 +170,9 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
@Test
public void responseEntitySse() throws Exception {
MethodParameter returnType = returnType(TestController.class, "handleResponseEntitySse");
ResponseEntity<SseEmitter> emitter = ResponseEntity.ok().header("foo", "bar").body(new SseEmitter());
this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
MethodParameter returnType = returnType("handleResponseEntitySse");
ResponseEntity<SseEmitter> entity = ResponseEntity.ok().header("foo", "bar").body(new SseEmitter());
handleReturnValue(entity, returnType);
assertTrue(this.request.isAsyncStarted());
assertEquals(200, this.response.getStatus());
@ -186,17 +182,21 @@ public class ResponseBodyEmitterReturnValueHandlerTests { @@ -186,17 +182,21 @@ public class ResponseBodyEmitterReturnValueHandlerTests {
@Test
public void responseEntitySseNoContent() throws Exception {
MethodParameter returnType = returnType(TestController.class, "handleResponseEntitySse");
ResponseEntity<?> emitter = ResponseEntity.noContent().build();
this.handler.handleReturnValue(emitter, returnType, this.mavContainer, this.webRequest);
MethodParameter returnType = returnType("handleResponseEntitySse");
ResponseEntity<?> entity = ResponseEntity.noContent().build();
handleReturnValue(entity, returnType);
assertFalse(this.request.isAsyncStarted());
assertEquals(204, this.response.getStatus());
}
private void handleReturnValue(Object returnValue, MethodParameter returnType) throws Exception {
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
this.handler.handleReturnValue(returnValue, returnType, mavContainer, this.webRequest);
}
private MethodParameter returnType(Class<?> clazz, String methodName) throws NoSuchMethodException {
Method method = clazz.getDeclaredMethod(methodName);
private MethodParameter returnType(String methodName) throws NoSuchMethodException {
Method method = TestController.class.getDeclaredMethod(methodName);
return new MethodParameter(method, -1);
}

Loading…
Cancel
Save