001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (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, 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.file.CmsObject;
033import org.opencms.loader.CmsJspLoader;
034import org.opencms.main.CmsLog;
035import org.opencms.main.I_CmsEventListener;
036import org.opencms.main.OpenCms;
037import org.opencms.security.CmsRole;
038import org.opencms.util.CmsCollectionsGenericWrapper;
039import org.opencms.util.CmsStringUtil;
040
041import java.util.Collection;
042import java.util.Collections;
043import java.util.HashSet;
044import java.util.Hashtable;
045import java.util.Iterator;
046import java.util.Map;
047import java.util.Set;
048
049import org.apache.commons.collections.map.LRUMap;
050import org.apache.commons.logging.Log;
051
052/**
053 * This class implements the FlexCache.<p>
054 *
055 * The data structure used is a two-level hashtable.
056 * This is optimized for the structure of the keys that are used to describe the
057 * caching behaviour of the entries.
058 * The first hash-level is calculated from the resource name, i.e. the
059 * name of the resource as it is referred to in the VFS of OpenCms.
060 * The second hash-level is calculated from the cache-key of the resource,
061 * which also is a String representing the specifc variation of the cached entry.<p>
062 *
063 * A suffix [online] or [offline] is appended to te resource name
064 * to distinguish between the online and offline projects of OpenCms.
065 * Also, for support of JSP based workplace pages, a suffix [workplace]
066 * is appended. The same cached workplace pages are used both in the online and
067 * all offline projects.<p>
068 *
069 * Entries in the first level of the cache are of type CmsFlexCacheVariation,
070 * which is a sub-class of CmsFlexCache.
071 * This class is a simple data type that contains of a Map of CmsFlexCacheEntries,
072 * with variations - Strings as keys.<p>
073 *
074 * Here's a short summary of used terms:
075 * <ul>
076 * <li><b>key:</b>
077 * A combination of a resource name and a variation.
078 * The data structure used is CmsFlexCacheKey.
079 * <li><b>resource:</b>
080 * A String with the resource name and an appended [online] of [offline] suffix.
081 * <li><b>variation:</b>
082 * A String describing a variation of a cached entry in the CmsFlexCache language.
083 * <li><b>entry:</b>
084 * A CmsFlexCacheEntry data structure which is describes a cached OpenCms resource.
085 * For every entry a key is saved which contains the resource name and the variation.
086 * </ul>
087 *
088 * Cache clearing is handled using events.
089 * The cache is fully flushed if an event {@link I_CmsEventListener#EVENT_PUBLISH_PROJECT}
090 * or {@link I_CmsEventListener#EVENT_CLEAR_CACHES} is caught.<p>
091 *
092 * @since 6.0.0
093 *
094 * @see org.opencms.flex.CmsFlexCacheKey
095 * @see org.opencms.flex.CmsFlexCacheEntry
096 * @see org.opencms.cache.CmsLruCache
097 * @see org.opencms.cache.I_CmsLruCacheObject
098 */
099public class CmsFlexCache extends Object implements I_CmsEventListener {
100
101    /**
102     * A simple data container class for the FlexCache variations.<p>
103     */
104    public static class CmsFlexCacheVariation extends Object {
105
106        /** The key belonging to the resource. */
107        public CmsFlexCacheKey m_key;
108
109        /** Maps variations to CmsFlexCacheEntries. */
110        public Map<String, I_CmsLruCacheObject> m_map;
111
112        /**
113         * Generates a new instance of CmsFlexCacheVariation.<p>
114         *
115         * @param theKey The (resource) key to contruct this variation list for
116         */
117        public CmsFlexCacheVariation(CmsFlexCacheKey theKey) {
118
119            m_key = theKey;
120            m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS);
121        }
122    }
123
124    /**
125     * Extended LRUMap that handles the variations in case a key is removed.<p>
126     */
127    class CmsFlexKeyMap extends LRUMap {
128
129        /** Serial version UID required for safe serialization. */
130        private static final long serialVersionUID = 6931995916013396902L;
131
132        /**
133         * Initialize the map with the given size.<p>
134         *
135         * @param maxSize the maximum number of key to cache
136         */
137        public CmsFlexKeyMap(int maxSize) {
138
139            super(maxSize);
140        }
141
142        /**
143         * Ensures that all variations that referenced by this key are released
144         * if the key is released.<p>
145         *
146         * @param entry the entry to remove
147         *
148         * @return <code>true</code> to actually delete the entry
149         *
150         * @see LRUMap#removeLRU(LinkEntry)
151         */
152        @Override
153        protected boolean removeLRU(LinkEntry entry) {
154
155            CmsFlexCacheVariation v = (CmsFlexCacheVariation)entry.getValue();
156            if (v == null) {
157                return true;
158            }
159            Map<String, I_CmsLruCacheObject> m = v.m_map;
160            if ((m == null) || (m.size() == 0)) {
161                return true;
162            }
163            Collection<I_CmsLruCacheObject> entries = m.values();
164            synchronized (m_variationCache) {
165                for (I_CmsLruCacheObject e : entries) {
166                    m_variationCache.remove(e);
167                }
168                v.m_map.clear();
169                v.m_map = null;
170                v.m_key = null;
171            }
172            return true;
173        }
174    }
175
176    /** Suffix to append to online cache entries. */
177    public static final String CACHE_OFFLINESUFFIX = " [offline]";
178
179    /** Suffix to append to online cache entries. */
180    public static final String CACHE_ONLINESUFFIX = " [online]";
181
182    /** Trigger for clearcache event: Clear complete cache. */
183    public static final int CLEAR_ALL = 0;
184
185    /** Trigger for clearcache event: Clear only entries. */
186    public static final int CLEAR_ENTRIES = 1;
187
188    /** Trigger for clearcache event: Clear complete offine cache. */
189    public static final int CLEAR_OFFLINE_ALL = 4;
190
191    /** Trigger for clearcache event: Clear only offline entries. */
192    public static final int CLEAR_OFFLINE_ENTRIES = 5;
193
194    /** Trigger for clearcache event: Clear complete online cache. */
195    public static final int CLEAR_ONLINE_ALL = 2;
196
197    /** Trigger for clearcache event: Clear only online entries. */
198    public static final int CLEAR_ONLINE_ENTRIES = 3;
199
200    /** Initial cache size, this should be a power of 2 because of the Java collections implementation. */
201    public static final int INITIAL_CAPACITY_CACHE = 512;
202
203    /** Initial size for variation lists, should be a power of 2. */
204    public static final int INITIAL_CAPACITY_VARIATIONS = 8;
205
206    /** Offline repository constant. */
207    public static final String REPOSITORY_OFFLINE = "offline";
208
209    /** Online repository constant. */
210    public static final String REPOSITORY_ONLINE = "online";
211
212    /** The log object for this class. */
213    private static final Log LOG = CmsLog.getLog(CmsFlexCache.class);
214
215    /** The LRU cache to organize the cached entries. */
216    protected CmsLruCache m_variationCache;
217
218    /** Indicates if offline resources should be cached or not. */
219    private boolean m_cacheOffline;
220
221    /** Indicates if the cache is enabled or not. */
222    private boolean m_enabled;
223
224    /** Map to store the entries for fast lookup. */
225    private Map<String, CmsFlexCacheVariation> m_keyCache;
226
227    /** Counter for the size. */
228    private int m_size;
229
230    /**
231     * Constructor for class CmsFlexCache.<p>
232     *
233     * The parameter "enabled" is used to control if the cache is
234     * actually on or off. Even if you don't need the cache, you still
235     * have to create an instance of it with enabled=false.
236     * This is because you need some of the FlexCache data structures
237     * for JSP inclusion buffering.<p>
238     *
239     * @param configuration the flex cache configuration
240     */
241    public CmsFlexCache(CmsFlexCacheConfiguration configuration) {
242
243        m_enabled = configuration.isCacheEnabled();
244        m_cacheOffline = configuration.isCacheOffline();
245
246        long maxCacheBytes = configuration.getMaxCacheBytes();
247        long avgCacheBytes = configuration.getAvgCacheBytes();
248        int maxEntryBytes = configuration.getMaxEntryBytes();
249        int maxKeys = configuration.getMaxKeys();
250
251        m_variationCache = new CmsLruCache(maxCacheBytes, avgCacheBytes, maxEntryBytes);
252        OpenCms.getMemoryMonitor().register(getClass().getName() + ".m_entryLruCache", m_variationCache);
253
254        if (m_enabled) {
255            CmsFlexKeyMap flexKeyMap = new CmsFlexKeyMap(maxKeys);
256            m_keyCache = Collections.synchronizedMap(
257                CmsCollectionsGenericWrapper.<String, CmsFlexCacheVariation> map(flexKeyMap));
258            OpenCms.getMemoryMonitor().register(getClass().getName() + ".m_resourceMap", flexKeyMap);
259
260            OpenCms.addCmsEventListener(
261                this,
262                new int[] {
263                    I_CmsEventListener.EVENT_PUBLISH_PROJECT,
264                    I_CmsEventListener.EVENT_CLEAR_CACHES,
265                    I_CmsEventListener.EVENT_FLEX_PURGE_JSP_REPOSITORY,
266                    I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR});
267        }
268        
269        if (LOG.isInfoEnabled()) {
270            LOG.info(
271                Messages.get().getBundle().key(
272                    Messages.INIT_FLEXCACHE_CREATED_2,
273                    Boolean.valueOf(m_enabled),
274                    Boolean.valueOf(m_cacheOffline)));
275        }
276    }
277
278    /**
279     * Indicates if offline project resources are cached.<p>
280     *
281     * @return true if offline projects are cached, false if not
282     */
283    public boolean cacheOffline() {
284
285        return m_cacheOffline;
286    }
287
288    /**
289     * Implements the CmsEvent interface,
290     * the FlexCache uses the events to clear itself in case a project is published.<p>
291     *
292     * @param event CmsEvent that has occurred
293     */
294    public void cmsEvent(org.opencms.main.CmsEvent event) {
295
296        if (!isEnabled()) {
297            return;
298        }
299
300        switch (event.getType()) {
301            case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
302            case I_CmsEventListener.EVENT_CLEAR_CACHES:
303                if (LOG.isDebugEnabled()) {
304                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_CLEAR_CACHE_0));
305                }
306                clear();
307                break;
308            case I_CmsEventListener.EVENT_FLEX_PURGE_JSP_REPOSITORY:
309                if (LOG.isDebugEnabled()) {
310                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_PURGE_REPOSITORY_0));
311                }
312                purgeJspRepository();
313                break;
314            case I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR:
315                if (LOG.isDebugEnabled()) {
316                    LOG.debug(
317                        Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_CLEAR_CACHE_PARTIALLY_0));
318                }
319                Map<String, ?> m = event.getData();
320                if (m == null) {
321                    break;
322                }
323                Integer it = null;
324                try {
325                    it = (Integer)m.get("action");
326                } catch (Exception e) {
327                    // it will be null
328                }
329                if (it == null) {
330                    break;
331                }
332                int i = it.intValue();
333                switch (i) {
334                    case CLEAR_ALL:
335                        clear();
336                        break;
337                    case CLEAR_ENTRIES:
338                        clearEntries();
339                        break;
340                    case CLEAR_ONLINE_ALL:
341                        clearOnline();
342                        break;
343                    case CLEAR_ONLINE_ENTRIES:
344                        clearOnlineEntries();
345                        break;
346                    case CLEAR_OFFLINE_ALL:
347                        clearOffline();
348                        break;
349                    case CLEAR_OFFLINE_ENTRIES:
350                        clearOfflineEntries();
351                        break;
352                    default:
353                        // no operation
354                }
355                break;
356            default:
357                // no operation
358        }
359    }
360
361    /**
362     * Returns the CmsFlexCacheKey data structure for a given
363     * key (i.e. resource name).<p>
364     *
365     * Useful if you want to show the cache key for a resources,
366     * like on the FlexCache administration page.<p>
367     *
368     * Only users with administrator permissions are allowed
369     * to perform this operation.<p>
370     *
371     * @param key the resource name for which to look up the variation for
372     * @param cms the CmsObject used for user authorization
373     * @return the CmsFlexCacheKey data structure found for the resource
374     */
375    public CmsFlexCacheKey getCachedKey(String key, CmsObject cms) {
376
377        if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
378            return null;
379        }
380        Object o = m_keyCache.get(key);
381        if (o != null) {
382            return ((CmsFlexCacheVariation)o).m_key;
383        }
384        return null;
385    }
386
387    /**
388     * Returns a set of all cached resource names.<p>
389     *
390     * Useful if you want to show a list of all cached resources,
391     * like on the FlexCache administration page.<p>
392     *
393     * Only users with administrator permissions are allowed
394     * to perform this operation.<p>
395     *
396     * @param cms the CmsObject used for user authorization
397     * @return a Set of cached resource names (which are of type String)
398     */
399    public Set<String> getCachedResources(CmsObject cms) {
400
401        if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
402            return null;
403        }
404        return m_keyCache.keySet();
405    }
406
407    /**
408     * Returns all variations in the cache for a given resource name.
409     * The variations are of type String.<p>
410     *
411     * Useful if you want to show a list of all cached entry - variations,
412     * like on the FlexCache administration page.<p>
413     *
414     * Only users with administrator permissions are allowed
415     * to perform this operation.<p>
416     *
417     * @param key the resource name for which to look up the variations for
418     * @param cms the CmsObject used for user authorization
419     * @return a Set of cached variations (which are of type String)
420     */
421    public Set<String> getCachedVariations(String key, CmsObject cms) {
422
423        if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
424            return null;
425        }
426        Object o = m_keyCache.get(key);
427        if (o != null) {
428            return ((CmsFlexCacheVariation)o).m_map.keySet();
429        }
430        return null;
431    }
432
433    /**
434     * Returns the LRU cache where the CacheEntries are cached.<p>
435     *
436     * @return the LRU cache where the CacheEntries are cached
437     */
438    public CmsLruCache getEntryLruCache() {
439
440        return m_variationCache;
441    }
442
443    /**
444     * Indicates if the cache is enabled (i.e. actually
445     * caching entries) or not.<p>
446     *
447     * @return true if the cache is enabled, false if not
448     */
449    public boolean isEnabled() {
450
451        return m_enabled;
452    }
453
454    /**
455     * Returns the total number of cached resource keys.
456     *
457     * @return the number of resource keys in the cache
458     */
459    public int keySize() {
460
461        if (!isEnabled()) {
462            return 0;
463        }
464        return m_keyCache.size();
465    }
466
467    /**
468     * Returns the total number of entries in the cache.<p>
469     *
470     * @return the number of entries in the cache
471     */
472    public int size() {
473
474        return m_variationCache.size();
475    }
476
477    /**
478     * Looks up a specific entry in the cache.<p>
479     *
480     * In case a found entry has a timeout set, it will be checked upon lookup.
481     * In case the timeout of the entry has been reached, it will be removed from
482     * the cache (and null will be returned in this case).<p>
483     *
484     * @param key The key to look for in the cache
485     * @return the entry found for the key, or null if key is not in the cache
486     */
487    CmsFlexCacheEntry get(CmsFlexRequestKey key) {
488
489        if (!isEnabled()) {
490            // cache is disabled
491            return null;
492        }
493        Object o = m_keyCache.get(key.getResource());
494        if (o != null) {
495            // found a matching key in the cache
496            CmsFlexCacheVariation v = (CmsFlexCacheVariation)o;
497            String variation = v.m_key.matchRequestKey(key);
498
499            if (CmsStringUtil.isEmpty(variation)) {
500                // requested resource is not cacheable
501                return null;
502            }
503            CmsFlexCacheEntry entry = (CmsFlexCacheEntry)v.m_map.get(variation);
504            if (entry == null) {
505                // no cache entry available for variation
506                return null;
507            }
508            if (entry.getDateExpires() < System.currentTimeMillis()) {
509                // cache entry avaiable but expired, remove entry
510                m_variationCache.remove(entry);
511                return null;
512            }
513            // return the found cache entry
514            return entry;
515        } else {
516            return null;
517        }
518    }
519
520    /**
521     * Returns the CmsFlexCacheKey data structure for a given resource name.<p>
522     *
523     * @param resource the resource name for which to look up the key for
524     * @return the CmsFlexCacheKey data structure found for the resource
525     */
526    CmsFlexCacheKey getKey(String resource) {
527
528        if (!isEnabled()) {
529            return null;
530        }
531        Object o = m_keyCache.get(resource);
532        if (o != null) {
533            if (LOG.isDebugEnabled()) {
534                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_FOUND_1, resource));
535            }
536            return ((CmsFlexCacheVariation)o).m_key;
537        } else {
538            if (LOG.isDebugEnabled()) {
539                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_NOT_FOUND_1, resource));
540            }
541            return null;
542        }
543    }
544
545    /**
546     * Checks if the cache is empty or if at last one element is contained.<p>
547     *
548     * @return true if the cache is empty, false otherwise
549     */
550    boolean isEmpty() {
551
552        if (!isEnabled()) {
553            return true;
554        }
555        return m_keyCache.isEmpty();
556    }
557
558    /**
559     * This method adds new entries to the cache.<p>
560     *
561     * The key describes the conditions under which the value can be cached.
562     * Usually the key belongs to the response.
563     * The variation describes the conditions under which the
564     * entry was created. This is usually calculated from the request.
565     * If the variation is != null, the entry is cachable.<p>
566     *
567     * @param key the key for the new value entry
568     * @param entry the CmsFlexCacheEntry to store in the cache
569     * @param variation the pre-calculated variation for the entry
570     * @return true if the value was added to the cache, false otherwise
571     */
572    boolean put(CmsFlexCacheKey key, CmsFlexCacheEntry entry, String variation) {
573
574        if (!isEnabled()) {
575            return false;
576        }
577        if (LOG.isDebugEnabled()) {
578            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_ENTRY_1, key.getResource()));
579        }
580        if (variation != null) {
581            // This is a cachable result
582            key.setVariation(variation);
583            if (LOG.isDebugEnabled()) {
584                LOG.debug(
585                    Messages.get().getBundle().key(
586                        Messages.LOG_FLEXCACHE_ADD_ENTRY_WITH_VARIATION_2,
587                        key.getResource(),
588                        key.getVariation()));
589            }
590            put(key, entry);
591            // Note that duplicates are NOT checked, it it assumed that this is done beforehand,
592            // while checking if the entry is already in the cache or not.
593            return true;
594        } else {
595            // Result is not cachable
596            if (LOG.isDebugEnabled()) {
597                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RESOURCE_NOT_CACHEABLE_0));
598            }
599            return false;
600        }
601    }
602
603    /**
604     * Adds a key with a new, empty variation map to the cache.<p>
605     *
606     * @param key the key to add to the cache.
607     */
608    void putKey(CmsFlexCacheKey key) {
609
610        if (!isEnabled()) {
611            return;
612        }
613        Object o = m_keyCache.get(key.getResource());
614        if (o == null) {
615            // No variation map for this resource yet, so create one
616            CmsFlexCacheVariation variationMap = new CmsFlexCacheVariation(key);
617            m_keyCache.put(key.getResource(), variationMap);
618            if (LOG.isDebugEnabled()) {
619                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_KEY_1, key.getResource()));
620            }
621        }
622        // If != null the key is already in the cache, so we just do nothing
623    }
624
625    /**
626     * Removes an entry from the cache.<p>
627     *
628     * @param key the key which describes the entry to remove from the cache
629     */
630    void remove(CmsFlexCacheKey key) {
631
632        if (!isEnabled()) {
633            return;
634        }
635        CmsFlexCacheVariation o = m_keyCache.get(key.getResource());
636        if (o != null) {
637            I_CmsLruCacheObject old = o.m_map.get(key.getVariation());
638            if (old != null) {
639                getEntryLruCache().remove(old);
640            }
641        }
642    }
643
644    /**
645     * Empties the cache completely.<p>
646     */
647    private synchronized void clear() {
648
649        if (!isEnabled()) {
650            return;
651        }
652        m_keyCache.clear();
653        m_size = 0;
654
655        m_variationCache.clear();
656
657        if (LOG.isInfoEnabled()) {
658            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_0));
659        }
660    }
661
662    /**
663     * Internal method to perform cache clearance.<p>
664     *
665     * It clears "one half" of the cache, i.e. either
666     * the online or the offline parts.
667     * A parameter is used to indicate if only
668     * the entries or keys and entries are to be cleared.<p>
669     *
670     * @param suffix used to distinguish between "[Online]" and "[Offline]" entries
671     * @param entriesOnly if <code>true</code>, only entries will be cleared, otherwise
672     *         the entries and the keys will be cleared
673     */
674    private synchronized void clearAccordingToSuffix(String suffix, boolean entriesOnly) {
675
676        Set<String> keys = new HashSet<String>(m_keyCache.keySet());
677        Iterator<String> i = keys.iterator();
678        while (i.hasNext()) {
679            String s = i.next();
680            if (s.endsWith(suffix)) {
681                CmsFlexCacheVariation v = m_keyCache.get(s);
682                if (entriesOnly) {
683                    // Clear only entry
684                    m_size -= v.m_map.size();
685                    Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator();
686                    while (allEntries.hasNext()) {
687                        I_CmsLruCacheObject nextObject = allEntries.next();
688                        allEntries.remove();
689                        m_variationCache.remove(nextObject);
690                    }
691                    v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS);
692                } else {
693                    // Clear key and entry
694                    m_size -= v.m_map.size();
695                    Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator();
696                    while (allEntries.hasNext()) {
697                        I_CmsLruCacheObject nextObject = allEntries.next();
698                        allEntries.remove();
699                        m_variationCache.remove(nextObject);
700                    }
701
702                    v.m_map = null;
703                    v.m_key = null;
704                    m_keyCache.remove(s);
705                }
706            }
707        }
708        if (LOG.isInfoEnabled()) {
709            LOG.info(
710                Messages.get().getBundle().key(
711                    Messages.LOG_FLEXCACHE_CLEAR_HALF_2,
712                    suffix,
713                    Boolean.valueOf(entriesOnly)));
714        }
715    }
716
717    /**
718     * Clears all entries in the cache, online or offline.<p>
719     *
720     * The keys are not cleared.<p>
721     *
722     * Only users with administrator permissions are allowed
723     * to perform this operation.<p>
724     */
725    private synchronized void clearEntries() {
726
727        if (!isEnabled()) {
728            return;
729        }
730        if (LOG.isInfoEnabled()) {
731            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ALL_0));
732        }
733        // create new set to avoid ConcurrentModificationExceptions
734        Set<String> cacheKeys = new HashSet<String>(m_keyCache.keySet());
735        Iterator<String> i = cacheKeys.iterator();
736        while (i.hasNext()) {
737            CmsFlexCacheVariation v = m_keyCache.get(i.next());
738            Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator();
739            while (allEntries.hasNext()) {
740                I_CmsLruCacheObject nextObject = allEntries.next();
741                allEntries.remove();
742                m_variationCache.remove(nextObject);
743            }
744            v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS);
745        }
746        m_size = 0;
747    }
748
749    /**
750     * Clears all entries and all keys from offline projects in the cache.<p>
751     *
752     * Cached resources from the online project are not touched.<p>
753     *
754     * Only users with administrator permissions are allowed
755     * to perform this operation.<p>
756     */
757    private void clearOffline() {
758
759        if (!isEnabled()) {
760            return;
761        }
762        if (LOG.isInfoEnabled()) {
763            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_KEYS_AND_ENTRIES_0));
764        }
765        clearAccordingToSuffix(CACHE_OFFLINESUFFIX, false);
766    }
767
768    /**
769     * Clears all entries from offline projects in the cache.<p>
770     *
771     * The keys from the offline projects are not cleared.
772     * Cached resources from the online project are not touched.<p>
773     *
774     * Only users with administrator permissions are allowed
775     * to perform this operation.<p>
776     */
777    private void clearOfflineEntries() {
778
779        if (!isEnabled()) {
780            return;
781        }
782        if (LOG.isInfoEnabled()) {
783            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_OFFLINE_ENTRIES_0));
784        }
785        clearAccordingToSuffix(CACHE_OFFLINESUFFIX, true);
786    }
787
788    /**
789     * Clears all entries and all keys from the online project in the cache.<p>
790     *
791     * Cached resources from the offline projects are not touched.<p>
792     *
793     * Only users with administrator permissions are allowed
794     * to perform this operation.<p>
795     */
796    private void clearOnline() {
797
798        if (!isEnabled()) {
799            return;
800        }
801        if (LOG.isInfoEnabled()) {
802            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_KEYS_AND_ENTRIES_0));
803        }
804        clearAccordingToSuffix(CACHE_ONLINESUFFIX, false);
805    }
806
807    /**
808     * Clears all entries from the online project in the cache.<p>
809     *
810     * The keys from the online project are not cleared.
811     * Cached resources from the offline projects are not touched.<p>
812     *
813     * Only users with administrator permissions are allowed
814     * to perform this operation.<p>
815     */
816    private void clearOnlineEntries() {
817
818        if (!isEnabled()) {
819            return;
820        }
821        if (LOG.isInfoEnabled()) {
822            LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_ENTRIES_0));
823        }
824        clearAccordingToSuffix(CACHE_ONLINESUFFIX, true);
825    }
826
827    /**
828     * This method purges the JSP repository dirs,
829     * i.e. it deletes all JSP files that OpenCms has written to the
830     * real FS.<p>
831     *
832     * Obviously this method must be used with caution.
833     * Purpose of this method is to allow
834     * a complete purge of all JSP pages on a machine after
835     * a major update of JSP templates was made.<p>
836     */
837    private synchronized void purgeJspRepository() {
838
839        CmsJspLoader cmsJspLoader = (CmsJspLoader)OpenCms.getResourceManager().getLoader(
840            CmsJspLoader.RESOURCE_LOADER_ID);
841
842        cmsJspLoader.triggerPurge(new Runnable() {
843
844            @SuppressWarnings("synthetic-access")
845            public void run() {
846
847                clear();
848            }
849        });
850    }
851
852    /**
853     * Save a value to the cache.<p>
854     *
855     * @param key the key under which the value is saved
856     * @param theCacheEntry the entry to cache
857     */
858    private void put(CmsFlexCacheKey key, CmsFlexCacheEntry theCacheEntry) {
859
860        CmsFlexCacheVariation o = m_keyCache.get(key.getResource());
861        if (key.getTimeout() > 0) {
862            theCacheEntry.setDateExpiresToNextTimeout(key.getTimeout());
863        }
864        if (o != null) {
865            // We already have a variation map for this resource
866            Map<String, I_CmsLruCacheObject> m = o.m_map;
867            boolean wasAdded = true;
868            if (!m.containsKey(key.getVariation())) {
869                wasAdded = m_variationCache.add(theCacheEntry);
870            } else {
871                wasAdded = m_variationCache.touch(theCacheEntry);
872            }
873
874            if (wasAdded) {
875                theCacheEntry.setVariationData(key.getVariation(), m);
876                m.put(key.getVariation(), theCacheEntry);
877            }
878        } else {
879            // No variation map for this resource yet, so create one
880            CmsFlexCacheVariation list = new CmsFlexCacheVariation(key);
881
882            boolean wasAdded = m_variationCache.add(theCacheEntry);
883
884            if (wasAdded) {
885                theCacheEntry.setVariationData(key.getVariation(), list.m_map);
886                list.m_map.put(key.getVariation(), theCacheEntry);
887                m_keyCache.put(key.getResource(), list);
888            }
889        }
890
891        if (LOG.isDebugEnabled()) {
892            LOG.debug(
893                Messages.get().getBundle().key(
894                    Messages.LOG_FLEXCACHE_ADDED_ENTRY_FOR_RESOURCE_WITH_VARIATION_3,
895                    new Integer(m_size),
896                    key.getResource(),
897                    key.getVariation()));
898            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADDED_ENTRY_1, theCacheEntry.toString()));
899        }
900    }
901}