diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java index d092d723b30..5ae4128589a 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java @@ -16,8 +16,12 @@ package org.springframework.test.web.servlet.samples.standalone.resultmatchers; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + import org.junit.Test; +import org.springframework.core.annotation.AliasFor; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.test.web.servlet.MockMvc; @@ -26,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import static org.hamcrest.Matchers.*; +import static org.springframework.http.HttpStatus.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; @@ -34,6 +39,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; * Examples of expectations on the status and the status reason found in the response. * * @author Rossen Stoyanchev + * @author Sam Brannen */ public class StatusAssertionTests { @@ -42,12 +48,14 @@ public class StatusAssertionTests { @Test public void testStatusInt() throws Exception { this.mockMvc.perform(get("/created")).andExpect(status().is(201)); + this.mockMvc.perform(get("/createdWithComposedAnnotation")).andExpect(status().is(201)); this.mockMvc.perform(get("/badRequest")).andExpect(status().is(400)); } @Test public void testHttpStatus() throws Exception { this.mockMvc.perform(get("/created")).andExpect(status().isCreated()); + this.mockMvc.perform(get("/createdWithComposedAnnotation")).andExpect(status().isCreated()); this.mockMvc.perform(get("/badRequest")).andExpect(status().isBadRequest()); } @@ -66,27 +74,43 @@ public class StatusAssertionTests { @Test public void testReasonMatcher() throws Exception { - this.mockMvc.perform(get("/badRequest")) - .andExpect(status().reason(endsWith("token"))); + this.mockMvc.perform(get("/badRequest")).andExpect(status().reason(endsWith("token"))); } + @RequestMapping + @ResponseStatus + @Retention(RetentionPolicy.RUNTIME) + @interface Get { + + @AliasFor(annotation = RequestMapping.class, attribute = "path") + String[] path() default {}; + + @AliasFor(annotation = ResponseStatus.class, attribute = "code") + HttpStatus status() default INTERNAL_SERVER_ERROR; + } + @Controller private static class StatusController { @RequestMapping("/created") - @ResponseStatus(HttpStatus.CREATED) + @ResponseStatus(CREATED) public @ResponseBody void created(){ } + @Get(path = "/createdWithComposedAnnotation", status = CREATED) + public @ResponseBody void createdWithComposedAnnotation() { + } + @RequestMapping("/badRequest") - @ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Expired token") + @ResponseStatus(code = BAD_REQUEST, reason = "Expired token") public @ResponseBody void badRequest(){ } @RequestMapping("/notImplemented") - @ResponseStatus(HttpStatus.NOT_IMPLEMENTED) + @ResponseStatus(NOT_IMPLEMENTED) public @ResponseBody void notImplemented(){ } } + } diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java index 5be84c00774..33a9b291bbf 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java @@ -25,7 +25,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.MethodParameter; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -33,15 +33,17 @@ import org.springframework.util.ClassUtils; /** * Encapsulates information about a handler method consisting of a * {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}. - * Provides convenient access to method parameters, method return value, method annotations. + * Provides convenient access to method parameters, the method return value, + * method annotations, etc. * *

The class may be created with a bean instance or with a bean name (e.g. lazy-init bean, - * prototype bean). Use {@link #createWithResolvedBean()} to obtain a {@link HandlerMethod} + * prototype bean). Use {@link #createWithResolvedBean()} to obtain a {@code HandlerMethod} * instance with a bean instance resolved through the associated {@link BeanFactory}. * * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Juergen Hoeller + * @author Sam Brannen * @since 3.1 */ public class HandlerMethod { @@ -98,7 +100,7 @@ public class HandlerMethod { /** * Create an instance from a bean name, a method, and a {@code BeanFactory}. * The method {@link #createWithResolvedBean()} may be used later to - * re-create the {@code HandlerMethod} with an initialized the bean. + * re-create the {@code HandlerMethod} with an initialized bean. */ public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) { Assert.hasText(beanName, "Bean name is required"); @@ -222,11 +224,14 @@ public class HandlerMethod { /** * Returns a single annotation on the underlying method traversing its super methods * if no annotation can be found on the given method itself. + *

Also supports merged composed annotations with attribute + * overrides as of Spring Framework 4.2.2. * @param annotationType the type of annotation to introspect the method for. * @return the annotation, or {@code null} if none found + * @see AnnotatedElementUtils#findMergedAnnotation */ public A getMethodAnnotation(Class annotationType) { - return AnnotationUtils.findAnnotation(this.method, annotationType); + return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType); } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java index 006b0784c0d..f3cdd873a29 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java @@ -56,6 +56,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.Ordered; import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -119,7 +120,7 @@ import org.springframework.web.util.WebUtils; /** * Implementation of the {@link org.springframework.web.servlet.HandlerAdapter} interface - * that maps handler methods based on HTTP paths, HTTP methods and request parameters + * that maps handler methods based on HTTP paths, HTTP methods, and request parameters * expressed through the {@link RequestMapping} annotation. * *

Supports request parameter binding through the {@link RequestParam} annotation. @@ -133,6 +134,7 @@ import org.springframework.web.util.WebUtils; * * @author Juergen Hoeller * @author Arjen Poutsma + * @author Sam Brannen * @since 2.5 * @see #setPathMatcher * @see #setMethodNameResolver @@ -911,19 +913,19 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue, ExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception { - ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class); - if (responseStatusAnn != null) { - HttpStatus responseStatus = responseStatusAnn.code(); - String reason = responseStatusAnn.reason(); + ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(handlerMethod, ResponseStatus.class); + if (responseStatus != null) { + HttpStatus statusCode = responseStatus.code(); + String reason = responseStatus.reason(); if (!StringUtils.hasText(reason)) { - webRequest.getResponse().setStatus(responseStatus.value()); + webRequest.getResponse().setStatus(statusCode.value()); } else { - webRequest.getResponse().sendError(responseStatus.value(), reason); + webRequest.getResponse().sendError(statusCode.value(), reason); } // to be picked up by the RedirectView - webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, responseStatus); + webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, statusCode); this.responseArgumentUsed = true; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java index 75ff89c72d7..7c2568b2490 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java @@ -43,6 +43,7 @@ import javax.xml.transform.Source; import org.springframework.core.ExceptionDepthComparator; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.http.HttpInputMessage; @@ -377,15 +378,15 @@ public class AnnotationMethodHandlerExceptionResolver extends AbstractHandlerExc private ModelAndView getModelAndView(Method handlerMethod, Object returnValue, ServletWebRequest webRequest) throws Exception { - ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class); - if (responseStatusAnn != null) { - HttpStatus responseStatus = responseStatusAnn.code(); - String reason = responseStatusAnn.reason(); + ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(handlerMethod, ResponseStatus.class); + if (responseStatus != null) { + HttpStatus statusCode = responseStatus.code(); + String reason = responseStatus.reason(); if (!StringUtils.hasText(reason)) { - webRequest.getResponse().setStatus(responseStatus.value()); + webRequest.getResponse().setStatus(statusCode.value()); } else { - webRequest.getResponse().sendError(responseStatus.value(), reason); + webRequest.getResponse().sendError(statusCode.value(), reason); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java index 7bf45276602..b9b9f788e05 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java @@ -22,7 +22,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; @@ -38,11 +38,14 @@ import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; * and the MVC Java config and the MVC namespace. * *

As of 4.2 this resolver also looks recursively for {@code @ResponseStatus} - * present on cause exceptions. + * present on cause exceptions, and as of 4.2.2 this resolver supports + * attribute overrides for {@code @ResponseStatus} in custom composed annotations. * * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Sam Brannen * @since 3.0 + * @see AnnotatedElementUtils#findMergedAnnotation */ public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware { @@ -59,7 +62,7 @@ public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionRes protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class); + ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (responseStatus != null) { try { return resolveResponseStatus(responseStatus, request, response, handler, ex); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java index 075457cd2ce..7103d09df71 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java @@ -20,19 +20,20 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.Writer; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.BindException; import java.net.SocketException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.junit.Before; import org.junit.Test; +import org.springframework.core.annotation.AliasFor; import org.springframework.http.HttpStatus; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.stereotype.Controller; -import org.springframework.util.ClassUtils; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; @@ -43,25 +44,18 @@ import static org.junit.Assert.*; /** * @author Arjen Poutsma * @author Juergen Hoeller + * @author Sam Brannen */ @Deprecated public class AnnotationMethodHandlerExceptionResolverTests { - private AnnotationMethodHandlerExceptionResolver exceptionResolver; + private final AnnotationMethodHandlerExceptionResolver exceptionResolver = new AnnotationMethodHandlerExceptionResolver(); - private MockHttpServletRequest request; + private final MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); - private MockHttpServletResponse response; + private final MockHttpServletResponse response = new MockHttpServletResponse(); - @Before - public void setUp() { - exceptionResolver = new AnnotationMethodHandlerExceptionResolver(); - request = new MockHttpServletRequest(); - response = new MockHttpServletResponse(); - request.setMethod("GET"); - } - @Test public void simpleWithIOException() { IOException ex = new IOException(); @@ -103,6 +97,16 @@ public class AnnotationMethodHandlerExceptionResolverTests { assertEquals("Invalid status code returned", 406, response.getStatus()); } + @Test + public void simpleWithNumberFormatExceptionAndComposedResponseStatusAnnotation() { + NumberFormatException ex = new NumberFormatException(); + SimpleController controller = new SimpleController(); + ModelAndView mav = exceptionResolver.resolveException(request, response, controller, ex); + assertNotNull("No ModelAndView returned", mav); + assertEquals("Invalid view name returned", "X:NumberFormatException", mav.getViewName()); + assertEquals("Invalid status code returned", 400, response.getStatus()); + } + @Test public void inherited() { IOException ex = new IOException(); @@ -155,6 +159,13 @@ public class AnnotationMethodHandlerExceptionResolverTests { assertNull(mav); } + @ResponseStatus + @Retention(RetentionPolicy.RUNTIME) + @interface ComposedResponseStatus { + + @AliasFor(annotation = ResponseStatus.class, attribute = "code") + HttpStatus responseStatus() default HttpStatus.INTERNAL_SERVER_ERROR; + } @Controller private static class SimpleController { @@ -162,18 +173,24 @@ public class AnnotationMethodHandlerExceptionResolverTests { @ExceptionHandler(IOException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public String handleIOException(IOException ex, HttpServletRequest request) { - return "X:" + ClassUtils.getShortName(ex.getClass()); + return "X:" + ex.getClass().getSimpleName(); } @ExceptionHandler(SocketException.class) @ResponseStatus(code = HttpStatus.NOT_ACCEPTABLE, reason = "This is simply unacceptable!") public String handleSocketException(Exception ex, HttpServletResponse response) { - return "Y:" + ClassUtils.getShortName(ex.getClass()); + return "Y:" + ex.getClass().getSimpleName(); } @ExceptionHandler(IllegalArgumentException.class) public String handleIllegalArgumentException(Exception ex) { - return ClassUtils.getShortName(ex.getClass()); + return ex.getClass().getSimpleName(); + } + + @ExceptionHandler(NumberFormatException.class) + @ComposedResponseStatus(responseStatus = HttpStatus.BAD_REQUEST) + public String handleNumberFormatException(NumberFormatException ex) { + return "X:" + ex.getClass().getSimpleName(); } } @@ -194,12 +211,12 @@ public class AnnotationMethodHandlerExceptionResolverTests { @ExceptionHandler({BindException.class, IllegalArgumentException.class}) public String handle1(Exception ex, HttpServletRequest request, HttpServletResponse response) throws IOException { - return ClassUtils.getShortName(ex.getClass()); + return ex.getClass().getSimpleName(); } @ExceptionHandler public String handle2(IllegalArgumentException ex) { - return ClassUtils.getShortName(ex.getClass()); + return ex.getClass().getSimpleName(); } } @@ -209,7 +226,7 @@ public class AnnotationMethodHandlerExceptionResolverTests { @ExceptionHandler(Exception.class) public void handle(Exception ex, Writer writer) throws IOException { - writer.write(ClassUtils.getShortName(ex.getClass())); + writer.write(ex.getClass().getSimpleName()); } } @@ -220,7 +237,7 @@ public class AnnotationMethodHandlerExceptionResolverTests { @ExceptionHandler(Exception.class) @ResponseBody public String handle(Exception ex) { - return ClassUtils.getShortName(ex.getClass()); + return ex.getClass().getSimpleName(); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java index 4df11749174..d615b3a2ee2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java @@ -16,16 +16,16 @@ package org.springframework.web.servlet.mvc.annotation; -import static org.junit.Assert.*; - +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Locale; -import org.junit.Before; import org.junit.Test; import org.springframework.beans.TypeMismatchException; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.support.StaticMessageSource; +import org.springframework.core.annotation.AliasFor; import org.springframework.http.HttpStatus; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; @@ -33,22 +33,21 @@ import org.springframework.tests.sample.beans.ITestBean; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; -/** @author Arjen Poutsma */ -public class ResponseStatusExceptionResolverTests { +import static org.junit.Assert.*; - private ResponseStatusExceptionResolver exceptionResolver; +/** + * Integration tests for {@link ResponseStatusExceptionResolver}. + * + * @author Arjen Poutsma + * @author Sam Brannen + */ +public class ResponseStatusExceptionResolverTests { - private MockHttpServletRequest request; + private final ResponseStatusExceptionResolver exceptionResolver = new ResponseStatusExceptionResolver(); - private MockHttpServletResponse response; + private final MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); - @Before - public void setUp() { - exceptionResolver = new ResponseStatusExceptionResolver(); - request = new MockHttpServletRequest(); - response = new MockHttpServletResponse(); - request.setMethod("GET"); - } + private final MockHttpServletResponse response = new MockHttpServletResponse(); @Test public void statusCode() { @@ -60,6 +59,16 @@ public class ResponseStatusExceptionResolverTests { assertTrue("Response has not been committed", response.isCommitted()); } + @Test + public void statusCodeFromComposedResponseStatus() { + StatusCodeFromComposedResponseStatusException ex = new StatusCodeFromComposedResponseStatusException(); + ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); + assertNotNull("No ModelAndView returned", mav); + assertTrue("No Empty ModelAndView returned", mav.isEmpty()); + assertEquals("Invalid status code", 400, response.getStatus()); + assertTrue("Response has not been committed", response.isCommitted()); + } + @Test public void statusCodeAndReason() { StatusCodeAndReasonException ex = new StatusCodeAndReasonException(); @@ -109,6 +118,7 @@ public class ResponseStatusExceptionResolverTests { assertEquals("Invalid status code", 410, response.getStatus()); } + @ResponseStatus(HttpStatus.BAD_REQUEST) @SuppressWarnings("serial") private static class StatusCodeException extends Exception { @@ -124,4 +134,17 @@ public class ResponseStatusExceptionResolverTests { private static class StatusCodeAndReasonMessageException extends Exception { } + @ResponseStatus + @Retention(RetentionPolicy.RUNTIME) + @interface ComposedResponseStatus { + + @AliasFor(annotation = ResponseStatus.class, attribute = "code") + HttpStatus responseStatus() default HttpStatus.INTERNAL_SERVER_ERROR; + } + + @ComposedResponseStatus(responseStatus = HttpStatus.BAD_REQUEST) + @SuppressWarnings("serial") + private static class StatusCodeFromComposedResponseStatusException extends Exception { + } + } 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 1f8c7c6523e..157efb024f5 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 @@ -16,9 +16,8 @@ package org.springframework.web.servlet.mvc.method.annotation; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; @@ -29,6 +28,7 @@ import javax.servlet.http.HttpServletResponse; import org.junit.Test; import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AliasFor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; @@ -49,6 +49,9 @@ 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.*; +import static org.mockito.Mockito.*; + /** * Test fixture with {@link ServletInvocableHandlerMethod}. * @@ -80,6 +83,16 @@ public class ServletInvocableHandlerMethodTests { assertEquals(HttpStatus.BAD_REQUEST.value(), this.response.getStatus()); } + @Test + public void invokeAndHandle_VoidWithComposedResponseStatus() throws Exception { + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "composedResponseStatus"); + handlerMethod.invokeAndHandle(this.webRequest, this.mavContainer); + + assertTrue("Null return value + @ComposedResponseStatus should result in 'request handled'", + this.mavContainer.isRequestHandled()); + assertEquals(HttpStatus.BAD_REQUEST.value(), this.response.getStatus()); + } + @Test public void invokeAndHandle_VoidWithHttpServletResponseArgument() throws Exception { this.argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver()); @@ -260,6 +273,13 @@ public class ServletInvocableHandlerMethodTests { return handlerMethod; } + @ResponseStatus + @Retention(RetentionPolicy.RUNTIME) + @interface ComposedResponseStatus { + + @AliasFor(annotation = ResponseStatus.class, attribute = "code") + HttpStatus responseStatus() default HttpStatus.INTERNAL_SERVER_ERROR; + } @SuppressWarnings("unused") private static class Handler { @@ -277,6 +297,10 @@ public class ServletInvocableHandlerMethodTests { return "foo"; } + @ComposedResponseStatus(responseStatus = HttpStatus.BAD_REQUEST) + public void composedResponseStatus() { + } + public void httpServletResponse(HttpServletResponse response) { }