1 /* 2 * Copyright 2002-2013 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.web.servlet.view; 18 19 import java.util.LinkedHashMap; 20 import java.util.Locale; 21 import java.util.Map; 22 import java.util.concurrent.ConcurrentHashMap; 23 import javax.servlet.http.HttpServletRequest; 24 import javax.servlet.http.HttpServletResponse; 25 26 import org.springframework.web.context.support.WebApplicationObjectSupport; 27 import org.springframework.web.servlet.View; 28 import org.springframework.web.servlet.ViewResolver; 29 30 /** 31 * Convenient base class for {@link org.springframework.web.servlet.ViewResolver} 32 * implementations. Caches {@link org.springframework.web.servlet.View} objects 33 * once resolved: This means that view resolution won't be a performance problem, 34 * no matter how costly initial view retrieval is. 35 * 36 * <p>Subclasses need to implement the {@link #loadView} template method, 37 * building the View object for a specific view name and locale. 38 * 39 * @author Rod Johnson 40 * @author Juergen Hoeller 41 * @see #loadView 42 */ 43 public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver { 44 45 /** Default maximum number of entries for the view cache: 1024 */ 46 public static final int DEFAULT_CACHE_LIMIT = 1024; 47 48 /** Dummy marker object for unresolved views in the cache Maps */ 49 private static final View UNRESOLVED_VIEW = new View() { 50 @Override 51 public String getContentType() { 52 return null; 53 } 54 @Override 55 public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) { 56 } 57 }; 58 59 60 /** The maximum number of entries in the cache */ 61 private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; 62 63 /** Whether we should refrain from resolving views again if unresolved once */ 64 private boolean cacheUnresolved = true; 65 66 /** Fast access cache for Views, returning already cached instances without a global lock */ 67 private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT); 68 69 /** Map from view key to View instance, synchronized for View creation */ 70 @SuppressWarnings("serial") 71 private final Map<Object, View> viewCreationCache = 72 new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) { 73 @Override 74 protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) { 75 if (size() > getCacheLimit()) { 76 viewAccessCache.remove(eldest.getKey()); 77 return true; 78 } 79 else { 80 return false; 81 } 82 } 83 }; 84 85 86 /** 87 * Specify the maximum number of entries for the view cache. 88 * Default is 1024. 89 */ 90 public void setCacheLimit(int cacheLimit) { 91 this.cacheLimit = cacheLimit; 92 } 93 94 /** 95 * Return the maximum number of entries for the view cache. 96 */ 97 public int getCacheLimit() { 98 return this.cacheLimit; 99 } 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 }