/* * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ package org.alfresco.repo.config; import java.util.Collections; import java.util.List; import java.util.Map; import org.alfresco.repo.cache.AbstractMTAsynchronouslyRefreshedCache; import org.alfresco.repo.config.ConfigDataCache.ConfigData; import org.alfresco.repo.config.xml.RepoXMLConfigService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.config.ConfigDeployment; import org.springframework.extensions.config.ConfigImpl; import org.springframework.extensions.config.ConfigSection; import org.springframework.extensions.config.ConfigService; import org.springframework.extensions.config.evaluator.Evaluator; import org.springframework.extensions.config.xml.elementreader.ConfigElementReader; import com.sun.star.uno.RuntimeException; /** * An innder class that uses the {@link RepoXMLConfigService config service} to asynchronously * refresh tenant config data. *

* Cluster-wide invalidation messages mean that we have to be very careful about only making * updates to caches when entries really change. However, the nature of the {@link ConfigService} * hierarchy makes it very difficult to do this in a deterministic manner without performing * write locks that run across tenants. By receiving asynchronous messages to refresh we no * longer have to rely on a cluster-aware cache. * * @author Derek Hulley * @author Andy Hind * @since 4.1.5 */ public class ConfigDataCache extends AbstractMTAsynchronouslyRefreshedCache { private static Log logger = LogFactory.getLog(ConfigDataCache.class); private RepoXMLConfigService repoXMLConfigService; /** * Set the config service. Depending on the order of injection, this might have to * be done by code. */ public void setRepoXMLConfigService(RepoXMLConfigService repoXMLConfigService) { this.repoXMLConfigService = repoXMLConfigService; } @Override protected ConfigData buildCache(String tenantId) { if (logger.isDebugEnabled()) { logger.debug("Received request to rebuild config data for tenant: " + tenantId); } ConfigData configData = repoXMLConfigService.getRepoConfig(tenantId); if (!(configData instanceof ImmutableConfigData)) { throw new RuntimeException("ConfigData must be immutable for the cache."); } if (logger.isDebugEnabled()) { logger.debug("Rebuilt config data for tenant: " + tenantId + " (" + configData + ")."); } return configData; } /** * Data containing all a tenant's required UI configuration * * @author various */ public static class ConfigData { private ConfigImpl globalConfig; private Map evaluators; private Map> sectionsByArea; private List sections; private Map elementReaders; private List configDeployments; public ConfigData() { } public ConfigImpl getGlobalConfig() { return globalConfig; } public void setGlobalConfig(ConfigImpl globalConfig) { this.globalConfig = globalConfig; } public Map getEvaluators() { return evaluators; } public void setEvaluators(Map evaluators) { this.evaluators = evaluators; } public Map> getSectionsByArea() { return sectionsByArea; } public void setSectionsByArea(Map> sectionsByArea) { this.sectionsByArea = sectionsByArea; } public List getSections() { return sections; } public void setSections(List sections) { this.sections = sections; } public Map getElementReaders() { return elementReaders; } public void setElementReaders(Map elementReaders) { this.elementReaders = elementReaders; } public List getConfigDeployments() { return configDeployments; } public void setConfigDeployments(List configDeployments) { this.configDeployments = configDeployments; } } /** * Immutable version of {@link ConfigData} to ensure cast-iron safety of data * being put into the caches. * * @author Derek Hulley * @since 4.1.5 */ public static class ImmutableConfigData extends ConfigData { /** * Local variable to allow setter use during construction */ private boolean locked = false; /** * Copy constructor that prevents any data from being changed. * * @param configData the config to copy */ public ImmutableConfigData(ConfigData configData) { /* * Each member is copied or protected in some way to ensure immutability */ List configDeployments = configData.getConfigDeployments(); if (configDeployments != null) { List configDeploymentsLocked = Collections.unmodifiableList(configDeployments); setConfigDeployments(configDeploymentsLocked); } else { setConfigDeployments(Collections.emptyList()); } Map elementReaders = configData.getElementReaders(); if (elementReaders != null) { Map elementReadersLocked = Collections.unmodifiableMap(elementReaders); setElementReaders(elementReadersLocked); } else { setElementReaders(Collections.emptyMap()); } Map evaluators = configData.getEvaluators(); if (evaluators != null) { Map evaluatorsLocked = Collections.unmodifiableMap(evaluators); setEvaluators(evaluatorsLocked); } else { setEvaluators(Collections.emptyMap()); } ConfigImpl globalConfig = configData.getGlobalConfig(); ImmutableConfig globalConfigLocked = new ImmutableConfig(globalConfig); setGlobalConfig(globalConfigLocked); List sections = configData.getSections(); if (sections != null) { List sectionsLocked = Collections.unmodifiableList(sections); setSections(sectionsLocked); } else { setSections(Collections.emptyList()); } Map> sectionsByArea = configData.getSectionsByArea(); if (sectionsByArea != null) { Map> sectionsByAreaLocked = Collections.unmodifiableMap(sectionsByArea); setSectionsByArea(sectionsByAreaLocked); } else { setSectionsByArea(Collections.>emptyMap()); } // Now prevent setters from being used locked = true; } @Override public void setGlobalConfig(ConfigImpl globalConfig) { if (locked) { throw new IllegalStateException("ConfigData has been locked."); } super.setGlobalConfig(globalConfig); } @Override public void setEvaluators(Map evaluators) { if (locked) { throw new IllegalStateException("ConfigData has been locked."); } super.setEvaluators(evaluators); } @Override public void setSectionsByArea(Map> sectionsByArea) { if (locked) { throw new IllegalStateException("ConfigData has been locked."); } super.setSectionsByArea(sectionsByArea); } @Override public void setSections(List sections) { if (locked) { throw new IllegalStateException("ConfigData has been locked."); } super.setSections(sections); } @Override public void setElementReaders(Map elementReaders) { if (locked) { throw new IllegalStateException("ConfigData has been locked."); } super.setElementReaders(elementReaders); } @Override public void setConfigDeployments(List configDeployments) { if (locked) { throw new IllegalStateException("ConfigData has been locked."); } super.setConfigDeployments(configDeployments); } } }