001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.mvc.method.annotation; 018 019import java.util.List; 020import java.util.Set; 021import javax.servlet.http.HttpServletRequest; 022import javax.servlet.http.HttpServletResponse; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.beans.ConversionNotSupportedException; 028import org.springframework.beans.TypeMismatchException; 029import org.springframework.http.HttpHeaders; 030import org.springframework.http.HttpMethod; 031import org.springframework.http.HttpStatus; 032import org.springframework.http.MediaType; 033import org.springframework.http.ResponseEntity; 034import org.springframework.http.converter.HttpMessageConverter; 035import org.springframework.http.converter.HttpMessageNotReadableException; 036import org.springframework.http.converter.HttpMessageNotWritableException; 037import org.springframework.util.CollectionUtils; 038import org.springframework.validation.BindException; 039import org.springframework.web.HttpMediaTypeNotAcceptableException; 040import org.springframework.web.HttpMediaTypeNotSupportedException; 041import org.springframework.web.HttpRequestMethodNotSupportedException; 042import org.springframework.web.bind.MethodArgumentNotValidException; 043import org.springframework.web.bind.MissingPathVariableException; 044import org.springframework.web.bind.MissingServletRequestParameterException; 045import org.springframework.web.bind.ServletRequestBindingException; 046import org.springframework.web.bind.annotation.ControllerAdvice; 047import org.springframework.web.bind.annotation.ExceptionHandler; 048import org.springframework.web.context.request.ServletWebRequest; 049import org.springframework.web.context.request.WebRequest; 050import org.springframework.web.context.request.async.AsyncRequestTimeoutException; 051import org.springframework.web.multipart.support.MissingServletRequestPartException; 052import org.springframework.web.servlet.NoHandlerFoundException; 053import org.springframework.web.util.WebUtils; 054 055/** 056 * A convenient base class for {@link ControllerAdvice @ControllerAdvice} classes 057 * that wish to provide centralized exception handling across all 058 * {@code @RequestMapping} methods through {@code @ExceptionHandler} methods. 059 * 060 * <p>This base class provides an {@code @ExceptionHandler} method for handling 061 * internal Spring MVC exceptions. This method returns a {@code ResponseEntity} 062 * for writing to the response with a {@link HttpMessageConverter message converter}, 063 * in contrast to 064 * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver 065 * DefaultHandlerExceptionResolver} which returns a 066 * {@link org.springframework.web.servlet.ModelAndView ModelAndView}. 067 * 068 * <p>If there is no need to write error content to the response body, or when 069 * using view resolution (e.g., via {@code ContentNegotiatingViewResolver}), 070 * then {@code DefaultHandlerExceptionResolver} is good enough. 071 * 072 * <p>Note that in order for an {@code @ControllerAdvice} subclass to be 073 * detected, {@link ExceptionHandlerExceptionResolver} must be configured. 074 * 075 * @author Rossen Stoyanchev 076 * @since 3.2 077 * @see #handleException(Exception, WebRequest) 078 * @see org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver 079 */ 080public abstract class ResponseEntityExceptionHandler { 081 082 /** 083 * Log category to use when no mapped handler is found for a request. 084 * @see #pageNotFoundLogger 085 */ 086 public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; 087 088 /** 089 * Specific logger to use when no mapped handler is found for a request. 090 * @see #PAGE_NOT_FOUND_LOG_CATEGORY 091 */ 092 protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); 093 094 /** 095 * Common logger for use in subclasses. 096 */ 097 protected final Log logger = LogFactory.getLog(getClass()); 098 099 100 /** 101 * Provides handling for standard Spring MVC exceptions. 102 * @param ex the target exception 103 * @param request the current request 104 */ 105 @SuppressWarnings("deprecation") 106 @ExceptionHandler({ 107 org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException.class, 108 HttpRequestMethodNotSupportedException.class, 109 HttpMediaTypeNotSupportedException.class, 110 HttpMediaTypeNotAcceptableException.class, 111 MissingPathVariableException.class, 112 MissingServletRequestParameterException.class, 113 ServletRequestBindingException.class, 114 ConversionNotSupportedException.class, 115 TypeMismatchException.class, 116 HttpMessageNotReadableException.class, 117 HttpMessageNotWritableException.class, 118 MethodArgumentNotValidException.class, 119 MissingServletRequestPartException.class, 120 BindException.class, 121 NoHandlerFoundException.class, 122 AsyncRequestTimeoutException.class 123 }) 124 public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception { 125 HttpHeaders headers = new HttpHeaders(); 126 if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) { 127 HttpStatus status = HttpStatus.NOT_FOUND; 128 return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex, headers, status, request); 129 } 130 else if (ex instanceof HttpRequestMethodNotSupportedException) { 131 HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED; 132 return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request); 133 } 134 else if (ex instanceof HttpMediaTypeNotSupportedException) { 135 HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE; 136 return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request); 137 } 138 else if (ex instanceof HttpMediaTypeNotAcceptableException) { 139 HttpStatus status = HttpStatus.NOT_ACCEPTABLE; 140 return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request); 141 } 142 else if (ex instanceof MissingPathVariableException) { 143 HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; 144 return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request); 145 } 146 else if (ex instanceof MissingServletRequestParameterException) { 147 HttpStatus status = HttpStatus.BAD_REQUEST; 148 return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request); 149 } 150 else if (ex instanceof ServletRequestBindingException) { 151 HttpStatus status = HttpStatus.BAD_REQUEST; 152 return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request); 153 } 154 else if (ex instanceof ConversionNotSupportedException) { 155 HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; 156 return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request); 157 } 158 else if (ex instanceof TypeMismatchException) { 159 HttpStatus status = HttpStatus.BAD_REQUEST; 160 return handleTypeMismatch((TypeMismatchException) ex, headers, status, request); 161 } 162 else if (ex instanceof HttpMessageNotReadableException) { 163 HttpStatus status = HttpStatus.BAD_REQUEST; 164 return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request); 165 } 166 else if (ex instanceof HttpMessageNotWritableException) { 167 HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; 168 return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request); 169 } 170 else if (ex instanceof MethodArgumentNotValidException) { 171 HttpStatus status = HttpStatus.BAD_REQUEST; 172 return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request); 173 } 174 else if (ex instanceof MissingServletRequestPartException) { 175 HttpStatus status = HttpStatus.BAD_REQUEST; 176 return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request); 177 } 178 else if (ex instanceof BindException) { 179 HttpStatus status = HttpStatus.BAD_REQUEST; 180 return handleBindException((BindException) ex, headers, status, request); 181 } 182 else if (ex instanceof NoHandlerFoundException) { 183 HttpStatus status = HttpStatus.NOT_FOUND; 184 return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request); 185 } 186 else if (ex instanceof AsyncRequestTimeoutException) { 187 HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE; 188 return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request); 189 } 190 else { 191 // Unknown exception, typically a wrapper with a common MVC exception as cause 192 // (since @ExceptionHandler type declarations also match first-level causes): 193 // We only deal with top-level MVC exceptions here, so let's rethrow the given 194 // exception for further processing through the HandlerExceptionResolver chain. 195 throw ex; 196 } 197 } 198 199 /** 200 * Customize the response for NoSuchRequestHandlingMethodException. 201 * <p>This method logs a warning and delegates to {@link #handleExceptionInternal}. 202 * @param ex the exception 203 * @param headers the headers to be written to the response 204 * @param status the selected response status 205 * @param request the current request 206 * @return a {@code ResponseEntity} instance 207 * @deprecated as of 4.3, along with {@link org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException} 208 */ 209 @Deprecated 210 protected ResponseEntity<Object> handleNoSuchRequestHandlingMethod( 211 org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex, 212 HttpHeaders headers, HttpStatus status, WebRequest request) { 213 214 pageNotFoundLogger.warn(ex.getMessage()); 215 216 return handleExceptionInternal(ex, null, headers, status, request); 217 } 218 219 /** 220 * Customize the response for HttpRequestMethodNotSupportedException. 221 * <p>This method logs a warning, sets the "Allow" header, and delegates to 222 * {@link #handleExceptionInternal}. 223 * @param ex the exception 224 * @param headers the headers to be written to the response 225 * @param status the selected response status 226 * @param request the current request 227 * @return a {@code ResponseEntity} instance 228 */ 229 protected ResponseEntity<Object> handleHttpRequestMethodNotSupported( 230 HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 231 232 pageNotFoundLogger.warn(ex.getMessage()); 233 234 Set<HttpMethod> supportedMethods = ex.getSupportedHttpMethods(); 235 if (!CollectionUtils.isEmpty(supportedMethods)) { 236 headers.setAllow(supportedMethods); 237 } 238 return handleExceptionInternal(ex, null, headers, status, request); 239 } 240 241 /** 242 * Customize the response for HttpMediaTypeNotSupportedException. 243 * <p>This method sets the "Accept" header and delegates to 244 * {@link #handleExceptionInternal}. 245 * @param ex the exception 246 * @param headers the headers to be written to the response 247 * @param status the selected response status 248 * @param request the current request 249 * @return a {@code ResponseEntity} instance 250 */ 251 protected ResponseEntity<Object> handleHttpMediaTypeNotSupported( 252 HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 253 254 List<MediaType> mediaTypes = ex.getSupportedMediaTypes(); 255 if (!CollectionUtils.isEmpty(mediaTypes)) { 256 headers.setAccept(mediaTypes); 257 } 258 259 return handleExceptionInternal(ex, null, headers, status, request); 260 } 261 262 /** 263 * Customize the response for HttpMediaTypeNotAcceptableException. 264 * <p>This method delegates to {@link #handleExceptionInternal}. 265 * @param ex the exception 266 * @param headers the headers to be written to the response 267 * @param status the selected response status 268 * @param request the current request 269 * @return a {@code ResponseEntity} instance 270 */ 271 protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable( 272 HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 273 274 return handleExceptionInternal(ex, null, headers, status, request); 275 } 276 277 /** 278 * Customize the response for MissingPathVariableException. 279 * <p>This method delegates to {@link #handleExceptionInternal}. 280 * @param ex the exception 281 * @param headers the headers to be written to the response 282 * @param status the selected response status 283 * @param request the current request 284 * @return a {@code ResponseEntity} instance 285 * @since 4.2 286 */ 287 protected ResponseEntity<Object> handleMissingPathVariable( 288 MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 289 290 return handleExceptionInternal(ex, null, headers, status, request); 291 } 292 293 /** 294 * Customize the response for MissingServletRequestParameterException. 295 * <p>This method delegates to {@link #handleExceptionInternal}. 296 * @param ex the exception 297 * @param headers the headers to be written to the response 298 * @param status the selected response status 299 * @param request the current request 300 * @return a {@code ResponseEntity} instance 301 */ 302 protected ResponseEntity<Object> handleMissingServletRequestParameter( 303 MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 304 305 return handleExceptionInternal(ex, null, headers, status, request); 306 } 307 308 /** 309 * Customize the response for ServletRequestBindingException. 310 * <p>This method delegates to {@link #handleExceptionInternal}. 311 * @param ex the exception 312 * @param headers the headers to be written to the response 313 * @param status the selected response status 314 * @param request the current request 315 * @return a {@code ResponseEntity} instance 316 */ 317 protected ResponseEntity<Object> handleServletRequestBindingException( 318 ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 319 320 return handleExceptionInternal(ex, null, headers, status, request); 321 } 322 323 /** 324 * Customize the response for ConversionNotSupportedException. 325 * <p>This method delegates to {@link #handleExceptionInternal}. 326 * @param ex the exception 327 * @param headers the headers to be written to the response 328 * @param status the selected response status 329 * @param request the current request 330 * @return a {@code ResponseEntity} instance 331 */ 332 protected ResponseEntity<Object> handleConversionNotSupported( 333 ConversionNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 334 335 return handleExceptionInternal(ex, null, headers, status, request); 336 } 337 338 /** 339 * Customize the response for TypeMismatchException. 340 * <p>This method delegates to {@link #handleExceptionInternal}. 341 * @param ex the exception 342 * @param headers the headers to be written to the response 343 * @param status the selected response status 344 * @param request the current request 345 * @return a {@code ResponseEntity} instance 346 */ 347 protected ResponseEntity<Object> handleTypeMismatch( 348 TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 349 350 return handleExceptionInternal(ex, null, headers, status, request); 351 } 352 353 /** 354 * Customize the response for HttpMessageNotReadableException. 355 * <p>This method delegates to {@link #handleExceptionInternal}. 356 * @param ex the exception 357 * @param headers the headers to be written to the response 358 * @param status the selected response status 359 * @param request the current request 360 * @return a {@code ResponseEntity} instance 361 */ 362 protected ResponseEntity<Object> handleHttpMessageNotReadable( 363 HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 364 365 return handleExceptionInternal(ex, null, headers, status, request); 366 } 367 368 /** 369 * Customize the response for HttpMessageNotWritableException. 370 * <p>This method delegates to {@link #handleExceptionInternal}. 371 * @param ex the exception 372 * @param headers the headers to be written to the response 373 * @param status the selected response status 374 * @param request the current request 375 * @return a {@code ResponseEntity} instance 376 */ 377 protected ResponseEntity<Object> handleHttpMessageNotWritable( 378 HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 379 380 return handleExceptionInternal(ex, null, headers, status, request); 381 } 382 383 /** 384 * Customize the response for MethodArgumentNotValidException. 385 * <p>This method delegates to {@link #handleExceptionInternal}. 386 * @param ex the exception 387 * @param headers the headers to be written to the response 388 * @param status the selected response status 389 * @param request the current request 390 * @return a {@code ResponseEntity} instance 391 */ 392 protected ResponseEntity<Object> handleMethodArgumentNotValid( 393 MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 394 395 return handleExceptionInternal(ex, null, headers, status, request); 396 } 397 398 /** 399 * Customize the response for MissingServletRequestPartException. 400 * <p>This method delegates to {@link #handleExceptionInternal}. 401 * @param ex the exception 402 * @param headers the headers to be written to the response 403 * @param status the selected response status 404 * @param request the current request 405 * @return a {@code ResponseEntity} instance 406 */ 407 protected ResponseEntity<Object> handleMissingServletRequestPart( 408 MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 409 410 return handleExceptionInternal(ex, null, headers, status, request); 411 } 412 413 /** 414 * Customize the response for BindException. 415 * <p>This method delegates to {@link #handleExceptionInternal}. 416 * @param ex the exception 417 * @param headers the headers to be written to the response 418 * @param status the selected response status 419 * @param request the current request 420 * @return a {@code ResponseEntity} instance 421 */ 422 protected ResponseEntity<Object> handleBindException( 423 BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 424 425 return handleExceptionInternal(ex, null, headers, status, request); 426 } 427 428 /** 429 * Customize the response for NoHandlerFoundException. 430 * <p>This method delegates to {@link #handleExceptionInternal}. 431 * @param ex the exception 432 * @param headers the headers to be written to the response 433 * @param status the selected response status 434 * @param request the current request 435 * @return a {@code ResponseEntity} instance 436 * @since 4.0 437 */ 438 protected ResponseEntity<Object> handleNoHandlerFoundException( 439 NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 440 441 return handleExceptionInternal(ex, null, headers, status, request); 442 } 443 444 /** 445 * Customize the response for AsyncRequestTimeoutException. 446 * <p>This method delegates to {@link #handleExceptionInternal}. 447 * @param ex the exception 448 * @param headers the headers to be written to the response 449 * @param status the selected response status 450 * @param webRequest the current request 451 * @return a {@code ResponseEntity} instance 452 * @since 4.2.8 453 */ 454 protected ResponseEntity<Object> handleAsyncRequestTimeoutException( 455 AsyncRequestTimeoutException ex, HttpHeaders headers, HttpStatus status, WebRequest webRequest) { 456 457 if (webRequest instanceof ServletWebRequest) { 458 ServletWebRequest servletRequest = (ServletWebRequest) webRequest; 459 HttpServletRequest request = servletRequest.getNativeRequest(HttpServletRequest.class); 460 HttpServletResponse response = servletRequest.getNativeResponse(HttpServletResponse.class); 461 if (response.isCommitted()) { 462 if (logger.isDebugEnabled()) { 463 logger.debug("Async timeout for " + request.getMethod() + " [" + request.getRequestURI() + "]"); 464 } 465 return null; 466 } 467 } 468 469 return handleExceptionInternal(ex, null, headers, status, webRequest); 470 } 471 472 /** 473 * A single place to customize the response body of all exception types. 474 * <p>The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE} 475 * request attribute and creates a {@link ResponseEntity} from the given 476 * body, headers, and status. 477 * @param ex the exception 478 * @param body the body for the response 479 * @param headers the headers for the response 480 * @param status the response status 481 * @param request the current request 482 */ 483 protected ResponseEntity<Object> handleExceptionInternal( 484 Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { 485 486 if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { 487 request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST); 488 } 489 return new ResponseEntity<Object>(body, headers, status); 490 } 491 492}