Support attribute overrides with @ResponseStatus
This commit introduces support for attribute overrides for @ResponseStatus when @ResponseStatus is used as a meta-annotation on a custom composed annotation. Specifically, this commit migrates all code that looks up @ResponseStatus from using AnnotationUtils.findAnnotation() to using AnnotatedElementUtils.findMergedAnnotation(). Issue: SPR-13441
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ResponseStatusExceptionResolver}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class ResponseStatusExceptionResolverTests {
|
||||
|
||||
private ResponseStatusExceptionResolver exceptionResolver;
|
||||
private final ResponseStatusExceptionResolver exceptionResolver = new ResponseStatusExceptionResolver();
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
private final MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
@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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user