/* * Copyright (C) 2005-2014 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.xml; import java.util.List; import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.config.ConfigDataCache; import org.alfresco.repo.config.ConfigDataCache.ConfigData; import org.alfresco.repo.config.ConfigDataCache.ImmutableConfigData; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.TenantDeployer; import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; import org.springframework.extensions.config.ConfigDeployment; import org.springframework.extensions.config.ConfigImpl; import org.springframework.extensions.config.ConfigSection; import org.springframework.extensions.config.ConfigSource; import org.springframework.extensions.config.evaluator.Evaluator; import org.springframework.extensions.config.xml.XMLConfigService; import org.springframework.extensions.config.xml.elementreader.ConfigElementReader; /** * XML-based configuration service which can optionally read config from the Repository */ public class RepoXMLConfigService extends XMLConfigService implements TenantDeployer { private static final Log logger = LogFactory.getLog(RepoXMLConfigService.class); /** * Configuration that is manipulated by the current thread.
* This is required because the super classes call back into this mechanism after * receiving the initial object, etc. */ private final ThreadLocal configUnderConstruction = new ThreadLocal(); // Dependencies private TransactionService transactionService; private ConfigDataCache configDataCache; public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } /** * Set the asynchronously-controlled cache. */ public void setConfigDataCache(ConfigDataCache configDataCache) { this.configDataCache = configDataCache; } /** * Constructs an XMLConfigService using the given config source */ public RepoXMLConfigService(ConfigSource configSource) { super(configSource); } @Override public List initConfig() { configDataCache.refresh(); // Just return whatever is there (no-one uses it) ConfigData configData = configDataCache.get(); return configData.getConfigDeployments(); } /** * Get the repository configuration data for a given tenant. * This does the low-level initialization of the configuration and does not do any * caching. * * @param tenantDomain the current tenant domain * @return return the repository configuration for the given tenant (never null) */ public ConfigData getRepoConfig(final String tenantDomain) { final RetryingTransactionCallback getConfigWork = new RetryingTransactionCallback() { @Override public Void execute() throws Throwable { // parse config RepoXMLConfigService.super.initConfig(); return null; } }; TenantUtil.TenantRunAsWork getConfigRunAs = new TenantUtil.TenantRunAsWork() { @Override public Void doWork() throws Exception { transactionService.getRetryingTransactionHelper().doInTransaction(getConfigWork, true); return null; } }; try { if (logger.isDebugEnabled()) { logger.debug( "Fetching repository config data for tenant: \n" + " Tenant Domain: " + tenantDomain); } // Put some mutable config onto the current thread and have the superclasses mess with that. ConfigData configData = new ConfigData(); configUnderConstruction.set(configData); // Do the work as system tenant, see ALF-19922 TenantUtil.runAsSystemTenant(getConfigRunAs, tenantDomain); // Now wrap the config so that it cannot be changed configData = new ImmutableConfigData(configData); // Done if (logger.isDebugEnabled()) { logger.debug( "Fetched repository config data for tenant: \n" + " Tenant Domain: " + tenantDomain + "\n" + " Config: " + configData); } return configData; } catch (Exception e) { throw new AlfrescoRuntimeException( "Failed to fetch repository config data for tenant \n" + " Tenant Domain: " + tenantDomain, e); } finally { configUnderConstruction.remove(); } } @Override public void destroy() { reset(); } /** * Resets the config values for the current tenant */ @Override public void reset() { configDataCache.refresh(); } @Override protected void onBootstrap(ApplicationEvent event) { // run as System on bootstrap AuthenticationUtil.runAs(new RunAsWork() { public Object doWork() { initConfig(); return null; } }, AuthenticationUtil.getSystemUserName()); } @Override protected void onShutdown(ApplicationEvent event) { // NOOP } @Override public void onEnableTenant() { initConfig(); // will be called in context of tenant } @Override public void onDisableTenant() { destroy(); // will be called in context of tenant } /** * Fetch the tenant-specific config data from the cache. If nothing is * available then the config will be created. *

* Note that this method is used during construction of the config as well. * When this occurs, a thread local value is used in place of the cached * value. It's not pretty. * * @return the tenant-specific configuration (never null) */ private ConfigData getConfigData() { if (configUnderConstruction.get() != null) { // We are busy building some config so we can return it return configUnderConstruction.get(); } // Go to the backing cache return configDataCache.get(); } @Override protected ConfigImpl getGlobalConfigImpl() { return getConfigData().getGlobalConfig(); } @Override protected void putGlobalConfig(ConfigImpl globalConfig) { getConfigData().setGlobalConfig(globalConfig); } @Override protected void removeGlobalConfig() { throw new UnsupportedOperationException("'destroy' method must destroy all config. Piecemeal destruction is not supported."); } @Override protected Map getEvaluators() { return getConfigData().getEvaluators(); } @Override protected void putEvaluators(Map evaluators) { getConfigData().setEvaluators(evaluators); } @Override protected void removeEvaluators() { throw new UnsupportedOperationException("'destroy' method must destroy all config. Piecemeal destruction is not supported."); } @Override public Map> getSectionsByArea() { return getConfigData().getSectionsByArea(); } @Override protected void putSectionsByArea(Map> sectionsByArea) { getConfigData().setSectionsByArea(sectionsByArea); } @Override protected void removeSectionsByArea() { throw new UnsupportedOperationException("'destroy' method must destroy all config. Piecemeal destruction is not supported."); } @Override public List getSections() { return getConfigData().getSections(); } @Override protected void putSections(List sections) { getConfigData().setSections(sections); } @Override protected void removeSections() { throw new UnsupportedOperationException("'destroy' method must destroy all config. Piecemeal destruction is not supported."); } @Override protected Map getElementReaders() { return getConfigData().getElementReaders(); } @Override protected void putElementReaders(Map elementReaders) { getConfigData().setElementReaders(elementReaders); } @Override protected void removeElementReaders() { throw new UnsupportedOperationException("'destroy' method must destroy all config. Piecemeal destruction is not supported."); } }