All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2019 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
 *
 *      https://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.List;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

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.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.lang.Nullable;
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.MissingPathVariableException;
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.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.util.WebUtils;

/**
 * A convenient base class for {@link ControllerAdvice @ControllerAdvice} classes
 * that wish to provide centralized exception handling across all
 * {@code @RequestMapping} methods through {@code @ExceptionHandler} methods.
 *
 * 

This base class provides an {@code @ExceptionHandler} method for handling * internal Spring MVC exceptions. This method returns a {@code ResponseEntity} * for writing to the response with a {@link HttpMessageConverter message converter}, * in contrast to * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver * DefaultHandlerExceptionResolver} which returns a * {@link org.springframework.web.servlet.ModelAndView ModelAndView}. * *

If there is no need to write error content to the response body, or when * using view resolution (e.g., via {@code ContentNegotiatingViewResolver}), * then {@code DefaultHandlerExceptionResolver} is good enough. * *

Note that in order for an {@code @ControllerAdvice} subclass to be * detected, {@link ExceptionHandlerExceptionResolver} must be configured. * * @author Rossen Stoyanchev * @since 3.2 * @see #handleException(Exception, WebRequest) * @see org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver */ public abstract class ResponseEntityExceptionHandler { /** * 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"; /** * Specific 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); /** * Common logger for use in subclasses. */ protected final Log logger = LogFactory.getLog(getClass()); /** * Provides handling for standard Spring MVC exceptions. * @param ex the target exception * @param request the current request */ @ExceptionHandler({ HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class, HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, ServletRequestBindingException.class, ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, MethodArgumentNotValidException.class, MissingServletRequestPartException.class, BindException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class }) @Nullable public final ResponseEntity handleException(Exception ex, WebRequest request) throws Exception { HttpHeaders headers = new HttpHeaders(); if (ex instanceof HttpRequestMethodNotSupportedException) { HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED; return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request); } else if (ex instanceof HttpMediaTypeNotSupportedException) { HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE; return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request); } else if (ex instanceof HttpMediaTypeNotAcceptableException) { HttpStatus status = HttpStatus.NOT_ACCEPTABLE; return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request); } else if (ex instanceof MissingPathVariableException) { HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request); } else if (ex instanceof MissingServletRequestParameterException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request); } else if (ex instanceof ServletRequestBindingException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request); } else if (ex instanceof ConversionNotSupportedException) { HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request); } else if (ex instanceof TypeMismatchException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleTypeMismatch((TypeMismatchException) ex, headers, status, request); } else if (ex instanceof HttpMessageNotReadableException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request); } else if (ex instanceof HttpMessageNotWritableException) { HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request); } else if (ex instanceof MethodArgumentNotValidException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request); } else if (ex instanceof MissingServletRequestPartException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request); } else if (ex instanceof BindException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleBindException((BindException) ex, headers, status, request); } else if (ex instanceof NoHandlerFoundException) { HttpStatus status = HttpStatus.NOT_FOUND; return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request); } else if (ex instanceof AsyncRequestTimeoutException) { HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE; return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request); } else { // Unknown exception, typically a wrapper with a common MVC exception as cause // (since @ExceptionHandler type declarations also match first-level causes): // We only deal with top-level MVC exceptions here, so let's rethrow the given // exception for further processing through the HandlerExceptionResolver chain. throw ex; } } /** * Customize the response for HttpRequestMethodNotSupportedException. *

This method logs a warning, sets the "Allow" header, and delegates to * {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleHttpRequestMethodNotSupported( HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { pageNotFoundLogger.warn(ex.getMessage()); Set supportedMethods = ex.getSupportedHttpMethods(); if (!CollectionUtils.isEmpty(supportedMethods)) { headers.setAllow(supportedMethods); } return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for HttpMediaTypeNotSupportedException. *

This method sets the "Accept" header and delegates to * {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleHttpMediaTypeNotSupported( HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { List mediaTypes = ex.getSupportedMediaTypes(); if (!CollectionUtils.isEmpty(mediaTypes)) { headers.setAccept(mediaTypes); } return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for HttpMediaTypeNotAcceptableException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleHttpMediaTypeNotAcceptable( HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for MissingPathVariableException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance * @since 4.2 */ protected ResponseEntity handleMissingPathVariable( MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for MissingServletRequestParameterException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleMissingServletRequestParameter( MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for ServletRequestBindingException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleServletRequestBindingException( ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for ConversionNotSupportedException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleConversionNotSupported( ConversionNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for TypeMismatchException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleTypeMismatch( TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for HttpMessageNotReadableException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleHttpMessageNotReadable( HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for HttpMessageNotWritableException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleHttpMessageNotWritable( HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for MethodArgumentNotValidException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for MissingServletRequestPartException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleMissingServletRequestPart( MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for BindException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance */ protected ResponseEntity handleBindException( BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for NoHandlerFoundException. *

This method delegates to {@link #handleExceptionInternal}. * @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 a {@code ResponseEntity} instance * @since 4.0 */ protected ResponseEntity handleNoHandlerFoundException( NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } /** * Customize the response for AsyncRequestTimeoutException. *

This method delegates to {@link #handleExceptionInternal}. * @param ex the exception * @param headers the headers to be written to the response * @param status the selected response status * @param webRequest the current request * @return a {@code ResponseEntity} instance * @since 4.2.8 */ @Nullable protected ResponseEntity handleAsyncRequestTimeoutException( AsyncRequestTimeoutException ex, HttpHeaders headers, HttpStatus status, WebRequest webRequest) { if (webRequest instanceof ServletWebRequest) { ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest; HttpServletResponse response = servletWebRequest.getResponse(); if (response != null && response.isCommitted()) { if (logger.isWarnEnabled()) { logger.warn("Async request timed out"); } return null; } } return handleExceptionInternal(ex, null, headers, status, webRequest); } /** * A single place to customize the response body of all exception types. *

The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE} * request attribute and creates a {@link ResponseEntity} from the given * body, headers, and status. * @param ex the exception * @param body the body for the response * @param headers the headers for the response * @param status the response status * @param request the current request */ protected ResponseEntity handleExceptionInternal( Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST); } return new ResponseEntity<>(body, headers, status); } }