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, 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.xml.containerpage;
029
030import org.opencms.cache.CmsVfsCache;
031import org.opencms.file.CmsResource;
032import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
033import org.opencms.main.CmsLog;
034import org.opencms.monitor.CmsMemoryMonitor;
035import org.opencms.util.CmsUUID;
036import org.opencms.xml.content.CmsXmlContent;
037
038import java.util.Iterator;
039import java.util.Map;
040import java.util.concurrent.locks.ReadWriteLock;
041import java.util.concurrent.locks.ReentrantReadWriteLock;
042
043import org.apache.commons.logging.Log;
044
045/**
046 * Cache object instance for simultaneously cache online and offline items.<p>
047 *
048 * @since 7.6
049 */
050public final class CmsADECache extends CmsVfsCache {
051
052    /** The log to use (static for performance reasons).<p> */
053    private static final Log LOG = CmsLog.getLog(CmsADECache.class);
054
055    /** Cache for offline container pages. */
056    private Map<String, CmsXmlContainerPage> m_containerPagesOffline;
057
058    /** Cache for online container pages. */
059    private Map<String, CmsXmlContainerPage> m_containerPagesOnline;
060
061    /** Cache for offline group containers. */
062    private Map<String, CmsXmlGroupContainer> m_groupContainersOffline;
063
064    /** Cache for online group containers. */
065    private Map<String, CmsXmlGroupContainer> m_groupContainersOnline;
066
067    /** Read-write lock to ensure that the cache maps aren't accessed while we iterate through them to remove invalid entries. */
068    private ReadWriteLock m_lock = new ReentrantReadWriteLock(true);
069
070    /**
071     * Initializes the cache. Only intended to be called during startup.<p>
072     *
073     * @param memMonitor the memory monitor instance
074     * @param cacheSettings the system cache settings
075     *
076     * @see org.opencms.main.OpenCmsCore#initConfiguration
077     */
078    public CmsADECache(CmsMemoryMonitor memMonitor, CmsADECacheSettings cacheSettings) {
079
080        initialize(memMonitor, cacheSettings);
081        registerEventListener();
082    }
083
084    /**
085     * Flushes the container pages cache.<p>
086     *
087     * @param online if to flush the online or offline cache
088     */
089    public void flushContainerPages(boolean online) {
090
091        try {
092            m_lock.writeLock().lock();
093            if (online) {
094                m_containerPagesOnline.clear();
095            } else {
096                m_containerPagesOffline.clear();
097            }
098        } finally {
099            m_lock.writeLock().unlock();
100        }
101    }
102
103    /**
104     * Flushes the group containers cache.<p>
105     *
106     * @param online if to flush the online or offline cache
107     */
108    public void flushGroupContainers(boolean online) {
109
110        try {
111            m_lock.writeLock().lock();
112            if (online) {
113                m_groupContainersOnline.clear();
114            } else {
115                m_groupContainersOffline.clear();
116            }
117        } finally {
118            m_lock.writeLock().unlock();
119        }
120    }
121
122    /**
123     * Returns the cached container page under the given key and for the given project.<p>
124     *
125     * @param key the cache key
126     * @param online if cached in online or offline project
127     *
128     * @return the cached container page or <code>null</code> if not found
129     */
130    public CmsXmlContainerPage getCacheContainerPage(String key, boolean online) {
131
132        try {
133            m_lock.readLock().lock();
134            CmsXmlContainerPage retValue;
135            if (online) {
136                retValue = m_containerPagesOnline.get(key);
137                if (LOG.isDebugEnabled()) {
138                    if (retValue == null) {
139                        LOG.debug(
140                            Messages.get().getBundle().key(
141                                Messages.LOG_DEBUG_CACHE_MISSED_ONLINE_1,
142                                new Object[] {key}));
143
144                    } else {
145                        LOG.debug(
146                            Messages.get().getBundle().key(
147                                Messages.LOG_DEBUG_CACHE_MATCHED_ONLINE_2,
148                                new Object[] {key, retValue}));
149                    }
150                }
151            } else {
152                retValue = m_containerPagesOffline.get(key);
153                if (LOG.isDebugEnabled()) {
154                    if (retValue == null) {
155                        LOG.debug(
156                            Messages.get().getBundle().key(
157                                Messages.LOG_DEBUG_CACHE_MISSED_OFFLINE_1,
158                                new Object[] {key}));
159
160                    } else {
161                        LOG.debug(
162                            Messages.get().getBundle().key(
163                                Messages.LOG_DEBUG_CACHE_MATCHED_OFFLINE_2,
164                                new Object[] {key, retValue}));
165                    }
166                }
167            }
168            if (retValue != null) {
169                //System.out.println("got cached page: " + retValue.getFile().getRootPath());
170            }
171            return retValue;
172        } finally {
173            m_lock.readLock().unlock();
174        }
175    }
176
177    /**
178     * Returns the cached group container under the given key and for the given project.<p>
179     *
180     * @param key the cache key
181     * @param online if cached in online or offline project
182     *
183     * @return the cached group container or <code>null</code> if not found
184     */
185    public CmsXmlGroupContainer getCacheGroupContainer(String key, boolean online) {
186
187        try {
188            m_lock.readLock().lock();
189            CmsXmlGroupContainer retValue;
190            if (online) {
191                retValue = m_groupContainersOnline.get(key);
192                if (LOG.isDebugEnabled()) {
193                    if (retValue == null) {
194                        LOG.debug(
195                            Messages.get().getBundle().key(
196                                Messages.LOG_DEBUG_CACHE_MISSED_ONLINE_1,
197                                new Object[] {key}));
198
199                    } else {
200                        LOG.debug(
201                            Messages.get().getBundle().key(
202                                Messages.LOG_DEBUG_CACHE_MATCHED_ONLINE_2,
203                                new Object[] {key, retValue}));
204                    }
205                }
206            } else {
207                retValue = m_groupContainersOffline.get(key);
208                if (LOG.isDebugEnabled()) {
209                    if (retValue == null) {
210                        LOG.debug(
211                            Messages.get().getBundle().key(
212                                Messages.LOG_DEBUG_CACHE_MISSED_OFFLINE_1,
213                                new Object[] {key}));
214
215                    } else {
216                        LOG.debug(
217                            Messages.get().getBundle().key(
218                                Messages.LOG_DEBUG_CACHE_MATCHED_OFFLINE_2,
219                                new Object[] {key, retValue}));
220                    }
221                }
222            }
223            return retValue;
224        } finally {
225            m_lock.readLock().unlock();
226        }
227    }
228
229    /**
230     * Returns the cache key for the given parameters.<p>
231     *
232     * @param structureId the container page's structure id
233     * @param keepEncoding if to keep the encoding while unmarshalling
234     *
235     * @return the cache key for the given container page and parameters
236     */
237    public String getCacheKey(CmsUUID structureId, boolean keepEncoding) {
238
239        return structureId.toString() + "_" + keepEncoding;
240    }
241
242    /**
243     * Caches the given container page under the given key and for the given project.<p>
244     *
245     * @param key the cache key
246     * @param containerPage the object to cache
247     * @param online if to cache in online or offline project
248     */
249    public void setCacheContainerPage(String key, CmsXmlContainerPage containerPage, boolean online) {
250
251        try {
252            m_lock.writeLock().lock();
253            //System.out.println("caching page:" + containerPage.getFile().getRootPath());
254
255            if (online) {
256                m_containerPagesOnline.put(key, containerPage);
257                if (LOG.isDebugEnabled()) {
258                    LOG.debug(
259                        Messages.get().getBundle().key(
260                            Messages.LOG_DEBUG_CACHE_SET_ONLINE_2,
261                            new Object[] {key, containerPage}));
262                }
263            } else {
264                m_containerPagesOffline.put(key, containerPage);
265                if (LOG.isDebugEnabled()) {
266                    LOG.debug(
267                        Messages.get().getBundle().key(
268                            Messages.LOG_DEBUG_CACHE_SET_OFFLINE_2,
269                            new Object[] {key, containerPage}));
270                }
271            }
272        } finally {
273            m_lock.writeLock().unlock();
274        }
275    }
276
277    /**
278     * Caches the given group container under the given key and for the given project.<p>
279     *
280     * @param key the cache key
281     * @param groupContainer the object to cache
282     * @param online if to cache in online or offline project
283     */
284    public void setCacheGroupContainer(String key, CmsXmlGroupContainer groupContainer, boolean online) {
285
286        try {
287            m_lock.writeLock().lock();
288            if (online) {
289                m_groupContainersOnline.put(key, groupContainer);
290                if (LOG.isDebugEnabled()) {
291                    LOG.debug(
292                        Messages.get().getBundle().key(
293                            Messages.LOG_DEBUG_CACHE_SET_ONLINE_2,
294                            new Object[] {key, groupContainer}));
295                }
296            } else {
297                m_groupContainersOffline.put(key, groupContainer);
298                if (LOG.isDebugEnabled()) {
299                    LOG.debug(
300                        Messages.get().getBundle().key(
301                            Messages.LOG_DEBUG_CACHE_SET_OFFLINE_2,
302                            new Object[] {key, groupContainer}));
303                }
304            }
305        } finally {
306            m_lock.writeLock().unlock();
307        }
308    }
309
310    /**
311     * Removes the container page identified by its structure id from the cache.<p>
312     *
313     * @param structureId the container page's structure id
314     * @param online if online or offline
315     */
316    public void uncacheContainerPage(CmsUUID structureId, boolean online) {
317
318        try {
319            m_lock.writeLock().lock();
320            if (online) {
321                m_containerPagesOnline.remove(getCacheKey(structureId, true));
322                m_containerPagesOnline.remove(getCacheKey(structureId, false));
323            } else {
324                m_containerPagesOffline.remove(getCacheKey(structureId, true));
325                m_containerPagesOffline.remove(getCacheKey(structureId, false));
326            }
327        } finally {
328            m_lock.writeLock().unlock();
329        }
330    }
331
332    /**
333     * Removes the group container identified by its structure id from the cache.<p>
334     *
335     * @param structureId the group container's structure id
336     * @param online if online or offline
337     */
338    public void uncacheGroupContainer(CmsUUID structureId, boolean online) {
339
340        try {
341            m_lock.writeLock().lock();
342            if (online) {
343                m_groupContainersOnline.remove(getCacheKey(structureId, true));
344                m_groupContainersOnline.remove(getCacheKey(structureId, false));
345            } else {
346                m_groupContainersOffline.remove(getCacheKey(structureId, true));
347                m_groupContainersOffline.remove(getCacheKey(structureId, false));
348            }
349        } finally {
350            m_lock.writeLock().unlock();
351        }
352    }
353
354    /**
355     * @see org.opencms.cache.CmsVfsCache#flush(boolean)
356     */
357    @Override
358    protected void flush(boolean online) {
359
360        try {
361            m_lock.writeLock().lock();
362            flushContainerPages(online);
363            flushGroupContainers(online);
364        } finally {
365            m_lock.writeLock().unlock();
366        }
367    }
368
369    /**
370     * @see org.opencms.cache.CmsVfsCache#uncacheResource(org.opencms.file.CmsResource)
371     */
372    @Override
373    protected void uncacheResource(CmsResource resource) {
374
375        try {
376            m_lock.writeLock().lock();
377            if (resource == null) {
378                LOG.warn(Messages.get().container(Messages.LOG_WARN_UNCACHE_NULL_0));
379                return;
380            }
381            if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
382                removeCachedContent(resource, m_containerPagesOffline);
383            } else {
384                removeCachedContent(resource, m_groupContainersOffline);
385            }
386        } finally {
387            m_lock.writeLock().unlock();
388        }
389    }
390
391    /**
392     * Initializes the caches.<p>
393     *
394     * @param memMonitor the memory monitor instance
395     * @param cacheSettings the system cache settings
396     */
397    private void initialize(CmsMemoryMonitor memMonitor, CmsADECacheSettings cacheSettings) {
398
399        // container page caches
400        m_containerPagesOffline = CmsMemoryMonitor.createLRUCacheMap(cacheSettings.getContainerPageOfflineSize());
401        memMonitor.register(CmsADECache.class.getName() + ".containerPagesOffline", m_containerPagesOffline);
402
403        m_containerPagesOnline = CmsMemoryMonitor.createLRUCacheMap(cacheSettings.getContainerPageOnlineSize());
404        memMonitor.register(CmsADECache.class.getName() + ".containerPagesOnline", m_containerPagesOnline);
405
406        // container page caches
407        m_groupContainersOffline = CmsMemoryMonitor.createLRUCacheMap(cacheSettings.getGroupContainerOfflineSize());
408        memMonitor.register(CmsADECache.class.getName() + ".groupContainersOffline", m_groupContainersOffline);
409
410        m_groupContainersOnline = CmsMemoryMonitor.createLRUCacheMap(cacheSettings.getGroupContainerOnlineSize());
411        memMonitor.register(CmsADECache.class.getName() + ".groupContainersOnline", m_groupContainersOnline);
412    }
413
414    /**
415     * Removes a cached XML content from the cache if it matches a given resource.<p>
416     *
417     * @param resource the resource for which the cached XML content should be removed
418     * @param cache the cache from which to remove the XML content
419     */
420    private <CONTENT extends CmsXmlContent> void removeCachedContent(CmsResource resource, Map<String, CONTENT> cache) {
421
422        Iterator<Map.Entry<String, CONTENT>> iterator = cache.entrySet().iterator();
423        while (iterator.hasNext()) {
424            Map.Entry<String, CONTENT> entry = iterator.next();
425            CONTENT content = entry.getValue();
426            CmsResource contentFile = content.getFile();
427            if (contentFile.getStructureId().equals(resource.getStructureId())
428                || contentFile.getResourceId().equals(resource.getResourceId())) {
429                iterator.remove();
430            }
431        }
432
433    }
434}