diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 82632ceeef..2e935fd494 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -5,12 +5,8 @@ - - - - - - + + @@ -21,6 +17,7 @@ + @@ -30,10 +27,8 @@ - - - - + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index c03d087d7b..989ed85871 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -226,12 +226,8 @@ - - - - - - + + diff --git a/config/alfresco/bootstrap/tenant-single-admin-context.xml b/config/alfresco/bootstrap/st-admin-context.xml old mode 100644 new mode 100755 similarity index 100% rename from config/alfresco/bootstrap/tenant-single-admin-context.xml rename to config/alfresco/bootstrap/st-admin-context.xml diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 67e725495a..cbb4f02b17 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -21,8 +21,10 @@ + + - + @@ -38,11 +40,6 @@ 14 - - - - - @@ -50,7 +47,16 @@ - + + + + + + + + + + @@ -66,9 +72,6 @@ - - - @@ -79,6 +82,12 @@ + + + + + + diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 63d829114e..4c070b0353 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -403,4 +403,22 @@ overflowToDisk="false" /> + + + + + + + + \ 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 1bc67dcd83..00eff08f75 100644 --- a/config/alfresco/extension/ehcache-custom.xml.sample.cluster +++ b/config/alfresco/extension/ehcache-custom.xml.sample.cluster @@ -708,5 +708,42 @@ replicateAsynchronously = false"/> - + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/extension/mt/mt-admin-context.xml.sample b/config/alfresco/extension/mt/mt-admin-context.xml.sample new file mode 100644 index 0000000000..5163eeeac8 --- /dev/null +++ b/config/alfresco/extension/mt/mt-admin-context.xml.sample @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alfresco.messages.tenant-interpreter-help + + + + + diff --git a/config/alfresco/extension/mt/mt-contentstore-context.xml.sample b/config/alfresco/extension/mt/mt-contentstore-context.xml.sample new file mode 100644 index 0000000000..dec5bd5289 --- /dev/null +++ b/config/alfresco/extension/mt/mt-contentstore-context.xml.sample @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + org.alfresco.cache.tenantFileStoresCache + + + + + + + + + + + + ${dir.contentstore} + + + + + + + + + + + + + + + + + + + + + + + + + + 14 + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/extension/mt/mt-context.xml.sample b/config/alfresco/extension/mt/mt-context.xml.sample new file mode 100644 index 0000000000..d2a1d3f43a --- /dev/null +++ b/config/alfresco/extension/mt/mt-context.xml.sample @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + org.alfresco.cache.tenantsCache + + + + + + + + + + + + + + + + org.alfresco.tenantsTransactionalCache + + + 10 + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/messages/tenant-interpreter-help.properties b/config/alfresco/messages/tenant-interpreter-help.properties new file mode 100755 index 0000000000..0fb131283b --- /dev/null +++ b/config/alfresco/messages/tenant-interpreter-help.properties @@ -0,0 +1 @@ +tenant_console.help=alfresco/messages/tenant-interpreter-help.txt diff --git a/config/alfresco/messages/tenant-interpreter-help.txt b/config/alfresco/messages/tenant-interpreter-help.txt new file mode 100755 index 0000000000..affdb360ed --- /dev/null +++ b/config/alfresco/messages/tenant-interpreter-help.txt @@ -0,0 +1,74 @@ +## +## Meta commands +## + +ok> help + + List this help. + +ok> r + + Repeat last command. + +ok> quit | exit + + Quit this console. + + +## +## Tenant Commands - for administering tenants +## + + +ok> show tenants + + List all tenants and show their details. + +ok> show tenant + + Show tenant details - status (ie. whether enabled or disabled) and root contentstore directory. + + Example: show tenant yyy.zzz.com + +ok> create [] + + Create tenant. By default the tenant will be enabled. It will have an admin + user called "admin@" with supplied admin password. The root + of the contentstore directory can be optionally specified, otherwise it + will default to the repository default root contentstore (as specified by + the dir.contentstore property). The default workflows will also be bootstrapped. + + Examples: create xxx.com h3ll0 + create yyy.zzz.com g00dby3 C:/tenantstores/yyy.zzz + +ok> createWithoutWorkflows [] + + Same as create, except the default workflows will not be bootstrapped. + +ok> bootstrapWorkflows + + Bootstrap the default workflows. + + Examples: bootstrapWorkflows yyy.zzz.com + +ok> changeAdminPassword + + Useful if the tenant's admin (admin@ enable + + Enable tenant so that is active and available for new logins + + Example: enable yyy.zzz.com + +ok> disable + + Disable tenant so that is inactive. Existing logins will fail on next usage. + + Example: enable yyy.zzz.com + +## +## end +## diff --git a/config/alfresco/repo-admin-context.xml b/config/alfresco/repo-admin-context.xml index c00c67880c..14b9e8d22d 100755 --- a/config/alfresco/repo-admin-context.xml +++ b/config/alfresco/repo-admin-context.xml @@ -37,7 +37,7 @@ - + diff --git a/config/alfresco/tenant-single-context.xml b/config/alfresco/st-context.xml old mode 100644 new mode 100755 similarity index 100% rename from config/alfresco/tenant-single-context.xml rename to config/alfresco/st-context.xml diff --git a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java index 2399827d42..66abcdd3d8 100755 --- a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java @@ -334,7 +334,13 @@ public class RepoAdminServiceImpl implements RepoAdminService NodeRef modelNodeRef = nodeRefs.get(0); - boolean isActive = ((Boolean)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE)).booleanValue(); + boolean isActive = false; + Boolean value = (Boolean)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE); + if (value != null) + { + isActive = value.booleanValue(); + } + QName modelQName = (QName)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_NAME); ModelDefinition modelDef = null; @@ -436,7 +442,13 @@ public class RepoAdminServiceImpl implements RepoAdminService NodeRef modelNodeRef = nodeRefs.get(0); - boolean isActive = ((Boolean)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE)).booleanValue(); + boolean isActive = false; + Boolean value = (Boolean)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE); + if (value != null) + { + isActive = value.booleanValue(); + } + modelQName = (QName)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_NAME); ModelDefinition modelDef = null; diff --git a/source/java/org/alfresco/repo/admin/patch/impl/GenericWorkflowPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/GenericWorkflowPatch.java index c6a8278055..57cb01f7aa 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/GenericWorkflowPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/GenericWorkflowPatch.java @@ -83,7 +83,7 @@ public class GenericWorkflowPatch extends AbstractPatch implements ApplicationCo props.put(WorkflowDeployer.REDEPLOY, "true"); } deployer.setWorkflowDefinitions(workflowDefinitions); - deployer.deploy(); + deployer.init(); // done return I18NUtil.getMessage(MSG_DEPLOYED, workflowDefinitions.size()); diff --git a/source/java/org/alfresco/repo/content/TenantRoutingFileContentStore.java b/source/java/org/alfresco/repo/content/TenantRoutingFileContentStore.java new file mode 100755 index 0000000000..8622731268 --- /dev/null +++ b/source/java/org/alfresco/repo/content/TenantRoutingFileContentStore.java @@ -0,0 +1,139 @@ +/* + * 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.content; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.content.filestore.FileContentStore; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.Tenant; +import org.alfresco.repo.tenant.TenantDeployer; +import org.alfresco.repo.tenant.TenantService; + +/** + * Content Store that supports tenant routing, if multi-tenancy is enabled. + * + * Note: Need to initialise before the dictionary service, in the case that models are dynamically loaded for the tenant. + */ +public class TenantRoutingFileContentStore extends AbstractRoutingContentStore implements TenantDeployer +{ + Map tenantFileStores = new HashMap(); + + private String defaultRootDirectory; + private TenantService tenantService; + + + public void setDefaultRootDir(String defaultRootDirectory) + { + this.defaultRootDirectory = defaultRootDirectory; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + protected ContentStore selectWriteStore(ContentContext ctx) + { + return getTenantFileStore(tenantService.getCurrentUserDomain()); + } + + public List getAllStores() + { + if (tenantService.isEnabled()) + { + String currentUser = AuthenticationUtil.getCurrentUserName(); + if ((currentUser == null) || (currentUser.equals(AuthenticationUtil.getSystemUserName()))) + { + // return enabled stores across all tenants, if running as system/null user, for example, ContentStoreCleaner scheduled job + List allEnabledStores = new ArrayList(); + for (String tenantDomain : tenantFileStores.keySet()) + { + allEnabledStores.add(tenantFileStores.get(tenantDomain)); // note: cache should only contain enabled stores + } + return allEnabledStores; + } + } + return Arrays.asList(getTenantFileStore(tenantService.getCurrentUserDomain())); + } + + private ContentStore getTenantFileStore(String tenantDomain) + { + return tenantFileStores.get(tenantDomain); + } + + private void putTenantFileStore(String tenantDomain, FileContentStore fileStore) + { + tenantFileStores.put(tenantDomain, fileStore); + } + + private void removeTenantFileStore(String tenantDomain) + { + tenantFileStores.remove(tenantDomain); + } + + public void init() + { + String tenantDomain = ""; + String rootDir = defaultRootDirectory; + + Tenant tenant = tenantService.getTenant(tenantService.getCurrentUserDomain()); + if (tenant != null) + { + if (tenant.getRootContentStoreDir() != null) + { + rootDir = tenant.getRootContentStoreDir(); + } + tenantDomain = tenant.getTenantDomain(); + } + + putTenantFileStore(tenantDomain, new FileContentStore(new File(rootDir))); + } + + public void destroy() + { + removeTenantFileStore(tenantService.getCurrentUserDomain()); + } + + public void onEnableTenant() + { + init(); + } + + public void onDisableTenant() + { + destroy(); + } + + public String getDefaultRootDir() + { + return this.defaultRootDirectory; + } +} diff --git a/source/java/org/alfresco/repo/i18n/MessageServiceImpl.java b/source/java/org/alfresco/repo/i18n/MessageServiceImpl.java index 9f5db3b263..17f3d4902d 100644 --- a/source/java/org/alfresco/repo/i18n/MessageServiceImpl.java +++ b/source/java/org/alfresco/repo/i18n/MessageServiceImpl.java @@ -175,8 +175,12 @@ public class MessageServiceImpl implements MessageService try { - writeLock.lock(); - tenantResourceBundleBaseNames.add(resBundlePath); + writeLock.lock(); + + if (! tenantResourceBundleBaseNames.contains(resBundlePath)) + { + tenantResourceBundleBaseNames.add(resBundlePath); + } logger.info("Registered message bundle '" + resBundlePath + "'"); @@ -333,7 +337,10 @@ public class MessageServiceImpl implements MessageService try { - resourcebundle = new PropertyResourceBundle(resBundleStream); + if (resBundleStream != null) + { + resourcebundle = new PropertyResourceBundle(resBundleStream); + } } catch (IOException ioe) { @@ -346,12 +353,15 @@ public class MessageServiceImpl implements MessageService resourcebundle = ResourceBundle.getBundle(resBundlePath, locale); } - // unload from the cached messages - Enumeration enumKeys = resourcebundle.getKeys(); - while (enumKeys.hasMoreElements() == true) + if (resourcebundle != null) { - String key = enumKeys.nextElement(); - props.remove(key); + // unload from the cached messages + Enumeration enumKeys = resourcebundle.getKeys(); + while (enumKeys.hasMoreElements() == true) + { + String key = enumKeys.nextElement(); + props.remove(key); + } } loadedBundles.remove(resBundlePath); @@ -480,7 +490,10 @@ public class MessageServiceImpl implements MessageService try { - resourcebundle = new PropertyResourceBundle(resBundleStream); + if (resBundleStream != null) + { + resourcebundle = new PropertyResourceBundle(resBundleStream); + } } catch (IOException ioe) { @@ -493,15 +506,18 @@ public class MessageServiceImpl implements MessageService resourcebundle = ResourceBundle.getBundle(resBundlePath, locale); } - Enumeration enumKeys = resourcebundle.getKeys(); - while (enumKeys.hasMoreElements() == true) + if (resourcebundle != null) { - String key = enumKeys.nextElement(); - props.put(key, resourcebundle.getString(key)); + Enumeration enumKeys = resourcebundle.getKeys(); + while (enumKeys.hasMoreElements() == true) + { + String key = enumKeys.nextElement(); + props.put(key, resourcebundle.getString(key)); + } + + loadedBundles.add(resBundlePath); + count++; } - - loadedBundles.add(resBundlePath); - count++; } } @@ -543,7 +559,8 @@ public class MessageServiceImpl implements MessageService if ((nodeRefs == null) || (nodeRefs.size() == 0)) { - throw new RuntimeException("Could not find message resource bundle " + storeRef + path); + logger.debug("Could not find message resource bundle " + storeRef + "/" + path); + return null; } } diff --git a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java index 85d366b63d..b9e07c25c6 100644 --- a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java +++ b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java @@ -323,8 +323,12 @@ public class ImporterBootstrap extends AbstractLifecycleBean UserTransaction userTransaction = transactionService.getUserTransaction(); Authentication authentication = authenticationComponent.getCurrentAuthentication(); - authenticationComponent.setSystemUserAsCurrentUser(); - + + if (authenticationComponent.getCurrentUserName() == null) + { + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + } + try { userTransaction.begin(); diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java new file mode 100755 index 0000000000..3cacd446ba --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -0,0 +1,882 @@ +/* + * 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 java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.transaction.UserTransaction; + +import net.sf.acegisecurity.providers.encoding.PasswordEncoder; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.admin.RepoModelDefinition; +import org.alfresco.repo.attributes.BooleanAttributeValue; +import org.alfresco.repo.attributes.MapAttribute; +import org.alfresco.repo.attributes.MapAttributeValue; +import org.alfresco.repo.attributes.StringAttributeValue; +import org.alfresco.repo.content.TenantRoutingFileContentStore; +import org.alfresco.repo.dictionary.DictionaryComponent; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.node.db.DbNodeServiceImpl; +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.workflow.WorkflowDeployer; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.AbstractLifecycleBean; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.core.io.ClassPathResource; + +/** + * MT Admin Service Implementation. + * + */ + +public class MultiTAdminServiceImpl extends AbstractLifecycleBean implements TenantAdminService, TenantDeployerService +{ + // Logger + private static Log logger = LogFactory.getLog(MultiTAdminServiceImpl.class); + + // Dependencies + private DbNodeServiceImpl nodeService; // TODO - replace with NodeService, when deleteStore is exposed via public API + private DictionaryComponent dictionaryComponent; + private RepoAdminService repoAdminService; + private AuthenticationComponent authenticationComponent; + private TransactionService transactionService; + private MultiTServiceImpl tenantService; + private AttributeService attributeService; + private PasswordEncoder passwordEncoder; + private TenantRoutingFileContentStore tenantFileContentStore; + private WorkflowService workflowService; + + + protected final static String REGEX_VALID_TENANT_NAME = "^[a-zA-Z0-9]([a-zA-Z0-9]|.[a-zA-Z0-9])*$"; // note: must also be a valid filename + + public void setNodeService(DbNodeServiceImpl dbNodeService) + { + this.nodeService = dbNodeService; + } + + public void setDictionaryComponent(DictionaryComponent dictionaryComponent) + { + this.dictionaryComponent = dictionaryComponent; + } + + public void setRepoAdminService(RepoAdminService repoAdminService) + { + this.repoAdminService = repoAdminService; + } + + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setTenantService(MultiTServiceImpl tenantService) + { + this.tenantService = tenantService; + } + + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + public void setPasswordEncoder(PasswordEncoder passwordEncoder) + { + this.passwordEncoder = passwordEncoder; + } + + public void setTenantFileContentStore(TenantRoutingFileContentStore tenantFileContentStore) + { + this.tenantFileContentStore = tenantFileContentStore; + } + + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + public static final String PROTOCOL_STORE_USER = "user"; + public static final String PROTOCOL_STORE_WORKSPACE = "workspace"; + public static final String PROTOCOL_STORE_SYSTEM = "system"; + public static final String PROTOCOL_STORE_ARCHIVE = "archive"; + public static final String STORE_BASE_ID_USER = "alfrescoUserStore"; + public static final String STORE_BASE_ID_SYSTEM = "system"; + public static final String STORE_BASE_ID_VERSION = "lightWeightVersionStore"; + public static final String STORE_BASE_ID_SPACES = "SpacesStore"; + + + private static final String TENANTS_ATTRIBUTE_PATH = "alfresco-tenants"; + private static final String TENANT_ATTRIBUTE_ENABLED = "enabled"; + private static final String TENANT_ROOT_CONTENT_STORE_DIR = "rootContentStoreDir"; + + private static final String ADMIN_BASENAME = TenantService.ADMIN_BASENAME; + + private List tenantDeployers = new ArrayList(); + + + @Override + protected void onBootstrap(ApplicationEvent event) + { + // initialise the tenant admin service and status of tenants (using attribute service) + // note: this requires that the repository schema has already been initialised + + // register dictionary - to allow enable/disable tenant callbacks + register(dictionaryComponent); + + // register file store - to allow enable/disable tenant callbacks + register(tenantFileContentStore); + + UserTransaction userTransaction = transactionService.getUserTransaction(); + authenticationComponent.setSystemUserAsCurrentUser(); + + try + { + userTransaction.begin(); + + // bootstrap Tenant Service internal cache + List tenants = getAllTenants(); + + if (tenants != null) + { + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + // this will also call tenant deployers registered so far ... + enableTenant(tenant.getTenantDomain(), true); + } + else + { + // explicitly disable, without calling disableTenant callback + disableTenant(tenant.getTenantDomain(), false); + } + } + + tenantService.register(this); // callback to refresh tenantStatus cache + } + + userTransaction.commit(); + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to bootstrap tenants", e); + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + tenantDeployers.clear(); + tenantDeployers = null; + } + + /** + * @see TenantAdminService.createTenant() + */ + public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword) + { + createTenant(tenantDomain, tenantAdminRawPassword, null); + } + + /** + * @see TenantAdminService.createTenant() + */ + public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword, String rootContentStoreDir) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + ParameterCheck.mandatory("tenantAdminRawPassword", tenantAdminRawPassword); + + if (! Pattern.matches(REGEX_VALID_TENANT_NAME, tenantDomain)) + { + throw new IllegalArgumentException(tenantDomain + " is not a valid tenant name (must match " + REGEX_VALID_TENANT_NAME + ")"); + } + + if (existsTenant(tenantDomain)) + { + throw new AlfrescoRuntimeException("Tenant already exists: " + tenantDomain); + } + else + { + authenticationComponent.setSystemUserAsCurrentUser(); + + if (rootContentStoreDir == null) + { + rootContentStoreDir = tenantFileContentStore.getDefaultRootDir(); + } + + // init - need to enable tenant (including tenant service) before stores bootstrap + Tenant tenant = new Tenant(tenantDomain, true, rootContentStoreDir); + putTenantAttributes(tenantDomain, tenant); + + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + dictionaryComponent.init(); + tenantFileContentStore.init(); + + // create tenant-specific stores + bootstrapUserTenantStore(tenantDomain, tenantAdminRawPassword); + bootstrapSystemTenantStore(tenantDomain); + bootstrapVersionTenantStore(tenantDomain); + bootstrapSpacesArchiveTenantStore(tenantDomain); + bootstrapSpacesTenantStore(tenantDomain); + + // notify listeners that tenant has been created & hence enabled + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onEnableTenant(); + } + + return null; + } + }, getTenantAdminUser(tenantDomain)); + } + + logger.info("Tenant created: " + tenantDomain); + } + + public boolean existsTenant(String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + return (getTenantAttributes(tenantDomain) != null); + } + + private void putTenantAttributes(String tenantDomain, Tenant tenant) + { + if (! attributeService.exists(TENANTS_ATTRIBUTE_PATH)) + { + // bootstrap + attributeService.setAttribute("", TENANTS_ATTRIBUTE_PATH, new MapAttributeValue()); + } + + MapAttribute tenantProps = new MapAttributeValue(); + tenantProps.put(TENANT_ATTRIBUTE_ENABLED, new BooleanAttributeValue(tenant.isEnabled())); + tenantProps.put(TENANT_ROOT_CONTENT_STORE_DIR, new StringAttributeValue(tenant.getRootContentStoreDir())); + + attributeService.setAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain, tenantProps); + + // update tenant status cache + ((MultiTServiceImpl)tenantService).putTenant(tenantDomain, tenant); + } + + private Tenant getTenantAttributes(String tenantDomain) + { + if (attributeService.exists(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain)) + { + MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain); + if (map != null) + { + return new Tenant(tenantDomain, + map.get(TENANT_ATTRIBUTE_ENABLED).getBooleanValue(), + map.get(TENANT_ROOT_CONTENT_STORE_DIR).getStringValue()); + } + } + + return null; + } + + public void enableTenant(String tenantDomain) + { + if (isEnabledTenant(tenantDomain)) + { + logger.warn("Tenant already enabled: " + tenantDomain); + } + + enableTenant(tenantDomain, true); + } + + private void enableTenant(String tenantDomain, boolean notifyTenantDeployers) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + Tenant tenant = getTenantAttributes(tenantDomain); + tenant = new Tenant(tenantDomain, true, tenant.getRootContentStoreDir()); // enable + putTenantAttributes(tenantDomain, tenant); + + if (notifyTenantDeployers) + { + // notify listeners that tenant has been enabled + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onEnableTenant(); + } + return null; + } + }, getTenantAdminUser(tenantDomain)); + } + + logger.info("Tenant enabled: " + tenantDomain); + } + + public void disableTenant(String tenantDomain) + { + if (! isEnabledTenant(tenantDomain)) + { + logger.warn("Tenant already disabled: " + tenantDomain); + } + + disableTenant(tenantDomain, true); + } + + public void disableTenant(String tenantDomain, boolean notifyTenantDeployers) + { + if (notifyTenantDeployers) + { + // notify listeners that tenant has been disabled + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onDisableTenant(); + } + return null; + } + }, getTenantAdminUser(tenantDomain)); + } + + // update tenant attributes / tenant cache - need to disable after notifying listeners (else they cannot disable) + Tenant tenant = getTenantAttributes(tenantDomain); + tenant = new Tenant(tenantDomain, false, tenant.getRootContentStoreDir()); // disable + putTenantAttributes(tenantDomain, tenant); + + logger.info("Tenant disabled: " + tenantDomain); + } + + public boolean isEnabledTenant(String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + Tenant tenant = getTenantAttributes(tenantDomain); + if (tenant != null) + { + return tenant.isEnabled(); + } + + return false; + } + + protected String getRootContentStoreDir(String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + Tenant tenant = getTenantAttributes(tenantDomain); + if (tenant != null) + { + return tenant.getRootContentStoreDir(); + } + + return null; + } + + protected void putRootContentStoreDir(String tenantDomain, String rootContentStoreDir) + { + Tenant tenant = getTenantAttributes(tenantDomain); + tenant = new Tenant(tenantDomain, tenant.isEnabled(), rootContentStoreDir); + putTenantAttributes(tenantDomain, tenant); + } + + public Tenant getTenant(String tenantDomain) + { + return new Tenant(tenantDomain, isEnabledTenant(tenantDomain), getRootContentStoreDir(tenantDomain)); + } + + public void bootstrapWorkflows() + { + // use this to deploy standard workflow process defs to the JBPM engine + WorkflowDeployer workflowBootstrap = (WorkflowDeployer)getApplicationContext().getBean("workflowBootstrap"); + + String resourceClasspath = null; + + // Workflow process definitions + try + { + List workflowDefs = workflowBootstrap.getWorkflowDefinitions(); + if (workflowDefs != null) + { + for (Properties workflowDefProps : workflowDefs) + { + resourceClasspath = workflowDefProps.getProperty(WorkflowDeployer.LOCATION); + ClassPathResource resource = new ClassPathResource(resourceClasspath); + workflowService.deployDefinition(workflowDefProps.getProperty(WorkflowDeployer.ENGINE_ID), resource.getInputStream(), workflowDefProps.getProperty(WorkflowDeployer.MIMETYPE)); + } + } + } + catch (IOException ioe) + { + throw new AlfrescoRuntimeException("Failed to find workflow process def: " + resourceClasspath); + } + + logger.info("Tenant workflows bootstrapped: " + tenantService.getCurrentUserDomain()); + } + + /** + * @see TenantAdminService.deleteTenant() + */ + public void deleteTenant(String tenantDomain) + { + if (! existsTenant(tenantDomain)) + { + throw new RuntimeException("Tenant does not exist: " + tenantDomain); + } + else + { + try + { + final String tenantAdminUser = getTenantAdminUser(tenantDomain); + + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + List workflowDefs = workflowService.getDefinitions(); + if (workflowDefs != null) + { + for (WorkflowDefinition workflowDef : workflowDefs) + { + workflowService.undeployDefinition(workflowDef.getId()); + } + } + + List messageResourceBundles = repoAdminService.getMessageBundles(); + if (messageResourceBundles != null) + { + for (String messageResourceBundle : messageResourceBundles) + { + repoAdminService.undeployMessageBundle(messageResourceBundle); + } + } + + List models = repoAdminService.getModels(); + if (models != null) + { + for (RepoModelDefinition model : models) + { + repoAdminService.undeployModel(model.getRepoName()); + } + } + + return null; + } + }, tenantAdminUser); + + + // delete tenant-specific stores + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_SPACES))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_ARCHIVE, STORE_BASE_ID_SPACES))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_SYSTEM, STORE_BASE_ID_SYSTEM))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_USER, STORE_BASE_ID_USER))); + + + // notify listeners that tenant has been deleted & hence disabled + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onDisableTenant(); + } + return null; + } + }, tenantAdminUser); + + // remove tenant + attributeService.removeAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain); + } + catch (Throwable t) + { + throw new AlfrescoRuntimeException("Failed to delete tenant: " + tenantDomain, t); + } + } + } + + /** + * @see TenantAdminService.getAllTenants() + */ + public List getAllTenants() + { + MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH); + + List tenants = new ArrayList(); + + if (map != null) + { + // note: getAllTenants is called first, by TenantDeployer - hence need to initialise the TenantService status cache + Set tenantDomains = map.keySet(); + + for (String tenantDomain : tenantDomains) + { + Tenant tenant = getTenantAttributes(tenantDomain); + tenants.add(new Tenant(tenantDomain, tenant.isEnabled(), tenant.getRootContentStoreDir())); + } + } + + return tenants; // list of tenants or empty list + } + + private void bootstrapUserTenantStore(String tenantDomain, char[] tenantAdminRawPassword) + { + // Bootstrap Tenant-Specific User Store + StoreRef bootstrapStoreRef = new StoreRef(PROTOCOL_STORE_USER, tenantService.getName(STORE_BASE_ID_USER, tenantDomain)); + + ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)getApplicationContext().getBean("userBootstrap"); + userImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + // override admin username property + String salt = null; // GUID.generate(); + Properties props = userImporterBootstrap.getConfiguration(); + + props.put("alfresco_user_store.adminusername", getTenantAdminUser(tenantDomain)); + props.put("alfresco_user_store.adminpassword", passwordEncoder.encodePassword(new String(tenantAdminRawPassword), salt)); + + // override guest username property + props.put("alfresco_user_store.guestusername", getTenantGuestUser(tenantDomain)); + + userImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + private void bootstrapSystemTenantStore(String tenantDomain) + { + // Bootstrap Tenant-Specific System Store + StoreRef bootstrapStoreRef = new StoreRef(PROTOCOL_STORE_SYSTEM, tenantService.getName(STORE_BASE_ID_SYSTEM, tenantDomain)); + + ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)getApplicationContext().getBean("systemBootstrap"); + systemImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + // override default property (workspace://SpacesStore) + List mustNotExistStoreUrls = new ArrayList(); + mustNotExistStoreUrls.add(new StoreRef(PROTOCOL_STORE_WORKSPACE, tenantService.getName(STORE_BASE_ID_USER, tenantDomain)).toString()); + systemImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); + + systemImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + private void bootstrapVersionTenantStore(String tenantDomain) + { + // Bootstrap Tenant-Specific Version Store + StoreRef bootstrapStoreRef = new StoreRef(PROTOCOL_STORE_WORKSPACE, tenantService.getName(STORE_BASE_ID_VERSION, tenantDomain)); + + ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)getApplicationContext().getBean("versionBootstrap"); + versionImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + versionImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + private void bootstrapSpacesArchiveTenantStore(String tenantDomain) + { + // Bootstrap Tenant-Specific Spaces Store + StoreRef bootstrapStoreRef = new StoreRef(PROTOCOL_STORE_ARCHIVE, tenantService.getName(STORE_BASE_ID_SPACES, tenantDomain)); + + ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)getApplicationContext().getBean("spacesArchiveBootstrap"); + spacesArchiveImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + // override default property (archive://SpacesStore) + List mustNotExistStoreUrls = new ArrayList(); + mustNotExistStoreUrls.add(new StoreRef(PROTOCOL_STORE_ARCHIVE, tenantService.getName(STORE_BASE_ID_SPACES, tenantDomain)).toString()); + spacesArchiveImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); + + spacesArchiveImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + private void bootstrapSpacesTenantStore(String tenantDomain) + { + // Bootstrap Tenant-Specific Spaces Store + StoreRef bootstrapStoreRef = new StoreRef(PROTOCOL_STORE_WORKSPACE, tenantService.getName(STORE_BASE_ID_SPACES, tenantDomain)); + + final ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)getApplicationContext().getBean("spacesBootstrap"); + spacesImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + // override admin username property + Properties props = spacesImporterBootstrap.getConfiguration(); + props.put("alfresco_user_store.adminusername", getTenantAdminUser(tenantDomain)); + + spacesImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + public void deployTenants(final TenantDeployer deployer, Log logger) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("Deployer must be provided"); + } + if (logger == null) + { + throw new AlfrescoRuntimeException("Logger must be provided"); + } + + if (tenantService.isEnabled()) + { + UserTransaction userTransaction = transactionService.getUserTransaction(); + authenticationComponent.setSystemUserAsCurrentUser(); + + List tenants = null; + try + { + userTransaction.begin(); + tenants = getAllTenants(); + userTransaction.commit(); + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to get tenants", e); + } + + String currentUser = AuthenticationUtil.getCurrentUserName(); + + if (tenants != null) + { + try + { + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + try + { + // switch to admin in order to deploy within context of tenant domain + // assumes each tenant has default "admin" user + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + // init the service within tenant context + deployer.init(); + return null; + } + }, getTenantAdminUser(tenant.getTenantDomain())); + + } + catch (Throwable e) + { + logger.error("Deployment failed" + e); + + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + logger.error(stringWriter.toString()); + + // tenant deploy failure should not necessarily affect other tenants + } + } + } + } + finally + { + if (currentUser != null) { AuthenticationUtil.setCurrentUser(currentUser); } + } + } + } + } + + public void undeployTenants(final TenantDeployer deployer, Log logger) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("Deployer must be provided"); + } + if (logger == null) + { + throw new AlfrescoRuntimeException("Logger must be provided"); + } + + if (tenantService.isEnabled()) + { + UserTransaction userTransaction = transactionService.getUserTransaction(); + authenticationComponent.setSystemUserAsCurrentUser(); + + List tenants = null; + try + { + userTransaction.begin(); + tenants = getAllTenants(); + userTransaction.commit(); + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to get tenants", e); + } + + String currentUser = AuthenticationUtil.getCurrentUserName(); + + if (tenants != null) + { + try + { + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + try + { + // switch to admin in order to deploy within context of tenant domain + // assumes each tenant has default "admin" user + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + // destroy the service within tenant context + deployer.destroy(); + return null; + } + }, getTenantAdminUser(tenant.getTenantDomain())); + + } + catch (Throwable e) + { + logger.error("Undeployment failed" + e); + + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + logger.error(stringWriter.toString()); + + // tenant undeploy failure should not necessarily affect other tenants + } + } + } + } + finally + { + if (currentUser != null) { AuthenticationUtil.setCurrentUser(currentUser); } + } + } + } + } + + public void register(TenantDeployer deployer) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("Deployer must be provided"); + } + + if (! tenantDeployers.contains(deployer)) + { + tenantDeployers.add(deployer); + } + } + + public void unregister(TenantDeployer deployer) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("Deployer must be provided"); + } + + if (tenantDeployers != null) + { + tenantDeployers.remove(deployer); + } + } + + public boolean isEnabled() + { + return tenantService.isEnabled(); + } + + public void resetCache(String tenantDomain) + { + if (existsTenant(tenantDomain)) + { + if (isEnabledTenant(tenantDomain)) + { + enableTenant(tenantDomain); + } + else + { + disableTenant(tenantDomain); + } + } + else + { + throw new AlfrescoRuntimeException("No such tenant " + tenantDomain); + } + } + + // local helper + private String getTenantAdminUser(String tenantDomain) + { + return tenantService.getDomainUser(ADMIN_BASENAME, tenantDomain); + } + + // local helper + private String getTenantGuestUser(String tenantDomain) + { + return tenantService.getDomainUser(authenticationComponent.getGuestUserName(), tenantDomain); + } +} diff --git a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java new file mode 100755 index 0000000000..1e6ec81516 --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java @@ -0,0 +1,568 @@ +/* + * 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 java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +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; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/* + * MT Service implementation + * + * Adapts names to be tenant specific or vice-versa. + */ +public class MultiTServiceImpl implements TenantService +{ + private static Log logger = LogFactory.getLog(MultiTServiceImpl.class); + + // clusterable cache of enabled/disabled tenants - managed via TenantAdmin Service + private SimpleCache tenantsCache; + + private MultiTAdminServiceImpl tenantAdminService = null; // registered (rather than injected) - to avoid circular dependency + + + public void setTenantsCache(SimpleCache tenantsCache) + { + this.tenantsCache = tenantsCache; + } + + public NodeRef getName(NodeRef nodeRef) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("NodeRef", nodeRef); + + return new NodeRef(nodeRef.getStoreRef().getProtocol(), getName(nodeRef.getStoreRef().getIdentifier()), nodeRef.getId()); + } + + public NodeRef getName(NodeRef inNodeRef, NodeRef nodeRef) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("InNodeRef", inNodeRef); + ParameterCheck.mandatory("NodeRef", nodeRef); + + int idx = inNodeRef.getStoreRef().getIdentifier().lastIndexOf(SEPARATOR); + if (idx != -1) + { + String tenantDomain = inNodeRef.getStoreRef().getIdentifier().substring(1, idx); + return new NodeRef(nodeRef.getStoreRef().getProtocol(), getName(nodeRef.getStoreRef().getIdentifier(), tenantDomain), nodeRef.getId()); + } + + return nodeRef; + } + + public StoreRef getName(StoreRef storeRef) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("StoreRef", storeRef); + + return new StoreRef(storeRef.getProtocol(), getName(storeRef.getIdentifier())); + } + + public StoreRef getName(String username, StoreRef storeRef) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("StoreRef", storeRef); + + if (username != null) { + int idx = username.lastIndexOf(SEPARATOR); + if ((idx > 0) && (idx < (username.length()-1))) + { + String tenantDomain = username.substring(idx+1); + return new StoreRef(storeRef.getProtocol(), getName(storeRef.getIdentifier(), tenantDomain)); + } + } + + return storeRef; + } + + protected String getName(String name, String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("name", name); + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + checkTenantEnabled(tenantDomain); + + int idx1 = name.indexOf(SEPARATOR); + if (idx1 != 0) + { + // no domain, so add it as a prefix (between two domain separators) + name = SEPARATOR + tenantDomain + SEPARATOR + name; + } + else + { + int idx2 = name.indexOf(SEPARATOR, 1); + String nameDomain = name.substring(1, idx2); + if (! tenantDomain.equals(nameDomain)) + { + throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = " + nameDomain); + } + } + + return name; + } + + public QName getName(NodeRef inNodeRef, QName name) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("InNodeRef", inNodeRef); + ParameterCheck.mandatory("Name", name); + + int idx = inNodeRef.getStoreRef().getIdentifier().lastIndexOf(SEPARATOR); + if (idx != -1) + { + String tenantDomain = inNodeRef.getStoreRef().getIdentifier().substring(1, idx); + checkTenantEnabled(tenantDomain); + return getName(name, tenantDomain); + } + + return name; + + } + + private QName getName(QName name, String tenantDomain) + { + String namespace = name.getNamespaceURI(); + int idx1 = namespace.indexOf(SEPARATOR); + if (idx1 == -1) + { + // no domain, so add it as a prefix (between two domain separators) + namespace = SEPARATOR + tenantDomain + SEPARATOR + namespace; + name = QName.createQName(namespace, name.getLocalName()); + } + else + { + int idx2 = namespace.indexOf(SEPARATOR, 1); + String nameDomain = namespace.substring(1, idx2); + if (! tenantDomain.equals(nameDomain)) + { + throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = " + nameDomain); + } + } + + return name; + } + + public String getName(String name) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("name", name); + + String tenantDomain = getCurrentUserDomain(); + + if (! tenantDomain.equals("")) + { + int idx1 = name.indexOf(SEPARATOR); + if (idx1 != 0) + { + // no tenant domain prefix, so add it + name = SEPARATOR + tenantDomain + SEPARATOR + name; + } + else + { + int idx2 = name.indexOf(SEPARATOR, 1); + String nameDomain = name.substring(1, idx2); + if (! tenantDomain.equals(nameDomain)) + { + throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = " + nameDomain); + } + } + } + + return name; + } + + public QName getBaseName(QName name, boolean forceForNonTenant) + { + String baseNamespaceURI = getBaseName(name.getNamespaceURI(), forceForNonTenant); + return QName.createQName(baseNamespaceURI, name.getLocalName()); + } + + public NodeRef getBaseName(NodeRef nodeRef) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("NodeRef", nodeRef); + + return new NodeRef(nodeRef.getStoreRef().getProtocol(), getBaseName(nodeRef.getStoreRef().getIdentifier()), nodeRef.getId()); + } + + public StoreRef getBaseName(StoreRef storeRef) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("StoreRef", storeRef); + + return new StoreRef(storeRef.getProtocol(), getBaseName(storeRef.getIdentifier())); + } + + public String getBaseName(String name) + { + // get base name, but don't force for non-tenant user (e.g. super admin) + return getBaseName(name, false); + } + + public String getBaseName(String name, boolean forceForNonTenant) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("name", name); + + String tenantDomain = getCurrentUserDomain(); + + int idx1 = name.indexOf(SEPARATOR); + if (idx1 == 0) + { + int idx2 = name.indexOf(SEPARATOR, 1); + String nameDomain = name.substring(1, idx2); + + if ((! tenantDomain.equals("")) && (! tenantDomain.equals(nameDomain))) + { + throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = " + nameDomain); + } + + if ((! tenantDomain.equals("")) || (forceForNonTenant)) + { + // remove tenant domain + name = name.substring(idx2+1); + } + } + + return name; + } + + public String getBaseNameUser(String name) + { + // can be null (e.g. for System user / during app ctx init) + if (name != null) + { + int idx = name.lastIndexOf(SEPARATOR); + if (idx != -1) + { + return name.substring(0, idx); + } + } + return name; + } + + public void checkDomainUser(String username) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("Username", username); + + String tenantDomain = getCurrentUserDomain(); + + if (! tenantDomain.equals("")) + { + int idx2 = username.lastIndexOf(SEPARATOR); + if ((idx2 > 0) && (idx2 < (username.length()-1))) + { + String tenantUserDomain = username.substring(idx2+1); + + if ((tenantUserDomain == null) || (! tenantDomain.equals(tenantUserDomain))) + { + throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = " + tenantUserDomain); + } + } + else + { + throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = "); + } + } + } + + public void checkDomain(String name) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("Name", name); + + String nameDomain = null; + + int idx1 = name.indexOf(SEPARATOR); + if (idx1 == 0) + { + int idx2 = name.indexOf(SEPARATOR, 1); + nameDomain = name.substring(1, idx2); + } + + String tenantDomain = getCurrentUserDomain(); + + if (((nameDomain == null) && (! tenantDomain.equals(""))) || + ((nameDomain != null) && (! nameDomain.equals(tenantDomain)))) + { + throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = " + nameDomain); + } + } + + public NodeRef getRootNode(NodeService nodeService, SearchService searchService, NamespaceService namespaceService, String rootPath, NodeRef rootNodeRef) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("NodeService", nodeService); + ParameterCheck.mandatory("SearchService", searchService); + ParameterCheck.mandatory("NamespaceService", namespaceService); + ParameterCheck.mandatory("RootPath", rootPath); + ParameterCheck.mandatory("RootNodeRef", rootNodeRef); + + String username = AuthenticationUtil.getCurrentUserName(); + StoreRef storeRef = getName(username, rootNodeRef.getStoreRef()); + + AuthenticationUtil.RunAsWork action = new GetRootNode(nodeService, searchService, namespaceService, rootPath, rootNodeRef, storeRef); + return getBaseName(AuthenticationUtil.runAs(action, AuthenticationUtil.getSystemUserName())); + } + + private class GetRootNode implements AuthenticationUtil.RunAsWork + { + NodeService nodeService; + SearchService searchService; + NamespaceService namespaceService; + String rootPath; + NodeRef rootNodeRef; + StoreRef storeRef; + + GetRootNode(NodeService nodeService, SearchService searchService, NamespaceService namespaceService, String rootPath, NodeRef rootNodeRef, StoreRef storeRef) + { + this.nodeService = nodeService; + this.searchService = searchService; + this.namespaceService = namespaceService; + this.rootPath = rootPath; + this.rootNodeRef = rootNodeRef; + this.storeRef = storeRef; + } + + public NodeRef doWork() throws Exception + { + // Get company home / root for the tenant domain + // Do this as the System user in case the tenant user does not have permission + + // Connect to the repo and ensure that the store exists + if (! nodeService.exists(storeRef)) + { + throw new AlfrescoRuntimeException("Store not created prior to application startup: " + storeRef); + } + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + // Find the root node for this device + List nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPath, null, namespaceService, false); + + if (nodeRefs.size() > 1) + { + throw new AlfrescoRuntimeException("Multiple possible roots for device: \n" + + " root path: " + rootPath + "\n" + + " results: " + nodeRefs); + } + else if (nodeRefs.size() == 0) + { + // nothing found + throw new AlfrescoRuntimeException("No root found for device: \n" + + " root path: " + rootPath); + } + else + { + // we found a node + rootNodeRef = nodeRefs.get(0); + } + + return rootNodeRef; + } + } + + public boolean isTenantUser() + { + return isTenantUser(AuthenticationUtil.getCurrentUserName()); + } + + public boolean isTenantUser(String username) + { + // can be null (e.g. for System user / during app ctx init) + if (username != null) { + int idx = username.lastIndexOf(SEPARATOR); + if ((idx > 0) && (idx < (username.length()-1))) + { + return true; + } + } + return false; + } + + public boolean isTenantName(String name) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("name", name); + + int idx1 = name.indexOf(SEPARATOR); + if (idx1 == 0) + { + int idx2 = name.indexOf(SEPARATOR, 1); + if (idx2 != -1) + { + return true; + } + } + + return false; + } + + public String getCurrentUserDomain() + { + String user = AuthenticationUtil.getCurrentUserName(); + + // can be null (e.g. for System user / during app ctx init) + if (user != null) + { + int idx = user.lastIndexOf(SEPARATOR); + if ((idx > 0) && (idx < (user.length()-1))) + { + String tenantDomain = user.substring(idx+1); + + checkTenantEnabled(tenantDomain); + + return tenantDomain; + } + } + + return ""; // default domain - non-tenant user + } + + public String getDomain(String name) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("name", name); + + String tenantDomain = getCurrentUserDomain(); + + String nameDomain = ""; + + int idx1 = name.indexOf(SEPARATOR); + if (idx1 == 0) + { + int idx2 = name.indexOf(SEPARATOR, 1); + nameDomain = name.substring(1, idx2); + + if ((! tenantDomain.equals("")) && (! tenantDomain.equals(nameDomain))) + { + throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = " + nameDomain); + } + } + + return nameDomain; + } + + public String getDomainUser(String baseUsername, String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("baseUsername", baseUsername); + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + if (! tenantDomain.equals("")) + { + if (baseUsername.contains(SEPARATOR)) + { + throw new AlfrescoRuntimeException("Invalid base username: " + baseUsername); + } + + if (tenantDomain.contains(SEPARATOR)) + { + throw new AlfrescoRuntimeException("Invalid tenant domain: " + tenantDomain); + } + + return baseUsername + SEPARATOR + tenantDomain; + } + else + { + return baseUsername; + } + } + + protected void checkTenantEnabled(String tenantDomain) + { + if (getTenant(tenantDomain).isEnabled() == false) + { + throw new AlfrescoRuntimeException("Tenant is not enabled: " + tenantDomain); + } + } + + public Tenant getTenant(String tenantDomain) + { + Tenant tenant = tenantsCache.get(tenantDomain); + if (tenant == null) + { + // backed by TenantAdminService - update this cache, e.g. could have been invalidated and/or expired + if (tenantAdminService != null) + { + tenant = tenantAdminService.getTenant(tenantDomain); + if (tenant == null) + { + throw new AlfrescoRuntimeException("No such tenant " + tenantDomain); + } + else + { + putTenant(tenantDomain, tenant); + } + } + } + + return tenant; + } + + public boolean isEnabled() + { + return true; + } + + // should only be called by Tenant Admin Service + protected void register(MultiTAdminServiceImpl tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + // should only be called by Tenant Admin Service + protected void putTenant(String tenantDomain, Tenant tenant) + { + if (logger.isDebugEnabled()) + { + logger.debug("putTenant " + tenantDomain); + } + tenantsCache.put(tenantDomain, tenant); + } + + // should only be called by Tenant Admin Service + protected void removeTenant(String tenantDomain) + { + if (logger.isDebugEnabled()) + { + logger.debug("removeTenant " + tenantDomain); + } + tenantsCache.remove(tenantDomain); + } + +} diff --git a/source/java/org/alfresco/repo/tenant/SingleTServiceImpl.java b/source/java/org/alfresco/repo/tenant/SingleTServiceImpl.java index e729fe4e41..d90ac8e823 100644 --- a/source/java/org/alfresco/repo/tenant/SingleTServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/SingleTServiceImpl.java @@ -112,11 +112,6 @@ public class SingleTServiceImpl implements TenantService { return rootNodeRef; } - - public NodeRef getCompanyHomeNode(NodeService nodeService, String username, StoreRef storeRef) - { - return null; - } public boolean isTenantUser() { diff --git a/source/java/org/alfresco/repo/tenant/TenantAdminService.java b/source/java/org/alfresco/repo/tenant/TenantAdminService.java new file mode 100755 index 0000000000..0178847525 --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/TenantAdminService.java @@ -0,0 +1,60 @@ +/* + * 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 java.util.List; + + +/** + * Tenant Admin Service interface. + *

+ * This interface provides administrative methods to provision and administer tenants. + * + */ + +public interface TenantAdminService extends TenantDeployerService +{ + public void createTenant(String tenantDomain, char[] adminRawPassword); + + public void createTenant(String tenantDomain, char[] adminRawPassword, String rootContentStoreDir); + + public boolean existsTenant(String tenantDomain); + + public void bootstrapWorkflows(); + + public void deleteTenant(String tenantDomain); + + public List getAllTenants(); + + public void enableTenant(String tenantDomain); + + public void disableTenant(String tenantDomain); + + public Tenant getTenant(String tenantDomain); + + public boolean isEnabledTenant(String tenantDomain); + + public boolean isEnabled(); +} diff --git a/source/java/org/alfresco/repo/tenant/TenantInterpreter.java b/source/java/org/alfresco/repo/tenant/TenantInterpreter.java new file mode 100755 index 0000000000..bcc27ea737 --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/TenantInterpreter.java @@ -0,0 +1,332 @@ +/* + * 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 java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.List; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.admin.BaseInterpreter; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.ClassPathResource; + +/** + * An interactive console for Tenants. + * + */ +public class TenantInterpreter extends BaseInterpreter +{ + // Service dependencies + + private TenantAdminService tenantAdminService; + + private AuthenticationService authenticationService; + + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public static BaseInterpreter getConsoleBean(ApplicationContext context) + { + return (TenantInterpreter)context.getBean("tenantInterpreter"); + } + + + protected boolean hasAuthority(String username) + { + // must be super "admin" for tenant administrator + return ((username != null) && (username.equals(BaseInterpreter.DEFAULT_ADMIN))); + } + + /** + * Execute a single command using the BufferedReader passed in for any data needed. + * + * TODO: Use decent parser! + * + * @param line The unparsed command + * @return The textual output of the command. + */ + public String executeCommand(String line) + throws IOException + { + String[] command = line.split(" "); + if (command.length == 0) + { + command = new String[1]; + command[0] = line; + } + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + + // repeat last command? + if (command[0].equals("r")) + { + if (lastCommand == null) + { + return "No command entered yet."; + } + return "repeating command " + lastCommand + "\n\n" + executeCommand(lastCommand); + } + + // remember last command + lastCommand = line; + + // execute command + if (command[0].equals("help")) + { + String helpFile = I18NUtil.getMessage("tenant_console.help"); + ClassPathResource helpResource = new ClassPathResource(helpFile); + byte[] helpBytes = new byte[500]; + InputStream helpStream = helpResource.getInputStream(); + try + { + int read = helpStream.read(helpBytes); + while (read != -1) + { + bout.write(helpBytes, 0, read); + read = helpStream.read(helpBytes); + } + } + finally + { + helpStream.close(); + } + } + + else if (command[0].equals("show")) + { + if (command.length < 2) + { + return "Syntax Error, try 'help'.\n"; + } + + else if (command[1].equals("tenants")) + { + List tenants = tenantAdminService.getAllTenants(); + + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + String rootContentStoreDir = tenant.getRootContentStoreDir(); + out.println("Enabled - Tenant: " + tenant.getTenantDomain() + " (" + rootContentStoreDir + ")"); + } + } + + out.println(""); + + for (Tenant tenant : tenants) + { + if (! tenant.isEnabled()) + { + String rootContentStoreDir = tenant.getRootContentStoreDir(); + out.println("Disabled - Tenant: " + tenant.getTenantDomain() + " (" + rootContentStoreDir + ")"); + } + } + } + + else if (command[1].equals("tenant")) + { + if (command.length != 3) + { + return "Syntax Error, try 'help'.\n"; + } + + String tenantDomain = new String(command[2]).toLowerCase(); + Tenant tenant = tenantAdminService.getTenant(tenantDomain); + + String rootContentStoreDir = tenant.getRootContentStoreDir(); + if (tenant.isEnabled()) + { + out.println("Enabled - Tenant: " + tenant.getTenantDomain() + " (" + rootContentStoreDir + ")"); + } + else + { + out.println("Disabled - Tenant: " + tenant.getTenantDomain() + " (" + rootContentStoreDir + ")"); + } + } + + else + { + return "No such sub-command, try 'help'.\n"; + } + } + + else if (command[0].equals("create")) + { + if ((command.length != 3) && (command.length != 4)) + { + return "Syntax Error, try 'help'.\n"; + } + + String newTenant = new String(command[1]).toLowerCase(); + String tenantAdminRawPassword = new String(command[2]); + + String createTenantArgs = newTenant + " " + tenantAdminRawPassword; + if (command.length == 4) + { + createTenantArgs = createTenantArgs + " " + new String(command[3]); + } + + out.print(executeCommand("createWithoutWorkflows " + createTenantArgs)); + out.print(executeCommand("bootstrapWorkflows " + newTenant)); + } + + else if (command[0].equals("createWithoutWorkflows")) + { + if ((command.length != 3) && (command.length != 4)) + { + return "Syntax Error, try 'help'.\n"; + } + + String newTenant = new String(command[1]).toLowerCase(); + char[] tenantAdminRawPassword = new String(command[2]).toCharArray(); + String rootContentStoreDir = null; + if (command.length == 4) + { + rootContentStoreDir = new String(command[3]); + } + + tenantAdminService.createTenant(newTenant, tenantAdminRawPassword, rootContentStoreDir); + + out.println("created tenant: " + newTenant); + } + + else if (command[0].equals("bootstrapWorkflows")) + { + if (command.length != 2) + { + return "Syntax Error, try 'help'.\n"; + } + + String newTenant = new String(command[1]).toLowerCase(); + + String tenantAdminUsername = tenantService.getDomainUser(TenantService.ADMIN_BASENAME, newTenant); + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + tenantAdminService.bootstrapWorkflows(); + return null; + } + }, tenantAdminUsername); + + out.println("bootstrap workflows deployed for tenant: " + newTenant); + } + + // TODO - not fully working yet + else if (command[0].equals("delete")) + { + if (command.length != 2) + { + return "Syntax Error, try 'help'.\n"; + } + + String tenantDomain = new String(command[1]).toLowerCase(); + + tenantAdminService.deleteTenant(tenantDomain); + out.println("Deleted tenant: " + tenantDomain); + } + + else if (command[0].equals("enable")) + { + if (command.length != 2) + { + return "Syntax Error, try 'help'.\n"; + } + + String tenantDomain = new String(command[1]).toLowerCase(); + + tenantAdminService.enableTenant(tenantDomain); + out.println("Enabled tenant: " + tenantDomain); + } + + else if (command[0].equals("disable")) + { + if (command.length != 2) + { + return "Syntax Error, try 'help'.\n"; + } + + String tenantDomain = new String(command[1]).toLowerCase(); + + tenantAdminService.disableTenant(tenantDomain); + out.println("Disabled tenant: " + tenantDomain); + } + + else if (command[0].equals("changeAdminPassword")) + { + if (command.length != 3) + { + return "Syntax Error, try 'help'.\n"; + } + + String tenantDomain = new String(command[1]).toLowerCase(); + + final String newPassword = new String(command[2]); + final String tenantAdminUsername = tenantService.getDomainUser(TenantService.ADMIN_BASENAME, tenantDomain); + + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + RetryingTransactionCallback txnWork = new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + authenticationService.setAuthentication(tenantAdminUsername, newPassword.toCharArray()); + return null; + } + }; + return transactionService.getRetryingTransactionHelper().doInTransaction(txnWork); + } + }, tenantAdminUsername); + } + + else + { + return "No such command, try 'help'.\n"; + } + + out.flush(); + String retVal = new String(bout.toByteArray()); + out.close(); + return retVal; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/tenant/TenantService.java b/source/java/org/alfresco/repo/tenant/TenantService.java index 412c9d24e5..f9f3d71a97 100644 --- a/source/java/org/alfresco/repo/tenant/TenantService.java +++ b/source/java/org/alfresco/repo/tenant/TenantService.java @@ -35,7 +35,7 @@ import org.alfresco.service.namespace.QName; /** * Tenant Service interface. *

- * This interface provides methods to support either Single-Tenant or Multi-Tenant implementations. + * This interface provides methods to support either ST or MT implementations. * */ public interface TenantService @@ -74,8 +74,6 @@ public interface TenantService public NodeRef getRootNode(NodeService nodeService, SearchService searchService, NamespaceService namespaceService, String rootPath, NodeRef rootNodeRef); - public NodeRef getCompanyHomeNode(NodeService nodeService, String username, StoreRef storeRef); - public boolean isTenantUser(); public boolean isTenantUser(String username); diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java index 39e6e7fb41..4c9dcd8cf3 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java @@ -34,6 +34,9 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.dictionary.DictionaryBootstrap; import org.alfresco.repo.dictionary.DictionaryDAO; 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.TenantService; import org.alfresco.service.cmr.view.ImporterException; import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowException; @@ -71,6 +74,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean private List workflowDefinitions; private List models = new ArrayList(); private List resourceBundles = new ArrayList(); + private TenantService tenantService; /** @@ -123,6 +127,16 @@ public class WorkflowDeployer extends AbstractLifecycleBean this.dictionaryDAO = dictionaryDAO; } + /** + * Sets the tenant service + * + * @param tenantService the tenant service + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + /** * Sets the Workflow Definitions * @@ -144,7 +158,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean } /** - * Sets the initial list of Workflow reosurce bundles to bootstrap with + * Sets the initial list of Workflow resource bundles to bootstrap with * * @param modelResources the model names */ @@ -152,11 +166,17 @@ public class WorkflowDeployer extends AbstractLifecycleBean { this.resourceBundles = labels; } + + // used by TenantAdminService when creating a new tenant and bootstrapping the pre-defined workflows + public List getWorkflowDefinitions() + { + return this.workflowDefinitions; + } /** * Deploy the Workflow Definitions */ - public void deploy() + public void init() { if (transactionService == null) { @@ -171,24 +191,30 @@ public class WorkflowDeployer extends AbstractLifecycleBean throw new ImporterException("Workflow Service must be provided"); } + String currentUser = authenticationComponent.getCurrentUserName(); + if (currentUser == null) + { + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + } + UserTransaction userTransaction = transactionService.getUserTransaction(); - authenticationComponent.setSystemUserAsCurrentUser(); try { userTransaction.begin(); - // bootstrap the workflow models and labels + // bootstrap the workflow models and static labels (from classpath) if (models != null && resourceBundles != null) { DictionaryBootstrap dictionaryBootstrap = new DictionaryBootstrap(); dictionaryBootstrap.setDictionaryDAO(dictionaryDAO); + dictionaryBootstrap.setTenantService(tenantService); dictionaryBootstrap.setModels(models); dictionaryBootstrap.setLabels(resourceBundles); - dictionaryBootstrap.bootstrap(); + dictionaryBootstrap.bootstrap(); // also registers with dictionary } - // bootstrap the workflow definitions + // bootstrap the workflow definitions (from classpath) if (workflowDefinitions != null) { for (Properties workflowDefinition : workflowDefinitions) @@ -239,19 +265,29 @@ public class WorkflowDeployer extends AbstractLifecycleBean { // rollback the transaction try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} - try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {} throw new AlfrescoRuntimeException("Workflow deployment failed", e); } finally { - authenticationComponent.clearCurrentSecurityContext(); + if (currentUser == null) + { + authenticationComponent.clearCurrentSecurityContext(); + } } } @Override protected void onBootstrap(ApplicationEvent event) { - deploy(); + // run as System on bootstrap + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + init(); + return null; + } + }, AuthenticationUtil.getSystemUserName()); } @Override