org.opencms.flex.CmsFlexCache Maven / Gradle / Ivy
Show all versions of opencms-core Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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.
*
* For further information about Alkacon Software GmbH & Co. KG, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.flex;
import org.opencms.cache.CmsLruCache;
import org.opencms.cache.I_CmsLruCacheObject;
import org.opencms.db.CmsPublishedResource;
import org.opencms.file.CmsObject;
import org.opencms.flex.CmsFlexBucketConfiguration.BucketSet;
import org.opencms.loader.CmsJspLoader;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.I_CmsEventListener;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsRole;
import org.opencms.util.CmsCollectionsGenericWrapper;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.logging.Log;
import com.google.common.collect.Lists;
/**
* This class implements the FlexCache.
*
* The data structure used is a two-level hashtable.
* This is optimized for the structure of the keys that are used to describe the
* caching behaviour of the entries.
* The first hash-level is calculated from the resource name, i.e. the
* name of the resource as it is referred to in the VFS of OpenCms.
* The second hash-level is calculated from the cache-key of the resource,
* which also is a String representing the specifc variation of the cached entry.
*
* A suffix [online] or [offline] is appended to te resource name
* to distinguish between the online and offline projects of OpenCms.
* Also, for support of JSP based workplace pages, a suffix [workplace]
* is appended. The same cached workplace pages are used both in the online and
* all offline projects.
*
* Entries in the first level of the cache are of type CmsFlexCacheVariation,
* which is a sub-class of CmsFlexCache.
* This class is a simple data type that contains of a Map of CmsFlexCacheEntries,
* with variations - Strings as keys.
*
* Here's a short summary of used terms:
*
* - key:
* A combination of a resource name and a variation.
* The data structure used is CmsFlexCacheKey.
*
- resource:
* A String with the resource name and an appended [online] of [offline] suffix.
*
- variation:
* A String describing a variation of a cached entry in the CmsFlexCache language.
*
- entry:
* A CmsFlexCacheEntry data structure which is describes a cached OpenCms resource.
* For every entry a key is saved which contains the resource name and the variation.
*
*
* Cache clearing is handled using events.
* The cache is fully flushed if an event {@link I_CmsEventListener#EVENT_PUBLISH_PROJECT}
* or {@link I_CmsEventListener#EVENT_CLEAR_CACHES} is caught.
*
* @since 6.0.0
*
* @see org.opencms.flex.CmsFlexCacheKey
* @see org.opencms.flex.CmsFlexCacheEntry
* @see org.opencms.cache.CmsLruCache
* @see org.opencms.cache.I_CmsLruCacheObject
*/
public class CmsFlexCache extends Object implements I_CmsEventListener {
/**
* A simple data container class for the FlexCache variations.
*/
public static class CmsFlexCacheVariation extends Object {
/** The key belonging to the resource. */
public CmsFlexCacheKey m_key;
/** Maps variations to CmsFlexCacheEntries. */
public Map m_map;
/**
* Generates a new instance of CmsFlexCacheVariation.
*
* @param theKey The (resource) key to contruct this variation list for
*/
public CmsFlexCacheVariation(CmsFlexCacheKey theKey) {
m_key = theKey;
m_map = new Hashtable(INITIAL_CAPACITY_VARIATIONS);
}
}
/**
* Extended LRUMap that handles the variations in case a key is removed.
*/
class CmsFlexKeyMap extends LRUMap {
/** Serial version UID required for safe serialization. */
private static final long serialVersionUID = 6931995916013396902L;
/**
* Initialize the map with the given size.
*
* @param maxSize the maximum number of key to cache
*/
public CmsFlexKeyMap(int maxSize) {
super(maxSize);
}
/**
* Ensures that all variations that referenced by this key are released
* if the key is released.
*
* @param entry the entry to remove
*
* @return true
to actually delete the entry
*
* @see LRUMap#removeLRU(LinkEntry)
*/
@Override
protected boolean removeLRU(LinkEntry entry) {
CmsFlexCacheVariation v = (CmsFlexCacheVariation)entry.getValue();
if (v == null) {
return true;
}
Map m = v.m_map;
if ((m == null) || (m.size() == 0)) {
return true;
}
// make a copy to safely iterate over because the line "m_variationCache.remove(e)" modifies the variation map for the key
Collection entries = new ArrayList(m.values());
synchronized (m_variationCache) {
for (I_CmsLruCacheObject e : entries) {
m_variationCache.remove(e);
}
v.m_map.clear();
v.m_map = null;
v.m_key = null;
}
return true;
}
}
/**Constant for distinguish cache action.*/
public static final String CACHE_ACTION = "action";
/** Suffix to append to online cache entries. */
public static final String CACHE_OFFLINESUFFIX = " [offline]";
/** Suffix to append to online cache entries. */
public static final String CACHE_ONLINESUFFIX = " [online]";
/** Trigger for clearcache event: Clear complete cache. */
public static final int CLEAR_ALL = 0;
/** Trigger for clearcache event: Clear only entries. */
public static final int CLEAR_ENTRIES = 1;
/** Trigger for clearcache event: Clear complete offine cache. */
public static final int CLEAR_OFFLINE_ALL = 4;
/** Trigger for clearcache event: Clear only offline entries. */
public static final int CLEAR_OFFLINE_ENTRIES = 5;
/** Trigger for clearcache event: Clear complete online cache. */
public static final int CLEAR_ONLINE_ALL = 2;
/** Trigger for clearcache event: Clear only online entries. */
public static final int CLEAR_ONLINE_ENTRIES = 3;
/** The configuration for the Flex cache buckets. */
public static final String CONFIG_PATH = "/system/config/flexconfig.properties";
/** Initial cache size, this should be a power of 2 because of the Java collections implementation. */
public static final int INITIAL_CAPACITY_CACHE = 512;
/** Initial size for variation lists, should be a power of 2. */
public static final int INITIAL_CAPACITY_VARIATIONS = 8;
/** Offline repository constant. */
public static final String REPOSITORY_OFFLINE = "offline";
/** Online repository constant. */
public static final String REPOSITORY_ONLINE = "online";
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsFlexCache.class);
/** The LRU cache to organize the cached entries. */
protected CmsLruCache m_variationCache;
/** The Flex bucket configuration. */
private CmsFlexBucketConfiguration m_bucketConfiguration;
/** Indicates if offline resources should be cached or not. */
private boolean m_cacheOffline;
/** The CMS object used for VFS operations. */
private CmsObject m_cmsObject;
/** Indicates if the cache is enabled or not. */
private boolean m_enabled;
/** Map to store the entries for fast lookup. */
private Map m_keyCache;
/** Counter for the size. */
private int m_size;
/**
* Constructor for class CmsFlexCache.
*
* The parameter "enabled" is used to control if the cache is
* actually on or off. Even if you don't need the cache, you still
* have to create an instance of it with enabled=false.
* This is because you need some of the FlexCache data structures
* for JSP inclusion buffering.
*
* @param configuration the flex cache configuration
*/
public CmsFlexCache(CmsFlexCacheConfiguration configuration) {
m_enabled = configuration.isCacheEnabled();
m_cacheOffline = configuration.isCacheOffline();
long maxCacheBytes = configuration.getMaxCacheBytes();
long avgCacheBytes = configuration.getAvgCacheBytes();
int maxEntryBytes = configuration.getMaxEntryBytes();
int maxKeys = configuration.getMaxKeys();
m_variationCache = new CmsLruCache(maxCacheBytes, avgCacheBytes, maxEntryBytes);
OpenCms.getMemoryMonitor().register(getClass().getName() + ".m_entryLruCache", m_variationCache);
if (m_enabled) {
CmsFlexKeyMap flexKeyMap = new CmsFlexKeyMap(maxKeys);
m_keyCache = Collections.synchronizedMap(
CmsCollectionsGenericWrapper. map(flexKeyMap));
OpenCms.getMemoryMonitor().register(getClass().getName() + ".m_resourceMap", flexKeyMap);
OpenCms.addCmsEventListener(
this,
new int[] {
I_CmsEventListener.EVENT_PUBLISH_PROJECT,
I_CmsEventListener.EVENT_CLEAR_CACHES,
I_CmsEventListener.EVENT_FLEX_PURGE_JSP_REPOSITORY,
I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR});
}
if (LOG.isInfoEnabled()) {
LOG.info(
Messages.get().getBundle().key(
Messages.INIT_FLEXCACHE_CREATED_2,
Boolean.valueOf(m_enabled),
Boolean.valueOf(m_cacheOffline)));
}
}
/**
* Copies the key set of a map while synchronizing on the map.
*
* @param map the map whose key set should be copied
* @return the copied key set
*/
private static Set synchronizedCopyKeys(Map map) {
if (map == null) {
return new HashSet();
}
synchronized (map) {
return new HashSet(map.keySet());
}
}
/**
* Copies a map while synchronizing on it.
*
* @param map the map to copy
* @return the copied map
*/
private static Map synchronizedCopyMap(Map map) {
if (map == null) {
return new HashMap();
}
synchronized (map) {
return new HashMap(map);
}
}
/**
* Indicates if offline project resources are cached.
*
* @return true if offline projects are cached, false if not
*/
public boolean cacheOffline() {
return m_cacheOffline;
}
/**
* Implements the CmsEvent interface,
* the FlexCache uses the events to clear itself in case a project is published.
*
* @param event CmsEvent that has occurred
*/
public void cmsEvent(org.opencms.main.CmsEvent event) {
if (!isEnabled()) {
return;
}
switch (event.getType()) {
case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
if (LOG.isDebugEnabled()) {
LOG.debug("FlexCache: Received event PUBLISH_PROJECT");
}
String publishIdStr = (String)(event.getData().get(I_CmsEventListener.KEY_PUBLISHID));
if (!CmsUUID.isValidUUID(publishIdStr)) {
clear();
} else {
try {
CmsUUID publishId = new CmsUUID(publishIdStr);
List publishedResources = m_cmsObject.readPublishedResources(publishId);
boolean updateConfiguration = false;
for (CmsPublishedResource res : publishedResources) {
if (res.getRootPath().equals(CONFIG_PATH)) {
updateConfiguration = true;
break;
}
}
CmsFlexBucketConfiguration bucketConfig = m_bucketConfiguration;
if (updateConfiguration) {
LOG.info("Flex bucket configuration was updated, re-initializing configuration...");
try {
m_bucketConfiguration = CmsFlexBucketConfiguration.loadFromVfsFile(
m_cmsObject,
CONFIG_PATH);
} catch (CmsException e) {
LOG.error(e.getLocalizedMessage(), e);
}
// Make sure no entries built for the old configuration remain in the cache
clear();
} else if (bucketConfig != null) {
boolean bucketClearOk = clearBucketsForPublishList(
bucketConfig,
publishId,
publishedResources);
if (!bucketClearOk) {
clear();
}
} else {
clear();
}
} catch (CmsException e1) {
LOG.error(e1.getLocalizedMessage(), e1);
clear();
}
}
break;
case I_CmsEventListener.EVENT_CLEAR_CACHES:
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_CLEAR_CACHE_0));
}
clear();
break;
case I_CmsEventListener.EVENT_FLEX_PURGE_JSP_REPOSITORY:
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_PURGE_REPOSITORY_0));
}
purgeJspRepository();
break;
case I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR:
if (LOG.isDebugEnabled()) {
LOG.debug(
Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_CLEAR_CACHE_PARTIALLY_0));
}
Map m = event.getData();
if (m == null) {
break;
}
Integer it = null;
try {
it = (Integer)m.get(CACHE_ACTION);
} catch (Exception e) {
// it will be null
}
if (it == null) {
LOG.error("Flex cache clear event with no action parameter received");
break;
}
int i = it.intValue();
switch (i) {
case CLEAR_ALL:
clear();
break;
case CLEAR_ENTRIES:
clearEntries();
break;
case CLEAR_ONLINE_ALL:
clearOnline();
break;
case CLEAR_ONLINE_ENTRIES:
clearOnlineEntries();
break;
case CLEAR_OFFLINE_ALL:
clearOffline();
break;
case CLEAR_OFFLINE_ENTRIES:
clearOfflineEntries();
break;
default:
// no operation
}
break;
default:
// no operation
}
}
/**
* Dumps keys and variations to a string buffer, for debug purposes.
*
* @param buffer the buffer to which the key information should be written
*/
public void dumpKeys(StringBuffer buffer) {
synchronized (this) {
for (Map.Entry entry : synchronizedCopyMap(m_keyCache).entrySet()) {
String key = entry.getKey();
CmsFlexCacheVariation variations = entry.getValue();
Map variationMap = variations.m_map;
for (Map.Entry varEntry : variationMap.entrySet()) {
String varKey = varEntry.getKey();
I_CmsLruCacheObject value = varEntry.getValue();
buffer.append(key + " VAR " + varKey + "\n");
if (value instanceof CmsFlexCacheEntry) {
CmsFlexCacheEntry singleCacheEntry = (CmsFlexCacheEntry)value;
BucketSet buckets = singleCacheEntry.getBucketSet();
if (buckets != null) {
buffer.append("buckets = " + buckets.toString() + "\n");
}
}
}
}
}
}
/**
* Returns the CmsFlexCacheKey data structure for a given
* key (i.e. resource name).
*
* Useful if you want to show the cache key for a resources,
* like on the FlexCache administration page.
*
* Only users with administrator permissions are allowed
* to perform this operation.
*
* @param key the resource name for which to look up the variation for
* @param cms the CmsObject used for user authorization
* @return the CmsFlexCacheKey data structure found for the resource
*/
public CmsFlexCacheKey getCachedKey(String key, CmsObject cms) {
if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
return null;
}
Object o = m_keyCache.get(key);
if (o != null) {
return ((CmsFlexCacheVariation)o).m_key;
}
return null;
}
/**
* Returns a set of all cached resource names.
*
* Useful if you want to show a list of all cached resources,
* like on the FlexCache administration page.
*
* Only users with administrator permissions are allowed
* to perform this operation.
*
* @param cms the CmsObject used for user authorization
* @return a Set of cached resource names (which are of type String)
*/
public Set getCachedResources(CmsObject cms) {
if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
return null;
}
return synchronizedCopyKeys(m_keyCache);
}
/**
* Returns all variations in the cache for a given resource name.
* The variations are of type String.
*
* Useful if you want to show a list of all cached entry - variations,
* like on the FlexCache administration page.
*
* Only users with administrator permissions are allowed
* to perform this operation.
*
* @param key the resource name for which to look up the variations for
* @param cms the CmsObject used for user authorization
* @return a Set of cached variations (which are of type String)
*/
public Set getCachedVariations(String key, CmsObject cms) {
if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
return null;
}
Object o = m_keyCache.get(key);
if (o != null) {
return synchronizedCopyKeys(((CmsFlexCacheVariation)o).m_map);
}
return null;
}
/**
* Returns the LRU cache where the CacheEntries are cached.
*
* @return the LRU cache where the CacheEntries are cached
*/
public CmsLruCache getEntryLruCache() {
return m_variationCache;
}
/**
* Initializes the flex cache.
*
* @param adminCms a CMS context with admin privileges
*/
public void initializeCms(CmsObject adminCms) {
try {
m_cmsObject = adminCms;
try {
String path = CONFIG_PATH;
if (m_cmsObject.existsResource(path)) {
LOG.info("Flex configuration found at " + CONFIG_PATH + ", initializing...");
m_bucketConfiguration = CmsFlexBucketConfiguration.loadFromVfsFile(m_cmsObject, path);
}
} catch (Exception e) {
LOG.error(e.getLocalizedMessage(), e);
}
} catch (Exception e) {
LOG.error(e.getLocalizedMessage(), e);
}
}
/**
* Indicates if the cache is enabled (i.e. actually
* caching entries) or not.
*
* @return true if the cache is enabled, false if not
*/
public boolean isEnabled() {
return m_enabled;
}
/**
* Returns the total number of cached resource keys.
*
* @return the number of resource keys in the cache
*/
public int keySize() {
if (!isEnabled()) {
return 0;
}
return m_keyCache.size();
}
/**
* Returns the total number of entries in the cache.
*
* @return the number of entries in the cache
*/
public int size() {
return m_variationCache.size();
}
/**
* Looks up a specific entry in the cache.
*
* In case a found entry has a timeout set, it will be checked upon lookup.
* In case the timeout of the entry has been reached, it will be removed from
* the cache (and null will be returned in this case).
*
* @param key The key to look for in the cache
* @return the entry found for the key, or null if key is not in the cache
*/
CmsFlexCacheEntry get(CmsFlexRequestKey key) {
if (!isEnabled()) {
// cache is disabled
return null;
}
Object o = m_keyCache.get(key.getResource());
if (o != null) {
// found a matching key in the cache
CmsFlexCacheVariation v = (CmsFlexCacheVariation)o;
String variation = v.m_key.matchRequestKey(key);
if (CmsStringUtil.isEmpty(variation)) {
// requested resource is not cacheable
return null;
}
CmsFlexCacheEntry entry = (CmsFlexCacheEntry)v.m_map.get(variation);
if (entry == null) {
// no cache entry available for variation
return null;
}
if (entry.getDateExpires() < System.currentTimeMillis()) {
// cache entry avaiable but expired, remove entry
m_variationCache.remove(entry);
return null;
}
// return the found cache entry
return entry;
} else {
return null;
}
}
/**
* Returns the CmsFlexCacheKey data structure for a given resource name.
*
* @param resource the resource name for which to look up the key for
* @return the CmsFlexCacheKey data structure found for the resource
*/
CmsFlexCacheKey getKey(String resource) {
if (!isEnabled()) {
return null;
}
Object o = m_keyCache.get(resource);
if (o != null) {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_FOUND_1, resource));
}
return ((CmsFlexCacheVariation)o).m_key;
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_NOT_FOUND_1, resource));
}
return null;
}
}
/**
* Checks if the cache is empty or if at last one element is contained.
*
* @return true if the cache is empty, false otherwise
*/
boolean isEmpty() {
if (!isEnabled()) {
return true;
}
return m_keyCache.isEmpty();
}
/**
* This method adds new entries to the cache.
*
* The key describes the conditions under which the value can be cached.
* Usually the key belongs to the response.
* The variation describes the conditions under which the
* entry was created. This is usually calculated from the request.
* If the variation is != null, the entry is cachable.
*
* @param key the key for the new value entry
* @param entry the CmsFlexCacheEntry to store in the cache
* @param variation the pre-calculated variation for the entry
* @param requestKey the request key from which the variation was determined
* @return true if the value was added to the cache, false otherwise
*/
boolean put(CmsFlexCacheKey key, CmsFlexCacheEntry entry, String variation, CmsFlexRequestKey requestKey) {
if (!isEnabled()) {
return false;
}
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_ENTRY_1, key.getResource()));
}
if (variation != null) {
// This is a cachable result
if (LOG.isDebugEnabled()) {
LOG.debug(
Messages.get().getBundle().key(
Messages.LOG_FLEXCACHE_ADD_ENTRY_WITH_VARIATION_2,
key.getResource(),
variation));
}
put(key, entry, variation);
if (m_bucketConfiguration != null) {
try {
List paths = key.getPathsForBuckets(requestKey);
if (paths.size() > 0) {
BucketSet buckets = m_bucketConfiguration.getBucketSet(paths);
entry.setBucketSet(buckets);
} else {
entry.setBucketSet(null); // bucket set of null means entries will be deleted for every publish job
}
} catch (Exception e) {
LOG.error(e.getLocalizedMessage(), e);
}
}
// Note that duplicates are NOT checked, it it assumed that this is done beforehand,
// while checking if the entry is already in the cache or not.
return true;
} else {
// Result is not cachable
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RESOURCE_NOT_CACHEABLE_0));
}
return false;
}
}
/**
* Adds a key with a new, empty variation map to the cache.
*
* @param key the key to add to the cache.
*/
void putKey(CmsFlexCacheKey key) {
if (!isEnabled()) {
return;
}
Object o = m_keyCache.get(key.getResource());
if (o == null) {
// No variation map for this resource yet, so create one
CmsFlexCacheVariation variationMap = new CmsFlexCacheVariation(key);
m_keyCache.put(key.getResource(), variationMap);
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_KEY_1, key.getResource()));
}
}
// If != null the key is already in the cache, so we just do nothing
}
/**
* Empties the cache completely.
*/
private synchronized void clear() {
if (!isEnabled()) {
return;
}
m_keyCache.clear();
m_size = 0;
m_variationCache.clear();
if (LOG.isInfoEnabled()) {
LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_0));
}
}
/**
* Internal method to perform cache clearance.
*
* It clears "one half" of the cache, i.e. either
* the online or the offline parts.
* A parameter is used to indicate if only
* the entries or keys and entries are to be cleared.
*
* @param suffix used to distinguish between "[Online]" and "[Offline]" entries
* @param entriesOnly if true
, only entries will be cleared, otherwise
* the entries and the keys will be cleared
*/
private synchronized void clearAccordingToSuffix(String suffix, boolean entriesOnly) {
Set keys = synchronizedCopyKeys(m_keyCache);
Iterator i = keys.iterator();
while (i.hasNext()) {
String s = i.next();
if (s.endsWith(suffix)) {
CmsFlexCacheVariation v = m_keyCache.get(s);
if (entriesOnly) {
// Clear only entry
m_size -= v.m_map.size();
Iterator allEntries = v.m_map.values().iterator();
while (allEntries.hasNext()) {
I_CmsLruCacheObject nextObject = allEntries.next();
allEntries.remove();
m_variationCache.remove(nextObject);
}
v.m_map = new Hashtable(INITIAL_CAPACITY_VARIATIONS);
} else {
// Clear key and entry
m_size -= v.m_map.size();
Iterator allEntries = v.m_map.values().iterator();
while (allEntries.hasNext()) {
I_CmsLruCacheObject nextObject = allEntries.next();
allEntries.remove();
m_variationCache.remove(nextObject);
}
v.m_map = null;
v.m_key = null;
m_keyCache.remove(s);
}
}
}
if (LOG.isInfoEnabled()) {
LOG.info(
Messages.get().getBundle().key(
Messages.LOG_FLEXCACHE_CLEAR_HALF_2,
suffix,
Boolean.valueOf(entriesOnly)));
}
}
/**
* Clears the Flex cache buckets matching the given publish list.
*
* @param bucketConfig the bucket configuration to be used for checking which flex cache entry should be purged
* @param publishId the publish id
* @param publishedResources the published resources
*
* @return true if the flex buckets could be cleared successfully (if this returns false, the flex cache should fall back to the old behavior, i.e. clearing everything)
*/
private boolean clearBucketsForPublishList(
CmsFlexBucketConfiguration bucketConfig,
CmsUUID publishId,
List publishedResources) {
long startTime = System.currentTimeMillis();
String p = "[" + publishId + "] "; // Prefix for log messages
try {
LOG.debug(p + "Trying bucket-based flex entry cleanup");
if (bucketConfig.shouldClearAll(publishedResources)) {
LOG.info(p + "Clearing Flex cache completely based on Flex bucket configuration.");
return false;
} else {
long totalEntries = 0;
long removedEntries = 0;
List paths = Lists.newArrayList();
for (CmsPublishedResource pubRes : publishedResources) {
paths.add(pubRes.getRootPath());
LOG.info(p + "Published resource: " + pubRes.getRootPath());
}
BucketSet publishListBucketSet = bucketConfig.getBucketSet(paths);
if (LOG.isInfoEnabled()) {
LOG.info(p + "Flex cache buckets for publish list: " + publishListBucketSet.toString());
}
synchronized (this) {
List entriesToDelete = Lists.newArrayList();
for (Map.Entry entry : synchronizedCopyMap(m_keyCache).entrySet()) {
CmsFlexCacheVariation variation = entry.getValue();
if (LOG.isDebugEnabled()) {
LOG.debug(p + "Processing entries for " + entry.getKey());
}
entriesToDelete.clear();
for (Map.Entry variationEntry : synchronizedCopyMap(
variation.m_map).entrySet()) {
CmsFlexCacheEntry flexEntry = (CmsFlexCacheEntry)(variationEntry.getValue());
totalEntries += 1;
BucketSet entryBucketSet = flexEntry.getBucketSet();
if (publishListBucketSet.matchForDeletion(entryBucketSet)) {
entriesToDelete.add(flexEntry);
if (LOG.isInfoEnabled()) {
LOG.info(p + "Match: " + variationEntry.getKey());
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(p + "No match: " + variationEntry.getKey());
}
}
}
for (CmsFlexCacheEntry entryToDelete : entriesToDelete) {
m_variationCache.remove(entryToDelete);
removedEntries += 1;
}
}
long endTime = System.currentTimeMillis();
LOG.info(
p
+ "Removed "
+ removedEntries
+ " of "
+ totalEntries
+ " Flex cache entries, took "
+ (endTime - startTime)
+ " milliseconds");
return true;
}
}
} catch (Exception e) {
LOG.error(p + "Exception while trying to selectively purge flex cache: " + e.getLocalizedMessage(), e);
return false;
}
}
/**
* Clears all entries in the cache, online or offline.
*
* The keys are not cleared.
*
* Only users with administrator permissions are allowed
* to perform this operation.
*/
private synchronized void clearEntries() {
if (!isEnabled()) {
return;
}
if (LOG.isInfoEnabled()) {
LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ALL_0));
}
// create new set to avoid ConcurrentModificationExceptions
Set cacheKeys = synchronizedCopyKeys(m_keyCache);
Iterator i = cacheKeys.iterator();
while (i.hasNext()) {
CmsFlexCacheVariation v = m_keyCache.get(i.next());
Iterator allEntries = v.m_map.values().iterator();
while (allEntries.hasNext()) {
I_CmsLruCacheObject nextObject = allEntries.next();
allEntries.remove();
m_variationCache.remove(nextObject);
}
v.m_map = new Hashtable(INITIAL_CAPACITY_VARIATIONS);
}
m_size = 0;
}
/**
* Clears all entries and all keys from offline projects in the cache.
*
* Cached resources from the online project are not touched.
*
* Only users with administrator permissions are allowed
* to perform this operation.
*/
private void clearOffline() {
if (!isEnabled()) {
return;
}
if (LOG.isInfoEnabled()) {
LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_KEYS_AND_ENTRIES_0));
}
clearAccordingToSuffix(CACHE_OFFLINESUFFIX, false);
}
/**
* Clears all entries from offline projects in the cache.
*
* The keys from the offline projects are not cleared.
* Cached resources from the online project are not touched.
*
* Only users with administrator permissions are allowed
* to perform this operation.
*/
private void clearOfflineEntries() {
if (!isEnabled()) {
return;
}
if (LOG.isInfoEnabled()) {
LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_OFFLINE_ENTRIES_0));
}
clearAccordingToSuffix(CACHE_OFFLINESUFFIX, true);
}
/**
* Clears all entries and all keys from the online project in the cache.
*
* Cached resources from the offline projects are not touched.
*
* Only users with administrator permissions are allowed
* to perform this operation.
*/
private void clearOnline() {
if (!isEnabled()) {
return;
}
if (LOG.isInfoEnabled()) {
LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_KEYS_AND_ENTRIES_0));
}
clearAccordingToSuffix(CACHE_ONLINESUFFIX, false);
}
/**
* Clears all entries from the online project in the cache.
*
* The keys from the online project are not cleared.
* Cached resources from the offline projects are not touched.
*
* Only users with administrator permissions are allowed
* to perform this operation.
*/
private void clearOnlineEntries() {
if (!isEnabled()) {
return;
}
if (LOG.isInfoEnabled()) {
LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_ENTRIES_0));
}
clearAccordingToSuffix(CACHE_ONLINESUFFIX, true);
}
/**
* This method purges the JSP repository dirs,
* i.e. it deletes all JSP files that OpenCms has written to the
* real FS.
*
* Obviously this method must be used with caution.
* Purpose of this method is to allow
* a complete purge of all JSP pages on a machine after
* a major update of JSP templates was made.
*/
private synchronized void purgeJspRepository() {
CmsJspLoader cmsJspLoader = (CmsJspLoader)OpenCms.getResourceManager().getLoader(
CmsJspLoader.RESOURCE_LOADER_ID);
cmsJspLoader.triggerPurge(new Runnable() {
@SuppressWarnings("synthetic-access")
public void run() {
clear();
}
});
}
/**
* Save a value to the cache.
*
* @param key the key under which the value is saved
* @param theCacheEntry the entry to cache
* @param variation the variation string
*/
private void put(CmsFlexCacheKey key, CmsFlexCacheEntry theCacheEntry, String variation) {
CmsFlexCacheVariation o = m_keyCache.get(key.getResource());
if (key.getTimeout() > 0) {
theCacheEntry.setDateExpiresToNextTimeout(key.getTimeout());
}
if (o != null) {
// We already have a variation map for this resource
Map m = o.m_map;
boolean wasAdded = true;
if (!m.containsKey(variation)) {
wasAdded = m_variationCache.add(theCacheEntry);
} else {
wasAdded = m_variationCache.touch(theCacheEntry);
}
if (wasAdded) {
theCacheEntry.setVariationData(variation, m);
m.put(variation, theCacheEntry);
}
} else {
// No variation map for this resource yet, so create one
CmsFlexCacheVariation list = new CmsFlexCacheVariation(key);
boolean wasAdded = m_variationCache.add(theCacheEntry);
if (wasAdded) {
theCacheEntry.setVariationData(variation, list.m_map);
list.m_map.put(variation, theCacheEntry);
m_keyCache.put(key.getResource(), list);
}
}
if (LOG.isDebugEnabled()) {
LOG.debug(
Messages.get().getBundle().key(
Messages.LOG_FLEXCACHE_ADDED_ENTRY_FOR_RESOURCE_WITH_VARIATION_3,
new Integer(m_size),
key.getResource(),
variation));
LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADDED_ENTRY_1, theCacheEntry.toString()));
}
}
}