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
This commit is contained in:
parent
2152f436f9
commit
971ccab038
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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 @@
|
|||
|
||||
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
|
|||
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
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
|||
*
|
||||
* @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
|
||||
|
|
|
@ -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;
|
|||
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
|
|||
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));
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
|||
|
||||
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
|
|||
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
|
|||
|
||||
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
|
|||
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
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
|||
|
||||
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 {
|
|||
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 {
|
|||
|
||||
@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 {
|
|||
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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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…
Reference in New Issue