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