MT extension

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6785 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jan Vonka
2007-09-14 11:20:02 +00:00
parent 6a83f91a6b
commit d9590d3677
25 changed files with 2414 additions and 67 deletions

View File

@@ -5,12 +5,8 @@
<beans > <beans >
<import resource="classpath:alfresco/cache-context.xml" /> <import resource="classpath:alfresco/cache-context.xml" />
<import resource="classpath:alfresco/st-context.xml"/>
<import resource="classpath:alfresco/tenant-single-context.xml"/> <import resource="classpath*:alfresco/extension/mt/mt-context.xml"/>
<!-- if available, will enable multi-tenancy -->
<import resource="classpath*:alfresco/tenant-multi-context.xml"/>
<import resource="classpath:alfresco/core-services-context.xml" /> <import resource="classpath:alfresco/core-services-context.xml" />
<import resource="classpath:alfresco/public-services-context.xml" /> <import resource="classpath:alfresco/public-services-context.xml" />
<import resource="classpath:alfresco/model-specific-services-context.xml" /> <import resource="classpath:alfresco/model-specific-services-context.xml" />
@@ -21,6 +17,7 @@
<import resource="classpath:alfresco/network-protocol-context.xml" /> <import resource="classpath:alfresco/network-protocol-context.xml" />
<import resource="classpath:alfresco/email-service-context.xml" /> <import resource="classpath:alfresco/email-service-context.xml" />
<import resource="classpath:alfresco/content-services-context.xml" /> <import resource="classpath:alfresco/content-services-context.xml" />
<import resource="classpath*:alfresco/extension/mt/mt-contentstore-context.xml"/>
<import resource="classpath:alfresco/hibernate-context.xml" /> <import resource="classpath:alfresco/hibernate-context.xml" />
<import resource="classpath:alfresco/ownable-services-context.xml" /> <import resource="classpath:alfresco/ownable-services-context.xml" />
<import resource="classpath:alfresco/template-services-context.xml" /> <import resource="classpath:alfresco/template-services-context.xml" />
@@ -30,10 +27,8 @@
<import resource="classpath:alfresco/authentication-services-context.xml" /> <import resource="classpath:alfresco/authentication-services-context.xml" />
<import resource="classpath:alfresco/policy-context.xml" /> <import resource="classpath:alfresco/policy-context.xml" />
<import resource="classpath:alfresco/import-export-context.xml" /> <import resource="classpath:alfresco/import-export-context.xml" />
<import resource="classpath:alfresco/bootstrap-context.xml" /> <import resource="classpath:alfresco/bootstrap-context.xml" />
<import resource="classpath:alfresco/repo-admin-context.xml"/>
<import resource="classpath:alfresco/repo-admin-context.xml"/>
<import resource="classpath:alfresco/workflow-context.xml" /> <import resource="classpath:alfresco/workflow-context.xml" />
<import resource="classpath:alfresco/jcr-api-context.xml" /> <import resource="classpath:alfresco/jcr-api-context.xml" />
<import resource="classpath:alfresco/avm-services-context.xml" /> <import resource="classpath:alfresco/avm-services-context.xml" />

View File

@@ -226,12 +226,8 @@
</property> </property>
</bean> </bean>
<!-- Bootstrap Single-Tenant Admin Context --> <import resource="classpath:alfresco/bootstrap/st-admin-context.xml"/>
<import resource="classpath:alfresco/bootstrap/tenant-single-admin-context.xml"/> <import resource="classpath*:alfresco/extension/mt/mt-admin-context.xml"/>
<!-- Bootstrap Multi-Tenant Admin Context (if available) -->
<!-- note: must be after schema bootstrap and before repo-admin and workflow -->
<import resource="classpath*:alfresco/bootstrap/tenant-multi-admin-context.xml"/>
<bean id="workflowBootstrap" parent="workflowDeployer"> <bean id="workflowBootstrap" parent="workflowDeployer">
<property name="workflowDefinitions"> <property name="workflowDefinitions">

View File

