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}