/* * Copyright (C) 2005-2010 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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Alfresco. If not, see . */ package org.alfresco.repo.config.xml; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.transaction.UserTransaction; 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; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantDeployer; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; /** * 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); /** * Lock objects */ private ReadWriteLock lock = new ReentrantReadWriteLock(); private Lock readLock = lock.readLock(); private Lock writeLock = lock.writeLock(); // Dependencies private TransactionService transactionService; private AuthenticationContext authenticationContext; private TenantAdminService tenantAdminService; // Internal cache (clusterable) private SimpleCache configDataCache; // used to reset the cache private ThreadLocal configDataThreadLocal = new ThreadLocal(); public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } public void setAuthenticationContext(AuthenticationContext authenticationContext) { this.authenticationContext = authenticationContext; } public void setTenantAdminService(TenantAdminService tenantAdminService) { this.tenantAdminService = tenantAdminService; } public void setConfigDataCache(SimpleCache configDataCache) { this.configDataCache = configDataCache; } /** * Constructs an XMLConfigService using the given config source * * @param configSource * A ConfigSource */ public RepoXMLConfigService(ConfigSource configSource) { super(configSource); } public List initConfig() { return resetRepoConfig().getConfigDeployments(); } private ConfigData initRepoConfig(String tenantDomain) { ConfigData configData = null; // can be null e.g. initial login, after fresh bootstrap String currentUser = authenticationContext.getCurrentUserName(); if (currentUser == null) { authenticationContext.setSystemUserAsCurrentUser(); } UserTransaction userTransaction = transactionService.getUserTransaction(); try { userTransaction.begin(); // parse config List configDeployments = super.initConfig(); configData = getConfigDataLocal(tenantDomain); if (configData != null) { configData.setConfigDeployments(configDeployments); } userTransaction.commit(); logger.info("Config initialised"); } catch(Throwable e) { try { userTransaction.rollback(); } catch (Exception ex) {} throw new AlfrescoRuntimeException("Failed to initialise config service", e); } finally { if (currentUser == null) { authenticationContext.clearCurrentSecurityContext(); } } return configData; } public void destroy() { super.destroy(); logger.info("Config destroyed"); } /** * Resets the config service */ public void reset() { resetRepoConfig(); } /** * Resets the config service */ private ConfigData resetRepoConfig() { if (logger.isDebugEnabled()) { logger.debug("Resetting repo config service"); } String tenantDomain = getTenantDomain(); try { destroy(); // create threadlocal, if needed ConfigData configData = getConfigDataLocal(tenantDomain); if (configData == null) { configData = new ConfigData(tenantDomain); this.configDataThreadLocal.set(configData); } configData = initRepoConfig(tenantDomain); if (configData == null) { // unexpected throw new AlfrescoRuntimeException("Failed to reset configData " + tenantDomain); } try { writeLock.lock(); configDataCache.put(tenantDomain, configData); } finally { writeLock.unlock(); } return configData; } finally { try { readLock.lock(); if (configDataCache.get(tenantDomain) != null) { this.configDataThreadLocal.set(null); // it's in the cache, clear the threadlocal } } finally { readLock.unlock(); } } } @Override protected void onBootstrap(ApplicationEvent event) { // run as System on bootstrap AuthenticationUtil.runAs(new RunAsWork() { public Object doWork() { initConfig(); return null; } }, AuthenticationUtil.getSystemUserName()); if ((tenantAdminService != null) && (tenantAdminService.isEnabled())) { tenantAdminService.deployTenants(this, logger); tenantAdminService.register(this); } } @Override protected void onShutdown(ApplicationEvent event) { // NOOP } public void onEnableTenant() { initConfig(); // will be called in context of tenant } public void onDisableTenant() { destroy(); // will be called in context of tenant } // re-entrant (eg. via reset) private ConfigData getConfigData() { String tenantDomain = getTenantDomain(); // check threadlocal first - return if set ConfigData configData = getConfigDataLocal(tenantDomain); if (configData != null) { return configData; // return local config } try { // check cache second - return if set readLock.lock(); configData = configDataCache.get(tenantDomain); if (configData != null) { return configData; // return cached config } } finally { readLock.unlock(); } // reset caches - may have been invalidated (e.g. in a cluster) configData = resetRepoConfig(); if (configData == null) { // unexpected throw new AlfrescoRuntimeException("Failed to get configData " + tenantDomain); } return configData; } // get threadlocal private ConfigData getConfigDataLocal(String tenantDomain) { ConfigData configData = this.configDataThreadLocal.get(); // check to see if domain switched (eg. during login) if ((configData != null) && (tenantDomain.equals(configData.getTenantDomain()))) { return configData; // return threadlocal, if set } return null; } private void removeConfigData() { try { writeLock.lock(); String tenantDomain = getTenantDomain(); if (configDataCache.get(tenantDomain) != null) { configDataCache.remove(tenantDomain); } } finally { writeLock.unlock(); } } @Override protected ConfigImpl getGlobalConfigImpl() { return getConfigData().getGlobalConfig(); } @Override protected void putGlobalConfig(ConfigImpl globalConfig) { getConfigData().setGlobalConfig(globalConfig); } @Override protected void removeGlobalConfig() { removeConfigData(); } @Override protected Map getEvaluators() { return getConfigData().getEvaluators(); } @Override protected void putEvaluators(Map evaluators) { getConfigData().setEvaluators(evaluators); } @Override protected void removeEvaluators() { removeConfigData(); } @Override protected Map> getSectionsByArea() { return getConfigData().getSectionsByArea(); } @Override protected void putSectionsByArea(Map> sectionsByArea) { getConfigData().setSectionsByArea(sectionsByArea); } @Override protected void removeSectionsByArea() { removeConfigData(); } @Override protected List getSections() { return getConfigData().getSections(); } @Override protected void putSections(List sections) { getConfigData().setSections(sections); } @Override protected void removeSections() { removeConfigData(); } @Override protected Map getElementReaders() { return getConfigData().getElementReaders(); } @Override protected void putElementReaders(Map elementReaders) { getConfigData().setElementReaders(elementReaders); } @Override protected void removeElementReaders() { removeConfigData(); } // local helper - returns tenant domain (or empty string if default non-tenant) private String getTenantDomain() { return tenantAdminService.getCurrentUserDomain(); } private class ConfigData { private ConfigImpl globalConfig; private Map evaluators; private Map> sectionsByArea; private List sections; private Map elementReaders; private List configDeployments; private String tenantDomain; public ConfigData(String tenantDomain) { this.tenantDomain = tenantDomain; } public String getTenantDomain() { return tenantDomain; } 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; } } }