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.CmsUUID; 042 043import java.util.Collections; 044import java.util.HashSet; 045import java.util.List; 046import java.util.Set; 047import java.util.concurrent.ConcurrentHashMap; 048import java.util.concurrent.LinkedBlockingQueue; 049import java.util.concurrent.TimeUnit; 050import java.util.stream.Collectors; 051 052import org.apache.commons.logging.Log; 053 054/** 055 * A cache which stores structure ids for URL names.<p> 056 * 057 * <p>Note that this cache may in some cases contain outdated structure ids for URL names, if an URL name has been removed for a content but 058 * is not yet mapped to a different content. 059 */ 060public class CmsDetailNameCache implements I_CmsGlobalConfigurationCache { 061 062 /** The delay between updates. */ 063 public static final int DELAY_MILLIS = 3000; 064 065 /** The logger for this class. */ 066 private static final Log LOG = CmsLog.getLog(CmsDetailNameCache.class); 067 068 /** Request attribute for telling the link substitution handler that it shouldn't use this cache. */ 069 public static final String ATTR_BYPASS = "bypass-detail-name-cache"; 070 071 /** Object used to wait on for updates. */ 072 private Object m_updateLock = new Object(); 073 074 /** The CMS context used by this cache. */ 075 private CmsObject m_cms; 076 077 /** The internal map from URL names to structure ids. */ 078 private volatile ConcurrentHashMap<String, CmsUUID> m_detailIdCache = new ConcurrentHashMap<>(); 079 080 /** The set of structure ids for which the URL names have to be updated. */ 081 private LinkedBlockingQueue<CmsUUID> m_changes = new LinkedBlockingQueue<>(); 082 083 /** 084 * Creates a new instance.<p> 085 * 086 * @param cms the CMS context to use 087 */ 088 public CmsDetailNameCache(CmsObject cms) { 089 090 m_cms = cms; 091 } 092 093 /** 094 * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#clear() 095 */ 096 public void clear() { 097 098 markForUpdate(CmsUUID.getNullUUID()); 099 100 } 101 102 /** 103 * Gets the structure id for a given URL name.<p> 104 * 105 * @param name the URL name 106 * @return the structure id for the URL name 107 */ 108 public CmsUUID getDetailId(String name) { 109 110 return m_detailIdCache.get(name); 111 } 112 113 /** 114 * Initializes the cache by scheduling the update actions and loading the initial cache contents.<p> 115 */ 116 public void initialize() { 117 118 OpenCms.getExecutor().scheduleWithFixedDelay(new Runnable() { 119 120 public void run() { 121 122 checkForUpdates(); 123 } 124 }, DELAY_MILLIS, DELAY_MILLIS, TimeUnit.MILLISECONDS); 125 reload(); 126 } 127 128 /** 129 * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#remove(org.opencms.db.CmsPublishedResource) 130 */ 131 public void remove(CmsPublishedResource pubRes) { 132 133 checkIfUpdateIsNeeded(pubRes.getStructureId(), pubRes.getRootPath(), pubRes.getType()); 134 } 135 136 /** 137 * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#remove(org.opencms.file.CmsResource) 138 */ 139 public void remove(CmsResource resource) { 140 141 checkIfUpdateIsNeeded(resource.getStructureId(), resource.getRootPath(), resource.getTypeId()); 142 143 } 144 145 /** 146 * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#update(org.opencms.db.CmsPublishedResource) 147 */ 148 public void update(CmsPublishedResource pubRes) { 149 150 checkIfUpdateIsNeeded(pubRes.getStructureId(), pubRes.getRootPath(), pubRes.getType()); 151 152 } 153 154 /** 155 * @see org.opencms.ade.configuration.I_CmsGlobalConfigurationCache#update(org.opencms.file.CmsResource) 156 */ 157 public void update(CmsResource resource) { 158 159 checkIfUpdateIsNeeded(resource.getStructureId(), resource.getRootPath(), resource.getTypeId()); 160 161 } 162 163 /** 164 * Waits until the cache is potentially updated for the next time. 165 * 166 * <p>This does include updates where there is actually nothing to update. 167 */ 168 public void waitForUpdate() { 169 170 synchronized (m_updateLock) { 171 try { 172 m_updateLock.wait(); 173 } catch (InterruptedException e) { 174 return; 175 } 176 } 177 } 178 179 /** 180 * Checks if any updates are necessary and if so, performs them.<p> 181 */ 182 void checkForUpdates() { 183 184 Set<CmsUUID> updateSet = new HashSet<>(); 185 m_changes.drainTo(updateSet); 186 if (!updateSet.isEmpty()) { 187 if (updateSet.contains(CmsUUID.getNullUUID())) { 188 LOG.info("Updating detail name cache: reloading..."); 189 reload(); 190 } else { 191 LOG.info("Updating detail name cache. Number of changed files: " + updateSet.size()); 192 for (CmsUUID id : updateSet) { 193 Set<String> urlNames = getUrlNames(id); 194 for (String urlName : urlNames) { 195 m_detailIdCache.put(urlName, id); 196 } 197 } 198 } 199 } 200 synchronized (m_updateLock) { 201 m_updateLock.notifyAll(); 202 } 203 } 204 205 /** 206 * Checks if the cache needs to be updated for the resource, and if so, marks the structure id for updating.<p> 207 * 208 * @param structureId the structure id for the resource 209 * @param rootPath the path of the resource 210 * @param typeId the resource type id 211 */ 212 private void checkIfUpdateIsNeeded(CmsUUID structureId, String rootPath, int typeId) { 213 214 try { 215 I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(typeId); 216 if ((resType instanceof CmsResourceTypeXmlContent) 217 && !OpenCms.getResourceManager().matchResourceType( 218 CmsResourceTypeXmlContainerPage.RESOURCE_TYPE_NAME, 219 typeId)) { 220 markForUpdate(structureId); 221 } 222 } catch (CmsLoaderException e) { 223 // resource type not found, just log an error 224 LOG.error(e.getLocalizedMessage(), e); 225 } 226 } 227 228 /** 229 * Reads the URL names for the id.<p> 230 * 231 * @param id the structure id of a resource 232 * @return the URL names for the resource 233 */ 234 private Set<String> getUrlNames(CmsUUID id) { 235 236 try { 237 CmsUrlNameMappingFilter filter = CmsUrlNameMappingFilter.ALL.filterStructureId(id); 238 Set<String> result = m_cms.readUrlNameMappings(filter).stream().map(entry -> entry.getName()).collect( 239 Collectors.toSet()); 240 return result; 241 } catch (Exception e) { 242 LOG.error(e.getLocalizedMessage(), e); 243 return Collections.emptySet(); 244 } 245 } 246 247 /** 248 * Marks the structure id for updating.<p> 249 * 250 * @param id the structure id to update 251 */ 252 private void markForUpdate(CmsUUID id) { 253 254 m_changes.add(id); 255 } 256 257 /** 258 * Loads the complete URL name data into the cache.<p> 259 */ 260 private void reload() { 261 262 ConcurrentHashMap<String, CmsUUID> newMap = new ConcurrentHashMap<String, CmsUUID>(); 263 try { 264 List<CmsUrlNameMappingEntry> mappings = m_cms.readUrlNameMappings(CmsUrlNameMappingFilter.ALL); 265 LOG.info("Initializing detail name cache with " + mappings.size() + " entries"); 266 for (CmsUrlNameMappingEntry entry : mappings) { 267 newMap.put(entry.getName(), entry.getStructureId()); 268 } 269 m_detailIdCache = newMap; 270 } catch (Exception e) { 271 LOG.error(e.getLocalizedMessage(), e); 272 } 273 } 274}