001/* 002 * Copyright 2002-2013 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.view; 018 019import java.util.LinkedHashMap; 020import java.util.Locale; 021import java.util.Map; 022import java.util.concurrent.ConcurrentHashMap; 023import javax.servlet.http.HttpServletRequest; 024import javax.servlet.http.HttpServletResponse; 025 026import org.springframework.web.context.support.WebApplicationObjectSupport; 027import org.springframework.web.servlet.View; 028import org.springframework.web.servlet.ViewResolver; 029 030/** 031 * Convenient base class for {@link org.springframework.web.servlet.ViewResolver} 032 * implementations. Caches {@link org.springframework.web.servlet.View} objects 033 * once resolved: This means that view resolution won't be a performance problem, 034 * no matter how costly initial view retrieval is. 035 * 036 * <p>Subclasses need to implement the {@link #loadView} template method, 037 * building the View object for a specific view name and locale. 038 * 039 * @author Rod Johnson 040 * @author Juergen Hoeller 041 * @see #loadView 042 */ 043public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver { 044 045 /** Default maximum number of entries for the view cache: 1024 */ 046 public static final int DEFAULT_CACHE_LIMIT = 1024; 047 048 /** Dummy marker object for unresolved views in the cache Maps */ 049 private static final View UNRESOLVED_VIEW = new View() { 050 @Override 051 public String getContentType() { 052 return null; 053 } 054 @Override 055 public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) { 056 } 057 }; 058 059 060 /** The maximum number of entries in the cache */ 061 private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; 062 063 /** Whether we should refrain from resolving views again if unresolved once */ 064 private boolean cacheUnresolved = true; 065 066 /** Fast access cache for Views, returning already cached instances without a global lock */ 067 private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT); 068 069 /** Map from view key to View instance, synchronized for View creation */ 070 @SuppressWarnings("serial") 071 private final Map<Object, View> viewCreationCache = 072 new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) { 073 @Override 074 protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) { 075 if (size() > getCacheLimit()) { 076 viewAccessCache.remove(eldest.getKey()); 077 return true; 078 } 079 else { 080 return false; 081 } 082 } 083 }; 084 085 086 /** 087 * Specify the maximum number of entries for the view cache. 088 * Default is 1024. 089 */ 090 public void setCacheLimit(int cacheLimit) { 091 this.cacheLimit = cacheLimit; 092 } 093 094 /** 095 * Return the maximum number of entries for the view cache. 096 */ 097 public int getCacheLimit() { 098 return this.cacheLimit; 099 } 100 101 /** 102 * Enable or disable caching. 103 * <p>This is equivalent to setting the {@link #setCacheLimit "cacheLimit"} 104 * property to the default limit (1024) or to 0, respectively. 105 * <p>Default is "true": caching is enabled. 106 * Disable this only for debugging and development. 107 */ 108 public void setCache(boolean cache) { 109 this.cacheLimit = (cache ? DEFAULT_CACHE_LIMIT : 0); 110 } 111 112 /** 113 * Return if caching is enabled. 114 */ 115 public boolean isCache() { 116 return (this.cacheLimit > 0); 117 } 118 119 /** 120 * Whether a view name once resolved to {@code null} should be cached and 121 * automatically resolved to {@code null} subsequently. 122 * <p>Default is "true": unresolved view names are being cached, as of Spring 3.1. 123 * Note that this flag only applies if the general {@link #setCache "cache"} 124 * flag is kept at its default of "true" as well. 125 * <p>Of specific interest is the ability for some AbstractUrlBasedView 126 * implementations (FreeMarker, Velocity, Tiles) to check if an underlying 127 * resource exists via {@link AbstractUrlBasedView#checkResource(Locale)}. 128 * With this flag set to "false", an underlying resource that re-appears 129 * is noticed and used. With the flag set to "true", one check is made only. 130 */ 131 public void setCacheUnresolved(boolean cacheUnresolved) { 132 this.cacheUnresolved = cacheUnresolved; 133 } 134 135 /** 136 * Return if caching of unresolved views is enabled. 137 */ 138 public boolean isCacheUnresolved() { 139 return this.cacheUnresolved; 140 } 141 142 143 @Override 144 public View resolveViewName(String viewName, Locale locale) throws Exception { 145 if (!isCache()) { 146 return createView(viewName, locale); 147 } 148 else { 149 Object cacheKey = getCacheKey(viewName, locale); 150 View view = this.viewAccessCache.get(cacheKey); 151 if (view == null) { 152 synchronized (this.viewCreationCache) { 153 view = this.viewCreationCache.get(cacheKey); 154 if (view == null) { 155 // Ask the subclass to create the View object. 156 view = createView(viewName, locale); 157 if (view == null && this.cacheUnresolved) { 158 view = UNRESOLVED_VIEW; 159 } 160 if (view != null) { 161 this.viewAccessCache.put(cacheKey, view); 162 this.viewCreationCache.put(cacheKey, view); 163 if (logger.isTraceEnabled()) { 164 logger.trace("Cached view [" + cacheKey + "]"); 165 } 166 } 167 } 168 } 169 } 170 return (view != UNRESOLVED_VIEW ? view : null); 171 } 172 } 173 174 /** 175 * Return the cache key for the given view name and the given locale. 176 * <p>Default is a String consisting of view name and locale suffix. 177 * Can be overridden in subclasses. 178 * <p>Needs to respect the locale in general, as a different locale can 179 * lead to a different view resource. 180 */ 181 protected Object getCacheKey(String viewName, Locale locale) { 182 return viewName + '_' + locale; 183 } 184 185 /** 186 * Provides functionality to clear the cache for a certain view. 187 * <p>This can be handy in case developer are able to modify views 188 * (e.g. Velocity templates) at runtime after which you'd need to 189 * clear the cache for the specified view. 190 * @param viewName the view name for which the cached view object 191 * (if any) needs to be removed 192 * @param locale the locale for which the view object should be removed 193 */ 194 public void removeFromCache(String viewName, Locale locale) { 195 if (!isCache()) { 196 logger.warn("View caching is SWITCHED OFF -- removal not necessary"); 197 } 198 else { 199 Object cacheKey = getCacheKey(viewName, locale); 200 Object cachedView; 201 synchronized (this.viewCreationCache) { 202 this.viewAccessCache.remove(cacheKey); 203 cachedView = this.viewCreationCache.remove(cacheKey); 204 } 205 if (logger.isDebugEnabled()) { 206 // Some debug output might be useful... 207 if (cachedView == null) { 208 logger.debug("No cached instance for view '" + cacheKey + "' was found"); 209 } 210 else { 211 logger.debug("Cache for view " + cacheKey + " has been cleared"); 212 } 213 } 214 } 215 } 216 217 /** 218 * Clear the entire view cache, removing all cached view objects. 219 * Subsequent resolve calls will lead to recreation of demanded view objects. 220 */ 221 public void clearCache() { 222 logger.debug("Clearing entire view cache"); 223 synchronized (this.viewCreationCache) { 224 this.viewAccessCache.clear(); 225 this.viewCreationCache.clear(); 226 } 227 } 228 229 230 /** 231 * Create the actual View object. 232 * <p>The default implementation delegates to {@link #loadView}. 233 * This can be overridden to resolve certain view names in a special fashion, 234 * before delegating to the actual {@code loadView} implementation 235 * provided by the subclass. 236 * @param viewName the name of the view to retrieve 237 * @param locale the Locale to retrieve the view for 238 * @return the View instance, or {@code null} if not found 239 * (optional, to allow for ViewResolver chaining) 240 * @throws Exception if the view couldn't be resolved 241 * @see #loadView 242 */ 243 protected View createView(String viewName, Locale locale) throws Exception { 244 return loadView(viewName, locale); 245 } 246 247 /** 248 * Subclasses must implement this method, building a View object 249 * for the specified view. The returned View objects will be 250 * cached by this ViewResolver base class. 251 * <p>Subclasses are not forced to support internationalization: 252 * A subclass that does not may simply ignore the locale parameter. 253 * @param viewName the name of the view to retrieve 254 * @param locale the Locale to retrieve the view for 255 * @return the View instance, or {@code null} if not found 256 * (optional, to allow for ViewResolver chaining) 257 * @throws Exception if the view couldn't be resolved 258 * @see #resolveViewName 259 */ 260 protected abstract View loadView(String viewName, Locale locale) throws Exception; 261 262}