001/* 002 * Copyright 2002-2020 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 java.io.IOException; 020 021import javax.servlet.http.HttpServletRequest; 022import javax.servlet.http.HttpServletResponse; 023 024import org.springframework.context.MessageSource; 025import org.springframework.context.MessageSourceAware; 026import org.springframework.context.i18n.LocaleContextHolder; 027import org.springframework.core.annotation.AnnotatedElementUtils; 028import org.springframework.lang.Nullable; 029import org.springframework.util.StringUtils; 030import org.springframework.web.bind.annotation.ResponseStatus; 031import org.springframework.web.server.ResponseStatusException; 032import org.springframework.web.servlet.ModelAndView; 033import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; 034 035/** 036 * A {@link org.springframework.web.servlet.HandlerExceptionResolver 037 * HandlerExceptionResolver} that uses the {@link ResponseStatus @ResponseStatus} 038 * annotation to map exceptions to HTTP status codes. 039 * 040 * <p>This exception resolver is enabled by default in the 041 * {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet} 042 * and the MVC Java config and the MVC namespace. 043 * 044 * <p>As of 4.2 this resolver also looks recursively for {@code @ResponseStatus} 045 * present on cause exceptions, and as of 4.2.2 this resolver supports 046 * attribute overrides for {@code @ResponseStatus} in custom composed annotations. 047 * 048 * <p>As of 5.0 this resolver also supports {@link ResponseStatusException}. 049 * 050 * @author Arjen Poutsma 051 * @author Rossen Stoyanchev 052 * @author Sam Brannen 053 * @since 3.0 054 * @see ResponseStatus 055 * @see ResponseStatusException 056 */ 057public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware { 058 059 @Nullable 060 private MessageSource messageSource; 061 062 063 @Override 064 public void setMessageSource(MessageSource messageSource) { 065 this.messageSource = messageSource; 066 } 067 068 069 @Override 070 @Nullable 071 protected ModelAndView doResolveException( 072 HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { 073 074 try { 075 if (ex instanceof ResponseStatusException) { 076 return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler); 077 } 078 079 ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); 080 if (status != null) { 081 return resolveResponseStatus(status, request, response, handler, ex); 082 } 083 084 if (ex.getCause() instanceof Exception) { 085 return doResolveException(request, response, handler, (Exception) ex.getCause()); 086 } 087 } 088 catch (Exception resolveEx) { 089 if (logger.isWarnEnabled()) { 090 logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx); 091 } 092 } 093 return null; 094 } 095 096 /** 097 * Template method that handles the {@link ResponseStatus @ResponseStatus} annotation. 098 * <p>The default implementation delegates to {@link #applyStatusAndReason} 099 * with the status code and reason from the annotation. 100 * @param responseStatus the {@code @ResponseStatus} annotation 101 * @param request current HTTP request 102 * @param response current HTTP response 103 * @param handler the executed handler, or {@code null} if none chosen at the 104 * time of the exception, e.g. if multipart resolution failed 105 * @param ex the exception 106 * @return an empty ModelAndView, i.e. exception resolved 107 */ 108 protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, 109 HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { 110 111 int statusCode = responseStatus.code().value(); 112 String reason = responseStatus.reason(); 113 return applyStatusAndReason(statusCode, reason, response); 114 } 115 116 /** 117 * Template method that handles an {@link ResponseStatusException}. 118 * <p>The default implementation applies the headers from 119 * {@link ResponseStatusException#getResponseHeaders()} and delegates to 120 * {@link #applyStatusAndReason} with the status code and reason from the 121 * exception. 122 * @param ex the exception 123 * @param request current HTTP request 124 * @param response current HTTP response 125 * @param handler the executed handler, or {@code null} if none chosen at the 126 * time of the exception, e.g. if multipart resolution failed 127 * @return an empty ModelAndView, i.e. exception resolved 128 * @since 5.0 129 */ 130 protected ModelAndView resolveResponseStatusException(ResponseStatusException ex, 131 HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws Exception { 132 133 ex.getResponseHeaders().forEach((name, values) -> 134 values.forEach(value -> response.addHeader(name, value))); 135 136 int statusCode = ex.getStatus().value(); 137 String reason = ex.getReason(); 138 return applyStatusAndReason(statusCode, reason, response); 139 } 140 141 /** 142 * Apply the resolved status code and reason to the response. 143 * <p>The default implementation sends a response error using 144 * {@link HttpServletResponse#sendError(int)} or 145 * {@link HttpServletResponse#sendError(int, String)} if there is a reason 146 * and then returns an empty ModelAndView. 147 * @param statusCode the HTTP status code 148 * @param reason the associated reason (may be {@code null} or empty) 149 * @param response current HTTP response 150 * @since 5.0 151 */ 152 protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response) 153 throws IOException { 154 155 if (!StringUtils.hasLength(reason)) { 156 response.sendError(statusCode); 157 } 158 else { 159 String resolvedReason = (this.messageSource != null ? 160 this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) : 161 reason); 162 response.sendError(statusCode, resolvedReason); 163 } 164 return new ModelAndView(); 165 } 166 167}