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}