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