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