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