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}