001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.flex;
029
030import org.opencms.cache.CmsLruCache;
031import org.opencms.cache.I_CmsLruCacheObject;
032import org.opencms.db.CmsPublishedResource;
033import org.opencms.file.CmsObject;
034import org.opencms.flex.CmsFlexBucketConfiguration.BucketSet;
035import org.opencms.loader.CmsJspLoader;
036import org.opencms.main.CmsException;
037import org.opencms.main.CmsLog;
038import org.opencms.main.I_CmsEventListener;
039import org.opencms.main.OpenCms;
040import org.opencms.security.CmsRole;
041import org.opencms.util.CmsCollectionsGenericWrapper;
042import org.opencms.util.CmsStringUtil;
043import org.opencms.util.CmsUUID;
044
045import java.util.ArrayList;
046import java.util.Collection;
047import java.util.Collections;
048import java.util.HashMap;
049import java.util.HashSet;
050import java.util.Hashtable;
051import java.util.Iterator;
052import java.util.List;
053import java.util.Map;
054import java.util.Set;
055
056import org.apache.commons.collections.map.LRUMap;
057import org.apache.commons.logging.Log;
058
059import com.google.common.collect.Lists;
060
061/**
062 * This class implements the FlexCache.<p>
063 *
064 * The data structure used is a two-level hashtable.
065 * This is optimized for the structure of the keys that are used to describe the
066 * caching behaviour of the entries.
067 * The first hash-level is calculated from the resource name, i.e. the
068 * name of the resource as it is referred to in the VFS of OpenCms.
069 * The second hash-level is calculated from the cache-key of the resource,
070 * which also is a String representing the specifc variation of the cached entry.<p>
071 *
072 * A suffix [online] or [offline] is appended to te resource name
073 * to distinguish between the online and offline projects of OpenCms.
074 * Also, for support of JSP based workplace pages, a suffix [workplace]
075 * is appended. The same cached workplace pages are used both in the online and
076 * all offline projects.<p>
077 *
078 * Entries in the first level of the cache are of type CmsFlexCacheVariation,
079 * which is a sub-class of CmsFlexCache.
080 * This class is a simple data type that contains of a Map of CmsFlexCacheEntries,
081 * with variations - Strings as keys.<p>
082 *
083 * Here's a short summary of used terms:
084 * <ul>
085 * <li><b>key:</b>
086 * A combination of a resource name and a variation.
087 * The data structure used is CmsFlexCacheKey.
088 * <li><b>resource:</b>
089 * A String with the resource name and an appended [online] of [offline] suffix.
090 * <li><b>variation:</b>
091 * A String describing a variation of a cached entry in the CmsFlexCache language.
092 * <li><b>entry:</b>
093 * A CmsFlexCacheEntry data structure which is describes a cached OpenCms resource.
094 * For every entry a key is saved which contains the resource name and the variation.
095 * </ul>
096 *
097 * Cache clearing is handled using events.
098 * The cache is fully flushed if an event {@link I_CmsEventListener#EVENT_PUBLISH_PROJECT}
099 * or {@link I_CmsEventListener#EVENT_CLEAR_CACHES} is caught.<p>
100 *
101 * @since 6.0.0
102 *
103 * @see org.opencms.flex.CmsFlexCacheKey
104 * @see org.opencms.flex.CmsFlexCacheEntry
105 * @see org.opencms.cache.CmsLruCache
106 * @see org.opencms.cache.I_CmsLruCacheObject
107 */
108public class CmsFlexCache extends Object implements I_CmsEventListener {
109
110    /**
111     * A simple data container class for the FlexCache variations.<p>
112     */
113    public static class CmsFlexCacheVariation extends Object {
114
115        /** The key belonging to the resource. */
116        public CmsFlexCacheKey m_key;
117
118        /** Maps variations to CmsFlexCacheEntries. */
119        public Map<String, I_CmsLruCacheObject> m_map;
120
121        /**
122         * Generates a new instance of CmsFlexCacheVariation.<p>
123         *
124         * @param theKey The (resource) key to contruct this variation list for
125         */
126        public CmsFlexCacheVariation(CmsFlexCacheKey theKey) {
127
128            m_key = theKey;
129            m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS);
130        }
131    }
132
133    /**
134     * Extended LRUMap that handles the variations in case a key is removed.<p>
135     */
136    class CmsFlexKeyMap extends LRUMap {
137
138        /** Serial version UID required for safe serialization. */
139        private static final long serialVersionUID = 6931995916013396902L;
140
141        /**
142         * Initialize the map with the given size.<p>
143         *
144         * @param maxSize the maximum number of key to cache
145         */
146        public CmsFlexKeyMap(int maxSize) {
147
148            super(maxSize);
149        }
150
151        /**
152         * Ensures that all variations that referenced by this key are released
153         * if the key is released.<p>
154         *
155         * @param entry the entry to remove
156         *
157         * @return <code>true</code> to actually delete the entry
158         *
159         * @see LRUMap#removeLRU(LinkEntry)
160         */
161        @Override
162        protected boolean removeLRU(LinkEntry entry) {
163
164            CmsFlexCacheVariation v = (CmsFlexCacheVariation)entry.getValue();
165            if (v == null) {
166                return true;
167            }
168            Map<String, I_CmsLruCacheObject> m = v.m_map;
169            if ((m == null) || (m.size() == 0)) {
170                return true;
171            }
172
173            // make a copy to safely iterate over because the line "m_variationCache.remove(e)" modifies the variation map for the key
174            Collection<I_CmsLruCacheObject> entries = new ArrayList<I_CmsLruCacheObject>(m.values());
175            synchronized (m_variationCache) {
176                for (I_CmsLruCacheObject e : entries) {
177                    m_variationCache.remove(e);
178                }
179                v.m_map.clear();
180                v.m_map = null;
181                v.m_key = null;
182            }
183            return true;
184        }
185    }
186
187    /**Constant for distinguish cache action.*/
188    public static final String CACHE_ACTION = "action";
189
190    /** Suffix to append to online cache entries. */
191    public static final String CACHE_OFFLINESUFFIX = " [offline]";
192
193    /** Suffix to append to online cache entries. */
194    public static final String CACHE_ONLINESUFFIX = " [online]";
195
196    /** Trigger for clearcache event: Clear complete cache. */
197    public static final int CLEAR_ALL = 0;
198
199    /** Trigger for clearcache event: Clear only entries. */
200    public static final int CLEAR_ENTRIES = 1;
201
202    /** Trigger for clearcache event: Clear complete offine cache. */
203    public static final int CLEAR_OFFLINE_ALL = 4;
204
205    /** Trigger for clearcache event: Clear only offline entries. */
206    public static final int CLEAR_OFFLINE_ENTRIES = 5;
207
208    /** Trigger for clearcache event: Clear complete online cache. */
209    public static final int CLEAR_ONLINE_ALL = 2;
210
211    /** Trigger for clearcache event: Clear only online entries. */
212    public static final int CLEAR_ONLINE_ENTRIES = 3;
213
214    /** The configuration for the Flex cache buckets. */
215    public static final String CONFIG_PATH = "/system/config/flexconfig.properties";
216
217    /** Initial cache size, this should be a power of 2 because of the Java collections implementation. */
218    public static final int INITIAL_CAPACITY_CACHE = 512;
219
220    /** Initial size for variation lists, should be a power of 2. */
221    public static final int INITIAL_CAPACITY_VARIATIONS = 8;
222
223    /** Offline repository constant. */
224    public static final String REPOSITORY_OFFLINE = "offline";
225
226    /** Online repository constant. */
227    public static final String REPOSITORY_ONLINE = "online";
228
229    /** The log object for this class. */
230    private static final Log LOG = CmsLog.getLog(CmsFlexCache.class);
231
232    /** The LRU cache to organize the cached entries. */
233    protected CmsLruCache m_variationCache;
234
235    /** The Flex bucket configuration. */
236    private CmsFlexBucketConfiguration m_bucketConfiguration;
237
238    /** Indicates if offline resources should be cached or not. */
239    private boolean m_cacheOffline;
240
241    /** The CMS object used for VFS operations. */
242    private CmsObject m_cmsObject;
243
244    /** Indicates if the cache is enabled or not. */
245    private boolean m_enabled;
246
247    /** Map to store the entries for fast lookup. */
248    private Map<String, CmsFlexCacheVariation> m_keyCache;
249
250    /** Counter for the size. */
251    private int m_size;
252
253    /**
254     * Constructor for class CmsFlexCache.<p>
255     *
256     * The parameter "enabled" is used to control if the cache is
257     * actually on or off. Even if you don't need the cache, you still
258     * have to create an instance of it with enabled=false.
259     * This is because you need some of the FlexCache data structures
260     * for JSP inclusion buffering.<p>
261     *
262     * @param configuration the flex cache configuration
263     */
264    public CmsFlexCache(CmsFlexCacheConfiguration configuration) {
265
266        m_enabled = configuration.isCacheEnabled();
267        m_cacheOffline = configuration.isCacheOffline();
268
269        long maxCacheBytes = configuration.getMaxCacheBytes();
270        long avgCacheBytes = configuration.getAvgCacheBytes();
271        int maxEntryBytes = configuration.getMaxEntryBytes();
272        int maxKeys = configuration.getMaxKeys();
273
274        m_variationCache = new CmsLruCache(maxCacheBytes, avgCacheBytes, maxEntryBytes);
275        OpenCms.getMemoryMonitor().register(getClass().getName() + ".m_entryLruCache", m_variationCache);
276
277        if (m_enabled) {
278            CmsFlexKeyMap flexKeyMap = new CmsFlexKeyMap(maxKeys);
279            m_keyCache = Collections.synchronizedMap(
280                CmsCollectionsGenericWrapper.<String, CmsFlexCacheVariation> map(flexKeyMap));
281            OpenCms.getMemoryMonitor().register(getClass().getName() + ".m_resourceMap", flexKeyMap);
282
283            OpenCms.addCmsEventListener(
284                this,
285                new int[] {
286                    I_CmsEventListener.EVENT_PUBLISH_PROJECT,
287                    I_CmsEventListener.EVENT_CLEAR_CACHES,
288                    I_CmsEventListener.EVENT_FLEX_PURGE_JSP_REPOSITORY,
289                    I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR});
290        }
291
292        if (LOG.isInfoEnabled()) {
293            LOG.info(
294                Messages.get().getBundle().key(
295                    Messages.INIT_FLEXCACHE_CREATED_2,
296                    Boolean.valueOf(m_enabled),
297                    Boolean.valueOf(m_cacheOffline)));
298        }
299    }
300
301    /**
302     * Copies the key set of a map while synchronizing on the map.<p>
303     *
304     * @param map the map whose key set should be copied
305     * @return the copied key set
306     */
307    private static <K, V> Set<K> synchronizedCopyKeys(Map<K, V> map) {
308
309        if (map == null) {
310            return new HashSet<K>();
311        }
312        synchronized (map) {
313            return new HashSet<K>(map.keySet());
314        }
315    }
316
317    /**
318     * Copies a map while synchronizing on it.<p>
319     *
320     * @param map the map to copy
321     * @return the copied map
322     */
323    private static <K, V> Map<K, V> synchronizedCopyMap(Map<K, V> map) {
324
325        if (map == null) {
326            return new HashMap<K, V>();
327        }
328
329        synchronized (map) {
330
331            return new HashMap<K, V>(map);
332        }
333    }
334
335    /**
336     * Indicates if offline project resources are cached.<p>
337     *
338     * @return true if offline projects are cached, false if not
339     */
340    public boolean cacheOffline() {
341
342        return m_cacheOffline;
343    }
344
345    /**
346     * Implements the CmsEvent interface,
347     * the FlexCache uses the events to clear itself in case a project is published.<p>
348     *
349     * @param event CmsEvent that has occurred
350     */
351    public void cmsEvent(org.opencms.main.CmsEvent event) {
352
353        if (!isEnabled()) {
354            return;
355        }
356
357        switch (event.getType()) {
358            case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
359                if (LOG.isDebugEnabled()) {
360                    LOG.debug("FlexCache: Received event PUBLISH_PROJECT");
361                }
362                String publishIdStr = (String)(event.getData().get(I_CmsEventListener.KEY_PUBLISHID));
363                if (!CmsUUID.isValidUUID(publishIdStr)) {
364                    clear();
365                } else {
366                    try {
367                        CmsUUID publishId = new CmsUUID(publishIdStr);
368                        List<CmsPublishedResource> publishedResources = m_cmsObject.readPublishedResources(publishId);
369                        boolean updateConfiguration = false;
370                        for (CmsPublishedResource res : publishedResources) {
371                            if (res.getRootPath().equals(CONFIG_PATH)) {
372                                updateConfiguration = true;
373                                break;
374                            }
375                        }
376                        CmsFlexBucketConfiguration bucketConfig = m_bucketConfiguration;
377                        if (updateConfiguration) {
378                            LOG.info("Flex bucket configuration was updated, re-initializing configuration...");
379                            try {
380                                m_bucketConfiguration = CmsFlexBucketConfiguration.loadFromVfsFile(
381                                    m_cmsObject,
382                                    CONFIG_PATH);
383                            } catch (CmsException e) {
384                                LOG.error(e.getLocalizedMessage(), e);
385                            }
386                            // Make sure no entries built for the old configuration remain in the cache
387                            clear();
388                        } else if (bucketConfig != null) {
389                            boolean bucketClearOk = clearBucketsForPublishList(
390                                bucketConfig,
391                                publishId,
392                                publishedResources);
393                            if (!bucketClearOk) {
394                                clear();
395                            }
396                        } else {
397                            clear();
398                        }
399
400                    } catch (CmsException e1) {
401                        LOG.error(e1.getLocalizedMessage(), e1);
402                        clear();
403                    }
404                }
405                break;
406            case I_CmsEventListener.EVENT_CLEAR_CACHES:
407                if (LOG.isDebugEnabled()) {
408                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_CLEAR_CACHE_0));
409                }
410                clear();
411                break;
412            case I_CmsEventListener.EVENT_FLEX_PURGE_JSP_REPOSITORY:
413                if (LOG.isDebugEnabled()) {
414                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_PURGE_REPOSITORY_0));
415                }
416                purgeJspRepository();
417                break;
418            case I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR:
419                if (LOG.isDebugEnabled()) {
420                    LOG.debug(
421                        Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_CLEAR_CACHE_PARTIALLY_0));
422                }
423                Map<String, ?> m = event.getData();
424                if (m == null) {
425                    break;
426                }
427                Integer it = null;
428                try {
429                    it = (Integer)m.get(CACHE_ACTION);
430                } catch (Exception e) {
431                    // it will be null
432                }
433                if (it == null) {
434                    break;
435                }
436                int i = it.intValue();
437                switch (i) {
438                    case CLEAR_ALL:
439                        clear();
440                        break;
441                    case CLEAR_ENTRIES:
442                        clearEntries();
443                        break;
444                    case CLEAR_ONLINE_ALL:
445                        clearOnline();
446                        break;
447                    case CLEAR_ONLINE_ENTRIES:
448                        clearOnlineEntries();
449                        break;
450                    case CLEAR_OFFLINE_ALL:
451                        clearOffline();
452                        break;
453                    case CLEAR_OFFLINE_ENTRIES:
454                        clearOfflineEntries();
455                        break;
456                    default:
457                        // no operation
458                }
459                break;
460            default:
461                // no operation
462        }
463    }
464
465    /**
466     * Dumps keys and variations to a string buffer, for debug purposes.<p>
467     *
468     * @param buffer the buffer to which the key information should be written
469     */
470    public void dumpKeys(StringBuffer buffer) {
471
472        synchronized (this) {
473            for (Map.Entry<String, CmsFlexCacheVariation> entry : synchronizedCopyMap(m_keyCache).entrySet()) {
474                String key = entry.getKey();
475                CmsFlexCacheVariation variations = entry.getValue();
476                Map<String, I_CmsLruCacheObject> variationMap = variations.m_map;
477                for (Map.Entry<String, I_CmsLruCacheObject> varEntry : variationMap.entrySet()) {
478                    String varKey = varEntry.getKey();
479                    I_CmsLruCacheObject value = varEntry.getValue();
480                    buffer.append(key + " VAR " + varKey + "\n");
481                    if (value instanceof CmsFlexCacheEntry) {
482                        CmsFlexCacheEntry singleCacheEntry = (CmsFlexCacheEntry)value;
483                        BucketSet buckets = singleCacheEntry.getBucketSet();
484                        if (buckets != null) {
485                            buffer.append("buckets = " + buckets.toString() + "\n");
486                        }
487                    }
488                }
489            }
490        }
491    }
492
493    /**
494     * Returns the CmsFlexCacheKey data structure for a given
495     * key (i.e. resource name).<p>
496     *
497     * Useful if you want to show the cache key for a resources,
498     * like on the FlexCache administration page.<p>
499     *
500     * Only users with administrator permissions are allowed
501     * to perform this operation.<p>
502     *
503     * @param key the resource name for which to look up the variation for
504     * @param cms the CmsObject used for user authorization
505     * @return the CmsFlexCacheKey data structure found for the resource
506     */
507    public CmsFlexCacheKey getCachedKey(String key, CmsObject cms) {
508
509        if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
510            return null;
511        }
512        Object o = m_keyCache.get(key);
513        if (o != null) {
514            return ((CmsFlexCacheVariation)o).m_key;
515        }
516        return null;
517    }
518
519    /**
520     * Returns a set of all cached resource names.<p>
521     *
522     * Useful if you want to show a list of all cached resources,
523     * like on the FlexCache administration page.<p>
524     *
525     * Only users with administrator permissions are allowed
526     * to perform this operation.<p>
527     *
528     * @param cms the CmsObject used for user authorization
529     * @return a Set of cached resource names (which are of type String)
530     */
531    public Set<String> getCachedResources(CmsObject cms) {
532
533        if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
534            return null;
535        }
536        return synchronizedCopyKeys(m_keyCache);
537    }
538
539    /**
540     * Returns all variations in the cache for a given resource name.
541     * The variations are of type String.<p>
542     *
543     * Useful if you want to show a list of all cached entry - variations,
544     * like on the FlexCache administration page.<p>
545     *
546     * Only users with administrator permissions are allowed
547     * to perform this operation.<p>
548     *
549     * @param key the resource name for which to look up the variations for
550     * @param cms the CmsObject used for user authorization
551     * @return a Set of cached variations (which are of type String)
552     */
553    public Set<String> getCachedVariations(String key, CmsObject cms) {
554
555        if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
556            return null;
557        }
558        Object o = m_keyCache.get(key);
559        if (o != null) {
560            return synchronizedCopyKeys(((CmsFlexCacheVariation)o).m_map);
561        }
562        return null;
563    }
564
565    /**
566     * Returns the LRU cache where the CacheEntries are cached.<p>
567     *
568     * @return the LRU cache where the CacheEntries are cached
569     */
570    public CmsLruCache getEntryLruCache() {
571
572        return m_variationCache;
573    }
574
575    /**
576     * Initializes the flex cache.<p>
577     *
578     * @param adminCms a CMS context with admin privileges
579     */
580    public void initializeCms(CmsObject adminCms) {
581
582        try {
583            m_cmsObject = adminCms;
584            try {
585                String path = CONFIG_PATH;
586                if (m_cmsObject.existsResource(path)) {
587                    LOG.info("Flex configuration found at " + CONFIG_PATH + ", initializing...");
588                    m_bucketConfiguration = CmsFlexBucketConfiguration.loadFromVfsFile(m_cmsObject, path);
589                }
590            } catch (Exception e) {
591                LOG.error(e.getLocalizedMessage(), e);
592            }
593        } catch (Exception e) {
594            LOG.error(e.getLocalizedMessage(), e);
595        }
596    }
597
598    /**
599     * Indicates if the cache is enabled (i.e. actually
600     * caching entries) or not.<p>
601     *
602     * @return true if the cache is enabled, false if not
603     */
604    public boolean isEnabled() {
605
606        return m_enabled;
607    }
608
609    /**
610     * Returns the total number of cached resource keys.
611     *
612     * @return the number of resource keys in the cache
613     */
614    public int keySize() {
615
616        if (!isEnabled()) {
617            return 0;
618        }
619        return m_keyCache.size();
620    }
621
622    /**
623     * Returns the total number of entries in the cache.<p>
624     *
625     * @return the number of entries in the cache
626     */
627    public int size() {
628
629        return m_variationCache.size();
630    }
631
632    /**
633     * Looks up a specific entry in the cache.<p>
634     *
635     * In case a found entry has a timeout set, it will be checked upon lookup.
636     * In case the timeout of the entry has been reached, it will be removed from
637     * the cache (and null will be returned in this case).<p>
638     *
639     * @param key The key to look for in the cache
640     * @return the entry found for the key, or null if key is not in the cache
641     */
642    CmsFlexCacheEntry get(CmsFlexRequestKey key) {
643
644        if (!isEnabled()) {
645            // cache is disabled
646            return null;
647        }
648        Object o = m_keyCache.get(key.getResource());
649        if (o != null) {
650            // found a matching key in the cache
651            CmsFlexCacheVariation v = (CmsFlexCacheVariation)o;
652            String variation = v.m_key.matchRequestKey(key);
653
654            if (CmsStringUtil.isEmpty(variation)) {
655                // requested resource is not cacheable
656                return null;
657            }
658            CmsFlexCacheEntry entry = (CmsFlexCacheEntry)v.m_map.get(variation);
659            if (entry == null) {
660                // no cache entry available for variation
661                return null;
662            }
663            if (entry.getDateExpires() < System.currentTimeMillis()) {
664                // cache entry avaiable but expired, remove entry
665                m_variationCache.remove(entry);
666                return null;
667            }
668            // return the found cache entry
669            return entry;
670        } else {
671            return null;
672        }
673    }
674
675    /**
676     * Returns the CmsFlexCacheKey data structure for a given resource name.<p>
677     *
678     * @param resource the resource name for which to look up the key for
679     * @return the CmsFlexCacheKey data structure found for the resource
680     */
681    CmsFlexCacheKey getKey(String resource) {
682
683        if (!isEnabled()) {
684            return null;
685        }
686        Object o = m_keyCache.get(resource);
687        if (o != null) {
688            if (LOG.isDebugEnabled()) {
689                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_FOUND_1, resource));
690            }
691            return ((CmsFlexCacheVariation)o).m_key;
692        } else {
693            if (LOG.isDebugEnabled()) {
694                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_NOT_FOUND_1, resource));
695            }
696            return null;
697        }
698    }
699
700    /**
701     * Checks if the cache is empty or if at last one element is contained.<p>
702     *
703     * @return true if the cache is empty, false otherwise
704     */
705    boolean isEmpty() {
706
707        if (!isEnabled()) {
708            return true;
709        }
710        return m_keyCache.isEmpty();
711    }
712
713    /**
714     * This method adds new entries to the cache.<p>
715     *
716     * The key describes the conditions under which the value can be cached.
717     * Usually the key belongs to the response.
718     * The variation describes the conditions under which the
719     * entry was created. This is usually calculated from the request.
720     * If the variation is != null, the entry is cachable.<p>
721     *
722     * @param key the key for the new value entry
723     * @param entry the CmsFlexCacheEntry to store in the cache
724     * @param variation the pre-calculated variation for the entry
725     * @param requestKey the request key from which the variation was determined
726     * @return true if the value was added to the cache, false otherwise
727     */
728    boolean put(CmsFlexCacheKey key, CmsFlexCacheEntry entry, String variation, CmsFlexRequestKey requestKey) {
729
730        if (!isEnabled()) {
731            return false;
732        }
733        if (LOG.isDebugEnabled()) {
734            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_ENTRY_1, key.getResource()));
735        }
736        if (variation != null) {
737            // This is a cachable result
738            if (LOG.isDebugEnabled()) {
739                LOG.debug(
740                    Messages.get().getBundle().key(
741                        Messages.LOG_FLEXCACHE_ADD_ENTRY_WITH_VARIATION_2,
742                        key.getResource(),
743                        variation));
744            }
745            put(key, entry, variation);
746            if (m_bucketConfiguration != null) {
747                try {
748                    List<String> paths = key.getPathsForBuckets(requestKey);
749                    if (paths.size() > 0) {
750                        BucketSet buckets = m_bucketConfiguration.getBucketSet(paths);
751                        entry.setBucketSet(buckets);
752                    } else {
753                        entry.setBucketSet(null); // bucket set of null means entries will be deleted for every publish job
754                    }
755                } catch (Exception e) {
756                    LOG.error(e.getLocalizedMessage(), e);
757                }
758            }
759            // Note that duplicates are NOT checked, it it assumed that this is done beforehand,
760            // while checking if the entry is already in the cache or not.
761            return true;
762        } else {
763            // Result is not cachable
764            if (LOG.isDebugEnabled()) {
765                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RESOURCE_NOT_CACHEABLE_0));
766            }
767            return false;
768        }
769    }
770
771    /**
772     * Adds a key with a new, empty variation map to the cache.<p>
773     *
774     * @param key the key to add to the cache.
775     */
776    void putKey(CmsFlexCacheKey key) {
777
778        if (!isEnabled()) {
779            return;
780        }
781        Object o = m_keyCache.get(key.getResource());
782        if (o == null) {
783            // No variation map for this resource yet, so create one
784            CmsFlexCacheVariation variationMap = new CmsFlexCacheVariation(key);
785            m_keyCache.put(key.getResource(), variationMap);
786            if (LOG.isDebugEnabled()) {
787                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_KEY_1, key.getResource()));
788            }
789        }
790        // If != null the key is already in the cache, so we just do nothing
791    }
792
793    /**
794     * Empties the cache completely.<p>
795     */
796    private synchronized void clear() {
797
798        if (!isEnabled()) {
799            return;
800        }
801        m_keyCache.clear();
802        m_size = 0;
803
804        m_variationCache.clear();
805
806        if (LOG.isInfoEnabled()) {
807            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_0));
808        }
809    }
810
811    /**
812     * Internal method to perform cache clearance.<p>
813     *
814     * It clears "one half" of the cache, i.e. either
815     * the online or the offline parts.
816     * A parameter is used to indicate if only
817     * the entries or keys and entries are to be cleared.<p>
818     *
819     * @param suffix used to distinguish between "[Online]" and "[Offline]" entries
820     * @param entriesOnly if <code>true</code>, only entries will be cleared, otherwise
821     *         the entries and the keys will be cleared
822     */
823    private synchronized void clearAccordingToSuffix(String suffix, boolean entriesOnly) {
824
825        Set<String> keys = synchronizedCopyKeys(m_keyCache);
826        Iterator<String> i = keys.iterator();
827        while (i.hasNext()) {
828            String s = i.next();
829            if (s.endsWith(suffix)) {
830                CmsFlexCacheVariation v = m_keyCache.get(s);
831                if (entriesOnly) {
832                    // Clear only entry
833                    m_size -= v.m_map.size();
834                    Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator();
835                    while (allEntries.hasNext()) {
836                        I_CmsLruCacheObject nextObject = allEntries.next();
837                        allEntries.remove();
838                        m_variationCache.remove(nextObject);
839                    }
840                    v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS);
841                } else {
842                    // Clear key and entry
843                    m_size -= v.m_map.size();
844                    Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator();
845                    while (allEntries.hasNext()) {
846                        I_CmsLruCacheObject nextObject = allEntries.next();
847                        allEntries.remove();
848                        m_variationCache.remove(nextObject);
849                    }
850
851                    v.m_map = null;
852                    v.m_key = null;
853                    m_keyCache.remove(s);
854                }
855            }
856        }
857        if (LOG.isInfoEnabled()) {
858            LOG.info(
859                Messages.get().getBundle().key(
860                    Messages.LOG_FLEXCACHE_CLEAR_HALF_2,
861                    suffix,
862                    Boolean.valueOf(entriesOnly)));
863        }
864    }
865
866    /**
867     * Clears the Flex cache buckets matching the given publish list.<p>
868     *
869     * @param bucketConfig the bucket configuration to be used for checking which flex cache entry should be purged
870     * @param publishId the publish id
871     * @param publishedResources the published resources
872     *
873     * @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)
874     */
875    private boolean clearBucketsForPublishList(
876        CmsFlexBucketConfiguration bucketConfig,
877        CmsUUID publishId,
878        List<CmsPublishedResource> publishedResources) {
879
880        long startTime = System.currentTimeMillis();
881        String p = "[" + publishId + "] "; // Prefix for log messages
882        try {
883
884            LOG.debug(p + "Trying bucket-based flex entry cleanup");
885            if (bucketConfig.shouldClearAll(publishedResources)) {
886                LOG.info(p + "Clearing Flex cache completely based on Flex bucket configuration.");
887                return false;
888            } else {
889                long totalEntries = 0;
890                long removedEntries = 0;
891                List<String> paths = Lists.newArrayList();
892                for (CmsPublishedResource pubRes : publishedResources) {
893                    paths.add(pubRes.getRootPath());
894                    LOG.info(p + "Published resource: " + pubRes.getRootPath());
895                }
896                BucketSet publishListBucketSet = bucketConfig.getBucketSet(paths);
897                if (LOG.isInfoEnabled()) {
898                    LOG.info(p + "Flex cache buckets for publish list: " + publishListBucketSet.toString());
899                }
900                synchronized (this) {
901                    List<CmsFlexCacheEntry> entriesToDelete = Lists.newArrayList();
902                    for (Map.Entry<String, CmsFlexCacheVariation> entry : synchronizedCopyMap(m_keyCache).entrySet()) {
903                        CmsFlexCacheVariation variation = entry.getValue();
904                        if (LOG.isDebugEnabled()) {
905                            LOG.debug(p + "Processing entries for " + entry.getKey());
906                        }
907                        entriesToDelete.clear();
908
909                        for (Map.Entry<String, I_CmsLruCacheObject> variationEntry : synchronizedCopyMap(
910                            variation.m_map).entrySet()) {
911                            CmsFlexCacheEntry flexEntry = (CmsFlexCacheEntry)(variationEntry.getValue());
912                            totalEntries += 1;
913                            BucketSet entryBucketSet = flexEntry.getBucketSet();
914                            if (publishListBucketSet.matchForDeletion(entryBucketSet)) {
915                                entriesToDelete.add(flexEntry);
916                                if (LOG.isInfoEnabled()) {
917                                    LOG.info(p + "Match: " + variationEntry.getKey());
918                                }
919                            } else {
920                                if (LOG.isDebugEnabled()) {
921                                    LOG.debug(p + "No match: " + variationEntry.getKey());
922                                }
923                            }
924                        }
925                        for (CmsFlexCacheEntry entryToDelete : entriesToDelete) {
926                            m_variationCache.remove(entryToDelete);
927                            removedEntries += 1;
928                        }
929                    }
930                    long endTime = System.currentTimeMillis();
931                    LOG.info(
932                        p
933                            + "Removed "
934                            + removedEntries
935                            + " of "
936                            + totalEntries
937                            + " Flex cache entries, took "
938                            + (endTime - startTime)
939                            + " milliseconds");
940                    return true;
941                }
942            }
943        } catch (Exception e) {
944            LOG.error(p + "Exception while trying to selectively purge flex cache: " + e.getLocalizedMessage(), e);
945            return false;
946        }
947    }
948
949    /**
950     * Clears all entries in the cache, online or offline.<p>
951     *
952     * The keys are not cleared.<p>
953     *
954     * Only users with administrator permissions are allowed
955     * to perform this operation.<p>
956     */
957    private synchronized void clearEntries() {
958
959        if (!isEnabled()) {
960            return;
961        }
962        if (LOG.isInfoEnabled()) {
963            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ALL_0));
964        }
965        // create new set to avoid ConcurrentModificationExceptions
966        Set<String> cacheKeys = synchronizedCopyKeys(m_keyCache);
967        Iterator<String> i = cacheKeys.iterator();
968        while (i.hasNext()) {
969            CmsFlexCacheVariation v = m_keyCache.get(i.next());
970            Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator();
971            while (allEntries.hasNext()) {
972                I_CmsLruCacheObject nextObject = allEntries.next();
973                allEntries.remove();
974                m_variationCache.remove(nextObject);
975            }
976            v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS);
977        }
978        m_size = 0;
979    }
980
981    /**
982     * Clears all entries and all keys from offline projects in the cache.<p>
983     *
984     * Cached resources from the online project are not touched.<p>
985     *
986     * Only users with administrator permissions are allowed
987     * to perform this operation.<p>
988     */
989    private void clearOffline() {
990
991        if (!isEnabled()) {
992            return;
993        }
994        if (LOG.isInfoEnabled()) {
995            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_KEYS_AND_ENTRIES_0));
996        }
997        clearAccordingToSuffix(CACHE_OFFLINESUFFIX, false);
998    }
999
1000    /**
1001     * Clears all entries from offline projects in the cache.<p>
1002     *
1003     * The keys from the offline projects are not cleared.
1004     * Cached resources from the online project are not touched.<p>
1005     *
1006     * Only users with administrator permissions are allowed
1007     * to perform this operation.<p>
1008     */
1009    private void clearOfflineEntries() {
1010
1011        if (!isEnabled()) {
1012            return;
1013        }
1014        if (LOG.isInfoEnabled()) {
1015            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_OFFLINE_ENTRIES_0));
1016        }
1017        clearAccordingToSuffix(CACHE_OFFLINESUFFIX, true);
1018    }
1019
1020    /**
1021     * Clears all entries and all keys from the online project in the cache.<p>
1022     *
1023     * Cached resources from the offline projects are not touched.<p>
1024     *
1025     * Only users with administrator permissions are allowed
1026     * to perform this operation.<p>
1027     */
1028    private void clearOnline() {
1029
1030        if (!isEnabled()) {
1031            return;
1032        }
1033        if (LOG.isInfoEnabled()) {
1034            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_KEYS_AND_ENTRIES_0));
1035        }
1036        clearAccordingToSuffix(CACHE_ONLINESUFFIX, false);
1037    }
1038
1039    /**
1040     * Clears all entries from the online project in the cache.<p>
1041     *
1042     * The keys from the online project are not cleared.
1043     * Cached resources from the offline projects are not touched.<p>
1044     *
1045     * Only users with administrator permissions are allowed
1046     * to perform this operation.<p>
1047     */
1048    private void clearOnlineEntries() {
1049
1050        if (!isEnabled()) {
1051            return;
1052        }
1053        if (LOG.isInfoEnabled()) {
1054            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_ENTRIES_0));
1055        }
1056        clearAccordingToSuffix(CACHE_ONLINESUFFIX, true);
1057    }
1058
1059    /**
1060     * This method purges the JSP repository dirs,
1061     * i.e. it deletes all JSP files that OpenCms has written to the
1062     * real FS.<p>
1063     *
1064     * Obviously this method must be used with caution.
1065     * Purpose of this method is to allow
1066     * a complete purge of all JSP pages on a machine after
1067     * a major update of JSP templates was made.<p>
1068     */
1069    private synchronized void purgeJspRepository() {
1070
1071        CmsJspLoader cmsJspLoader = (CmsJspLoader)OpenCms.getResourceManager().getLoader(
1072            CmsJspLoader.RESOURCE_LOADER_ID);
1073
1074        cmsJspLoader.triggerPurge(new Runnable() {
1075
1076            @SuppressWarnings("synthetic-access")
1077            public void run() {
1078
1079                clear();
1080            }
1081        });
1082    }
1083
1084    /**
1085     * Save a value to the cache.<p>
1086     *
1087     * @param key the key under which the value is saved
1088     * @param theCacheEntry the entry to cache
1089     * @param variation the variation string 
1090     */
1091    private void put(CmsFlexCacheKey key, CmsFlexCacheEntry theCacheEntry, String variation) {
1092
1093        CmsFlexCacheVariation o = m_keyCache.get(key.getResource());
1094        if (key.getTimeout() > 0) {
1095            theCacheEntry.setDateExpiresToNextTimeout(key.getTimeout());
1096        }
1097        if (o != null) {
1098            // We already have a variation map for this resource
1099            Map<String, I_CmsLruCacheObject> m = o.m_map;
1100            boolean wasAdded = true;
1101            if (!m.containsKey(variation)) {
1102                wasAdded = m_variationCache.add(theCacheEntry);
1103            } else {
1104                wasAdded = m_variationCache.touch(theCacheEntry);
1105            }
1106
1107            if (wasAdded) {
1108                theCacheEntry.setVariationData(variation, m);
1109                m.put(variation, theCacheEntry);
1110            }
1111        } else {
1112            // No variation map for this resource yet, so create one
1113            CmsFlexCacheVariation list = new CmsFlexCacheVariation(key);
1114
1115            boolean wasAdded = m_variationCache.add(theCacheEntry);
1116
1117            if (wasAdded) {
1118                theCacheEntry.setVariationData(variation, list.m_map);
1119                list.m_map.put(variation, theCacheEntry);
1120                m_keyCache.put(key.getResource(), list);
1121            }
1122        }
1123
1124        if (LOG.isDebugEnabled()) {
1125            LOG.debug(
1126                Messages.get().getBundle().key(
1127                    Messages.LOG_FLEXCACHE_ADDED_ENTRY_FOR_RESOURCE_WITH_VARIATION_3,
1128                    new Integer(m_size),
1129                    key.getResource(),
1130                    variation));
1131            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADDED_ENTRY_1, theCacheEntry.toString()));
1132        }
1133    }
1134}