@@ -21,8 +21,10 @@
<ref bean="deletedContentStore" /> <ref bean="deletedContentStore" />
</property> </property>
</bean> </bean>
<!-- Abstract bean definition defining base definition for content store cleaner -->
<!-- Performs the content cleanup --> <!-- Performs the content cleanup -->
<bean id="contentStoreCleaner" class="org.alfresco.repo.content.cleanup.ContentStoreCleaner" > <bean id="baseContentStoreCleaner" class="org.alfresco.repo.content.cleanup.ContentStoreCleaner" abstract="true">
<property name="dictionaryService"> <property name="dictionaryService">
<ref bean="dictionaryService" /> <ref bean="dictionaryService" />
</property> </property>
@@ -38,11 +40,6 @@
<property name="protectDays" > <property name="protectDays" >
<value>14</value> <value>14</value>
</property> </property>
<property name="stores" >
<list>
<ref bean="fileContentStore" />
</list>
</property>
<property name="listeners" > <property name="listeners" >
<list> <list>
<ref bean="deletedContentBackupListener" /> <ref bean="deletedContentBackupListener" />
@@ -50,7 +47,16 @@
</property> </property>
</bean> </bean>
<bean id="contentService" class="org.alfresco.repo.content.RoutingContentService" init-method="init"> <bean id="contentStoreCleaner" parent="baseContentStoreCleaner">
<property name="stores" >
<list>
<ref bean="fileContentStore" />
</list>
</property>
</bean>
<!-- Abstract bean definition defining base definition for content service -->
<bean id="baseContentService" class="org.alfresco.repo.content.RoutingContentService" abstract="true" init-method="init">
<property name="transactionService"> <property name="transactionService">
<ref bean="transactionService" /> <ref bean="transactionService" />
</property> </property>
@@ -66,9 +72,6 @@
<property name="transformerRegistry"> <property name="transformerRegistry">
<ref bean="contentTransformerRegistry" /> <ref bean="contentTransformerRegistry" />
</property> </property>
<property name="store">
<ref bean="fileContentStore" />
</property>
<property name="policyComponent"> <property name="policyComponent">
<ref bean="policyComponent" /> <ref bean="policyComponent" />
</property> </property>
@@ -79,6 +82,12 @@
<ref bean="transformer.ImageMagick" /> <ref bean="transformer.ImageMagick" />
</property> </property>
</bean> </bean>
<bean id="contentService" parent="baseContentService">
<property name="store">
<ref bean="fileContentStore" />
</property>
</bean>
<bean id="mimetypeConfigService" class="org.alfresco.config.xml.XMLConfigService" init-method="init"> <bean id="mimetypeConfigService" class="org.alfresco.config.xml.XMLConfigService" init-method="init">
<constructor-arg> <constructor-arg>

View File

@@ -403,4 +403,22 @@
overflowToDisk="false" overflowToDisk="false"
/> />
<!-- Tenants Cache -->
<cache
name="org.alfresco.cache.tenantsCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false"
/>
<!-- Tenant-based Routing File Content Store -->
<cache
name="org.alfresco.cache.tenantFileStoresCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="false"
/>
</ehcache> </ehcache>

View File

@@ -708,5 +708,42 @@
replicateAsynchronously = false"/> replicateAsynchronously = false"/>
</cache> </cache>
<!-- Tenants Cache -->
<cache
name="org.alfresco.cache.tenantsCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts = false,
replicateUpdates = true,
replicateRemovals = true,
replicateUpdatesViaCopy = false,
replicateAsynchronously = false"/>
</cache>
<!-- Tenant-based Routing File Content Store -->
<cache
name="org.alfresco.cache.tenantFileStoresCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts = false,
replicateUpdates = true,
replicateRemovals = true,
replicateUpdatesViaCopy = false,
replicateAsynchronously = false"/>
</cache>
</ehcache> </ehcache>

View File

@@ -0,0 +1,43 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<!-- -->
<!-- MT Admin Service Implementation -->
<!-- -->
<bean id="tenantAdminService" class="org.alfresco.repo.tenant.MultiTAdminServiceImpl">
<!--
<property name="nodeService" ref="NodeService"/>
-->
<property name="nodeService" ref="dbNodeServiceImpl"/> <!-- TODO - go direct, until we expose deleteStore via public NodeService API -->
<property name="dictionaryComponent" ref="dictionaryService"/>
<property name="authenticationComponent" ref="authenticationComponent"/>
<property name="repoAdminService" ref="RepoAdminService"/>
<property name="tenantService" ref="tenantService"/>
<property name="transactionService" ref="transactionComponent"/>
<property name="attributeService" ref="AttributeService"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
<property name="tenantFileContentStore" ref="tenantFileContentStore"/>
<property name="workflowService" ref="WorkflowService"/>
</bean>
<bean id="tenantInterpreter" class="org.alfresco.repo.tenant.TenantInterpreter">
<property name="transactionService" ref="transactionComponent"/>
<property name="tenantAdminService" ref="tenantAdminService"/>
<property name="tenantService" ref="tenantService"/>
<property name="authenticationService" ref="AuthenticationService"/>
</bean>
<bean id="tenantInterpreterHelp" class="org.alfresco.i18n.ResourceBundleBootstrapComponent">
<property name="resourceBundles">
<list>
<value>alfresco.messages.tenant-interpreter-help</value>
</list>
</property>
</bean>
</beans>

