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}