SPR-5625 - Allow for exceptions to be annotated with a @ResponseStatus annotation

This commit is contained in:
Arjen Poutsma 2009-03-28 11:19:45 +00:00
parent 3ade31bb51
commit 04b3edca33
4 changed files with 203 additions and 1 deletions

View File

@ -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. <p>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 "";
}

View File

@ -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

View File

@ -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. <p>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 <code>null</code> 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 <code>null</code> 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();
}
}

View File

@ -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 {
}
}