001/*
002 * File   : $Source$
003 * Date   : $Date$
004 * Version: $Revision$
005 *
006 * This library is part of OpenCms -
007 * the Open Source Content Management System
008 *
009 * Copyright (C) 2002 - 2011 Alkacon Software (http://www.alkacon.com)
010 *
011 * This library is free software; you can redistribute it and/or
012 * modify it under the terms of the GNU Lesser General Public
013 * License as published by the Free Software Foundation; either
014 * version 2.1 of the License, or (at your option) any later version.
015 *
016 * This library is distributed in the hope that it will be useful,
017 * but WITHOUT ANY WARRANTY; without even the implied warranty of
018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 * Lesser General Public License for more details.
020 *
021 * For further information about Alkacon Software, please see the
022 * company website: http://www.alkacon.com
023 *
024 * For further information about OpenCms, please see the
025 * project website: http://www.opencms.org
026 *
027 * You should have received a copy of the GNU Lesser General Public
028 * License along with this library; if not, write to the Free Software
029 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
030 */
031
032package org.opencms.ade.containerpage.inherited;
033
034import org.opencms.ade.configuration.CmsSynchronizedUpdateSet;
035import org.opencms.ade.configuration.I_CmsGlobalConfigurationCache;
036import org.opencms.db.CmsPublishedResource;
037import org.opencms.db.CmsResourceState;
038import org.opencms.file.CmsFile;
039import org.opencms.file.CmsObject;
040import org.opencms.file.CmsResource;
041import org.opencms.file.CmsResourceFilter;
042import org.opencms.file.CmsVfsResourceNotFoundException;
043import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.OpenCms;
047import org.opencms.util.CmsFileUtil;
048import org.opencms.util.CmsUUID;
049
050import java.util.ArrayList;
051import java.util.Collection;
052import java.util.List;
053import java.util.Map;
054import java.util.Set;
055import java.util.concurrent.ScheduledFuture;
056import java.util.concurrent.TimeUnit;
057
058import org.apache.commons.logging.Log;
059
060import com.google.common.collect.Maps;
061
062/**
063 * A cache class for storing inherited container configurations.<p>
064 */
065public class CmsContainerConfigurationCache implements I_CmsGlobalConfigurationCache {
066
067    /** Interval used to check for changes. */
068    public static final long UPDATE_INTERVAL_MILLIS = 500;
069
070    /** The standard file name for inherited container configurations. */
071    public static final String INHERITANCE_CONFIG_FILE_NAME = ".inherited";
072
073    /** UUID used to signal a cache clear. */
074    public static final CmsUUID UPDATE_ALL = CmsUUID.getNullUUID();
075
076    /** The logger instance for this class. */
077    public static final Log LOG = CmsLog.getLog(CmsContainerConfigurationCache.class);
078
079    /** A flag which indicates whether this cache is initialized. */
080    protected boolean m_initialized;
081
082    /** The CMS context used for this cache's VFS operations. */
083    private CmsObject m_cms;
084
085    /** The name of this cache, used for testing/debugging purposes. */
086    private String m_name;
087
088    /** The current cache state. */
089    private volatile CmsContainerConfigurationCacheState m_state = new CmsContainerConfigurationCacheState(
090        new ArrayList<CmsContainerConfigurationGroup>());
091
092    /** Future used to cancel an already scheduled task if initialize() is called again. */
093    private ScheduledFuture<?> m_taskFuture;
094
095    /** The set of IDs to update. */
096    private CmsSynchronizedUpdateSet<CmsUUID> m_updateSet = new CmsSynchronizedUpdateSet<CmsUUID>();
097
098    /**
099     * Creates a new cache instance for inherited containers.<p>
100     *
101     * @param cms the CMS context to use for VFS operations.
102     * @param name the name of the cache, for debugging/testing purposes
103     *
104     * @throws CmsException if something goes wrong
105     */
106    public CmsContainerConfigurationCache(CmsObject cms, String name)
107    throws CmsException {
108
109        m_cms = OpenCms.initCmsObject(cms);
110        m_name = name;
111    }
112
113    /**
114     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#clear()
115     */
116    public synchronized void clear() {
117
118        m_updateSet.add(UPDATE_ALL);
119    }
120
121    /**
122     * Processes all enqueued inheritance container updates.<p>
123     */
124    public synchronized void flushUpdates() {
125
126        Set<CmsUUID> updateIds = m_updateSet.removeAll();
127        if (!updateIds.isEmpty()) {
128            if (updateIds.contains(UPDATE_ALL)) {
129                initialize();
130            } else {
131                Map<CmsUUID, CmsContainerConfigurationGroup> groups = loadFromIds(updateIds);
132                CmsContainerConfigurationCacheState state = m_state.updateWithChangedGroups(groups);
133                m_state = state;
134            }
135        }
136    }
137
138    /**
139     * Gets the current cache contents.<p>
140     *
141     * @return the cache contents
142     */
143    public CmsContainerConfigurationCacheState getState() {
144
145        return m_state;
146    }
147
148    /**
149     * Initializes the cache.<p>
150     */
151    public synchronized void initialize() {
152
153        LOG.info("Initializing inheritance group cache: " + m_name);
154        if (m_taskFuture != null) {
155            // in case initialize has been called before on this object, cancel the existing task
156            m_taskFuture.cancel(false);
157            m_taskFuture = null;
158        }
159        if (m_cms.existsResource("/", CmsResourceFilter.IGNORE_EXPIRATION)) {
160            try {
161                List<CmsResource> resources = m_cms.readResources(
162                    "/",
163                    CmsResourceFilter.IGNORE_EXPIRATION.addRequireType(
164                        OpenCms.getResourceManager().getResourceType(
165                            CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME)),
166                    true);
167                m_state = new CmsContainerConfigurationCacheState(load(resources).values());
168            } catch (Exception e) {
169                LOG.error(e.getLocalizedMessage(), e);
170            }
171        }
172        Runnable updateAction = new Runnable() {
173
174            public void run() {
175
176                try {
177                    flushUpdates();
178                } catch (Throwable e) {
179                    LOG.error(e.getLocalizedMessage(), e);
180                }
181            }
182        };
183        m_taskFuture = OpenCms.getExecutor().scheduleWithFixedDelay(
184            updateAction,
185            UPDATE_INTERVAL_MILLIS,
186            UPDATE_INTERVAL_MILLIS,
187            TimeUnit.MILLISECONDS);
188    }
189
190    /**
191     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#remove(org.opencms.db.CmsPublishedResource)
192     */
193    public void remove(CmsPublishedResource resource) {
194
195        remove(resource.getStructureId(), resource.getRootPath(), resource.getType());
196    }
197
198    /**
199     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#remove(org.opencms.file.CmsResource)
200     */
201    public void remove(CmsResource resource) {
202
203        remove(resource.getStructureId(), resource.getRootPath(), resource.getTypeId());
204    }
205
206    /**
207     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#update(org.opencms.db.CmsPublishedResource)
208     */
209    public void update(CmsPublishedResource resource) {
210
211        update(resource.getStructureId(), resource.getRootPath(), resource.getType(), resource.getState());
212    }
213
214    /**
215     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#update(org.opencms.file.CmsResource)
216     */
217    public void update(CmsResource resource) {
218
219        update(resource.getStructureId(), resource.getRootPath(), resource.getTypeId(), resource.getState());
220    }
221
222    /**
223     * Returns the base path for a given configuration file.
224     *
225     * E.g. the result for the input '/sites/default/.container-config' will be '/sites/default'.<p>
226     *
227     * @param rootPath the root path of the configuration file
228     *
229     * @return the base path for the configuration file
230     */
231    protected String getBasePath(String rootPath) {
232
233        if (rootPath.endsWith(INHERITANCE_CONFIG_FILE_NAME)) {
234            return rootPath.substring(0, rootPath.length() - INHERITANCE_CONFIG_FILE_NAME.length());
235        }
236        return rootPath;
237    }
238
239    /**
240     * Gets the cache key for a given base path.<p>
241     *
242     * @param basePath the base path
243     *
244     * @return the cache key for the base path
245     */
246    protected String getCacheKey(String basePath) {
247
248        assert !basePath.endsWith(INHERITANCE_CONFIG_FILE_NAME);
249        return CmsFileUtil.addTrailingSeparator(basePath);
250    }
251
252    /**
253     * Checks whether a given combination of path and resource type belongs to an inherited container configuration file.<p>
254     *
255     * @param rootPath the root path of the resource
256     * @param type the type id of the resource
257     *
258     * @return true if the given root path / type combination matches an inherited container configuration file
259     */
260    protected boolean isContainerConfiguration(String rootPath, int type) {
261
262        return OpenCms.getResourceManager().matchResourceType(
263            CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME,
264            type)
265            && !CmsResource.isTemporaryFileName(rootPath)
266            && rootPath.endsWith("/" + INHERITANCE_CONFIG_FILE_NAME);
267    }
268
269    /**
270     * Loads the inheritance groups from a list of resources.<p>
271     *
272     * If the configuration for a given resource can't be read, the corresponding map entry will be null in the result.
273     *
274     * @param resources the resources
275     * @return the inheritance group configurations, with structure ids of the corresponding resources as keys
276     */
277    protected Map<CmsUUID, CmsContainerConfigurationGroup> load(Collection<CmsResource> resources) {
278
279        Map<CmsUUID, CmsContainerConfigurationGroup> result = Maps.newHashMap();
280        for (CmsResource resource : resources) {
281            CmsContainerConfigurationGroup parsedGroup = null;
282            try {
283                CmsFile file = m_cms.readFile(resource);
284                CmsContainerConfigurationParser parser = new CmsContainerConfigurationParser(m_cms);
285                parser.parse(file);
286                parsedGroup = new CmsContainerConfigurationGroup(parser.getParsedResults());
287                parsedGroup.setResource(resource);
288            } catch (CmsVfsResourceNotFoundException e) {
289                LOG.debug(e.getLocalizedMessage(), e);
290            } catch (Throwable e) {
291                LOG.error(e.getLocalizedMessage(), e);
292            }
293            // if the group couldn't be read or parsed for some reason, the map value is null
294            result.put(resource.getStructureId(), parsedGroup);
295        }
296        return result;
297    }
298
299    /**
300     * Loads the inheritance groups from the resources with structure ids from the given list.<p>
301     *
302     * If the configuration for a given id can't be read, the corresponding map entry will be null in the result.
303     *
304     * @param structureIds the structure ids
305     * @return the inheritance group configurations, with structure ids of the corresponding resources as keys
306     */
307    protected Map<CmsUUID, CmsContainerConfigurationGroup> loadFromIds(Collection<CmsUUID> structureIds) {
308
309        Map<CmsUUID, CmsContainerConfigurationGroup> result = Maps.newHashMap();
310        for (CmsUUID id : structureIds) {
311            CmsContainerConfigurationGroup parsedGroup = null;
312            try {
313                CmsResource resource = m_cms.readResource(id, CmsResourceFilter.IGNORE_EXPIRATION);
314                CmsFile file = m_cms.readFile(resource);
315                CmsContainerConfigurationParser parser = new CmsContainerConfigurationParser(m_cms);
316                parser.parse(file);
317                parsedGroup = new CmsContainerConfigurationGroup(parser.getParsedResults());
318                parsedGroup.setResource(resource);
319            } catch (CmsVfsResourceNotFoundException e) {
320                LOG.debug(e.getLocalizedMessage(), e);
321            } catch (Throwable e) {
322                LOG.error(e.getLocalizedMessage(), e);
323            }
324            // if the group couldn't be read or parsed for some reason, the map value is null
325            result.put(id, parsedGroup);
326        }
327        return result;
328    }
329
330    /**
331     * Removes a resource from the cache.<p>
332     *
333     * @param structureId the structure id of the resource
334     * @param rootPath the root path of the resource
335     *
336     * @param type the resource type
337     */
338    protected void remove(CmsUUID structureId, String rootPath, int type) {
339
340        if (!isContainerConfiguration(rootPath, type)) {
341            return;
342        }
343        m_updateSet.add(structureId);
344    }
345
346    /**
347     * Updates a resource in the cache.<p>
348     *
349     * @param structureId the structure id of the resource
350     * @param rootPath the root path of the resource
351     * @param type the resource type
352     * @param state the resource state
353     */
354    protected void update(CmsUUID structureId, String rootPath, int type, CmsResourceState state) {
355
356        if (!isContainerConfiguration(rootPath, type)) {
357            return;
358        }
359        m_updateSet.add(structureId);
360    }
361}