1 /* 2 * Copyright 2002-2012 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 23 import org.springframework.web.context.support.WebApplicationObjectSupport; 24 import org.springframework.web.servlet.View; 25 import org.springframework.web.servlet.ViewResolver; 26 27 /** 28 * Convenient base class for {@link org.springframework.web.servlet.ViewResolver} 29 * implementations. Caches {@link org.springframework.web.servlet.View} objects 30 * once resolved: This means that view resolution won't be a performance problem, 31 * no matter how costly initial view retrieval is. 32 * 33 * <p>Subclasses need to implement the {@link #loadView} template method, 34 * building the View object for a specific view name and locale. 35 * 36 * @author Rod Johnson 37 * @author Juergen Hoeller 38 * @see #loadView 39 */ 40 public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver { 41 42 /** Default maximum number of entries for the view cache: 1024 */ 43 public static final int DEFAULT_CACHE_LIMIT = 1024; 44 45 46 private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; 47 48 /** Whether we should refrain from resolving views again if unresolved once */ 49 private boolean cacheUnresolved = true; 50 51 /** Map from view key to View instance */ 52 private final Map<Object, View> viewCache = 53 new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) { 54 @Override 55 protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) { 56 return size() > getCacheLimit(); 57 } 58 }; 59 60 61 /** 62 * Specify the maximum number of entries for the view cache. 63 * Default is 1024. 64 */ setCacheLimit(int cacheLimit)65 public void setCacheLimit(int cacheLimit) { 66 this.cacheLimit = cacheLimit; 67 } 68 69 /** 70 * Return the maximum number of entries for the view cache. 71 */ getCacheLimit()72 public int getCacheLimit() { 73 return this.cacheLimit; 74 } 75 76 /** 77 * Enable or disable caching. 78 * <p>This is equivalent to setting the {@link #setCacheLimit "cacheLimit"} 79 * property to the default limit (1024) or to 0, respectively. 80 * <p>Default is "true": caching is enabled. 81 * Disable this only for debugging and development. 82 */ setCache(boolean cache)83 public void setCache(boolean cache) { 84 this.cacheLimit = (cache ? DEFAULT_CACHE_LIMIT : 0); 85 } 86 87 /** 88 * Return if caching is enabled. 89 */ isCache()90 public boolean isCache() { 91 return (this.cacheLimit > 0); 92 } 93 94 /** 95 * Whether a view name once resolved to {@code null} should be cached and 96 * automatically resolved to {@code null} subsequently. 97 * <p>Default is "true": unresolved view names are being cached, as of Spring 3.1. 98 * Note that this flag only applies if the general {@link #setCache "cache"} 99 * flag is kept at its default of "true" as well. 100 * <p>Of specific interest is the ability for some AbstractUrlBasedView 101 * implementations (FreeMarker, Velocity, Tiles) to check if an underlying 102 * resource exists via {@link AbstractUrlBasedView#checkResource(Locale)}. 103 * With this flag set to "false", an underlying resource that re-appears 104 * is noticed and used. With the flag set to "true", one check is made only. 105 */ setCacheUnresolved(boolean cacheUnresolved)106 public void setCacheUnresolved(boolean cacheUnresolved) { 107 this.cacheUnresolved = cacheUnresolved; 108 } 109 110 /** 111 * Return if caching of unresolved views is enabled. 112 */ isCacheUnresolved()113 public boolean isCacheUnresolved() { 114 return this.cacheUnresolved; 115 } 116 117 resolveViewName(String viewName, Locale locale)118 public View resolveViewName(String viewName, Locale locale) throws Exception { 119 if (!isCache()) { 120 return createView(viewName, locale); 121 } 122 else { 123 Object cacheKey = getCacheKey(viewName, locale); 124 synchronized (this.viewCache) { 125 View view = this.viewCache.get(cacheKey); 126 if (view == null && (!this.cacheUnresolved || !this.viewCache.containsKey(cacheKey))) { 127 // Ask the subclass to create the View object. 128 view = createView(viewName, locale); 129 if (view != null || this.cacheUnresolved) { 130 this.viewCache.put(cacheKey, view); 131 if (logger.isTraceEnabled()) { 132 logger.trace("Cached view [" + cacheKey + "]"); 133 } 134 } 135 } 136 return view; 137 } 138 } 139 } 140 141 /** 142 * Return the cache key for the given view name and the given locale. 143 * <p>Default is a String consisting of view name and locale suffix. 144 * Can be overridden in subclasses. 145 * <p>Needs to respect the locale in general, as a different locale can 146 * lead to a different view resource. 147 */ getCacheKey(String viewName, Locale locale)148 protected Object getCacheKey(String viewName, Locale locale) { 149 return viewName + "_" + locale; 150 } 151 152 /** 153 * Provides functionality to clear the cache for a certain view. 154 * <p>This can be handy in case developer are able to modify views 155 * (e.g. Velocity templates) at runtime after which you'd need to 156 * clear the cache for the specified view. 157 * @param viewName the view name for which the cached view object 158 * (if any) needs to be removed 159 * @param locale the locale for which the view object should be removed 160 */ removeFromCache(String viewName, Locale locale)161 public void removeFromCache(String viewName, Locale locale) { 162 if (!isCache()) { 163 logger.warn("View caching is SWITCHED OFF -- removal not necessary"); 164 } 165 else { 166 Object cacheKey = getCacheKey(viewName, locale); 167 Object cachedView; 168 synchronized (this.viewCache) { 169 cachedView = this.viewCache.remove(cacheKey); 170 } 171 if (cachedView == null) { 172 // Some debug output might be useful... 173 if (logger.isDebugEnabled()) { 174 logger.debug("No cached instance for view '" + cacheKey + "' was found"); 175 } 176 } 177 else { 178 if (logger.isDebugEnabled()) { 179 logger.debug("Cache for view " + cacheKey + " has been cleared"); 180 } 181 } 182 } 183 } 184 185 /** 186 * Clear the entire view cache, removing all cached view objects. 187 * Subsequent resolve calls will lead to recreation of demanded view objects. 188 */ clearCache()189 public void clearCache() { 190 logger.debug("Clearing entire view cache"); 191 synchronized (this.viewCache) { 192 this.viewCache.clear(); 193 } 194 } 195 196 197 /** 198 * Create the actual View object. 199 * <p>The default implementation delegates to {@link #loadView}. 200 * This can be overridden to resolve certain view names in a special fashion, 201 * before delegating to the actual <code>loadView</code> implementation 202 * provided by the subclass. 203 * @param viewName the name of the view to retrieve 204 * @param locale the Locale to retrieve the view for 205 * @return the View instance, or <code>null</code> if not found 206 * (optional, to allow for ViewResolver chaining) 207 * @throws Exception if the view couldn't be resolved 208 * @see #loadView 209 */ createView(String viewName, Locale locale)210 protected View createView(String viewName, Locale locale) throws Exception { 211 return loadView(viewName, locale); 212 } 213 214 /** 215 * Subclasses must implement this method, building a View object 216 * for the specified view. The returned View objects will be 217 * cached by this ViewResolver base class. 218 * <p>Subclasses are not forced to support internationalization: 219 * A subclass that does not may simply ignore the locale parameter. 220 * @param viewName the name of the view to retrieve 221 * @param locale the Locale to retrieve the view for 222 * @return the View instance, or <code>null</code> if not found 223 * (optional, to allow for ViewResolver chaining) 224 * @throws Exception if the view couldn't be resolved 225 * @see #resolveViewName 226 */ loadView(String viewName, Locale locale)227 protected abstract View loadView(String viewName, Locale locale) throws Exception; 228 229 } 230