001/* 002 * Copyright 2002-2019 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.handler; 018 019import java.util.Set; 020 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.core.Ordered; 028import org.springframework.lang.Nullable; 029import org.springframework.util.StringUtils; 030import org.springframework.web.servlet.HandlerExceptionResolver; 031import org.springframework.web.servlet.ModelAndView; 032 033/** 034 * Abstract base class for {@link HandlerExceptionResolver} implementations. 035 * 036 * <p>Supports mapped {@linkplain #setMappedHandlers handlers} and 037 * {@linkplain #setMappedHandlerClasses handler classes} that the resolver 038 * should be applied to and implements the {@link Ordered} interface. 039 * 040 * @author Arjen Poutsma 041 * @author Juergen Hoeller 042 * @author Sam Brannen 043 * @since 3.0 044 */ 045public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered { 046 047 private static final String HEADER_CACHE_CONTROL = "Cache-Control"; 048 049 050 /** Logger available to subclasses. */ 051 protected final Log logger = LogFactory.getLog(getClass()); 052 053 private int order = Ordered.LOWEST_PRECEDENCE; 054 055 @Nullable 056 private Set<?> mappedHandlers; 057 058 @Nullable 059 private Class<?>[] mappedHandlerClasses; 060 061 @Nullable 062 private Log warnLogger; 063 064 private boolean preventResponseCaching = false; 065 066 067 public void setOrder(int order) { 068 this.order = order; 069 } 070 071 @Override 072 public int getOrder() { 073 return this.order; 074 } 075 076 /** 077 * Specify the set of handlers that this exception resolver should apply to. 078 * <p>The exception mappings and the default error view will only apply to the specified handlers. 079 * <p>If no handlers or handler classes are set, the exception mappings and the default error 080 * view will apply to all handlers. This means that a specified default error view will be used 081 * as a fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be 082 * ignored in this case. 083 */ 084 public void setMappedHandlers(Set<?> mappedHandlers) { 085 this.mappedHandlers = mappedHandlers; 086 } 087 088 /** 089 * Specify the set of classes that this exception resolver should apply to. 090 * <p>The exception mappings and the default error view will only apply to handlers of the 091 * specified types; the specified types may be interfaces or superclasses of handlers as well. 092 * <p>If no handlers or handler classes are set, the exception mappings and the default error 093 * view will apply to all handlers. This means that a specified default error view will be used 094 * as a fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be 095 * ignored in this case. 096 */ 097 public void setMappedHandlerClasses(Class<?>... mappedHandlerClasses) { 098 this.mappedHandlerClasses = mappedHandlerClasses; 099 } 100 101 /** 102 * Set the log category for warn logging. The name will be passed to the underlying logger 103 * implementation through Commons Logging, getting interpreted as a log category according 104 * to the logger's configuration. If {@code null} or empty String is passed, warn logging 105 * is turned off. 106 * <p>By default there is no warn logging although subclasses like 107 * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver} 108 * can change that default. Specify this setting to activate warn logging into a specific 109 * category. Alternatively, override the {@link #logException} method for custom logging. 110 * @see org.apache.commons.logging.LogFactory#getLog(String) 111 * @see java.util.logging.Logger#getLogger(String) 112 */ 113 public void setWarnLogCategory(String loggerName) { 114 this.warnLogger = (StringUtils.hasLength(loggerName) ? LogFactory.getLog(loggerName) : null); 115 } 116 117 /** 118 * Specify whether to prevent HTTP response caching for any view resolved 119 * by this exception resolver. 120 * <p>Default is {@code false}. Switch this to {@code true} in order to 121 * automatically generate HTTP response headers that suppress response caching. 122 */ 123 public void setPreventResponseCaching(boolean preventResponseCaching) { 124 this.preventResponseCaching = preventResponseCaching; 125 } 126 127 128 /** 129 * Check whether this resolver is supposed to apply (i.e. if the supplied handler 130 * matches any of the configured {@linkplain #setMappedHandlers handlers} or 131 * {@linkplain #setMappedHandlerClasses handler classes}), and then delegate 132 * to the {@link #doResolveException} template method. 133 */ 134 @Override 135 @Nullable 136 public ModelAndView resolveException( 137 HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { 138 139 if (shouldApplyTo(request, handler)) { 140 prepareResponse(ex, response); 141 ModelAndView result = doResolveException(request, response, handler, ex); 142 if (result != null) { 143 // Print debug message when warn logger is not enabled. 144 if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) { 145 logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result)); 146 } 147 // Explicitly configured warn logger in logException method. 148 logException(ex, request); 149 } 150 return result; 151 } 152 else { 153 return null; 154 } 155 } 156 157 /** 158 * Check whether this resolver is supposed to apply to the given handler. 159 * <p>The default implementation checks against the configured 160 * {@linkplain #setMappedHandlers handlers} and 161 * {@linkplain #setMappedHandlerClasses handler classes}, if any. 162 * @param request current HTTP request 163 * @param handler the executed handler, or {@code null} if none chosen 164 * at the time of the exception (for example, if multipart resolution failed) 165 * @return whether this resolved should proceed with resolving the exception 166 * for the given request and handler 167 * @see #setMappedHandlers 168 * @see #setMappedHandlerClasses 169 */ 170 protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { 171 if (handler != null) { 172 if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) { 173 return true; 174 } 175 if (this.mappedHandlerClasses != null) { 176 for (Class<?> handlerClass : this.mappedHandlerClasses) { 177 if (handlerClass.isInstance(handler)) { 178 return true; 179 } 180 } 181 } 182 } 183 // Else only apply if there are no explicit handler mappings. 184 return (this.mappedHandlers == null && this.mappedHandlerClasses == null); 185 } 186 187 /** 188 * Log the given exception at warn level, provided that warn logging has been 189 * activated through the {@link #setWarnLogCategory "warnLogCategory"} property. 190 * <p>Calls {@link #buildLogMessage} in order to determine the concrete message to log. 191 * @param ex the exception that got thrown during handler execution 192 * @param request current HTTP request (useful for obtaining metadata) 193 * @see #setWarnLogCategory 194 * @see #buildLogMessage 195 * @see org.apache.commons.logging.Log#warn(Object, Throwable) 196 */ 197 protected void logException(Exception ex, HttpServletRequest request) { 198 if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) { 199 this.warnLogger.warn(buildLogMessage(ex, request)); 200 } 201 } 202 203 /** 204 * Build a log message for the given exception, occurred during processing the given request. 205 * @param ex the exception that got thrown during handler execution 206 * @param request current HTTP request (useful for obtaining metadata) 207 * @return the log message to use 208 */ 209 protected String buildLogMessage(Exception ex, HttpServletRequest request) { 210 return "Resolved [" + ex + "]"; 211 } 212 213 /** 214 * Prepare the response for the exceptional case. 215 * <p>The default implementation prevents the response from being cached, 216 * if the {@link #setPreventResponseCaching "preventResponseCaching"} property 217 * has been set to "true". 218 * @param ex the exception that got thrown during handler execution 219 * @param response current HTTP response 220 * @see #preventCaching 221 */ 222 protected void prepareResponse(Exception ex, HttpServletResponse response) { 223 if (this.preventResponseCaching) { 224 preventCaching(response); 225 } 226 } 227 228 /** 229 * Prevents the response from being cached, through setting corresponding 230 * HTTP {@code Cache-Control: no-store} header. 231 * @param response current HTTP response 232 */ 233 protected void preventCaching(HttpServletResponse response) { 234 response.addHeader(HEADER_CACHE_CONTROL, "no-store"); 235 } 236 237 238 /** 239 * Actually resolve the given exception that got thrown during handler execution, 240 * returning a {@link ModelAndView} that represents a specific error page if appropriate. 241 * <p>May be overridden in subclasses, in order to apply specific exception checks. 242 * Note that this template method will be invoked <i>after</i> checking whether this 243 * resolved applies ("mappedHandlers" etc), so an implementation may simply proceed 244 * with its actual exception handling. 245 * @param request current HTTP request 246 * @param response current HTTP response 247 * @param handler the executed handler, or {@code null} if none chosen at the time 248 * of the exception (for example, if multipart resolution failed) 249 * @param ex the exception that got thrown during handler execution 250 * @return a corresponding {@code ModelAndView} to forward to, 251 * or {@code null} for default processing in the resolution chain 252 */ 253 @Nullable 254 protected abstract ModelAndView doResolveException( 255 HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex); 256 257}