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.ade.configuration;
029
030import org.opencms.db.CmsPublishedResource;
031import org.opencms.db.urlname.CmsUrlNameMappingEntry;
032import org.opencms.db.urlname.CmsUrlNameMappingFilter;
033import org.opencms.file.CmsObject;
034import org.opencms.file.CmsResource;
035import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
036import org.opencms.file.types.CmsResourceTypeXmlContent;
037import org.opencms.file.types.I_CmsResourceType;
038import org.opencms.loader.CmsLoaderException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.util.CmsManyToOneMap;
042import org.opencms.util.CmsUUID;
043
044import java.util.Collections;
045import java.util.HashSet;
046import java.util.List;
047import java.util.Set;
048import java.util.concurrent.TimeUnit;
049
050import org.apache.commons.logging.Log;
051
052import com.google.common.collect.Sets;
053
054/**
055 * A cache which stores structure ids for URL names.<p>
056 */
057public class CmsDetailNameCache implements I_CmsGlobalConfigurationCache {
058
059    /** The delay between updates. */
060    public static final int DELAY_MILLIS = 10000;
061
062    /** The logger for this class. */
063    private static final Log LOG = CmsLog.getLog(CmsDetailNameCache.class);
064
065    /** The CMS context used by this cache. */
066    private CmsObject m_cms;
067
068    /** The internal map from URL names to structure ids. */
069    private CmsManyToOneMap<String, CmsUUID> m_detailIdCache = new CmsManyToOneMap<String, CmsUUID>();
070
071    /** The set of structure ids for which the URL names have to be updated. */
072    private Set<CmsUUID> m_updateSet = Sets.newHashSet();
073
074    /**
075     * Creates a new instance.<p>
076     *
077     * @param cms the CMS context to use
078     */
079    public CmsDetailNameCache(CmsObject cms) {
080
081        m_cms = cms;
082    }
083
084    /**
085     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#clear()
086     */
087    public void clear() {
088
089        markForUpdate(CmsUUID.getNullUUID());
090
091    }
092
093    /**
094     * Gets the structure id for a given URL name.<p>
095     *
096     * @param name the URL name
097     * @return the structure id for the URL name
098     */
099    public CmsUUID getDetailId(String name) {
100
101        return m_detailIdCache.get(name);
102    }
103
104    /**
105     * Initializes the cache by scheduling the update actions and loading the initial cache contents.<p>
106     */
107    public void initialize() {
108
109        OpenCms.getExecutor().scheduleWithFixedDelay(new Runnable() {
110
111            public void run() {
112
113                checkForUpdates();
114            }
115        }, DELAY_MILLIS, DELAY_MILLIS, TimeUnit.MILLISECONDS);
116        reload();
117    }
118
119    /**
120     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#remove(org.opencms.db.CmsPublishedResource)
121     */
122    public void remove(CmsPublishedResource pubRes) {
123
124        checkIfUpdateIsNeeded(pubRes.getStructureId(), pubRes.getRootPath(), pubRes.getType());
125    }
126
127    /**
128     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#remove(org.opencms.file.CmsResource)
129     */
130    public void remove(CmsResource resource) {
131
132        checkIfUpdateIsNeeded(resource.getStructureId(), resource.getRootPath(), resource.getTypeId());
133
134    }
135
136    /**
137     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#update(org.opencms.db.CmsPublishedResource)
138     */
139    public void update(CmsPublishedResource pubRes) {
140
141        checkIfUpdateIsNeeded(pubRes.getStructureId(), pubRes.getRootPath(), pubRes.getType());
142
143    }
144
145    /**
146     * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#update(org.opencms.file.CmsResource)
147     */
148    public void update(CmsResource resource) {
149
150        checkIfUpdateIsNeeded(resource.getStructureId(), resource.getRootPath(), resource.getTypeId());
151
152    }
153
154    /**
155     * Checks if any updates are necessary and if so, performs them.<p>
156     */
157    synchronized void checkForUpdates() {
158
159        if (!m_updateSet.isEmpty()) {
160            Set<CmsUUID> copiedIds = Sets.newHashSet(m_updateSet);
161            m_updateSet.clear();
162
163            if (copiedIds.contains(CmsUUID.getNullUUID())) {
164                LOG.info("Updating detail name cache: reloading...");
165                reload();
166            } else {
167                LOG.info("Updating detail name cache. Number of changed files: " + copiedIds.size());
168                CmsManyToOneMap<String, CmsUUID> cacheCopy = new CmsManyToOneMap<String, CmsUUID>(m_detailIdCache);
169                for (CmsUUID id : copiedIds) {
170                    Set<String> urlNames = getUrlNames(id);
171                    cacheCopy.removeValue(id);
172                    for (String urlName : urlNames) {
173                        cacheCopy.put(urlName, id);
174                    }
175                }
176                m_detailIdCache = cacheCopy;
177            }
178        }
179    }
180
181    /**
182     * Checks if the cache needs to be updated for the resource, and if so, marks the structure id for updating.<p>
183     *
184     * @param structureId the structure id for the resource
185     * @param rootPath the path of the resource
186     * @param typeId the resource type id
187     */
188    private void checkIfUpdateIsNeeded(CmsUUID structureId, String rootPath, int typeId) {
189
190        try {
191            I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(typeId);
192            if ((resType instanceof CmsResourceTypeXmlContent)
193                && !OpenCms.getResourceManager().matchResourceType(
194                    CmsResourceTypeXmlContainerPage.RESOURCE_TYPE_NAME,
195                    typeId)) {
196                markForUpdate(structureId);
197            }
198        } catch (CmsLoaderException e) {
199            // resource type not found, just log an error
200            LOG.error(e.getLocalizedMessage(), e);
201        }
202    }
203
204    /**
205     * Reads the URL names for the id.<p>
206     *
207     * @param id the structure id of a resource
208     * @return the URL names for the resource
209     */
210    private Set<String> getUrlNames(CmsUUID id) {
211
212        try {
213            return new HashSet<String>(m_cms.readUrlNamesForAllLocales(id));
214        } catch (Exception e) {
215            LOG.error(e.getLocalizedMessage(), e);
216            return Collections.emptySet();
217        }
218    }
219
220    /**
221     * Marks the structure id for updating.<p>
222     *
223     * @param id the structure id to update
224     */
225    private synchronized void markForUpdate(CmsUUID id) {
226
227        m_updateSet.add(id);
228    }
229
230    /**
231     * Loads the complete URL name data into the cache.<p>
232     */
233    private void reload() {
234
235        CmsManyToOneMap<String, CmsUUID> newMap = new CmsManyToOneMap<String, CmsUUID>();
236        try {
237            List<CmsUrlNameMappingEntry> mappings = m_cms.readUrlNameMappings(CmsUrlNameMappingFilter.ALL);
238            LOG.info("Initializing detail name cache with " + mappings.size() + " entries");
239            for (CmsUrlNameMappingEntry entry : mappings) {
240                newMap.put(entry.getName(), entry.getStructureId());
241            }
242            m_detailIdCache = newMap;
243        } catch (Exception e) {
244            LOG.error(e.getLocalizedMessage(), e);
245        }
246    }
247}