diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java index bc9246c046c..a554ebe7844 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java @@ -174,9 +174,10 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { } /** - * Return a ServletInvocableHandlerMethod that will process the value returned - * from an async operation essentially either applying return value handling or - * raising an exception if the end result is an Exception. + * Create a ServletInvocableHandlerMethod that will return the given value from an + * async operation instead of invoking the controller method again. The async result + * value is then either processed as if the controller method returned it or an + * exception is raised if the async result value itself is an Exception. */ ServletInvocableHandlerMethod wrapConcurrentResult(final Object result) { @@ -197,9 +198,12 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { /** - * A ServletInvocableHandlerMethod sub-class that invokes a given - * {@link Callable} and "inherits" the annotations of the containing class - * instance, useful for invoking a Callable returned from a HandlerMethod. + * A sub-class of {@link HandlerMethod} that invokes the given {@link Callable} + * instead of the target controller method. This is useful for resuming processing + * with the result of an async operation. The goal is to process the value returned + * from the Callable as if it was returned by the target controller method, i.e. + * taking into consideration both method and type-level controller annotations (e.g. + * {@code @ResponseBody}, {@code @ResponseStatus}, etc). */ private class CallableHandlerMethod extends ServletInvocableHandlerMethod { @@ -208,6 +212,17 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { this.setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers); } + /** + * Bridge to type-level annotations of the target controller method. + */ + @Override + public Class getBeanType() { + return ServletInvocableHandlerMethod.this.getBeanType(); + } + + /** + * Bridge to method-level annotations of the target controller method. + */ @Override public A getMethodAnnotation(Class annotationType) { return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java index 5365749ed5d..6cc92004a8a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java @@ -15,12 +15,9 @@ */ package org.springframework.web.servlet.mvc.method.annotation; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; import javax.servlet.http.HttpServletResponse; @@ -28,13 +25,18 @@ import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; @@ -42,6 +44,8 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandlerCom import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.view.RedirectView; +import static org.junit.Assert.*; + /** * Test fixture with {@link ServletInvocableHandlerMethod}. * @@ -73,7 +77,7 @@ public class ServletInvocableHandlerMethodTests { @Test public void invokeAndHandle_VoidWithResponseStatus() throws Exception { - ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatus"); + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "responseStatus"); handlerMethod.invokeAndHandle(webRequest, mavContainer); assertTrue("Null return value + @ResponseStatus should result in 'request handled'", @@ -85,7 +89,7 @@ public class ServletInvocableHandlerMethodTests { public void invokeAndHandle_VoidWithHttpServletResponseArgument() throws Exception { argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver()); - ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("httpServletResponse", HttpServletResponse.class); + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "httpServletResponse", HttpServletResponse.class); handlerMethod.invokeAndHandle(webRequest, mavContainer); assertTrue("Null return value + HttpServletResponse arg should result in 'request handled'", @@ -98,7 +102,7 @@ public class ServletInvocableHandlerMethodTests { int lastModifiedTimestamp = 1000 * 1000; webRequest.checkNotModified(lastModifiedTimestamp); - ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("notModified"); + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "notModified"); handlerMethod.invokeAndHandle(webRequest, mavContainer); assertTrue("Null return value + 'not modified' request should result in 'request handled'", @@ -109,7 +113,7 @@ public class ServletInvocableHandlerMethodTests { @Test public void invokeAndHandle_NotVoidWithResponseStatusAndReason() throws Exception { - ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatusWithReason"); + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "responseStatusWithReason"); handlerMethod.invokeAndHandle(webRequest, mavContainer); assertTrue("When a phrase is used, the response should not be used any more", mavContainer.isRequestHandled()); @@ -121,7 +125,7 @@ public class ServletInvocableHandlerMethodTests { public void invokeAndHandle_Exception() throws Exception { returnValueHandlers.addHandler(new ExceptionRaisingReturnValueHandler()); - ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("handle"); + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "handle"); handlerMethod.invokeAndHandle(webRequest, mavContainer); fail("Expected exception"); } @@ -133,7 +137,7 @@ public class ServletInvocableHandlerMethodTests { returnValueHandlers.addHandler(new ViewNameMethodReturnValueHandler()); // Invoke without a request parameter (String return value) - ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("dynamicReturnValue", String.class); + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "dynamicReturnValue", String.class); handlerMethod.invokeAndHandle(webRequest, mavContainer); assertNotNull(mavContainer.getView()); @@ -146,11 +150,45 @@ public class ServletInvocableHandlerMethodTests { assertEquals("view", mavContainer.getViewName()); } + @Test + public void wrapConcurrentResult_MethodLevelResponseBody() throws Exception { + wrapConcurrentResult_ResponseBody(new MethodLevelResponseBodyHandler()); + } + + @Test + public void wrapConcurrentResult_TypeLevelResponseBody() throws Exception { + wrapConcurrentResult_ResponseBody(new TypeLevelResponseBodyHandler()); + } + + private void wrapConcurrentResult_ResponseBody(Object handler) throws Exception { + List> converters = new ArrayList>(); + converters.add(new StringHttpMessageConverter()); + returnValueHandlers.addHandler(new RequestResponseBodyMethodProcessor(converters)); + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(handler, "handle"); + handlerMethod = handlerMethod.wrapConcurrentResult("bar"); + handlerMethod.invokeAndHandle(webRequest, mavContainer); + + assertEquals("bar", response.getContentAsString()); + } + + @Test + public void wrapConcurrentResult_ResponseEntity() throws Exception { + List> converters = new ArrayList>(); + converters.add(new StringHttpMessageConverter()); + returnValueHandlers.addHandler(new HttpEntityMethodProcessor(converters)); + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new ResponseEntityHandler(), "handle"); + handlerMethod = handlerMethod.wrapConcurrentResult(new ResponseEntity<>("bar", HttpStatus.OK)); + handlerMethod.invokeAndHandle(webRequest, mavContainer); + + assertEquals("bar", response.getContentAsString()); + } - private ServletInvocableHandlerMethod getHandlerMethod(String methodName, Class... argTypes) - throws NoSuchMethodException { - Method method = Handler.class.getDeclaredMethod(methodName, argTypes); - ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new Handler(), method); + + private ServletInvocableHandlerMethod getHandlerMethod(Object controller, + String methodName, Class... argTypes) throws NoSuchMethodException { + + Method method = controller.getClass().getDeclaredMethod(methodName, argTypes); + ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(controller, method); handlerMethod.setHandlerMethodArgumentResolvers(argumentResolvers); handlerMethod.setHandlerMethodReturnValueHandlers(returnValueHandlers); return handlerMethod; @@ -183,6 +221,31 @@ public class ServletInvocableHandlerMethodTests { } } + private static class MethodLevelResponseBodyHandler { + + @ResponseBody + public DeferredResult handle() { + return new DeferredResult<>(); + } + } + + @ResponseBody + private static class TypeLevelResponseBodyHandler { + + @SuppressWarnings("unused") + public DeferredResult handle() { + return new DeferredResult(); + } + } + + private static class ResponseEntityHandler { + + public DeferredResult> handle() { + return new DeferredResult<>(); + } + } + + private static class ExceptionRaisingReturnValueHandler implements HandlerMethodReturnValueHandler { @Override