diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java new file mode 100644 index 00000000000..ea241ff198f --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2009 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.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.http.HttpStatus; + +/** + * Marks an exception class with the status code and reason that should be returned whenever said exception is thrown. + * + * @author Arjen Poutsma + * @since 3.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ResponseStatus { + + /** + * The status code to use for the response. + * + * @see javax.servlet.http.HttpServletResponse#setStatus(int) + */ + HttpStatus value(); + + /** + * The reason to be used for the response.
If this element is not set, it will default to the standard status + * message for the status code. + * + * @see javax.servlet.http.HttpServletResponse#sendError(int, String) + */ + String reason() default ""; + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties index c550bd71275..634ec163e3a 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties @@ -13,7 +13,8 @@ org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.m org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter -org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.handler.DefaultHandlerExceptionResolver +org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.handler.DefaultHandlerExceptionResolver,\ + org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java new file mode 100644 index 00000000000..f2852a0da7a --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2009 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.annotation; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; + +/** + * Implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver HandlerExceptionResolver} + * interface that uses the {@link ResponseStatus @ResponseStatus} annotation to map exceptions to HTTP status codes. + * + * @author Arjen Poutsma + * @since 3.0 + */ +public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver { + + @Override + protected ModelAndView doResolveException(HttpServletRequest request, + HttpServletResponse response, + Object handler, + Exception ex) { + ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class); + if (responseStatus != null) { + try { + return resolveResponseStatus(responseStatus, request, response, handler, ex); + } + catch (Exception resolveEx) { + logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx); + } + } + return null; + } + + /** + * Template method that handles {@link ResponseStatus @ResponseStatus} annotation.
Default implementation send a
+ * response error using {@link HttpServletResponse#sendError(int)}, or {@link HttpServletResponse#sendError(int,
+ * String)} if the annotation has a {@linkplain ResponseStatus#reason() reason}. Returns an empty ModelAndView.
+ *
+ * @param responseStatus the annotation
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @param handler the executed handler, or null if none chosen at the time of the exception (for example,
+ * if multipart resolution failed)
+ * @param ex the exception that got thrown during handler execution
+ * @return a corresponding ModelAndView to forward to, or null for default processing
+ */
+ protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus,
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Object handler,
+ Exception ex) throws Exception {
+ int statusCode = responseStatus.value().value();
+ String reason = responseStatus.reason();
+ if (!StringUtils.hasLength(reason)) {
+ response.sendError(statusCode);
+ }
+ else {
+ response.sendError(statusCode, reason);
+ }
+ return new ModelAndView();
+ }
+}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java
new file mode 100644
index 00000000000..d993d994e0c
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java
@@ -0,0 +1,66 @@
+package org.springframework.web.servlet.mvc.annotation;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.servlet.ModelAndView;
+
+/** @author Arjen Poutsma */
+public class ResponseStatusExceptionResolverTests {
+
+ private ResponseStatusExceptionResolver exceptionResolver;
+
+ private MockHttpServletRequest request;
+
+ private MockHttpServletResponse response;
+
+ @Before
+ public void setUp() {
+ exceptionResolver = new ResponseStatusExceptionResolver();
+ request = new MockHttpServletRequest();
+ response = new MockHttpServletResponse();
+ request.setMethod("GET");
+ }
+
+ @Test
+ public void statusCode() {
+ StatusCodeException ex = new StatusCodeException();
+ 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());
+ }
+
+ @Test
+ public void statusCodeAndReason() {
+ StatusCodeAndReasonException ex = new StatusCodeAndReasonException();
+ ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex);
+ assertNotNull("No ModelAndView returned", mav);
+ assertTrue("No Empty ModelAndView returned", mav.isEmpty());
+ assertEquals("Invalid status code", 410, response.getStatus());
+ assertEquals("Invalid status reason", "You suck!", response.getErrorMessage());
+ }
+
+ @Test
+ public void notAnnotated() {
+ Exception ex = new Exception();
+ exceptionResolver.resolveException(request, response, null, ex);
+ ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex);
+ assertNull("ModelAndView returned", mav);
+ }
+
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ private static class StatusCodeException extends Exception {
+
+ }
+
+ @ResponseStatus(value = HttpStatus.GONE, reason = "You suck!")
+ private static class StatusCodeAndReasonException extends Exception {
+
+ }
+}