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