diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 99a645e731..e2955195cd 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -292,5 +292,43 @@ eternal="true" overflowToDisk="true" /> - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/extension/ehcache-custom.xml.sample.cluster b/config/alfresco/extension/ehcache-custom.xml.sample.cluster index 4467734168..290add6a39 100644 --- a/config/alfresco/extension/ehcache-custom.xml.sample.cluster +++ b/config/alfresco/extension/ehcache-custom.xml.sample.cluster @@ -517,6 +517,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/tenant-single-context.xml b/config/alfresco/tenant-single-context.xml index af6d58b828..8bbdecc72f 100644 --- a/config/alfresco/tenant-single-context.xml +++ b/config/alfresco/tenant-single-context.xml @@ -8,5 +8,7 @@ + + diff --git a/source/java/org/alfresco/repo/config/source/RepoUrlConfigSource.java b/source/java/org/alfresco/repo/config/source/RepoUrlConfigSource.java new file mode 100644 index 0000000000..e82a8528a6 --- /dev/null +++ b/source/java/org/alfresco/repo/config/source/RepoUrlConfigSource.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program 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 2 + * of the License, or (at your option) any later version. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.config.source; + +import java.io.InputStream; +import java.util.List; + +import org.alfresco.config.ConfigException; +import org.alfresco.config.source.UrlConfigSource; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.InvalidStoreRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; + +/** + * ConfigSource that looks for a prefix to determine where to look for the config.
+ * Valid prefixes are: + *
    + *
  • :// the location provided is a path to a repository file
  • + *
+ * as well as those defined in the core (UrlConfigSource) + * + * Example store URLs + * workspace://SpacesStore/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.webclient_extension.childname}/cm:web-client-config-custom.xml + * workspace://SpacesStore/app:company_home/app:dictionary/cm:webclient_extension/cm:web-client-config-custom.xml + */ +public class RepoUrlConfigSource extends UrlConfigSource +{ + private TenantService tenantService; + private SearchService searchService; + private ContentService contentService; + private NamespaceService namespaceService; + private NodeService nodeService; + + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + + public RepoUrlConfigSource(String sourceLocation) + { + super(sourceLocation); + } + + public RepoUrlConfigSource(List sourceLocations) + { + super(sourceLocations); + } + + + public InputStream getInputStream(String sourceUrl) + { + // determine the config source + try + { + return super.getInputStream(sourceUrl); + } + catch (ConfigException ce) + { + int idx = sourceUrl.indexOf(StoreRef.URI_FILLER); + if (idx != -1) + { + // assume this is a repository location + int idx2 = sourceUrl.indexOf("/", idx+3); + + String store = sourceUrl.substring(0, idx2); + String path = sourceUrl.substring(idx2); + + StoreRef storeRef = tenantService.getName(new StoreRef(store)); + NodeRef rootNode = null; + + try + { + rootNode = nodeService.getRootNode(storeRef); + } + catch (InvalidStoreRefException e) + { + throw ce; + } + + List nodeRefs = searchService.selectNodes(rootNode, path, null, namespaceService, false); + + if (nodeRefs.size() == 0) + { + // if none found, then simply skip + return null; + } + else if (nodeRefs.size() > 1) + { + // unexpected + throw new ConfigException("Found duplicate config sources in the repository " + sourceUrl); + } + + NodeRef nodeRef = nodeRefs.get(0); + + ContentReader cr = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + + return cr.getContentInputStream(); + } + else + { + // not a repository url + throw ce; + } + } + } +} diff --git a/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java new file mode 100644 index 0000000000..a3169129e3 --- /dev/null +++ b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java @@ -0,0 +1,594 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program 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 2 + * of the License, or (at your option) any later version. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +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.alfresco.config.ConfigImpl; +import org.alfresco.config.ConfigSection; +import org.alfresco.config.ConfigSource; +import org.alfresco.config.evaluator.Evaluator; +import org.alfresco.config.xml.XMLConfigService; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +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.TenantDeployerService; +import org.alfresco.repo.tenant.TenantService; +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 AuthenticationComponent authenticationComponent; + private TenantDeployerService tenantDeployerService; + private TenantService tenantService; + + // Internal caches that are clusterable + private SimpleCache globalConfigCache; + private SimpleCache> evaluatorsCache; + private SimpleCache>> sectionsByAreaCache; + private SimpleCache> sectionsCache; + private SimpleCache> elementReadersCache; + + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + public void setTenantDeployerService(TenantDeployerService tenantDeployerService) + { + this.tenantDeployerService = tenantDeployerService; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + + public void setGlobalConfigCache(SimpleCache globalConfigCache) + { + this.globalConfigCache = globalConfigCache; + } + + public void setEvaluatorsCache(SimpleCache> evaluatorsCache) + { + this.evaluatorsCache = evaluatorsCache; + } + + public void setSectionsByAreaCache(SimpleCache>> sectionsByAreaCache) + { + this.sectionsByAreaCache = sectionsByAreaCache; + } + + public void setSectionsCache(SimpleCache> sectionsCache) + { + this.sectionsCache = sectionsCache; + } + + public void setElementReadersCache(SimpleCache> elementReadersCache) + { + this.elementReadersCache = elementReadersCache; + } + + + /** + * Constructs an XMLConfigService using the given config source + * + * @param configSource + * A ConfigSource + */ + public RepoXMLConfigService(ConfigSource configSource) + { + super(configSource); + } + + public void init() + { + // can be null e.g. initial login, after fresh bootstrap + String currentUser = authenticationComponent.getCurrentUserName(); + if (currentUser == null) + { + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + } + + UserTransaction userTransaction = transactionService.getUserTransaction(); + + try + { + userTransaction.begin(); + + super.init(); // parse config and initialise caches + + 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) + { + authenticationComponent.clearCurrentSecurityContext(); + } + } + } + + public void destroy() + { + super.destroy(); + + logger.info("Config destroyed"); + } + + + @Override + protected void onBootstrap(ApplicationEvent event) + { + // run as System on bootstrap + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + init(); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + if (tenantService.isEnabled() && (tenantDeployerService != null)) + { + tenantDeployerService.deployTenants(this, logger); + tenantDeployerService.register(this); + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // run as System on shutdown + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + destroy(); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + if (tenantService.isEnabled() && (tenantDeployerService != null)) + { + tenantDeployerService.undeployTenants(this, logger); + tenantDeployerService.unregister(this); + } + } + + public void onEnableTenant() + { + init(); // will be called in context of tenant + } + + public void onDisableTenant() + { + destroy(); // will be called in context of tenant + } + + + @Override + protected ConfigImpl getGlobalConfigImpl() + { + String tenantDomain = getTenantDomain(); + ConfigImpl globalConfig = null; + try + { + readLock.lock(); + globalConfig = globalConfigCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (globalConfig == null) + { + reset(); // reset caches - may have been invalidated (e.g. in a cluster) + + try + { + readLock.lock(); + globalConfig = globalConfigCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (globalConfig == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to reset caches (globalConfigCache) " + tenantDomain); + } + } + return globalConfig; + } + + @Override + protected void putGlobalConfig(ConfigImpl globalConfig) + { + try + { + writeLock.lock(); + globalConfigCache.put(getTenantDomain(), globalConfig); + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected void removeGlobalConfig() + { + try + { + writeLock.lock(); + String tenantDomain = getTenantDomain(); + if (globalConfigCache.get(tenantDomain) != null) + { + globalConfigCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected Map getEvaluators() + { + String tenantDomain = getTenantDomain(); + Map evaluators = null; + try + { + readLock.lock(); + evaluators = evaluatorsCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (evaluators == null) + { + reset(); // reset caches - may have been invalidated (e.g. in a cluster) + + try + { + readLock.lock(); + evaluators = evaluatorsCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (evaluators == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to reset caches (evaluatorsCache) " + tenantDomain); + } + } + return evaluators; + } + + @Override + protected void putEvaluators(Map evaluators) + { + try + { + writeLock.lock(); + evaluatorsCache.put(getTenantDomain(), evaluators); + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected void removeEvaluators() + { + try + { + writeLock.lock(); + String tenantDomain = getTenantDomain(); + if (evaluatorsCache.get(tenantDomain) != null) + { + evaluatorsCache.get(tenantDomain).clear(); + evaluatorsCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected Map> getSectionsByArea() + { + String tenantDomain = getTenantDomain(); + Map> sectionsByArea = null; + try + { + readLock.lock(); + sectionsByArea = sectionsByAreaCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (sectionsByArea == null) + { + reset(); // reset caches - may have been invalidated (e.g. in a cluster) + + try + { + readLock.lock(); + sectionsByArea = sectionsByAreaCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (sectionsByArea == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to reset caches (sectionsByAreaCache) " + tenantDomain); + } + } + return sectionsByArea; + } + + @Override + protected void putSectionsByArea(Map> sectionsByArea) + { + try + { + writeLock.lock(); + sectionsByAreaCache.put(getTenantDomain(), sectionsByArea); + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected void removeSectionsByArea() + { + try + { + writeLock.lock(); + String tenantDomain = getTenantDomain(); + if (sectionsByAreaCache.get(tenantDomain) != null) + { + sectionsByAreaCache.get(tenantDomain).clear(); + sectionsByAreaCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected List getSections() + { + String tenantDomain = getTenantDomain(); + List sections = null; + try + { + readLock.lock(); + sections = sectionsCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (sections == null) + { + reset(); // reset caches - may have been invalidated (e.g. in a cluster) + + try + { + readLock.lock(); + sections = sectionsCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (sections == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to reset caches (sectionsCache) " + tenantDomain); + } + } + return sections; + } + + @Override + protected void putSections(List sections) + { + try + { + writeLock.lock(); + sectionsCache.put(getTenantDomain(), sections); + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected void removeSections() + { + try + { + writeLock.lock(); + String tenantDomain = getTenantDomain(); + if (sectionsCache.get(tenantDomain) != null) + { + sectionsCache.get(tenantDomain).clear(); + sectionsCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected Map getElementReaders() + { + String tenantDomain = getTenantDomain(); + Map elementReaders = null; + try + { + readLock.lock(); + elementReaders = elementReadersCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (elementReaders == null) + { + reset(); // reset caches - may have been invalidated (e.g. in a cluster) + + try + { + readLock.lock(); + elementReaders = elementReadersCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (elementReaders == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to reset caches (elementReadersCache) " + tenantDomain); + } + } + return elementReaders; + } + + @Override + protected void putElementReaders(Map elementReader) + { + try + { + writeLock.lock(); + elementReadersCache.put(getTenantDomain(), elementReader); + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected void removeElementReaders() + { + try + { + writeLock.lock(); + String tenantDomain = getTenantDomain(); + if (elementReadersCache.get(tenantDomain) != null) + { + elementReadersCache.get(tenantDomain).clear(); + elementReadersCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + // local helper - returns tenant domain (or empty string if default non-tenant) + private String getTenantDomain() + { + return tenantService.getCurrentUserDomain(); + } +} diff --git a/source/java/org/alfresco/repo/tenant/SingleTDeployerServiceImpl.java b/source/java/org/alfresco/repo/tenant/SingleTDeployerServiceImpl.java new file mode 100644 index 0000000000..9a7f4d8923 --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/SingleTDeployerServiceImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program 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 2 + * of the License, or (at your option) any later version. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.tenant; + +import org.apache.commons.logging.Log; + +/** + * Empty Tenant Deployer Service implementation (for Single-Tenant / Single-Instance) + */ + +public class SingleTDeployerServiceImpl implements TenantDeployerService +{ + public void deployTenants(final TenantDeployer deployer, Log logger) + { + // NOOP + } + + public void undeployTenants(final TenantDeployer deployer, Log logger) + { + // NOOP + } + + public void register(TenantDeployer tenantDeployer) + { + // NOOP + } + + public void unregister(TenantDeployer tenantDeployer) + { + // NOOP + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/tenant/TenantDeployer.java b/source/java/org/alfresco/repo/tenant/TenantDeployer.java new file mode 100644 index 0000000000..a53037157b --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/TenantDeployer.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program 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 2 + * of the License, or (at your option) any later version. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.tenant; + + +/** + * Tenant Deployer interface. + *

+ * This interface allows components to be notified of tenant events. + * Components will register with TenantAdminService. + * Also callbacks used during bootstrap (init) and shutdown (destroy) + * + */ + +public interface TenantDeployer +{ + public void onEnableTenant(); + + public void onDisableTenant(); + + // callback for bootstrap (for each tenant) + public void init(); + + // callback for shutdown (for each tenant) + public void destroy(); +} diff --git a/source/java/org/alfresco/repo/tenant/TenantDeployerService.java b/source/java/org/alfresco/repo/tenant/TenantDeployerService.java new file mode 100644 index 0000000000..9d575006a4 --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/TenantDeployerService.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program 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 2 + * of the License, or (at your option) any later version. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.tenant; + +import org.apache.commons.logging.Log; + + +/** + * Tenant Deployer Service interface. + *

+ * This interface allows components to register with the Tenant Deployer Service. + * Components can then deploy and undeploy tenants during bootstrap and shutdown. + * + */ + +public interface TenantDeployerService +{ + public void deployTenants(final TenantDeployer deployer, Log logger); + + public void undeployTenants(final TenantDeployer deployer, Log logger); + + public void register(TenantDeployer tenantDeployer); + + public void unregister(TenantDeployer tenantDeployer); +}