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.annotation;
018
019import javax.servlet.http.HttpServletRequest;
020import javax.servlet.http.HttpServletResponse;
021
022import org.springframework.context.MessageSource;
023import org.springframework.context.MessageSourceAware;
024import org.springframework.context.i18n.LocaleContextHolder;
025import org.springframework.core.annotation.AnnotatedElementUtils;
026import org.springframework.util.StringUtils;
027import org.springframework.web.bind.annotation.ResponseStatus;
028import org.springframework.web.servlet.ModelAndView;
029import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
030
031/**
032 * A {@link org.springframework.web.servlet.HandlerExceptionResolver
033 * HandlerExceptionResolver} that uses the {@link ResponseStatus @ResponseStatus}
034 * annotation to map exceptions to HTTP status codes.
035 *
036 * <p>This exception resolver is enabled by default in the
037 * {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
038 * and the MVC Java config and the MVC namespace.
039 *
040 * <p>As of 4.2 this resolver also looks recursively for {@code @ResponseStatus}
041 * present on cause exceptions, and as of 4.2.2 this resolver supports
042 * attribute overrides for {@code @ResponseStatus} in custom composed annotations.
043 *
044 * @author Arjen Poutsma
045 * @author Rossen Stoyanchev
046 * @author Sam Brannen
047 * @since 3.0
048 * @see ResponseStatus
049 */
050public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {
051
052        private MessageSource messageSource;
053
054
055        @Override
056        public void setMessageSource(MessageSource messageSource) {
057                this.messageSource = messageSource;
058        }
059
060
061        @Override
062        protected ModelAndView doResolveException(
063                        HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
064
065                ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
066                if (status != null) {
067                        try {
068                                return resolveResponseStatus(status, request, response, handler, ex);
069                        }
070                        catch (Exception resolveEx) {
071                                logger.warn("ResponseStatus handling resulted in exception", resolveEx);
072                        }
073                }
074                else if (ex.getCause() instanceof Exception) {
075                        return doResolveException(request, response, handler, (Exception) ex.getCause());
076                }
077                return null;
078        }
079
080        /**
081         * Template method that handles the {@link ResponseStatus @ResponseStatus} annotation.
082         * <p>The default implementation sends a response error using
083         * {@link HttpServletResponse#sendError(int)} or
084         * {@link HttpServletResponse#sendError(int, String)} if the annotation has a
085         * {@linkplain ResponseStatus#reason() reason} and then returns an empty ModelAndView.
086         * @param responseStatus the annotation
087         * @param request current HTTP request
088         * @param response current HTTP response
089         * @param handler the executed handler, or {@code null} if none chosen at the
090         * time of the exception, e.g. if multipart resolution failed
091         * @param ex the exception
092         * @return an empty ModelAndView, i.e. exception resolved
093         */
094        protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
095                        HttpServletResponse response, Object handler, Exception ex) throws Exception {
096
097                int statusCode = responseStatus.code().value();
098                String reason = responseStatus.reason();
099                if (!StringUtils.hasLength(reason)) {
100                        response.sendError(statusCode);
101                }
102                else {
103                        String resolvedReason = (this.messageSource != null ?
104                                        this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
105                                        reason);
106                        response.sendError(statusCode, resolvedReason);
107                }
108                return new ModelAndView();
109        }
110
111}