View File

@@ -0,0 +1,76 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<!-- ======================================= -->
<!-- Tenant Routing File Content Store Cache -->
<!-- ======================================= -->
<bean name="tenantFileStoresCache" class="org.alfresco.repo.cache.EhCacheAdapter">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
<property name="cacheManager">
<ref bean="internalEHCacheManager" />
</property>
<property name="cacheName">
<value>org.alfresco.cache.tenantFileStoresCache</value>
</property>
</bean>
</property>
</bean>
<!-- -->
<!-- Tenant Routing File Content Store -->
<!-- -->
<bean id="tenantFileContentStore" class="org.alfresco.repo.content.TenantRoutingFileContentStore" init-method="init">
<property name="defaultRootDir">
<value>${dir.contentstore}</value>
</property>
<property name="tenantService">
<ref bean="tenantService" />
</property>
<property name="storesCache">
<ref bean="tenantFileStoresCache" />
</property>
</bean>
<!-- override content store cleaner to use tenant routing file content store -->
<!-- Performs the content cleanup -->
<bean id="contentStoreCleaner" class="org.alfresco.repo.content.cleanup.ContentStoreCleaner" >
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="nodeDaoService" >
<ref bean="nodeDaoService" />
</property>
<property name="avmNodeDAO">
<ref bean="avmNodeDAO"/>
</property>
<property name="transactionService" >
<ref bean="transactionService" />
</property>
<property name="protectDays" >
<value>14</value>
</property>
<property name="stores" >
<list>
<ref bean="tenantFileContentStore" />
</list>
</property>
<property name="listeners" >
<list>
<ref bean="deletedContentBackupListener" />
</list>
</property>
</bean>
<!-- override content service to use tenant routing file content store -->
<bean id="contentService" parent="baseContentService">
<property name="store">
<ref bean="tenantFileContentStore" />
</property>
</bean>
</beans>

View File

@@ -0,0 +1,55 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<!-- ===================================== -->
<!-- Tenants Cache -->
<!-- ===================================== -->
<!-- The cross-transaction shared cache for in-memory Tenants -->
<bean name="tenantsSharedCache" class="org.alfresco.repo.cache.EhCacheAdapter">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
<property name="cacheManager">
<ref bean="internalEHCacheManager" />
</property>
<property name="cacheName">
<value>org.alfresco.cache.tenantsCache</value>
</property>
</bean>
</property>
</bean>
<!-- The transactional cache for in-memory Tenants -->
<bean name="tenantsCache" class="org.alfresco.repo.cache.TransactionalCache">
<property name="sharedCache">
<ref bean="tenantsSharedCache" />
</property>
<property name="cacheManager" >
<ref bean="transactionalEHCacheManager" />
</property>
<property name="name">
<value>org.alfresco.tenantsTransactionalCache</value>
</property>
<property name="maxCacheSize">
<value>10</value>
</property>
</bean>
<!-- -->
<!-- MT Service Implementation -->
<!-- -->
<bean id="tenantService" class="org.alfresco.repo.tenant.MultiTServiceImpl">
<property name="tenantsCache">
<ref bean="tenantsCache"/>
</property>
</bean>
</beans>

View File

@@ -0,0 +1 @@
tenant_console.help=alfresco/messages/tenant-interpreter-help.txt

View File

