org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver Maven / Gradle / Ivy
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
/**
* Abstract base class for {@link HandlerExceptionResolver} implementations.
*
* Supports mapped {@linkplain #setMappedHandlers handlers} and
* {@linkplain #setMappedHandlerClasses handler classes} that the resolver
* should be applied to and implements the {@link Ordered} interface.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
private static final String HEADER_CACHE_CONTROL = "Cache-Control";
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
private int order = Ordered.LOWEST_PRECEDENCE;
@Nullable
private Set mappedHandlers;
@Nullable
private Class[] mappedHandlerClasses;
@Nullable
private Log warnLogger;
private boolean preventResponseCaching = false;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
/**
* Specify the set of handlers that this exception resolver should apply to.
*
The exception mappings and the default error view will only apply to the specified handlers.
*
If no handlers or handler classes are set, the exception mappings and the default error
* view will apply to all handlers. This means that a specified default error view will be used
* as a fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be
* ignored in this case.
*/
public void setMappedHandlers(Set mappedHandlers) {
this.mappedHandlers = mappedHandlers;
}
/**
* Specify the set of classes that this exception resolver should apply to.
*
The exception mappings and the default error view will only apply to handlers of the
* specified types; the specified types may be interfaces or superclasses of handlers as well.
*
If no handlers or handler classes are set, the exception mappings and the default error
* view will apply to all handlers. This means that a specified default error view will be used
* as a fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be
* ignored in this case.
*/
public void setMappedHandlerClasses(Class... mappedHandlerClasses) {
this.mappedHandlerClasses = mappedHandlerClasses;
}
/**
* Set the log category for warn logging. The name will be passed to the underlying logger
* implementation through Commons Logging, getting interpreted as a log category according
* to the logger's configuration. If {@code null} or empty String is passed, warn logging
* is turned off.
*
By default there is no warn logging although subclasses like
* {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}
* can change that default. Specify this setting to activate warn logging into a specific
* category. Alternatively, override the {@link #logException} method for custom logging.
* @see org.apache.commons.logging.LogFactory#getLog(String)
* @see java.util.logging.Logger#getLogger(String)
*/
public void setWarnLogCategory(String loggerName) {
this.warnLogger = (StringUtils.hasLength(loggerName) ? LogFactory.getLog(loggerName) : null);
}
/**
* Specify whether to prevent HTTP response caching for any view resolved
* by this exception resolver.
*
Default is {@code false}. Switch this to {@code true} in order to
* automatically generate HTTP response headers that suppress response caching.
*/
public void setPreventResponseCaching(boolean preventResponseCaching) {
this.preventResponseCaching = preventResponseCaching;
}
/**
* Check whether this resolver is supposed to apply (i.e. if the supplied handler
* matches any of the configured {@linkplain #setMappedHandlers handlers} or
* {@linkplain #setMappedHandlerClasses handler classes}), and then delegate
* to the {@link #doResolveException} template method.
*/
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
/**
* Check whether this resolver is supposed to apply to the given handler.
*
The default implementation checks against the configured
* {@linkplain #setMappedHandlers handlers} and
* {@linkplain #setMappedHandlerClasses handler classes}, if any.
* @param request current HTTP request
* @param handler the executed handler, or {@code null} if none chosen
* at the time of the exception (for example, if multipart resolution failed)
* @return whether this resolved should proceed with resolving the exception
* for the given request and handler
* @see #setMappedHandlers
* @see #setMappedHandlerClasses
*/
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (Class handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
return !hasHandlerMappings();
}
/**
* Whether there are any handler mappings registered via
* {@link #setMappedHandlers(Set)} or {@link #setMappedHandlerClasses(Class[])}.
* @since 5.3
*/
protected boolean hasHandlerMappings() {
return (this.mappedHandlers != null || this.mappedHandlerClasses != null);
}
/**
* Log the given exception at warn level, provided that warn logging has been
* activated through the {@link #setWarnLogCategory "warnLogCategory"} property.
*
Calls {@link #buildLogMessage} in order to determine the concrete message to log.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @see #setWarnLogCategory
* @see #buildLogMessage
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
*/
protected void logException(Exception ex, HttpServletRequest request) {
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(buildLogMessage(ex, request));
}
}
/**
* Build a log message for the given exception, occurred during processing the given request.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
return "Resolved [" + ex + "]";
}
/**
* Prepare the response for the exceptional case.
*
The default implementation prevents the response from being cached,
* if the {@link #setPreventResponseCaching "preventResponseCaching"} property
* has been set to "true".
* @param ex the exception that got thrown during handler execution
* @param response current HTTP response
* @see #preventCaching
*/
protected void prepareResponse(Exception ex, HttpServletResponse response) {
if (this.preventResponseCaching) {
preventCaching(response);
}
}
/**
* Prevents the response from being cached, through setting corresponding
* HTTP {@code Cache-Control: no-store} header.
* @param response current HTTP response
*/
protected void preventCaching(HttpServletResponse response) {
response.addHeader(HEADER_CACHE_CONTROL, "no-store");
}
/**
* Actually resolve the given exception that got thrown during handler execution,
* returning a {@link ModelAndView} that represents a specific error page if appropriate.
*
May be overridden in subclasses, in order to apply specific exception checks.
* Note that this template method will be invoked after checking whether this
* resolved applies ("mappedHandlers" etc), so an implementation may simply proceed
* with its actual exception handling.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen at the time
* of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding {@code ModelAndView} to forward to,
* or {@code null} for default processing in the resolution chain
*/
@Nullable
protected abstract ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}