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}