Browse Source
The new class is functionally equivalent to the DefaultHandlerExceptionResolver (i.e. it translates Spring MVC exceptions to various status codes) but uses an @ExceptionHandler returning a ResponseEntity<Object>, which means it can be customized to write error content to the body of the response. Issue: SPR-9290pull/120/merge
4 changed files with 751 additions and 74 deletions
@ -0,0 +1,397 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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.method.annotation; |
||||||
|
|
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.apache.commons.logging.Log; |
||||||
|
import org.apache.commons.logging.LogFactory; |
||||||
|
import org.springframework.beans.ConversionNotSupportedException; |
||||||
|
import org.springframework.beans.TypeMismatchException; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpMethod; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException; |
||||||
|
import org.springframework.http.converter.HttpMessageNotWritableException; |
||||||
|
import org.springframework.util.CollectionUtils; |
||||||
|
import org.springframework.validation.BindException; |
||||||
|
import org.springframework.web.HttpMediaTypeNotAcceptableException; |
||||||
|
import org.springframework.web.HttpMediaTypeNotSupportedException; |
||||||
|
import org.springframework.web.HttpRequestMethodNotSupportedException; |
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException; |
||||||
|
import org.springframework.web.bind.MissingServletRequestParameterException; |
||||||
|
import org.springframework.web.bind.ServletRequestBindingException; |
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice; |
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler; |
||||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||||
|
import org.springframework.web.context.request.WebRequest; |
||||||
|
import org.springframework.web.multipart.support.MissingServletRequestPartException; |
||||||
|
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; |
||||||
|
|
||||||
|
/** |
||||||
|
* A convenient base for classes with {@link ExceptionHandler} methods providing |
||||||
|
* infrastructure to handle standard Spring MVC exceptions. The functionality is |
||||||
|
* equivalent to that of the {@link DefaultHandlerExceptionResolver} except it |
||||||
|
* can be customized to write error content to the body of the response. If there |
||||||
|
* is no need to write error content, use {@code DefaultHandlerExceptionResolver} |
||||||
|
* instead. |
||||||
|
* |
||||||
|
* <p>It is expected the sub-classes will be annotated with |
||||||
|
* {@link ControllerAdvice @ControllerAdvice} and that |
||||||
|
* {@link ExceptionHandlerExceptionResolver} is configured to ensure this class
|
||||||
|
* applies to exceptions from any {@link RequestMapping @RequestMapping} method. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 3.2 |
||||||
|
* |
||||||
|
* @see org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver |
||||||
|
*/ |
||||||
|
public abstract class ExceptionHandlerSupport { |
||||||
|
|
||||||
|
protected final Log logger = LogFactory.getLog(getClass()); |
||||||
|
|
||||||
|
/** |
||||||
|
* Log category to use when no mapped handler is found for a request. |
||||||
|
* @see #pageNotFoundLogger |
||||||
|
*/ |
||||||
|
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Additional logger to use when no mapped handler is found for a request. |
||||||
|
* @see #PAGE_NOT_FOUND_LOG_CATEGORY |
||||||
|
*/ |
||||||
|
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Provides handling for standard Spring MVC exceptions. |
||||||
|
* @param ex the target exception |
||||||
|
* @param request the current request |
||||||
|
*/ |
||||||
|
@ExceptionHandler(value={ |
||||||
|
NoSuchRequestHandlingMethodException.class, |
||||||
|
HttpRequestMethodNotSupportedException.class, |
||||||
|
HttpMediaTypeNotSupportedException.class, |
||||||
|
HttpMediaTypeNotAcceptableException.class, |
||||||
|
MissingServletRequestParameterException.class, |
||||||
|
ServletRequestBindingException.class, |
||||||
|
ConversionNotSupportedException.class, |
||||||
|
TypeMismatchException.class, |
||||||
|
HttpMessageNotReadableException.class, |
||||||
|
HttpMessageNotWritableException.class, |
||||||
|
MethodArgumentNotValidException.class, |
||||||
|
MissingServletRequestPartException.class, |
||||||
|
BindException.class |
||||||
|
}) |
||||||
|
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) { |
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders(); |
||||||
|
|
||||||
|
HttpStatus status; |
||||||
|
Object body; |
||||||
|
|
||||||
|
if (ex instanceof NoSuchRequestHandlingMethodException) { |
||||||
|
status = HttpStatus.NOT_FOUND; |
||||||
|
body = handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof HttpRequestMethodNotSupportedException) { |
||||||
|
status = HttpStatus.METHOD_NOT_ALLOWED; |
||||||
|
body = handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof HttpMediaTypeNotSupportedException) { |
||||||
|
status = HttpStatus.UNSUPPORTED_MEDIA_TYPE; |
||||||
|
body = handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof HttpMediaTypeNotAcceptableException) { |
||||||
|
status = HttpStatus.NOT_ACCEPTABLE; |
||||||
|
body = handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof MissingServletRequestParameterException) { |
||||||
|
status = HttpStatus.BAD_REQUEST; |
||||||
|
body = handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof ServletRequestBindingException) { |
||||||
|
status = HttpStatus.BAD_REQUEST; |
||||||
|
body = handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof ConversionNotSupportedException) { |
||||||
|
status = HttpStatus.INTERNAL_SERVER_ERROR; |
||||||
|
body = handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof TypeMismatchException) { |
||||||
|
status = HttpStatus.BAD_REQUEST; |
||||||
|
body = handleTypeMismatch((TypeMismatchException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof HttpMessageNotReadableException) { |
||||||
|
status = HttpStatus.BAD_REQUEST; |
||||||
|
body = handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof HttpMessageNotWritableException) { |
||||||
|
status = HttpStatus.INTERNAL_SERVER_ERROR; |
||||||
|
body = handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof MethodArgumentNotValidException) { |
||||||
|
status = HttpStatus.BAD_REQUEST; |
||||||
|
body = handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof MissingServletRequestPartException) { |
||||||
|
status = HttpStatus.BAD_REQUEST; |
||||||
|
body = handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else if (ex instanceof BindException) { |
||||||
|
status = HttpStatus.BAD_REQUEST; |
||||||
|
body = handleBindException((BindException) ex, headers, status, request); |
||||||
|
} |
||||||
|
else { |
||||||
|
logger.warn("Unknown exception type: " + ex.getClass().getName()); |
||||||
|
status = HttpStatus.INTERNAL_SERVER_ERROR; |
||||||
|
body = handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
return new ResponseEntity<Object>(body, headers, status); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* A single place to customize the response body of all Exception types. |
||||||
|
* This method returns {@code null} by default. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
*/ |
||||||
|
protected Object handleExceptionInternal(Exception ex, HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for NoSuchRequestHandlingMethodException. |
||||||
|
* This method logs a warning and delegates to |
||||||
|
* {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
pageNotFoundLogger.warn(ex.getMessage()); |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for HttpRequestMethodNotSupportedException. |
||||||
|
* This method logs a warning, sets the "Allow" header, and delegates to |
||||||
|
* {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
pageNotFoundLogger.warn(ex.getMessage()); |
||||||
|
|
||||||
|
Set<HttpMethod> mediaTypes = new HashSet<HttpMethod>(); |
||||||
|
for (String value : ex.getSupportedMethods()) { |
||||||
|
mediaTypes.add(HttpMethod.valueOf(value)); |
||||||
|
} |
||||||
|
if (!mediaTypes.isEmpty()) { |
||||||
|
headers.setAllow(mediaTypes); |
||||||
|
} |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for HttpMediaTypeNotSupportedException. |
||||||
|
* This method sets the "Accept" header and delegates to |
||||||
|
* {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
List<MediaType> mediaTypes = ex.getSupportedMediaTypes(); |
||||||
|
if (!CollectionUtils.isEmpty(mediaTypes)) { |
||||||
|
headers.setAccept(mediaTypes); |
||||||
|
} |
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for HttpMediaTypeNotAcceptableException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for MissingServletRequestParameterException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleMissingServletRequestParameter(MissingServletRequestParameterException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for ServletRequestBindingException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleServletRequestBindingException(ServletRequestBindingException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for ConversionNotSupportedException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleConversionNotSupported(ConversionNotSupportedException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for TypeMismatchException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, |
||||||
|
HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for HttpMessageNotReadableException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleHttpMessageNotReadable(HttpMessageNotReadableException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for HttpMessageNotWritableException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleHttpMessageNotWritable(HttpMessageNotWritableException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for MethodArgumentNotValidException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleMethodArgumentNotValid(MethodArgumentNotValidException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for MissingServletRequestPartException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleMissingServletRequestPart(MissingServletRequestPartException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Customize the response for BindException. |
||||||
|
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}. |
||||||
|
* @param ex the exception |
||||||
|
* @param headers the headers to be written to the response |
||||||
|
* @param status the selected response status |
||||||
|
* @param request the current request |
||||||
|
* @return an Object or {@code null} |
||||||
|
*/ |
||||||
|
protected Object handleBindException(BindException ex, HttpHeaders headers, |
||||||
|
HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
return handleExceptionInternal(ex, headers, status, request); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,233 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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.method.annotation; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.EnumSet; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.beans.ConversionNotSupportedException; |
||||||
|
import org.springframework.beans.TypeMismatchException; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpMethod; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException; |
||||||
|
import org.springframework.http.converter.HttpMessageNotWritableException; |
||||||
|
import org.springframework.mock.web.MockHttpServletRequest; |
||||||
|
import org.springframework.mock.web.MockHttpServletResponse; |
||||||
|
import org.springframework.validation.BindException; |
||||||
|
import org.springframework.web.HttpMediaTypeNotAcceptableException; |
||||||
|
import org.springframework.web.HttpMediaTypeNotSupportedException; |
||||||
|
import org.springframework.web.HttpRequestMethodNotSupportedException; |
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException; |
||||||
|
import org.springframework.web.bind.MissingServletRequestParameterException; |
||||||
|
import org.springframework.web.bind.ServletRequestBindingException; |
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice; |
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler; |
||||||
|
import org.springframework.web.context.request.ServletWebRequest; |
||||||
|
import org.springframework.web.context.request.WebRequest; |
||||||
|
import org.springframework.web.context.support.StaticWebApplicationContext; |
||||||
|
import org.springframework.web.multipart.support.MissingServletRequestPartException; |
||||||
|
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; |
||||||
|
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test fixture for {@link ExceptionHandlerSupport}. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
*/ |
||||||
|
public class ExceptionHandlerSupportTests { |
||||||
|
|
||||||
|
private ExceptionHandlerSupport exceptionHandlerSupport; |
||||||
|
|
||||||
|
private DefaultHandlerExceptionResolver defaultExceptionResolver; |
||||||
|
|
||||||
|
private WebRequest request; |
||||||
|
|
||||||
|
private HttpServletRequest servletRequest; |
||||||
|
|
||||||
|
private MockHttpServletResponse servletResponse; |
||||||
|
|
||||||
|
|
||||||
|
@Before |
||||||
|
public void setup() { |
||||||
|
this.servletRequest = new MockHttpServletRequest(); |
||||||
|
this.servletResponse = new MockHttpServletResponse(); |
||||||
|
this.request = new ServletWebRequest(this.servletRequest, this.servletResponse); |
||||||
|
|
||||||
|
this.exceptionHandlerSupport = new ApplicationExceptionHandler(); |
||||||
|
this.defaultExceptionResolver = new DefaultHandlerExceptionResolver(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void supportsAllDefaultHandlerExceptionResolverExceptionTypes() throws Exception { |
||||||
|
|
||||||
|
Method annotMethod = ExceptionHandlerSupport.class.getMethod("handleException", Exception.class, WebRequest.class); |
||||||
|
ExceptionHandler annot = annotMethod.getAnnotation(ExceptionHandler.class); |
||||||
|
List<Class<? extends Throwable>> supportedTypes = Arrays.asList(annot.value()); |
||||||
|
|
||||||
|
for (Method method : DefaultHandlerExceptionResolver.class.getDeclaredMethods()) { |
||||||
|
Class<?>[] paramTypes = method.getParameterTypes(); |
||||||
|
if (method.getName().startsWith("handle") && (paramTypes.length == 4)) { |
||||||
|
String name = paramTypes[0].getSimpleName(); |
||||||
|
assertTrue("@ExceptionHandler is missing " + name, supportedTypes.contains(paramTypes[0])); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void noSuchRequestHandlingMethod() { |
||||||
|
Exception ex = new NoSuchRequestHandlingMethodException("GET", TestController.class); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void httpRequestMethodNotSupported() { |
||||||
|
List<String> supported = Arrays.asList("POST", "DELETE"); |
||||||
|
Exception ex = new HttpRequestMethodNotSupportedException("GET", supported); |
||||||
|
|
||||||
|
ResponseEntity<Object> responseEntity = testException(ex); |
||||||
|
assertEquals(EnumSet.of(HttpMethod.POST, HttpMethod.DELETE), responseEntity.getHeaders().getAllow()); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void handleHttpMediaTypeNotSupported() { |
||||||
|
List<MediaType> acceptable = Arrays.asList(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML); |
||||||
|
Exception ex = new HttpMediaTypeNotSupportedException(MediaType.APPLICATION_JSON, acceptable); |
||||||
|
|
||||||
|
ResponseEntity<Object> responseEntity = testException(ex); |
||||||
|
assertEquals(acceptable, responseEntity.getHeaders().getAccept()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void httpMediaTypeNotAcceptable() { |
||||||
|
Exception ex = new HttpMediaTypeNotAcceptableException(""); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void missingServletRequestParameter() { |
||||||
|
Exception ex = new MissingServletRequestParameterException("param", "type"); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void servletRequestBindingException() { |
||||||
|
Exception ex = new ServletRequestBindingException("message"); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void conversionNotSupported() { |
||||||
|
Exception ex = new ConversionNotSupportedException(new Object(), Object.class, null); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void typeMismatch() { |
||||||
|
Exception ex = new TypeMismatchException("foo", String.class); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void httpMessageNotReadable() { |
||||||
|
Exception ex = new HttpMessageNotReadableException("message"); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void httpMessageNotWritable() { |
||||||
|
Exception ex = new HttpMessageNotWritableException(""); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void methodArgumentNotValid() { |
||||||
|
Exception ex = new MethodArgumentNotValidException(null, null); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void missingServletRequestPart() { |
||||||
|
Exception ex = new MissingServletRequestPartException("partName"); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void bindException() { |
||||||
|
Exception ex = new BindException(new Object(), "name"); |
||||||
|
testException(ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void controllerAdvice() throws Exception { |
||||||
|
StaticWebApplicationContext cxt = new StaticWebApplicationContext(); |
||||||
|
cxt.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class); |
||||||
|
cxt.refresh(); |
||||||
|
|
||||||
|
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); |
||||||
|
resolver.setApplicationContext(cxt); |
||||||
|
resolver.afterPropertiesSet(); |
||||||
|
|
||||||
|
ServletRequestBindingException ex = new ServletRequestBindingException("message"); |
||||||
|
resolver.resolveException(this.servletRequest, this.servletResponse, null, ex); |
||||||
|
|
||||||
|
assertEquals(400, this.servletResponse.getStatus()); |
||||||
|
assertEquals("error content", this.servletResponse.getContentAsString()); |
||||||
|
assertEquals("someHeaderValue", this.servletResponse.getHeader("someHeader")); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private ResponseEntity<Object> testException(Exception ex) { |
||||||
|
ResponseEntity<Object> responseEntity = this.exceptionHandlerSupport.handleException(ex, this.request); |
||||||
|
this.defaultExceptionResolver.resolveException(this.servletRequest, this.servletResponse, null, ex); |
||||||
|
|
||||||
|
assertEquals(this.servletResponse.getStatus(), responseEntity.getStatusCode().value()); |
||||||
|
|
||||||
|
return responseEntity; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private static class TestController { |
||||||
|
} |
||||||
|
|
||||||
|
@ControllerAdvice |
||||||
|
private static class ApplicationExceptionHandler extends ExceptionHandlerSupport { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object handleServletRequestBindingException(ServletRequestBindingException ex, |
||||||
|
HttpHeaders headers, HttpStatus status, WebRequest request) { |
||||||
|
|
||||||
|
headers.set("someHeader", "someHeaderValue"); |
||||||
|
return "error content"; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue