/*
* (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* Anahide Tchertchian
*/
package org.nuxeo.ecm.platform.contentview.jsf;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.nuxeo.ecm.platform.ui.web.cache.LRUCachingMap;
/**
* Cache for content views, handling cache keys set on content views.
* <p>
* Each content view instance will be cached if its cache key is not null. Each
* instance will be cached using the cache key so its state is restored. Also
* handles refresh of caches when receiving events configured on the content
* view.
*
* @author Anahide Tchertchian
* @since 5.4
*/
public class ContentViewCache implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Default cache size, set to 5 instances per content view
*/
public static final Integer DEFAULT_CACHE_SIZE = new Integer(5);
protected final Map<String, String> namedCacheKeys = new HashMap<String, String>();
protected final Map<String, ContentView> namedContentViews = new HashMap<String, ContentView>();
protected final Map<String, Map<String, ContentView>> cacheInstances = new HashMap<String, Map<String, ContentView>>();
/**
* Map holding content view names that need their page provider to be
* refreshed for a given event
*/
protected final Map<String, Set<String>> refreshEventToContentViewName = new HashMap<String, Set<String>>();
/**
* Map holding content view names that need their page provider to be reset
* for a given event
*/
protected final Map<String, Set<String>> resetEventToContentViewName = new HashMap<String, Set<String>>();
/**
* Add given content view to the cache, resolving its cache key and
* initializing it with its cache size.
* <p>
* Since 5.7, content views with a cache size <= 0 will be cached anyhow to
* handle selections, and rendering is in charge of forcing cache reset
* when re-displaying the page.
*/
public void add(ContentView cView) {
if (cView != null) {
String cacheKey = cView.getCacheKey();
if (cacheKey == null) {
// no cache
return;
}
String name = cView.getName();
Integer cacheSize = cView.getCacheSize();
if (cacheSize == null) {
cacheSize = DEFAULT_CACHE_SIZE;
}
if (cacheSize.intValue() <= 0) {
// if cacheSize <= 0, selection actions will not behave
// accurately => behave as if cacheSize was 1 in this case, and
// use a dummy cache key. Template rendering will force cache
// refresh when rendering the page, as if content view was not
// cached.
cacheSize = Integer.valueOf(1);
}
Map<String, ContentView> cacheEntry = cacheInstances.get(name);
if (cacheEntry == null) {
cacheEntry = new LRUCachingMap<String, ContentView>(
cacheSize.intValue());
}
cacheEntry.put(cacheKey, cView);
cacheInstances.put(name, cacheEntry);
namedCacheKeys.put(name, cacheKey);
namedContentViews.put(name, cView);
List<String> events = cView.getRefreshEventNames();
if (events != null && !events.isEmpty()) {
for (String event : events) {
if (refreshEventToContentViewName.containsKey(event)) {
refreshEventToContentViewName.get(event).add(name);
} else {
Set<String> set = new HashSet<String>();
set.add(name);
refreshEventToContentViewName.put(event, set);
}
}
}
events = cView.getResetEventNames();
if (events != null && !events.isEmpty()) {
for (String event : events) {
if (resetEventToContentViewName.containsKey(event)) {
resetEventToContentViewName.get(event).add(name);
} else {
Set<String> set = new HashSet<String>();
set.add(name);
resetEventToContentViewName.put(event, set);
}
}
}
}
}
/**
* Returns cached content view with given name, or null if not found.
*/
public ContentView get(String name) {
ContentView cView = namedContentViews.get(name);
if (cView != null) {
String oldCacheKey = namedCacheKeys.get(name);
String newCacheKey = cView.getCacheKey();
if (newCacheKey != null && !newCacheKey.equals(oldCacheKey)) {
Map<String, ContentView> contentViews = cacheInstances.get(name);
if (contentViews.containsKey(newCacheKey)) {
cView = contentViews.get(newCacheKey);
// refresh named caches
namedCacheKeys.put(name, newCacheKey);
namedContentViews.put(name, cView);
} else {
// cache not here or expired => return null
return null;
}
}
}
return cView;
}
/**
* Refresh page providers for content views in the cache with given
* name.refreshEventToContentViewName
* <p>
* Other contextual information set on the content view and the page
* provider will be kept.
*/
public void refresh(String contentViewName, boolean rewind) {
ContentView cv = namedContentViews.get(contentViewName);
if (cv != null) {
if (rewind) {
cv.refreshAndRewindPageProvider();
} else {
cv.refreshPageProvider();
}
}
Map<String, ContentView> instances = cacheInstances.get(contentViewName);
if (instances != null) {
for (ContentView cView : instances.values()) {
// avoid refreshing twice the same content view, see NXP-13604
if (cView != null && !cView.equals(cv)) {
if (rewind) {
cView.refreshAndRewindPageProvider();
} else {
cView.refreshPageProvider();
}
}
}
}
}
/**
* Resets page providers for content views in the cache with given name.
* <p>
* Other contextual information set on the content view will be kept.
*/
public void resetPageProvider(String contentViewName) {
ContentView cv = namedContentViews.get(contentViewName);
if (cv != null) {
cv.resetPageProvider();
}
Map<String, ContentView> instances = cacheInstances.get(contentViewName);
if (instances != null) {
for (ContentView cView : instances.values()) {
if (cView != null) {
cView.resetPageProvider();
}
}
}
}
/**
* Resets page providers aggregates.
*
* @since 6.0
*/
public void resetPageProviderAggregates(String contentViewName) {
ContentView cv = namedContentViews.get(contentViewName);
if (cv != null) {
cv.resetPageProviderAggregates();
}
Map<String, ContentView> instances = cacheInstances.get(contentViewName);
if (instances != null) {
for (ContentView cView : instances.values()) {
if (cView != null) {
cView.resetPageProviderAggregates();
}
}
}
}
/**
* Refresh page providers for content views having declared given event as
* a refresh event.
* <p>
* Other contextual information set on the content view and the page
* provider will be kept.
*/
public void refreshOnEvent(String eventName) {
if (eventName != null) {
Set<String> contentViewNames = refreshEventToContentViewName.get(eventName);
if (contentViewNames != null) {
for (String contentViewName : contentViewNames) {
refresh(contentViewName, false);
}
}
}
}
/**
* Resets page providers for content views having declared given event as a
* reset event.
* <p>
* Other contextual information set on the content view will be kept.
*/
public void resetPageProviderOnEvent(String eventName) {
if (eventName != null) {
Set<String> contentViewNames = resetEventToContentViewName.get(eventName);
if (contentViewNames != null) {
for (String contentViewName : contentViewNames) {
resetPageProvider(contentViewName);
}
}
}
}
/**
* Resets all cached information for given content view.
*/
public void reset(String contentViewName) {
namedContentViews.remove(contentViewName);
namedCacheKeys.remove(contentViewName);
cacheInstances.remove(contentViewName);
}
/**
* Resets all cached information for all content views
*/
public void resetAllContent() {
namedContentViews.clear();
namedCacheKeys.clear();
cacheInstances.clear();
}
/**
* Resets all cached information for all content views, as well as
* configuration caches (refresh and reset events linked to content views).
*/
public void resetAll() {
resetAllContent();
refreshEventToContentViewName.clear();
resetEventToContentViewName.clear();
}
/**
* Iterates over all cached content view instances to refresh them.
* <p>
* Can be costly if some page providers need to fetch content or perform
* costly operations at refresh.
*
* @since 5.7
*/
public void refreshAll() {
refreshAll(false);
}
/**
* Iterates over all cached content view instances to refresh them.
* <p>
* Can be costly if some page providers need to fetch content or perform
* costly operations at refresh.
*
* @since 5.7
*/
public void refreshAndRewindAll() {
refreshAll(true);
}
/**
* @since 5.7
*/
protected void refreshAll(boolean rewind) {
Set<String> cvNames = namedContentViews.keySet();
for (String cvName : cvNames) {
refresh(cvName, rewind);
}
}
}