001/* 002 * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Anahide Tchertchian 016 */ 017package org.nuxeo.ecm.platform.contentview.jsf; 018 019import java.io.Serializable; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import org.nuxeo.ecm.platform.ui.web.cache.LRUCachingMap; 027 028/** 029 * Cache for content views, handling cache keys set on content views. 030 * <p> 031 * Each content view instance will be cached if its cache key is not null. Each instance will be cached using the cache 032 * key so its state is restored. Also handles refresh of caches when receiving events configured on the content view. 033 * 034 * @author Anahide Tchertchian 035 * @since 5.4 036 */ 037public class ContentViewCache implements Serializable { 038 039 private static final long serialVersionUID = 1L; 040 041 /** 042 * Default cache size, set to 5 instances per content view 043 */ 044 public static final Integer DEFAULT_CACHE_SIZE = new Integer(5); 045 046 protected final Map<String, String> namedCacheKeys = new HashMap<String, String>(); 047 048 protected final Map<String, ContentView> namedContentViews = new HashMap<String, ContentView>(); 049 050 protected final Map<String, Map<String, ContentView>> cacheInstances = new HashMap<String, Map<String, ContentView>>(); 051 052 /** 053 * Map holding content view names that need their page provider to be refreshed for a given event 054 */ 055 protected final Map<String, Set<String>> refreshEventToContentViewName = new HashMap<String, Set<String>>(); 056 057 /** 058 * Map holding content view names that need their page provider to be reset for a given event 059 */ 060 protected final Map<String, Set<String>> resetEventToContentViewName = new HashMap<String, Set<String>>(); 061 062 /** 063 * Add given content view to the cache, resolving its cache key and initializing it with its cache size. 064 * <p> 065 * Since 5.7, content views with a cache size <= 0 will be cached anyhow to handle selections, and rendering is in 066 * charge of forcing cache reset when re-displaying the page. 067 */ 068 public void add(ContentView cView) { 069 if (cView != null) { 070 String cacheKey = cView.getCacheKey(); 071 if (cacheKey == null) { 072 // no cache 073 return; 074 } 075 String name = cView.getName(); 076 Integer cacheSize = cView.getCacheSize(); 077 if (cacheSize == null) { 078 cacheSize = DEFAULT_CACHE_SIZE; 079 } 080 if (cacheSize.intValue() <= 0) { 081 // if cacheSize <= 0, selection actions will not behave 082 // accurately => behave as if cacheSize was 1 in this case, and 083 // use a dummy cache key. Template rendering will force cache 084 // refresh when rendering the page, as if content view was not 085 // cached. 086 cacheSize = Integer.valueOf(1); 087 } 088 089 Map<String, ContentView> cacheEntry = cacheInstances.get(name); 090 if (cacheEntry == null) { 091 cacheEntry = new LRUCachingMap<String, ContentView>(cacheSize.intValue()); 092 } 093 cacheEntry.put(cacheKey, cView); 094 cacheInstances.put(name, cacheEntry); 095 namedCacheKeys.put(name, cacheKey); 096 namedContentViews.put(name, cView); 097 List<String> events = cView.getRefreshEventNames(); 098 if (events != null && !events.isEmpty()) { 099 for (String event : events) { 100 if (refreshEventToContentViewName.containsKey(event)) { 101 refreshEventToContentViewName.get(event).add(name); 102 } else { 103 Set<String> set = new HashSet<String>(); 104 set.add(name); 105 refreshEventToContentViewName.put(event, set); 106 } 107 } 108 } 109 events = cView.getResetEventNames(); 110 if (events != null && !events.isEmpty()) { 111 for (String event : events) { 112 if (resetEventToContentViewName.containsKey(event)) { 113 resetEventToContentViewName.get(event).add(name); 114 } else { 115 Set<String> set = new HashSet<String>(); 116 set.add(name); 117 resetEventToContentViewName.put(event, set); 118 } 119 } 120 } 121 } 122 } 123 124 /** 125 * Returns cached content view with given name, or null if not found. 126 */ 127 public ContentView get(String name) { 128 ContentView cView = namedContentViews.get(name); 129 if (cView != null) { 130 String oldCacheKey = namedCacheKeys.get(name); 131 String newCacheKey = cView.getCacheKey(); 132 if (newCacheKey != null && !newCacheKey.equals(oldCacheKey)) { 133 Map<String, ContentView> contentViews = cacheInstances.get(name); 134 if (contentViews.containsKey(newCacheKey)) { 135 cView = contentViews.get(newCacheKey); 136 // refresh named caches 137 namedCacheKeys.put(name, newCacheKey); 138 namedContentViews.put(name, cView); 139 } else { 140 // cache not here or expired => return null 141 return null; 142 } 143 } 144 } 145 return cView; 146 } 147 148 /** 149 * Refresh page providers for content views in the cache with given name.refreshEventToContentViewName 150 * <p> 151 * Other contextual information set on the content view and the page provider will be kept. 152 */ 153 public void refresh(String contentViewName, boolean rewind) { 154 ContentView cv = namedContentViews.get(contentViewName); 155 if (cv != null) { 156 if (rewind) { 157 cv.refreshAndRewindPageProvider(); 158 } else { 159 cv.refreshPageProvider(); 160 } 161 } 162 Map<String, ContentView> instances = cacheInstances.get(contentViewName); 163 if (instances != null) { 164 for (ContentView cView : instances.values()) { 165 // avoid refreshing twice the same content view, see NXP-13604 166 if (cView != null && !cView.equals(cv)) { 167 if (rewind) { 168 cView.refreshAndRewindPageProvider(); 169 } else { 170 cView.refreshPageProvider(); 171 } 172 } 173 } 174 } 175 } 176 177 /** 178 * Resets page providers for content views in the cache with given name. 179 * <p> 180 * Other contextual information set on the content view will be kept. 181 */ 182 public void resetPageProvider(String contentViewName) { 183 ContentView cv = namedContentViews.get(contentViewName); 184 if (cv != null) { 185 cv.resetPageProvider(); 186 } 187 Map<String, ContentView> instances = cacheInstances.get(contentViewName); 188 if (instances != null) { 189 for (ContentView cView : instances.values()) { 190 if (cView != null) { 191 cView.resetPageProvider(); 192 } 193 } 194 } 195 } 196 197 /** 198 * Resets page providers aggregates. 199 * 200 * @since 6.0 201 */ 202 public void resetPageProviderAggregates(String contentViewName) { 203 ContentView cv = namedContentViews.get(contentViewName); 204 if (cv != null) { 205 cv.resetPageProviderAggregates(); 206 } 207 Map<String, ContentView> instances = cacheInstances.get(contentViewName); 208 if (instances != null) { 209 for (ContentView cView : instances.values()) { 210 if (cView != null) { 211 cView.resetPageProviderAggregates(); 212 } 213 } 214 } 215 } 216 217 /** 218 * Refresh page providers for content views having declared given event as a refresh event. 219 * <p> 220 * Other contextual information set on the content view and the page provider will be kept. 221 */ 222 public void refreshOnEvent(String eventName) { 223 if (eventName != null) { 224 Set<String> contentViewNames = refreshEventToContentViewName.get(eventName); 225 if (contentViewNames != null) { 226 for (String contentViewName : contentViewNames) { 227 refresh(contentViewName, false); 228 } 229 } 230 } 231 } 232 233 /** 234 * Resets page providers for content views having declared given event as a reset event. 235 * <p> 236 * Other contextual information set on the content view will be kept. 237 */ 238 public void resetPageProviderOnEvent(String eventName) { 239 if (eventName != null) { 240 Set<String> contentViewNames = resetEventToContentViewName.get(eventName); 241 if (contentViewNames != null) { 242 for (String contentViewName : contentViewNames) { 243 resetPageProvider(contentViewName); 244 } 245 } 246 } 247 } 248 249 /** 250 * Resets all cached information for given content view. 251 */ 252 public void reset(String contentViewName) { 253 namedContentViews.remove(contentViewName); 254 namedCacheKeys.remove(contentViewName); 255 cacheInstances.remove(contentViewName); 256 } 257 258 /** 259 * Resets all cached information for all content views 260 */ 261 public void resetAllContent() { 262 namedContentViews.clear(); 263 namedCacheKeys.clear(); 264 cacheInstances.clear(); 265 } 266 267 /** 268 * Resets all cached information for all content views, as well as configuration caches (refresh and reset events 269 * linked to content views). 270 */ 271 public void resetAll() { 272 resetAllContent(); 273 refreshEventToContentViewName.clear(); 274 resetEventToContentViewName.clear(); 275 } 276 277 /** 278 * Iterates over all cached content view instances to refresh them. 279 * <p> 280 * Can be costly if some page providers need to fetch content or perform costly operations at refresh. 281 * 282 * @since 5.7 283 */ 284 public void refreshAll() { 285 refreshAll(false); 286 } 287 288 /** 289 * Iterates over all cached content view instances to refresh them. 290 * <p> 291 * Can be costly if some page providers need to fetch content or perform costly operations at refresh. 292 * 293 * @since 5.7 294 */ 295 public void refreshAndRewindAll() { 296 refreshAll(true); 297 } 298 299 /** 300 * @since 5.7 301 */ 302 protected void refreshAll(boolean rewind) { 303 Set<String> cvNames = namedContentViews.keySet(); 304 for (String cvName : cvNames) { 305 refresh(cvName, rewind); 306 } 307 } 308 309}