001/*
002 * Copyright 2002-2018 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.support;
018
019import java.io.IOException;
020import java.util.List;
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.core.Ordered;
030import org.springframework.http.MediaType;
031import org.springframework.http.converter.HttpMessageNotReadableException;
032import org.springframework.http.converter.HttpMessageNotWritableException;
033import org.springframework.util.CollectionUtils;
034import org.springframework.util.StringUtils;
035import org.springframework.validation.BindException;
036import org.springframework.validation.BindingResult;
037import org.springframework.web.HttpMediaTypeNotAcceptableException;
038import org.springframework.web.HttpMediaTypeNotSupportedException;
039import org.springframework.web.HttpRequestMethodNotSupportedException;
040import org.springframework.web.bind.MethodArgumentNotValidException;
041import org.springframework.web.bind.MissingPathVariableException;
042import org.springframework.web.bind.MissingServletRequestParameterException;
043import org.springframework.web.bind.ServletRequestBindingException;
044import org.springframework.web.bind.annotation.ModelAttribute;
045import org.springframework.web.bind.annotation.RequestBody;
046import org.springframework.web.bind.annotation.RequestPart;
047import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
048import org.springframework.web.multipart.MultipartFile;
049import org.springframework.web.multipart.support.MissingServletRequestPartException;
050import org.springframework.web.servlet.ModelAndView;
051import org.springframework.web.servlet.NoHandlerFoundException;
052import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
053
054/**
055 * The default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver}
056 * interface, resolving standard Spring MVC exceptions and translating them to corresponding
057 * HTTP status codes.
058 *
059 * <p>This exception resolver is enabled by default in the common Spring
060 * {@link org.springframework.web.servlet.DispatcherServlet}.
061 *
062 * @author Arjen Poutsma
063 * @author Rossen Stoyanchev
064 * @author Juergen Hoeller
065 * @since 3.0
066 * @see org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
067 * @see #handleNoSuchRequestHandlingMethod
068 * @see #handleHttpRequestMethodNotSupported
069 * @see #handleHttpMediaTypeNotSupported
070 * @see #handleMissingServletRequestParameter
071 * @see #handleServletRequestBindingException
072 * @see #handleTypeMismatch
073 * @see #handleHttpMessageNotReadable
074 * @see #handleHttpMessageNotWritable
075 * @see #handleMethodArgumentNotValidException
076 * @see #handleMissingServletRequestParameter
077 * @see #handleMissingServletRequestPartException
078 * @see #handleBindException
079 */
080public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
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         * Additional 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        /**
096         * Sets the {@linkplain #setOrder(int) order} to {@link #LOWEST_PRECEDENCE}.
097         */
098        public DefaultHandlerExceptionResolver() {
099                setOrder(Ordered.LOWEST_PRECEDENCE);
100                setWarnLogCategory(getClass().getName());
101        }
102
103
104        @Override
105        @SuppressWarnings("deprecation")
106        protected ModelAndView doResolveException(
107                        HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
108
109                try {
110                        if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
111                                return handleNoSuchRequestHandlingMethod(
112                                                (org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex,
113                                                request, response, handler);
114                        }
115                        else if (ex instanceof HttpRequestMethodNotSupportedException) {
116                                return handleHttpRequestMethodNotSupported(
117                                                (HttpRequestMethodNotSupportedException) ex, request, response, handler);
118                        }
119                        else if (ex instanceof HttpMediaTypeNotSupportedException) {
120                                return handleHttpMediaTypeNotSupported(
121                                                (HttpMediaTypeNotSupportedException) ex, request, response, handler);
122                        }
123                        else if (ex instanceof HttpMediaTypeNotAcceptableException) {
124                                return handleHttpMediaTypeNotAcceptable(
125                                                (HttpMediaTypeNotAcceptableException) ex, request, response, handler);
126                        }
127                        else if (ex instanceof MissingPathVariableException) {
128                                return handleMissingPathVariable(
129                                                (MissingPathVariableException) ex, request, response, handler);
130                        }
131                        else if (ex instanceof MissingServletRequestParameterException) {
132                                return handleMissingServletRequestParameter(
133                                                (MissingServletRequestParameterException) ex, request, response, handler);
134                        }
135                        else if (ex instanceof ServletRequestBindingException) {
136                                return handleServletRequestBindingException(
137                                                (ServletRequestBindingException) ex, request, response, handler);
138                        }
139                        else if (ex instanceof ConversionNotSupportedException) {
140                                return handleConversionNotSupported(
141                                                (ConversionNotSupportedException) ex, request, response, handler);
142                        }
143                        else if (ex instanceof TypeMismatchException) {
144                                return handleTypeMismatch(
145                                                (TypeMismatchException) ex, request, response, handler);
146                        }
147                        else if (ex instanceof HttpMessageNotReadableException) {
148                                return handleHttpMessageNotReadable(
149                                                (HttpMessageNotReadableException) ex, request, response, handler);
150                        }
151                        else if (ex instanceof HttpMessageNotWritableException) {
152                                return handleHttpMessageNotWritable(
153                                                (HttpMessageNotWritableException) ex, request, response, handler);
154                        }
155                        else if (ex instanceof MethodArgumentNotValidException) {
156                                return handleMethodArgumentNotValidException(
157                                                (MethodArgumentNotValidException) ex, request, response, handler);
158                        }
159                        else if (ex instanceof MissingServletRequestPartException) {
160                                return handleMissingServletRequestPartException(
161                                                (MissingServletRequestPartException) ex, request, response, handler);
162                        }
163                        else if (ex instanceof BindException) {
164                                return handleBindException((BindException) ex, request, response, handler);
165                        }
166                        else if (ex instanceof NoHandlerFoundException) {
167                                return handleNoHandlerFoundException(
168                                                (NoHandlerFoundException) ex, request, response, handler);
169                        }
170                        else if (ex instanceof AsyncRequestTimeoutException) {
171                                return handleAsyncRequestTimeoutException(
172                                                (AsyncRequestTimeoutException) ex, request, response, handler);
173                        }
174                }
175                catch (Exception handlerException) {
176                        if (logger.isWarnEnabled()) {
177                                logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in exception", handlerException);
178                        }
179                }
180                return null;
181        }
182
183        /**
184         * Handle the case where no request handler method was found.
185         * <p>The default implementation logs a warning, sends an HTTP 404 error, and returns
186         * an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen,
187         * or the NoSuchRequestHandlingMethodException could be rethrown as-is.
188         * @param ex the NoSuchRequestHandlingMethodException to be handled
189         * @param request current HTTP request
190         * @param response current HTTP response
191         * @param handler the executed handler, or {@code null} if none chosen
192         * at the time of the exception (for example, if multipart resolution failed)
193         * @return an empty ModelAndView indicating the exception was handled
194         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
195         * @deprecated as of 4.3, along with {@link org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException}
196         */
197        @Deprecated
198        protected ModelAndView handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex,
199                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
200
201                pageNotFoundLogger.warn(ex.getMessage());
202                response.sendError(HttpServletResponse.SC_NOT_FOUND);
203                return new ModelAndView();
204        }
205
206        /**
207         * Handle the case where no request handler method was found for the particular HTTP request method.
208         * <p>The default implementation logs a warning, sends an HTTP 405 error, sets the "Allow" header,
209         * and returns an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen,
210         * or the HttpRequestMethodNotSupportedException could be rethrown as-is.
211         * @param ex the HttpRequestMethodNotSupportedException to be handled
212         * @param request current HTTP request
213         * @param response current HTTP response
214         * @param handler the executed handler, or {@code null} if none chosen
215         * at the time of the exception (for example, if multipart resolution failed)
216         * @return an empty ModelAndView indicating the exception was handled
217         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
218         */
219        protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
220                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
221
222                String[] supportedMethods = ex.getSupportedMethods();
223                if (supportedMethods != null) {
224                        response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
225                }
226                response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
227                return new ModelAndView();
228        }
229
230        /**
231         * Handle the case where no {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}
232         * were found for the PUT or POSTed content.
233         * <p>The default implementation sends an HTTP 415 error, sets the "Accept" header,
234         * and returns an empty {@code ModelAndView}. Alternatively, a fallback view could
235         * be chosen, or the HttpMediaTypeNotSupportedException could be rethrown as-is.
236         * @param ex the HttpMediaTypeNotSupportedException to be handled
237         * @param request current HTTP request
238         * @param response current HTTP response
239         * @param handler the executed handler
240         * @return an empty ModelAndView indicating the exception was handled
241         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
242         */
243        protected ModelAndView handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
244                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
245
246                response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
247                List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
248                if (!CollectionUtils.isEmpty(mediaTypes)) {
249                        response.setHeader("Accept", MediaType.toString(mediaTypes));
250                }
251                return new ModelAndView();
252        }
253
254        /**
255         * Handle the case where no {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}
256         * were found that were acceptable for the client (expressed via the {@code Accept} header.
257         * <p>The default implementation sends an HTTP 406 error and returns an empty {@code ModelAndView}.
258         * Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotAcceptableException
259         * could be rethrown as-is.
260         * @param ex the HttpMediaTypeNotAcceptableException to be handled
261         * @param request current HTTP request
262         * @param response current HTTP response
263         * @param handler the executed handler
264         * @return an empty ModelAndView indicating the exception was handled
265         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
266         */
267        protected ModelAndView handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex,
268                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
269
270                response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
271                return new ModelAndView();
272        }
273
274        /**
275         * Handle the case when a declared path variable does not match any extracted URI variable.
276         * <p>The default implementation sends an HTTP 500 error, and returns an empty {@code ModelAndView}.
277         * Alternatively, a fallback view could be chosen, or the MissingPathVariableException
278         * could be rethrown as-is.
279         * @param ex the MissingPathVariableException to be handled
280         * @param request current HTTP request
281         * @param response current HTTP response
282         * @param handler the executed handler
283         * @return an empty ModelAndView indicating the exception was handled
284         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
285         * @since 4.2
286         */
287        protected ModelAndView handleMissingPathVariable(MissingPathVariableException ex,
288                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
289
290                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
291                return new ModelAndView();
292        }
293
294        /**
295         * Handle the case when a required parameter is missing.
296         * <p>The default implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}.
297         * Alternatively, a fallback view could be chosen, or the MissingServletRequestParameterException
298         * could be rethrown as-is.
299         * @param ex the MissingServletRequestParameterException to be handled
300         * @param request current HTTP request
301         * @param response current HTTP response
302         * @param handler the executed handler
303         * @return an empty ModelAndView indicating the exception was handled
304         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
305         */
306        protected ModelAndView handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
307                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
308
309                response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
310                return new ModelAndView();
311        }
312
313        /**
314         * Handle the case when an unrecoverable binding exception occurs - e.g. required header, required cookie.
315         * <p>The default implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}.
316         * Alternatively, a fallback view could be chosen, or the exception could be rethrown as-is.
317         * @param ex the exception to be handled
318         * @param request current HTTP request
319         * @param response current HTTP response
320         * @param handler the executed handler
321         * @return an empty ModelAndView indicating the exception was handled
322         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
323         */
324        protected ModelAndView handleServletRequestBindingException(ServletRequestBindingException ex,
325                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
326
327                response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
328                return new ModelAndView();
329        }
330
331        /**
332         * Handle the case when a {@link org.springframework.web.bind.WebDataBinder} conversion cannot occur.
333         * <p>The default implementation sends an HTTP 500 error, and returns an empty {@code ModelAndView}.
334         * Alternatively, a fallback view could be chosen, or the ConversionNotSupportedException could be
335         * rethrown as-is.
336         * @param ex the ConversionNotSupportedException to be handled
337         * @param request current HTTP request
338         * @param response current HTTP response
339         * @param handler the executed handler
340         * @return an empty ModelAndView indicating the exception was handled
341         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
342         */
343        protected ModelAndView handleConversionNotSupported(ConversionNotSupportedException ex,
344                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
345
346                sendServerError(ex, request, response);
347                return new ModelAndView();
348        }
349
350        /**
351         * Handle the case when a {@link org.springframework.web.bind.WebDataBinder} conversion error occurs.
352         * <p>The default implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}.
353         * Alternatively, a fallback view could be chosen, or the TypeMismatchException could be rethrown as-is.
354         * @param ex the TypeMismatchException to be handled
355         * @param request current HTTP request
356         * @param response current HTTP response
357         * @param handler the executed handler
358         * @return an empty ModelAndView indicating the exception was handled
359         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
360         */
361        protected ModelAndView handleTypeMismatch(TypeMismatchException ex,
362                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
363
364                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
365                return new ModelAndView();
366        }
367
368        /**
369         * Handle the case where a {@linkplain org.springframework.http.converter.HttpMessageConverter message converter}
370         * cannot read from a HTTP request.
371         * <p>The default implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}.
372         * Alternatively, a fallback view could be chosen, or the HttpMessageNotReadableException could be
373         * rethrown as-is.
374         * @param ex the HttpMessageNotReadableException to be handled
375         * @param request current HTTP request
376         * @param response current HTTP response
377         * @param handler the executed handler
378         * @return an empty ModelAndView indicating the exception was handled
379         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
380         */
381        protected ModelAndView handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
382                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
383
384                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
385                return new ModelAndView();
386        }
387
388        /**
389         * Handle the case where a
390         * {@linkplain org.springframework.http.converter.HttpMessageConverter message converter}
391         * cannot write to a HTTP request.
392         * <p>The default implementation sends an HTTP 500 error, and returns an empty {@code ModelAndView}.
393         * Alternatively, a fallback view could be chosen, or the HttpMessageNotWritableException could
394         * be rethrown as-is.
395         * @param ex the HttpMessageNotWritableException to be handled
396         * @param request current HTTP request
397         * @param response current HTTP response
398         * @param handler the executed handler
399         * @return an empty ModelAndView indicating the exception was handled
400         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
401         */
402        protected ModelAndView handleHttpMessageNotWritable(HttpMessageNotWritableException ex,
403                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
404
405                sendServerError(ex, request, response);
406                return new ModelAndView();
407        }
408
409        /**
410         * Handle the case where an argument annotated with {@code @Valid} such as
411         * an {@link RequestBody} or {@link RequestPart} argument fails validation.
412         * <p>By default, an HTTP 400 error is sent back to the client.
413         * @param request current HTTP request
414         * @param response current HTTP response
415         * @param handler the executed handler
416         * @return an empty ModelAndView indicating the exception was handled
417         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
418         */
419        protected ModelAndView handleMethodArgumentNotValidException(MethodArgumentNotValidException ex,
420                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
421
422                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
423                return new ModelAndView();
424        }
425
426        /**
427         * Handle the case where an {@linkplain RequestPart @RequestPart}, a {@link MultipartFile},
428         * or a {@code javax.servlet.http.Part} argument is required but is missing.
429         * <p>By default, an HTTP 400 error is sent back to the client.
430         * @param request current HTTP request
431         * @param response current HTTP response
432         * @param handler the executed handler
433         * @return an empty ModelAndView indicating the exception was handled
434         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
435         */
436        protected ModelAndView handleMissingServletRequestPartException(MissingServletRequestPartException ex,
437                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
438
439                response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
440                return new ModelAndView();
441        }
442
443        /**
444         * Handle the case where an {@linkplain ModelAttribute @ModelAttribute} method
445         * argument has binding or validation errors and is not followed by another
446         * method argument of type {@link BindingResult}.
447         * <p>By default, an HTTP 400 error is sent back to the client.
448         * @param request current HTTP request
449         * @param response current HTTP response
450         * @param handler the executed handler
451         * @return an empty ModelAndView indicating the exception was handled
452         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
453         */
454        protected ModelAndView handleBindException(BindException ex, HttpServletRequest request,
455                        HttpServletResponse response, Object handler) throws IOException {
456
457                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
458                return new ModelAndView();
459        }
460
461        /**
462         * Handle the case where no handler was found during the dispatch.
463         * <p>The default implementation sends an HTTP 404 error and returns an empty
464         * {@code ModelAndView}. Alternatively, a fallback view could be chosen,
465         * or the NoHandlerFoundException could be rethrown as-is.
466         * @param ex the NoHandlerFoundException to be handled
467         * @param request current HTTP request
468         * @param response current HTTP response
469         * @param handler the executed handler, or {@code null} if none chosen
470         * at the time of the exception (for example, if multipart resolution failed)
471         * @return an empty ModelAndView indicating the exception was handled
472         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
473         * @since 4.0
474         */
475        protected ModelAndView handleNoHandlerFoundException(NoHandlerFoundException ex,
476                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
477
478                response.sendError(HttpServletResponse.SC_NOT_FOUND);
479                return new ModelAndView();
480        }
481
482        /**
483         * Handle the case where an async request timed out.
484         * <p>The default implementation sends an HTTP 503 error.
485         * @param ex the {@link AsyncRequestTimeoutException }to be handled
486         * @param request current HTTP request
487         * @param response current HTTP response
488         * @param handler the executed handler, or {@code null} if none chosen
489         * at the time of the exception (for example, if multipart resolution failed)
490         * @return an empty ModelAndView indicating the exception was handled
491         * @throws IOException potentially thrown from {@link HttpServletResponse#sendError}
492         * @since 4.2.8
493         */
494        protected ModelAndView handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex,
495                        HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
496
497                if (!response.isCommitted()) {
498                        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
499                }
500                else {
501                        logger.warn("Async request timed out");
502                }
503                return new ModelAndView();
504        }
505
506        /**
507         * Invoked to send a server error. Sets the status to 500 and also sets the
508         * request attribute "javax.servlet.error.exception" to the Exception.
509         */
510        protected void sendServerError(Exception ex, HttpServletRequest request, HttpServletResponse response)
511                        throws IOException {
512
513                request.setAttribute("javax.servlet.error.exception", ex);
514                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
515        }
516
517}