@@ -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 <tenant domain>
Show tenant details - status (ie. whether enabled or disabled) and root contentstore directory.
Example: show tenant yyy.zzz.com
ok> create <tenant domain> <tenant admin password> [<root contentstore dir>]
Create tenant. By default the tenant will be enabled. It will have an admin
user called "admin@<tenant domain>" 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 <tenant domain> <tenant admin password> [<root contentstore dir>]
Same as create, except the default workflows will not be bootstrapped.
ok> bootstrapWorkflows <tenant domain>
Bootstrap the default workflows.
Examples: bootstrapWorkflows yyy.zzz.com
ok> changeAdminPassword <tenant domain> <tenant admin password>
Useful if the tenant's admin (admin@<tenant domain) has forgotten their password.
Example: changeAdminPassword yyy.zzz.com n3wpassw0rd
ok> enable <tenant domain>
Enable tenant so that is active and available for new logins
Example: enable yyy.zzz.com
ok> disable <tenant domain>
Disable tenant so that is inactive. Existing logins will fail on next usage.
Example: enable yyy.zzz.com
##
## end
##

View File

@@ -37,7 +37,7 @@
<!-- Load any additional models/messages from repo into data dictionary --> <!-- Load any additional models/messages from repo into data dictionary -->
<!-- note: needs to match import-export-context.xml locations --> <!-- note: needs to match boostrap-context.xml locations (customModelsSpace.acp and customMessagesSpace.xml) -->
<bean id="customModelsRepositoryLocation" class="org.alfresco.repo.dictionary.RepositoryLocation"> <bean id="customModelsRepositoryLocation" class="org.alfresco.repo.dictionary.RepositoryLocation">
<!-- other properties will be defaulted, but can be overriden here --> <!-- other properties will be defaulted, but can be overriden here -->

View File

@@ -334,7 +334,13 @@ public class RepoAdminServiceImpl implements RepoAdminService
NodeRef modelNodeRef = nodeRefs.get(0); 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); QName modelQName = (QName)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_NAME);
ModelDefinition modelDef = null; ModelDefinition modelDef = null;
@@ -436,7 +442,13 @@ public class RepoAdminServiceImpl implements RepoAdminService
NodeRef modelNodeRef = nodeRefs.get(0); 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); modelQName = (QName)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_NAME);
ModelDefinition modelDef = null; ModelDefinition modelDef = null;

View File

@@ -83,7 +83,7 @@ public class GenericWorkflowPatch extends AbstractPatch implements ApplicationCo
props.put(WorkflowDeployer.REDEPLOY, "true"); props.put(WorkflowDeployer.REDEPLOY, "true");
} }
deployer.setWorkflowDefinitions(workflowDefinitions); deployer.setWorkflowDefinitions(workflowDefinitions);
deployer.deploy(); deployer.init();
// done // done
return I18NUtil.getMessage(MSG_DEPLOYED, workflowDefinitions.size()); return I18NUtil.getMessage(MSG_DEPLOYED, workflowDefinitions.size());

View File

@@ -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<String, FileContentStore> tenantFileStores = new HashMap<String, FileContentStore>();
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<ContentStore> 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<ContentStore> allEnabledStores = new ArrayList<ContentStore>();
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;
}
}

View File

@@ -175,8 +175,12 @@ public class MessageServiceImpl implements MessageService
try try
{ {
writeLock.lock(); writeLock.lock();
tenantResourceBundleBaseNames.add(resBundlePath);
if (! tenantResourceBundleBaseNames.contains(resBundlePath))
{
tenantResourceBundleBaseNames.add(resBundlePath);
}
logger.info("Registered message bundle '" + resBundlePath + "'"); logger.info("Registered message bundle '" + resBundlePath + "'");
@@ -333,7 +337,10 @@ public class MessageServiceImpl implements MessageService
try try
{ {
resourcebundle = new PropertyResourceBundle(resBundleStream); if (resBundleStream != null)
{
resourcebundle = new PropertyResourceBundle(resBundleStream);
}
} }
catch (IOException ioe) catch (IOException ioe)
{ {
@@ -346,12 +353,15 @@ public class MessageServiceImpl implements MessageService
resourcebundle = ResourceBundle.getBundle(resBundlePath, locale); resourcebundle = ResourceBundle.getBundle(resBundlePath, locale);
} }
// unload from the cached messages if (resourcebundle != null)
Enumeration<String> enumKeys = resourcebundle.getKeys();
while (enumKeys.hasMoreElements() == true)
{ {
String key = enumKeys.nextElement(); // unload from the cached messages
props.remove(key); Enumeration<String> enumKeys = resourcebundle.getKeys();
while (enumKeys.hasMoreElements() == true)
{
String key = enumKeys.nextElement();
props.remove(key);
}
} }
loadedBundles.remove(resBundlePath); loadedBundles.remove(resBundlePath);
@@ -480,7 +490,10 @@ public class MessageServiceImpl implements MessageService
try try
{ {
resourcebundle = new PropertyResourceBundle(resBundleStream); if (resBundleStream != null)
{
resourcebundle = new PropertyResourceBundle(resBundleStream);
}
} }
catch (IOException ioe) catch (IOException ioe)
{ {
@@ -493,15 +506,18 @@ public class MessageServiceImpl implements MessageService
resourcebundle = ResourceBundle.getBundle(resBundlePath, locale); resourcebundle = ResourceBundle.getBundle(resBundlePath, locale);
} }
Enumeration<String> enumKeys = resourcebundle.getKeys(); if (resourcebundle != null)
while (enumKeys.hasMoreElements() == true)
{ {
String key = enumKeys.nextElement(); Enumeration<String> enumKeys = resourcebundle.getKeys();
props.put(key, resourcebundle.getString(key)); 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)) 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;
} }
} }

