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.mvc;
018
019import java.util.Enumeration;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.Properties;
023
024import javax.servlet.ServletException;
025import javax.servlet.http.HttpServletRequest;
026import javax.servlet.http.HttpServletResponse;
027
028import org.springframework.http.CacheControl;
029import org.springframework.lang.Nullable;
030import org.springframework.util.AntPathMatcher;
031import org.springframework.util.Assert;
032import org.springframework.util.PathMatcher;
033import org.springframework.web.servlet.HandlerInterceptor;
034import org.springframework.web.servlet.HandlerMapping;
035import org.springframework.web.servlet.ModelAndView;
036import org.springframework.web.servlet.support.WebContentGenerator;
037import org.springframework.web.util.UrlPathHelper;
038
039/**
040 * Handler interceptor that checks the request and prepares the response.
041 * Checks for supported methods and a required session, and applies the
042 * specified {@link org.springframework.http.CacheControl} builder.
043 * See superclass bean properties for configuration options.
044 *
045 * <p>All the settings supported by this interceptor can also be set on
046 * {@link AbstractController}. This interceptor is mainly intended for applying
047 * checks and preparations to a set of controllers mapped by a HandlerMapping.
048 *
049 * @author Juergen Hoeller
050 * @author Brian Clozel
051 * @since 27.11.2003
052 * @see AbstractController
053 */
054public class WebContentInterceptor extends WebContentGenerator implements HandlerInterceptor {
055
056        private UrlPathHelper urlPathHelper = new UrlPathHelper();
057
058        private PathMatcher pathMatcher = new AntPathMatcher();
059
060        private Map<String, Integer> cacheMappings = new HashMap<>();
061
062        private Map<String, CacheControl> cacheControlMappings = new HashMap<>();
063
064
065        public WebContentInterceptor() {
066                // No restriction of HTTP methods by default,
067                // in particular for use with annotated controllers...
068                super(false);
069        }
070
071
072        /**
073         * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
074         * <p>Only relevant for the "cacheMappings" setting.
075         * @see #setCacheMappings
076         * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
077         */
078        public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
079                this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
080        }
081
082        /**
083         * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
084         * <p>Only relevant for the "cacheMappings" setting.
085         * @see #setCacheMappings
086         * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
087         */
088        public void setUrlDecode(boolean urlDecode) {
089                this.urlPathHelper.setUrlDecode(urlDecode);
090        }
091
092        /**
093         * Set the UrlPathHelper to use for resolution of lookup paths.
094         * <p>Use this to override the default UrlPathHelper with a custom subclass,
095         * or to share common UrlPathHelper settings across multiple HandlerMappings
096         * and MethodNameResolvers.
097         * <p>Only relevant for the "cacheMappings" setting.
098         * @see #setCacheMappings
099         * @see org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#setUrlPathHelper
100         */
101        public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
102                Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
103                this.urlPathHelper = urlPathHelper;
104        }
105
106        /**
107         * Map specific URL paths to specific cache seconds.
108         * <p>Overrides the default cache seconds setting of this interceptor.
109         * Can specify "-1" to exclude a URL path from default caching.
110         * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
111         * and a various Ant-style pattern matches, e.g. a registered "/t*" matches
112         * both "/test" and "/team". For details, see the AntPathMatcher javadoc.
113         * <p><b>NOTE:</b> Path patterns are not supposed to overlap. If a request
114         * matches several mappings, it is effectively undefined which one will apply
115         * (due to the lack of key ordering in {@code java.util.Properties}).
116         * @param cacheMappings a mapping between URL paths (as keys) and
117         * cache seconds (as values, need to be integer-parsable)
118         * @see #setCacheSeconds
119         * @see org.springframework.util.AntPathMatcher
120         */
121        public void setCacheMappings(Properties cacheMappings) {
122                this.cacheMappings.clear();
123                Enumeration<?> propNames = cacheMappings.propertyNames();
124                while (propNames.hasMoreElements()) {
125                        String path = (String) propNames.nextElement();
126                        int cacheSeconds = Integer.parseInt(cacheMappings.getProperty(path));
127                        this.cacheMappings.put(path, cacheSeconds);
128                }
129        }
130
131        /**
132         * Map specific URL paths to a specific {@link org.springframework.http.CacheControl}.
133         * <p>Overrides the default cache seconds setting of this interceptor.
134         * Can specify a empty {@link org.springframework.http.CacheControl} instance
135         * to exclude a URL path from default caching.
136         * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
137         * and a various Ant-style pattern matches, e.g. a registered "/t*" matches
138         * both "/test" and "/team". For details, see the AntPathMatcher javadoc.
139         * <p><b>NOTE:</b> Path patterns are not supposed to overlap. If a request
140         * matches several mappings, it is effectively undefined which one will apply
141         * (due to the lack of key ordering in the underlying {@code java.util.HashMap}).
142         * @param cacheControl the {@code CacheControl} to use
143         * @param paths the URL paths that will map to the given {@code CacheControl}
144         * @since 4.2
145         * @see #setCacheSeconds
146         * @see org.springframework.util.AntPathMatcher
147         */
148        public void addCacheMapping(CacheControl cacheControl, String... paths) {
149                for (String path : paths) {
150                        this.cacheControlMappings.put(path, cacheControl);
151                }
152        }
153
154        /**
155         * Set the PathMatcher implementation to use for matching URL paths
156         * against registered URL patterns, for determining cache mappings.
157         * Default is AntPathMatcher.
158         * @see #addCacheMapping
159         * @see #setCacheMappings
160         * @see org.springframework.util.AntPathMatcher
161         */
162        public void setPathMatcher(PathMatcher pathMatcher) {
163                Assert.notNull(pathMatcher, "PathMatcher must not be null");
164                this.pathMatcher = pathMatcher;
165        }
166
167
168        @Override
169        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
170                        throws ServletException {
171
172                checkRequest(request);
173
174                String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
175
176                CacheControl cacheControl = lookupCacheControl(lookupPath);
177                Integer cacheSeconds = lookupCacheSeconds(lookupPath);
178                if (cacheControl != null) {
179                        if (logger.isTraceEnabled()) {
180                                logger.trace("Applying " + cacheControl);
181                        }
182                        applyCacheControl(response, cacheControl);
183                }
184                else if (cacheSeconds != null) {
185                        if (logger.isTraceEnabled()) {
186                                logger.trace("Applying cacheSeconds " + cacheSeconds);
187                        }
188                        applyCacheSeconds(response, cacheSeconds);
189                }
190                else {
191                        if (logger.isTraceEnabled()) {
192                                logger.trace("Applying default cacheSeconds");
193                        }
194                        prepareResponse(response);
195                }
196
197                return true;
198        }
199
200        /**
201         * Look up a {@link org.springframework.http.CacheControl} instance for the given URL path.
202         * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
203         * and various Ant-style pattern matches, e.g. a registered "/t*" matches
204         * both "/test" and "/team". For details, see the AntPathMatcher class.
205         * @param urlPath the URL the bean is mapped to
206         * @return the associated {@code CacheControl}, or {@code null} if not found
207         * @see org.springframework.util.AntPathMatcher
208         */
209        @Nullable
210        protected CacheControl lookupCacheControl(String urlPath) {
211                // Direct match?
212                CacheControl cacheControl = this.cacheControlMappings.get(urlPath);
213                if (cacheControl != null) {
214                        return cacheControl;
215                }
216                // Pattern match?
217                for (Map.Entry<String, CacheControl> entry : this.cacheControlMappings.entrySet()) {
218                        if (this.pathMatcher.match(entry.getKey(), urlPath)) {
219                                return entry.getValue();
220                        }
221                }
222                return null;
223        }
224
225        /**
226         * Look up a cacheSeconds integer value for the given URL path.
227         * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
228         * and various Ant-style pattern matches, e.g. a registered "/t*" matches
229         * both "/test" and "/team". For details, see the AntPathMatcher class.
230         * @param urlPath the URL the bean is mapped to
231         * @return the cacheSeconds integer value, or {@code null} if not found
232         * @see org.springframework.util.AntPathMatcher
233         */
234        @Nullable
235        protected Integer lookupCacheSeconds(String urlPath) {
236                // Direct match?
237                Integer cacheSeconds = this.cacheMappings.get(urlPath);
238                if (cacheSeconds != null) {
239                        return cacheSeconds;
240                }
241                // Pattern match?
242                for (Map.Entry<String, Integer> entry : this.cacheMappings.entrySet()) {
243                        if (this.pathMatcher.match(entry.getKey(), urlPath)) {
244                                return entry.getValue();
245                        }
246                }
247                return null;
248        }
249
250
251        /**
252         * This implementation is empty.
253         */
254        @Override
255        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
256                        @Nullable ModelAndView modelAndView) throws Exception {
257        }
258
259        /**
260         * This implementation is empty.
261         */
262        @Override
263        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
264                        @Nullable Exception ex) throws Exception {
265        }
266
267}