View File

@@ -323,8 +323,12 @@ public class ImporterBootstrap extends AbstractLifecycleBean
UserTransaction userTransaction = transactionService.getUserTransaction(); UserTransaction userTransaction = transactionService.getUserTransaction();
Authentication authentication = authenticationComponent.getCurrentAuthentication(); Authentication authentication = authenticationComponent.getCurrentAuthentication();
authenticationComponent.setSystemUserAsCurrentUser();
if (authenticationComponent.getCurrentUserName() == null)
{
authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName());
}
try try
{ {
userTransaction.begin(); userTransaction.begin();

View File

@@ -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<TenantDeployer> tenantDeployers = new ArrayList<TenantDeployer>();
@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<Tenant> 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<Object>()
{
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<Object>()
{
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<Object>()
{
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<Properties> 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<Object>()
{
public Object doWork()
{
List<WorkflowDefinition> workflowDefs = workflowService.getDefinitions();
if (workflowDefs != null)
{
for (WorkflowDefinition workflowDef : workflowDefs)
{
workflowService.undeployDefinition(workflowDef.getId());
}
}
List<String> messageResourceBundles = repoAdminService.getMessageBundles();
if (messageResourceBundles != null)
{
for (String messageResourceBundle : messageResourceBundles)
{
repoAdminService.undeployMessageBundle(messageResourceBundle);
}
}
List<RepoModelDefinition> 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<Object>()
{
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<Tenant> getAllTenants()
{
MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH);
List<Tenant> tenants = new ArrayList<Tenant>();
if (map != null)
{
// note: getAllTenants is called first, by TenantDeployer - hence need to initialise the TenantService status cache
Set<String> 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<String> mustNotExistStoreUrls = new ArrayList<String>();
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<String> mustNotExistStoreUrls = new ArrayList<String>();
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<Tenant> 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<Object>()
{
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<Tenant> 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<Object>()
{
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);
}
}

View File

@@ -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<String, Tenant> tenantsCache;
private MultiTAdminServiceImpl tenantAdminService = null; // registered (rather than injected) - to avoid circular dependency
public void setTenantsCache(SimpleCache<String, Tenant> 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 = <none>");
}
}
}
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<NodeRef> action = new GetRootNode(nodeService, searchService, namespaceService, rootPath, rootNodeRef, storeRef);
return getBaseName(AuthenticationUtil.runAs(action, AuthenticationUtil.getSystemUserName()));
}
private class GetRootNode implements AuthenticationUtil.RunAsWork<NodeRef>
{
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<NodeRef> 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);
}
}

View File

@@ -112,11 +112,6 @@ public class SingleTServiceImpl implements TenantService
{ {
return rootNodeRef; return rootNodeRef;
} }
public NodeRef getCompanyHomeNode(NodeService nodeService, String username, StoreRef storeRef)
{
return null;
}
public boolean isTenantUser() public boolean isTenantUser()
{ {

View File

@@ -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.
* <p>
* 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<Tenant> getAllTenants();
public void enableTenant(String tenantDomain);
public void disableTenant(String tenantDomain);
public Tenant getTenant(String tenantDomain);
public boolean isEnabledTenant(String tenantDomain);
public boolean isEnabled();
}

View File

@@ -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<Tenant> 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<Object>()
{
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<Object>()
{
public Object doWork() throws Exception
{
RetryingTransactionCallback<Object> txnWork = new RetryingTransactionCallback<Object>()
{
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;
}
}

View File

@@ -35,7 +35,7 @@ import org.alfresco.service.namespace.QName;
/** /**
* Tenant Service interface. * Tenant Service interface.
* <p> * <p>
* 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 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 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();
public boolean isTenantUser(String username); public boolean isTenantUser(String username);

View File

@@ -34,6 +34,9 @@ import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.dictionary.DictionaryBootstrap; import org.alfresco.repo.dictionary.DictionaryBootstrap;
import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.security.authentication.AuthenticationComponent; 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.view.ImporterException;
import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowDeployment;
import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowException;
@@ -71,6 +74,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean
private List<Properties> workflowDefinitions; private List<Properties> workflowDefinitions;
private List<String> models = new ArrayList<String>(); private List<String> models = new ArrayList<String>();
private List<String> resourceBundles = new ArrayList<String>(); private List<String> resourceBundles = new ArrayList<String>();
private TenantService tenantService;
/** /**
@@ -123,6 +127,16 @@ public class WorkflowDeployer extends AbstractLifecycleBean
this.dictionaryDAO = dictionaryDAO; this.dictionaryDAO = dictionaryDAO;
} }
/**
* Sets the tenant service
*
* @param tenantService the tenant service
*/
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
/** /**
* Sets the Workflow Definitions * 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 * @param modelResources the model names
*/ */
@@ -152,11 +166,17 @@ public class WorkflowDeployer extends AbstractLifecycleBean
{ {
this.resourceBundles = labels; this.resourceBundles = labels;
} }
// used by TenantAdminService when creating a new tenant and bootstrapping the pre-defined workflows
public List<Properties> getWorkflowDefinitions()
{
return this.workflowDefinitions;
}
/** /**
* Deploy the Workflow Definitions * Deploy the Workflow Definitions
*/ */
public void deploy() public void init()
{ {
if (transactionService == null) if (transactionService == null)
{ {
@@ -171,24 +191,30 @@ public class WorkflowDeployer extends AbstractLifecycleBean
throw new ImporterException("Workflow Service must be provided"); throw new ImporterException("Workflow Service must be provided");
} }
String currentUser = authenticationComponent.getCurrentUserName();
if (currentUser == null)
{
authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName());
}
UserTransaction userTransaction = transactionService.getUserTransaction(); UserTransaction userTransaction = transactionService.getUserTransaction();
authenticationComponent.setSystemUserAsCurrentUser();
try try
{ {
userTransaction.begin(); userTransaction.begin();
// bootstrap the workflow models and labels // bootstrap the workflow models and static labels (from classpath)
if (models != null && resourceBundles != null) if (models != null && resourceBundles != null)
{ {
DictionaryBootstrap dictionaryBootstrap = new DictionaryBootstrap(); DictionaryBootstrap dictionaryBootstrap = new DictionaryBootstrap();
dictionaryBootstrap.setDictionaryDAO(dictionaryDAO); dictionaryBootstrap.setDictionaryDAO(dictionaryDAO);
dictionaryBootstrap.setTenantService(tenantService);
dictionaryBootstrap.setModels(models); dictionaryBootstrap.setModels(models);
dictionaryBootstrap.setLabels(resourceBundles); dictionaryBootstrap.setLabels(resourceBundles);
dictionaryBootstrap.bootstrap(); dictionaryBootstrap.bootstrap(); // also registers with dictionary
} }
// bootstrap the workflow definitions // bootstrap the workflow definitions (from classpath)
if (workflowDefinitions != null) if (workflowDefinitions != null)
{ {
for (Properties workflowDefinition : workflowDefinitions) for (Properties workflowDefinition : workflowDefinitions)
@@ -239,19 +265,29 @@ public class WorkflowDeployer extends AbstractLifecycleBean
{ {
// rollback the transaction // rollback the transaction
try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {}
try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {}
throw new AlfrescoRuntimeException("Workflow deployment failed", e); throw new AlfrescoRuntimeException("Workflow deployment failed", e);
} }
finally finally
{ {
authenticationComponent.clearCurrentSecurityContext(); if (currentUser == null)
{
authenticationComponent.clearCurrentSecurityContext();
}
} }
} }
@Override @Override
protected void onBootstrap(ApplicationEvent event) protected void onBootstrap(ApplicationEvent event)
{ {
deploy(); // run as System on bootstrap
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork()
{
init();
return null;
}
}, AuthenticationUtil.getSystemUserName());
} }
@Override @Override