mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
1Merged V4.0-BUG-FIX to HEAD
35438: Merged BRANCHES/DEV/THOR0 to BRANCHES/DEV/V4.0-BUG-FIX: - fix merge issue (THOR-4 / ALF-13756) 35446: Merged BRANCHES/DEV/THOR0 to BRANCHES/DEV/V4.0-BUG-FIX: 29422: record-only 29453: build/test fix (AspectTest, PolicyTest, WebScriptTestSuite) 35448: ALF-13770: Merged V3.4-BUG-FIX (3.4.10) to V4.0-BUG-FIX (4.0.2) 35447: ALF-13769: Merged V3.4.8 (3.4.8.7) to V3.4-BUG-FIX (3.4.10) 35435: ALF-11535 Home Folder Synchronizer fails when destination folder already exists - HomeFolderProviderSynchronizerTest was broken on build m/c because PersonTest (in the same suite) created its own UserNameMatcherImpl and left it attached to the personServiceImpl. 35413: ALF-11535 Home Folder Synchronizer (HFS) fails when destination folder already exists - HomeFolderManager no longer returns an existing folder (unless the provider is an ExistingPathBasedHomeFolderProvider*), but will append -N (where N is an integer) so that a new folder is always created. This fixes an unreported bug (when case sensitive user names are in use) that users created in Share that only differ in case would have shared the same home folder. - Modified HFS to log more 'info' rather than 'debug' messages so it is possible for administrators to understand the moves and errors better. - Modified HFS to understand that Alfresco does not allow duplicate folders/content when case is ignored. - Added unit test for case insensitive user names. - Modified HFS to allows folder structure to change case on re-sync 35451: Fix for ALF-13503 Add SOLR client API tests to the SystemBuildTest project - missed keystore from checkin 35454: Improved solution for ALF-13286 - after changes to "SiteService" ProxyFactoryBean definition from Andy. - now checks user ability to execute the SiteService.createSite() method based on ACLs defined - avoiding AccessDeniedException. 35462: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/V4.0-BUG-FIX: - minor manual merge (to avoid future conflict) 35465: Fix for ALF-13454 - Advanced search date picker missing the additional pop up 35475: ALF-12780 - CIFS and TextEdit shuffle 35495: ALF-13753: Prevent users from editing the name of locked documents in Share via the insitu editor git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@35499 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -14,25 +14,26 @@
|
|||||||
<bean id="spacesArchiveBootstrap-mt" parent="spacesArchiveBootstrap-base" singleton="false" />
|
<bean id="spacesArchiveBootstrap-mt" parent="spacesArchiveBootstrap-base" singleton="false" />
|
||||||
<bean id="spacesBootstrap-mt" parent="spacesBootstrap-base" singleton="false" />
|
<bean id="spacesBootstrap-mt" parent="spacesBootstrap-base" singleton="false" />
|
||||||
|
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<!-- MT Admin Service Implementation -->
|
<!-- MT Admin Service Implementation -->
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
<bean id="tenantAdminService" parent="baseMultiTAdminService" />
|
<bean id="tenantAdminService" parent="baseMultiTAdminService" class="org.alfresco.repo.tenant.MultiTAdminServiceImpl" />
|
||||||
|
|
||||||
<bean id="tenantInterpreter" class="org.alfresco.repo.tenant.TenantInterpreter" parent="interpreterBase">
|
<bean id="tenantInterpreter" class="org.alfresco.repo.tenant.TenantInterpreter" parent="interpreterBase">
|
||||||
<property name="tenantAdminService" ref="tenantAdminService"/>
|
<property name="tenantAdminService" ref="tenantAdminService"/>
|
||||||
<property name="tenantService" ref="tenantService"/>
|
<property name="tenantService" ref="tenantService"/>
|
||||||
<property name="authenticationService" ref="AuthenticationService"/>
|
<property name="authenticationService" ref="AuthenticationService"/>
|
||||||
<property name="baseAdminUsername"><value>${alfresco_user_store.adminusername}</value></property>
|
<property name="baseAdminUsername"><value>${alfresco_user_store.adminusername}</value></property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="tenantInterpreterHelp" class="org.alfresco.i18n.ResourceBundleBootstrapComponent">
|
<bean id="tenantInterpreterHelp" class="org.alfresco.i18n.ResourceBundleBootstrapComponent">
|
||||||
<property name="resourceBundles">
|
<property name="resourceBundles">
|
||||||
<list>
|
<list>
|
||||||
<value>alfresco.messages.tenant-interpreter-help</value>
|
<value>alfresco.messages.tenant-interpreter-help</value>
|
||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
|
||||||
|
@@ -3,60 +3,12 @@
|
|||||||
|
|
||||||
<beans>
|
<beans>
|
||||||
|
|
||||||
<!-- ======================================= -->
|
<!-- -->
|
||||||
<!-- Tenant Routing File Content Store Cache -->
|
<!-- (Tenant Routing) File Content Store -->
|
||||||
<!-- ======================================= -->
|
<!-- -->
|
||||||
|
|
||||||
<bean name="tenantFileStoresCache" class="org.alfresco.repo.cache.EhCacheAdapter">
|
<bean id="fileContentStore" class="org.alfresco.repo.content.TenantRoutingFileContentStore" parent="baseTenantRoutingContentStore">
|
||||||
<property name="cache">
|
<property name="defaultRootDir" value="${dir.contentstore}" />
|
||||||
<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>
|
</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="eagerContentStoreCleaner" class="org.alfresco.repo.content.cleanup.EagerContentStoreCleaner" init-method="init">
|
|
||||||
<property name="eagerOrphanCleanup" >
|
|
||||||
<value>${system.content.eagerOrphanCleanup}</value>
|
|
||||||
</property>
|
|
||||||
<property name="stores" >
|
|
||||||
<list>
|
|
||||||
<ref bean="tenantFileContentStore" />
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
<property name="listeners" >
|
|
||||||
<ref bean="deletedContentBackupListeners" />
|
|
||||||
</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>
|
</beans>
|
||||||
|
@@ -7,9 +7,8 @@
|
|||||||
<!-- (Tenant Routing) File Content Store -->
|
<!-- (Tenant Routing) File Content Store -->
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
<bean id="fileContentStore" class="org.alfresco.repo.tenant.TenantRoutingFileContentStore" parent="baseTenantRoutingContentStore">
|
<bean id="fileContentStore" class="org.alfresco.repo.content.TenantRoutingFileContentStore" parent="baseTenantRoutingContentStore">
|
||||||
<property name="defaultRootDir" value="${dir.contentstore}" />
|
<property name="defaultRootDir" value="${dir.contentstore}" />
|
||||||
<property name="contentLimitProvider" ref="defaultContentLimitProvider"/>
|
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
@@ -2,49 +2,15 @@
|
|||||||
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
|
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
|
||||||
|
|
||||||
<beans>
|
<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="name">
|
|
||||||
<value>org.alfresco.tenantsTransactionalCache</value>
|
|
||||||
</property>
|
|
||||||
<property name="maxCacheSize">
|
|
||||||
<value>100</value>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- -->
|
|
||||||
<!-- MT Service Implementation -->
|
<!-- MT Service Implementation -->
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
<bean id="tenantService" class="org.alfresco.repo.tenant.MultiTServiceImpl">
|
<bean id="tenantService" class="org.alfresco.repo.tenant.MultiTServiceImpl">
|
||||||
|
|
||||||
<property name="tenantsCache">
|
<property name="tenantAdminDAO">
|
||||||
<ref bean="tenantsCache"/>
|
<ref bean="tenantAdminDAO"/>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
</bean>
|
</bean>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
|
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
|
||||||
|
|
||||||
<beans>
|
<beans>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Bootstrap Data: Prototype beans for the per-tenant bootstrap.
|
Bootstrap Data: Prototype beans for the per-tenant bootstrap.
|
||||||
These are fetched by the MultiTAdminServiceImpl when new tenant is created.
|
These are fetched by the MultiTAdminServiceImpl when new tenant is created.
|
||||||
@@ -13,9 +13,8 @@
|
|||||||
<bean id="version2Bootstrap-mt" parent="version2Bootstrap-base" singleton="false" />
|
<bean id="version2Bootstrap-mt" parent="version2Bootstrap-base" singleton="false" />
|
||||||
<bean id="spacesArchiveBootstrap-mt" parent="spacesArchiveBootstrap-base" singleton="false" />
|
<bean id="spacesArchiveBootstrap-mt" parent="spacesArchiveBootstrap-base" singleton="false" />
|
||||||
<bean id="spacesBootstrap-mt" parent="spacesBootstrap-base" singleton="false" />
|
<bean id="spacesBootstrap-mt" parent="spacesBootstrap-base" singleton="false" />
|
||||||
|
|
||||||
|
<bean id="baseMultiTAdminService" abstract="true">
|
||||||
<bean id="baseMultiTAdminService" class="org.alfresco.repo.tenant.MultiTAdminServiceImpl" abstract="true">
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<property name="nodeService" ref="NodeService"/>
|
<property name="nodeService" ref="NodeService"/>
|
||||||
@@ -28,13 +27,17 @@
|
|||||||
<property name="transactionService" ref="transactionComponent"/>
|
<property name="transactionService" ref="transactionComponent"/>
|
||||||
<property name="tenantAdminDAO" ref="tenantAdminDAO"/>
|
<property name="tenantAdminDAO" ref="tenantAdminDAO"/>
|
||||||
<property name="passwordEncoder" ref="passwordEncoder"/>
|
<property name="passwordEncoder" ref="passwordEncoder"/>
|
||||||
<property name="tenantFileContentStore" ref="tenantFileContentStore"/>
|
<property name="tenantFileContentStore" ref="fileContentStore"/> <!-- see 'mt-contentstore-context.xml' (or any further overrides) -->
|
||||||
<property name="workflowService" ref="WorkflowService"/>
|
<property name="workflowService" ref="WorkflowService"/>
|
||||||
<property name="repositoryExporterService" ref="repositoryExporterComponent"/>
|
<property name="repositoryExporterService" ref="repositoryExporterComponent"/>
|
||||||
<property name="moduleService" ref="moduleService"/>
|
<property name="moduleService" ref="moduleService"/>
|
||||||
<property name="baseAdminUsername"><value>${alfresco_user_store.adminusername}</value></property>
|
<property name="baseAdminUsername" value="${alfresco_user_store.adminusername}"/>
|
||||||
<property name="thumbnailRegistry" ref="thumbnailRegistry"/>
|
<property name="thumbnailRegistry" ref="thumbnailRegistry"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="baseTenantRoutingContentStore" class="org.alfresco.repo.content.TenantRoutingFileContentStore" abstract="true">
|
||||||
|
<property name="tenantService" ref="tenantService" />
|
||||||
|
<property name="storesCache" ref="routingContentStoreCache" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
@@ -153,6 +153,7 @@
|
|||||||
<property name="sitesPermissionsCleaner" ref="sitesPermissionsCleaner"/>
|
<property name="sitesPermissionsCleaner" ref="sitesPermissionsCleaner"/>
|
||||||
<property name="cannedQueryRegistry" ref="siteCannedQueryRegistry"/>
|
<property name="cannedQueryRegistry" ref="siteCannedQueryRegistry"/>
|
||||||
<property name="policyComponent" ref="policyComponent"/>
|
<property name="policyComponent" ref="policyComponent"/>
|
||||||
|
<property name="publicServiceAccessService" ref="PublicServiceAccessService" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="siteServiceScript" parent="baseJavaScriptExtension" class="org.alfresco.repo.site.script.ScriptSiteService">
|
<bean id="siteServiceScript" parent="baseJavaScriptExtension" class="org.alfresco.repo.site.script.ScriptSiteService">
|
||||||
|
@@ -145,8 +145,10 @@
|
|||||||
<property name="scenarios">
|
<property name="scenarios">
|
||||||
<list>
|
<list>
|
||||||
<!-- TextEdit Mac Lion -->
|
<!-- TextEdit Mac Lion -->
|
||||||
<bean id="deleteShuffle" class="org.alfresco.filesys.repo.rules.ScenarioLockedDeleteShuffle">
|
<bean id="tempDeleteShuffle" class="org.alfresco.filesys.repo.rules.ScenarioTempDeleteShuffle">
|
||||||
<property name="pattern"><value>^\._.txt*</value></property>
|
<property name="pattern"><value>^.*\.txt$</value></property>
|
||||||
|
|
||||||
|
<property name="tempDirPattern"><value>.*(\\\..*\\)+.*</value></property>
|
||||||
<property name="timeout"><value>60000</value></property>
|
<property name="timeout"><value>60000</value></property>
|
||||||
<property name="ranking"><value>HIGH</value></property>
|
<property name="ranking"><value>HIGH</value></property>
|
||||||
</bean>
|
</bean>
|
||||||
@@ -176,7 +178,7 @@
|
|||||||
</bean>
|
</bean>
|
||||||
<!-- Powerpoint Mac 2011 -->
|
<!-- Powerpoint Mac 2011 -->
|
||||||
<bean id="createShufflePPT2011" class="org.alfresco.filesys.repo.rules.ScenarioCreateDeleteRenameShuffle">
|
<bean id="createShufflePPT2011" class="org.alfresco.filesys.repo.rules.ScenarioCreateDeleteRenameShuffle">
|
||||||
<property name="pattern"><value>^[^\._].*[0-9].pptx$</value></property>
|
<property name="pattern"><value>^[^\._].*[0-9].ppt[x]*$</value></property>
|
||||||
<property name="timeout"><value>60000</value></property>
|
<property name="timeout"><value>60000</value></property>
|
||||||
<property name="ranking"><value>HIGH</value></property>
|
<property name="ranking"><value>HIGH</value></property>
|
||||||
</bean>
|
</bean>
|
||||||
|
@@ -4733,18 +4733,16 @@ public class ContentDiskDriverTest extends TestCase
|
|||||||
* This test tries to simulate the cifs shuffling that is done
|
* This test tries to simulate the cifs shuffling that is done
|
||||||
* from Save from Mac Lion by TextEdit
|
* from Save from Mac Lion by TextEdit
|
||||||
*
|
*
|
||||||
* a) Lock file created. (._test.txt)
|
* a) Temp file created in temporary folder (test.txt)
|
||||||
* b) Temp file created in temporary folder (test.txt)
|
* b) Resource fork file created in temporary folder (._test.txt)
|
||||||
* c) Target file deleted
|
* b) Target file deleted
|
||||||
* d) Temp file renamed to target file.
|
* c) Temp file moved to target file.
|
||||||
* e) Lock file deleted
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public void DISABLED_TestScenarioMacLionTextEdit() throws Exception
|
public void testScenarioMacLionTextEdit() throws Exception
|
||||||
{
|
{
|
||||||
logger.debug("testScenarioLionTextEdit");
|
logger.debug("testScenarioLionTextEdit");
|
||||||
final String FILE_NAME = "test.txt";
|
final String FILE_NAME = "test.txt";
|
||||||
final String LOCK_FILE_NAME = "._test.txt";
|
final String FORK_FILE_NAME = "._test.txt";
|
||||||
final String TEMP_FILE_NAME = "test.txt";
|
final String TEMP_FILE_NAME = "test.txt";
|
||||||
|
|
||||||
final String UPDATED_TEXT = "Mac Lion Text Updated Content";
|
final String UPDATED_TEXT = "Mac Lion Text Updated Content";
|
||||||
@@ -4813,23 +4811,10 @@ public class ContentDiskDriverTest extends TestCase
|
|||||||
driver.writeFile(testSession, testConnection, testContext.tempFileHandle, testContentBytes, 0, testContentBytes.length, 0);
|
driver.writeFile(testSession, testConnection, testContext.tempFileHandle, testContentBytes, 0, testContentBytes.length, 0);
|
||||||
driver.closeFile(testSession, testConnection, testContext.tempFileHandle);
|
driver.closeFile(testSession, testConnection, testContext.tempFileHandle);
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
tran.doInTransaction(createFileCB, false, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a) create the lock file
|
|
||||||
*/
|
|
||||||
RetryingTransactionCallback<Void> createLockFileCB = new RetryingTransactionCallback<Void>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void execute() throws Throwable
|
|
||||||
{
|
|
||||||
/**
|
/**
|
||||||
* Create the lock file we are going to use
|
* Create the temp resource fork file we are going to use
|
||||||
*/
|
*/
|
||||||
FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + LOCK_FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0);
|
createFileParams = new FileOpenParams(TEST_TEMP_DIR + "\\" + FORK_FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0);
|
||||||
testContext.lockFileHandle = driver.createFile(testSession, testConnection, createFileParams);
|
testContext.lockFileHandle = driver.createFile(testSession, testConnection, createFileParams);
|
||||||
assertNotNull(testContext.lockFileHandle);
|
assertNotNull(testContext.lockFileHandle);
|
||||||
testContext.lockFileHandle.closeFile();
|
testContext.lockFileHandle.closeFile();
|
||||||
@@ -4840,12 +4825,11 @@ public class ContentDiskDriverTest extends TestCase
|
|||||||
testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME);
|
testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME);
|
||||||
nodeService.addAspect(testContext.testNodeRef, ContentModel.ASPECT_VERSIONABLE, null);
|
nodeService.addAspect(testContext.testNodeRef, ContentModel.ASPECT_VERSIONABLE, null);
|
||||||
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
tran.doInTransaction(createLockFileCB, false, true);
|
tran.doInTransaction(createFileCB, false, true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* b) Delete the target file
|
* b) Delete the target file
|
||||||
*/
|
*/
|
||||||
@@ -4869,28 +4853,15 @@ public class ContentDiskDriverTest extends TestCase
|
|||||||
public Void execute() throws Throwable
|
public Void execute() throws Throwable
|
||||||
{
|
{
|
||||||
driver.renameFile(testSession, testConnection, TEST_TEMP_DIR + "\\" + TEMP_FILE_NAME, TEST_DIR + "\\" + FILE_NAME);
|
driver.renameFile(testSession, testConnection, TEST_TEMP_DIR + "\\" + TEMP_FILE_NAME, TEST_DIR + "\\" + FILE_NAME);
|
||||||
|
driver.renameFile(testSession, testConnection, TEST_TEMP_DIR + "\\" + FORK_FILE_NAME, TEST_DIR + "\\" + FORK_FILE_NAME);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
tran.doInTransaction(moveTempFileCB, false, true);
|
tran.doInTransaction(moveTempFileCB, false, true);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* d) Delete Lock File
|
* Validate results.
|
||||||
*/
|
*/
|
||||||
RetryingTransactionCallback<Void> deleteLockFileCB = new RetryingTransactionCallback<Void>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void execute() throws Throwable
|
|
||||||
{
|
|
||||||
driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + LOCK_FILE_NAME);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tran.doInTransaction(deleteLockFileCB, false, true);
|
|
||||||
|
|
||||||
RetryingTransactionCallback<Void> validateCB = new RetryingTransactionCallback<Void>() {
|
RetryingTransactionCallback<Void> validateCB = new RetryingTransactionCallback<Void>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -4900,7 +4871,7 @@ public class ContentDiskDriverTest extends TestCase
|
|||||||
NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME);
|
NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME);
|
||||||
|
|
||||||
assertEquals("shuffledNode ref is different", shuffledNodeRef, testContext.testNodeRef);
|
assertEquals("shuffledNode ref is different", shuffledNodeRef, testContext.testNodeRef);
|
||||||
assertTrue("", nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_VERSIONABLE));
|
assertTrue("node is not versionable", nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_VERSIONABLE));
|
||||||
|
|
||||||
ContentReader reader = contentService.getReader(shuffledNodeRef, ContentModel.PROP_CONTENT);
|
ContentReader reader = contentService.getReader(shuffledNodeRef, ContentModel.PROP_CONTENT);
|
||||||
assertNotNull("Reader is null", reader);
|
assertNotNull("Reader is null", reader);
|
||||||
|
@@ -70,6 +70,11 @@ public class NonTransactionalRuleContentDiskDriver implements ExtendedDiskInterf
|
|||||||
*/
|
*/
|
||||||
private class DriverState
|
private class DriverState
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* key, value pair storage for the session
|
||||||
|
*/
|
||||||
|
Map<String, Object> sessionState = new ConcurrentHashMap<String, Object>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of folderName to Evaluator Context.
|
* Map of folderName to Evaluator Context.
|
||||||
*/
|
*/
|
||||||
@@ -150,16 +155,7 @@ public class NonTransactionalRuleContentDiskDriver implements ExtendedDiskInterf
|
|||||||
String folder = paths[0];
|
String folder = paths[0];
|
||||||
String file = paths[1];
|
String file = paths[1];
|
||||||
|
|
||||||
EvaluatorContext ctx = driverState.contextMap.get(folder);
|
EvaluatorContext ctx = getEvaluatorContext(driverState, folder);
|
||||||
if(ctx == null)
|
|
||||||
{
|
|
||||||
ctx = ruleEvaluator.createContext();
|
|
||||||
driverState.contextMap.put(folder, ctx);
|
|
||||||
if(logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("new driver context: " + folder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Operation o = new CloseFileOperation(file, param, rootNode, param.getFullName(), param.hasDeleteOnClose());
|
Operation o = new CloseFileOperation(file, param, rootNode, param.getFullName(), param.hasDeleteOnClose());
|
||||||
Command c = ruleEvaluator.evaluate(ctx, o);
|
Command c = ruleEvaluator.evaluate(ctx, o);
|
||||||
@@ -586,7 +582,7 @@ public class NonTransactionalRuleContentDiskDriver implements ExtendedDiskInterf
|
|||||||
EvaluatorContext ctx = driverState.contextMap.get(folder);
|
EvaluatorContext ctx = driverState.contextMap.get(folder);
|
||||||
if(ctx == null)
|
if(ctx == null)
|
||||||
{
|
{
|
||||||
ctx = ruleEvaluator.createContext();
|
ctx = ruleEvaluator.createContext(driverState.sessionState);
|
||||||
driverState.contextMap.put(folder, ctx);
|
driverState.contextMap.put(folder, ctx);
|
||||||
if(logger.isDebugEnabled())
|
if(logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
|
@@ -6,11 +6,25 @@
|
|||||||
package org.alfresco.filesys.repo.rules;
|
package org.alfresco.filesys.repo.rules;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EvaluatorContext
|
* EvaluatorContext
|
||||||
*/
|
*/
|
||||||
public interface EvaluatorContext
|
public interface EvaluatorContext
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Get the current scenario instances for this context
|
||||||
|
* @return a list of the curent scenario instances
|
||||||
|
*/
|
||||||
public List<ScenarioInstance> getScenarioInstances();
|
public List<ScenarioInstance> getScenarioInstances();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the session state for this context.
|
||||||
|
* @return the session state for this context.
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getSessionState();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.filesys.repo.rules;
|
package org.alfresco.filesys.repo.rules;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Rule Evaluator evaluates the operation and returns
|
* The Rule Evaluator evaluates the operation and returns
|
||||||
* details of the commands to implement those operations.
|
* details of the commands to implement those operations.
|
||||||
@@ -31,7 +33,7 @@ public interface RuleEvaluator
|
|||||||
* An evaluator context groups operations together.
|
* An evaluator context groups operations together.
|
||||||
* @return the new context.
|
* @return the new context.
|
||||||
*/
|
*/
|
||||||
public EvaluatorContext createContext();
|
public EvaluatorContext createContext(Map<String, Object>sessionContext);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate the scenarios contained within the context against the current operation
|
* Evaluate the scenarios contained within the context against the current operation
|
||||||
|
@@ -33,17 +33,23 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
* The Rule Evaluator evaluates the operation and returns
|
* The Rule Evaluator evaluates the operation and returns
|
||||||
* details of the commands to implement those operations.
|
* details of the commands to implement those operations.
|
||||||
* <p>
|
* <p>
|
||||||
* It is configured with a list of scenarios.
|
* It is configured with a list of scenarios which act as factories for scenario instances.
|
||||||
*/
|
*/
|
||||||
public class RuleEvaluatorImpl implements RuleEvaluator
|
public class RuleEvaluatorImpl implements RuleEvaluator
|
||||||
{
|
{
|
||||||
private static Log logger = LogFactory.getLog(RuleEvaluatorImpl.class);
|
private static Log logger = LogFactory.getLog(RuleEvaluatorImpl.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The evaluator context
|
* The evaluator context, one for each folder
|
||||||
*/
|
*/
|
||||||
private class EvaluatorContextImpl implements EvaluatorContext
|
private class EvaluatorContextImpl implements EvaluatorContext
|
||||||
{
|
{
|
||||||
|
Map<String, Object>sessionState;
|
||||||
|
|
||||||
|
EvaluatorContextImpl (Map<String, Object>sessionState)
|
||||||
|
{
|
||||||
|
this.sessionState = sessionState;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Current instances of scenarios
|
* Current instances of scenarios
|
||||||
*/
|
*/
|
||||||
@@ -53,6 +59,12 @@ public class RuleEvaluatorImpl implements RuleEvaluator
|
|||||||
public List<ScenarioInstance> getScenarioInstances()
|
public List<ScenarioInstance> getScenarioInstances()
|
||||||
{
|
{
|
||||||
return currentScenarioInstances;
|
return currentScenarioInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getSessionState()
|
||||||
|
{
|
||||||
|
return sessionState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +99,7 @@ public class RuleEvaluatorImpl implements RuleEvaluator
|
|||||||
{
|
{
|
||||||
for(Scenario scenario : scenarios)
|
for(Scenario scenario : scenarios)
|
||||||
{
|
{
|
||||||
ScenarioInstance instance = scenario.createInstance(context.getScenarioInstances(), operation);
|
ScenarioInstance instance = scenario.createInstance(context, operation);
|
||||||
if(instance != null)
|
if(instance != null)
|
||||||
{
|
{
|
||||||
context.getScenarioInstances().add(instance);
|
context.getScenarioInstances().add(instance);
|
||||||
@@ -161,9 +173,11 @@ public class RuleEvaluatorImpl implements RuleEvaluator
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EvaluatorContext createContext()
|
public EvaluatorContext createContext(Map<String, Object>sessionState)
|
||||||
{
|
{
|
||||||
return new EvaluatorContextImpl();
|
EvaluatorContextImpl impl = new EvaluatorContextImpl(sessionState);
|
||||||
|
|
||||||
|
return impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -35,7 +35,7 @@ public interface Scenario
|
|||||||
* @param operation the operation to be performed
|
* @param operation the operation to be performed
|
||||||
* @return the scenario instance or null if a new instance is not required.
|
* @return the scenario instance or null if a new instance is not required.
|
||||||
*/
|
*/
|
||||||
ScenarioInstance createInstance(final List<ScenarioInstance> currentInstances, Operation operation);
|
ScenarioInstance createInstance(EvaluatorContext ctx, Operation operation);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -51,7 +51,7 @@ public class ScenarioCreateDeleteRenameShuffle implements Scenario
|
|||||||
private Ranking ranking = Ranking.HIGH;
|
private Ranking ranking = Ranking.HIGH;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScenarioInstance createInstance(final List<ScenarioInstance> currentInstances, Operation operation)
|
public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This scenario is triggered by a create of a file matching
|
* This scenario is triggered by a create of a file matching
|
||||||
|
@@ -46,11 +46,11 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* If this filter is active then this is what happens.
|
* If this filter is active then this is what happens.
|
||||||
* a) New file created. New file created (X).
|
* a) New file created. New file created.
|
||||||
* b) Existing file deleted (Y to Z). File moved to temporary location.
|
* b) Existing file deleted. File moved to temporary location instead.
|
||||||
* c) Rename - Scenario fires
|
* c) Rename - Scenario fires
|
||||||
* - File moved back from temporary location
|
* - File moved back from temporary location
|
||||||
* - Content updated/
|
* - Content updated.
|
||||||
* - temporary file deleted
|
* - temporary file deleted
|
||||||
*/
|
*/
|
||||||
public class ScenarioCreateDeleteRenameShuffleInstance implements ScenarioInstance
|
public class ScenarioCreateDeleteRenameShuffleInstance implements ScenarioInstance
|
||||||
|
@@ -52,7 +52,7 @@ public class ScenarioCreateShuffle implements Scenario
|
|||||||
private Ranking ranking = Ranking.HIGH;
|
private Ranking ranking = Ranking.HIGH;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScenarioInstance createInstance(final List<ScenarioInstance> currentInstances, Operation operation)
|
public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This scenario is triggered by a create of a file matching
|
* This scenario is triggered by a create of a file matching
|
||||||
|
@@ -51,7 +51,7 @@ public class ScenarioDoubleRenameShuffle implements Scenario
|
|||||||
private Ranking ranking = Ranking.HIGH;
|
private Ranking ranking = Ranking.HIGH;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScenarioInstance createInstance(final List<ScenarioInstance> currentInstances, Operation operation)
|
public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This scenario is triggered by a rename of a file matching
|
* This scenario is triggered by a rename of a file matching
|
||||||
|
@@ -47,7 +47,7 @@ public class ScenarioLockedDeleteShuffle implements Scenario
|
|||||||
private Ranking ranking = Ranking.HIGH;
|
private Ranking ranking = Ranking.HIGH;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScenarioInstance createInstance(final List<ScenarioInstance> currentInstances, Operation operation)
|
public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This scenario is triggered by a create of a file matching
|
* This scenario is triggered by a create of a file matching
|
||||||
|
@@ -54,7 +54,7 @@ public class ScenarioOpenFile implements Scenario
|
|||||||
private long timeout = 300000;
|
private long timeout = 300000;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScenarioInstance createInstance(final List<ScenarioInstance> currentInstances, Operation operation)
|
public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This scenario is triggered by an open or create of a new file
|
* This scenario is triggered by an open or create of a new file
|
||||||
@@ -71,7 +71,7 @@ public class ScenarioOpenFile implements Scenario
|
|||||||
if(c.getName().matches(pattern))
|
if(c.getName().matches(pattern))
|
||||||
{
|
{
|
||||||
|
|
||||||
if(checkScenarioActive(c.getName(),currentInstances))
|
if(checkScenarioActive(c.getName(),ctx.getScenarioInstances()))
|
||||||
{
|
{
|
||||||
logger.debug("scenario already active for name" + c.getName());
|
logger.debug("scenario already active for name" + c.getName());
|
||||||
return null;
|
return null;
|
||||||
@@ -102,7 +102,7 @@ public class ScenarioOpenFile implements Scenario
|
|||||||
|
|
||||||
if(o.getName().matches(pattern))
|
if(o.getName().matches(pattern))
|
||||||
{
|
{
|
||||||
if(checkScenarioActive(o.getName(),currentInstances))
|
if(checkScenarioActive(o.getName(),ctx.getScenarioInstances()))
|
||||||
{
|
{
|
||||||
logger.debug("scenario already active for name" + o.getName());
|
logger.debug("scenario already active for name" + o.getName());
|
||||||
return null;
|
return null;
|
||||||
|
@@ -50,7 +50,7 @@ public class ScenarioRenameShuffle implements Scenario
|
|||||||
private long timeout = 30000;
|
private long timeout = 30000;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScenarioInstance createInstance(final List<ScenarioInstance> currentInstances, Operation operation)
|
public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This scenario is triggered by a rename of a file matching
|
* This scenario is triggered by a rename of a file matching
|
||||||
|
@@ -34,7 +34,7 @@ public class ScenarioSimpleNonBuffered implements Scenario
|
|||||||
private Ranking ranking = Ranking.LOW;
|
private Ranking ranking = Ranking.LOW;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScenarioInstance createInstance(final List<ScenarioInstance> currentInstances, Operation operation)
|
public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The bog standard scenario is always interested.
|
* The bog standard scenario is always interested.
|
||||||
|
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2005-2011 Alfresco Software Limited.
|
||||||
|
*
|
||||||
|
* This file is part of Alfresco
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.alfresco.filesys.repo.rules;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking;
|
||||||
|
import org.alfresco.filesys.repo.rules.operations.CreateFileOperation;
|
||||||
|
import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation;
|
||||||
|
import org.alfresco.jlan.server.filesys.FileName;
|
||||||
|
import org.alfresco.util.MaxSizeMap;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A temp delete shuffle.
|
||||||
|
*
|
||||||
|
* Files are created in a temporary directory
|
||||||
|
* and then a delete and move.
|
||||||
|
*/
|
||||||
|
public class ScenarioTempDeleteShuffle implements Scenario
|
||||||
|
{
|
||||||
|
private static Log logger = LogFactory.getLog(ScenarioTempDeleteShuffle.class);
|
||||||
|
|
||||||
|
protected final static String SCENARIO_KEY = "org.alfresco.filesys.repo.rules.ScenarioTempDeleteShuffle";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The regex pattern of a create that will identify a temporary directory.
|
||||||
|
*/
|
||||||
|
private Pattern tempDirPattern;
|
||||||
|
private String strTempDirPattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The regex pattern of a create that will trigger a new instance of
|
||||||
|
* the scenario.
|
||||||
|
*/
|
||||||
|
private Pattern pattern;
|
||||||
|
private String strPattern;
|
||||||
|
|
||||||
|
|
||||||
|
private long timeout = 30000;
|
||||||
|
|
||||||
|
private Ranking ranking = Ranking.HIGH;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This scenario is triggered by a delete of a file matching
|
||||||
|
* the pattern
|
||||||
|
*/
|
||||||
|
if(operation instanceof CreateFileOperation)
|
||||||
|
{
|
||||||
|
CreateFileOperation c = (CreateFileOperation)operation;
|
||||||
|
|
||||||
|
// check whether file is below .TemporaryItems
|
||||||
|
String path = c.getPath();
|
||||||
|
|
||||||
|
// if path contains .TemporaryItems
|
||||||
|
Matcher d = tempDirPattern.matcher(path);
|
||||||
|
if(d.matches())
|
||||||
|
{
|
||||||
|
logger.debug("pattern matches temp dir folder so this is a new create in a temp dir");
|
||||||
|
Matcher m = pattern.matcher(c.getName());
|
||||||
|
if(m.matches())
|
||||||
|
{
|
||||||
|
// and how to lock - since we are already have one lock on the scenarios/folder here
|
||||||
|
// this is a potential deadlock and synchronization bottleneck
|
||||||
|
Map<String, String> createdTempFiles = (Map<String,String>)ctx.getSessionState().get(SCENARIO_KEY);
|
||||||
|
|
||||||
|
if(createdTempFiles == null)
|
||||||
|
{
|
||||||
|
synchronized(ctx.getSessionState())
|
||||||
|
{
|
||||||
|
logger.debug("created new temp file map and added it to the session state");
|
||||||
|
createdTempFiles = (Map<String,String>)ctx.getSessionState().get(SCENARIO_KEY);
|
||||||
|
if(createdTempFiles == null)
|
||||||
|
{
|
||||||
|
createdTempFiles = Collections.synchronizedMap(new MaxSizeMap<String, String>(5, false));
|
||||||
|
ctx.getSessionState().put(SCENARIO_KEY, createdTempFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createdTempFiles.put(c.getName(), c.getName());
|
||||||
|
|
||||||
|
// TODO - Return a different scenario instance here ???
|
||||||
|
// So it can time out and have anti-patterns etc?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(operation instanceof DeleteFileOperation)
|
||||||
|
{
|
||||||
|
DeleteFileOperation c = (DeleteFileOperation)operation;
|
||||||
|
|
||||||
|
Matcher m = pattern.matcher(c.getName());
|
||||||
|
if(m.matches())
|
||||||
|
{
|
||||||
|
Map<String, String> createdTempFiles = (Map<String,String>)ctx.getSessionState().get(SCENARIO_KEY);
|
||||||
|
|
||||||
|
if(createdTempFiles != null)
|
||||||
|
{
|
||||||
|
if(createdTempFiles.containsKey(c.getName()))
|
||||||
|
{
|
||||||
|
if(logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("New Scenario Temp Delete Shuffle Instance:" + c.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScenarioTempDeleteShuffleInstance instance = new ScenarioTempDeleteShuffleInstance() ;
|
||||||
|
instance.setTimeout(timeout);
|
||||||
|
instance.setRanking(ranking);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No not interested.
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPattern(String pattern)
|
||||||
|
{
|
||||||
|
this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
|
||||||
|
this.strPattern = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPattern()
|
||||||
|
{
|
||||||
|
return this.strPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTempDirPattern(String tempDirPattern)
|
||||||
|
{
|
||||||
|
this.tempDirPattern = Pattern.compile(tempDirPattern, Pattern.CASE_INSENSITIVE);
|
||||||
|
this.strTempDirPattern = tempDirPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTempDirPattern()
|
||||||
|
{
|
||||||
|
return this.strTempDirPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(long timeout)
|
||||||
|
{
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeout()
|
||||||
|
{
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRanking(Ranking ranking)
|
||||||
|
{
|
||||||
|
this.ranking = ranking;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Ranking getRanking()
|
||||||
|
{
|
||||||
|
return ranking;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
||||||
|
*
|
||||||
|
* This file is part of Alfresco
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.alfresco.filesys.repo.rules;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.filesys.repo.rules.commands.CompoundCommand;
|
||||||
|
import org.alfresco.filesys.repo.rules.commands.CopyContentCommand;
|
||||||
|
import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand;
|
||||||
|
import org.alfresco.filesys.repo.rules.commands.RenameFileCommand;
|
||||||
|
import org.alfresco.filesys.repo.rules.operations.CreateFileOperation;
|
||||||
|
import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation;
|
||||||
|
import org.alfresco.filesys.repo.rules.operations.MoveFileOperation;
|
||||||
|
import org.alfresco.filesys.repo.rules.operations.RenameFileOperation;
|
||||||
|
import org.alfresco.jlan.server.filesys.FileName;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an instance of a "temp delete shuffle" triggered by a delete of a file matching
|
||||||
|
* a newly created file in a temporary directory.
|
||||||
|
*
|
||||||
|
* <p> First implemented for TextEdit from MacOS Lion
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Sequence of operations.
|
||||||
|
* a) Temporary Directory Created
|
||||||
|
* b) Temporary file created in temporary directory.
|
||||||
|
* c) Target file deleted
|
||||||
|
* d) Temp file moved in place of target file.
|
||||||
|
* e) Temporary directory deleted.
|
||||||
|
* <p>
|
||||||
|
* If this filter is active then this is what happens.
|
||||||
|
* a) Temp file created - in another folder.
|
||||||
|
* b) Existing file deleted. Scenario kicks in to rename rather than delete.
|
||||||
|
* c) New file moved into place (X to Y). Scenario kicks in
|
||||||
|
* 1) renames file from step c
|
||||||
|
* 2) copies content from temp file to target file
|
||||||
|
* 3) deletes temp file.
|
||||||
|
* d) Clean up scenario.
|
||||||
|
*/
|
||||||
|
public class ScenarioTempDeleteShuffleInstance implements ScenarioInstance
|
||||||
|
{
|
||||||
|
private static Log logger = LogFactory.getLog(ScenarioTempDeleteShuffleInstance.class);
|
||||||
|
|
||||||
|
enum InternalState
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
DELETE_SUBSTITUTED, // Scenario has intervened and renamed rather than delete
|
||||||
|
MOVED
|
||||||
|
}
|
||||||
|
|
||||||
|
InternalState internalState = InternalState.NONE;
|
||||||
|
|
||||||
|
private Date startTime = new Date();
|
||||||
|
|
||||||
|
private String lockName;
|
||||||
|
|
||||||
|
private Ranking ranking;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout in ms. Default 30 seconds.
|
||||||
|
*/
|
||||||
|
private long timeout = 60000;
|
||||||
|
|
||||||
|
private boolean isComplete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep track of deletes that we substitute with a rename
|
||||||
|
* could be more than one if scenarios overlap
|
||||||
|
*
|
||||||
|
* From, TempFileName
|
||||||
|
*/
|
||||||
|
private Map<String, String> deletes = new HashMap<String, String>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate the next operation
|
||||||
|
* @param operation
|
||||||
|
*/
|
||||||
|
public Command evaluate(Operation operation)
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anti-pattern : timeout
|
||||||
|
*/
|
||||||
|
Date now = new Date();
|
||||||
|
if(now.getTime() > startTime.getTime() + getTimeout())
|
||||||
|
{
|
||||||
|
if(logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("Instance timed out lockName:" + lockName);
|
||||||
|
isComplete = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (internalState)
|
||||||
|
{
|
||||||
|
|
||||||
|
case NONE:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looking for target file being deleted
|
||||||
|
*
|
||||||
|
* Need to intervene and replace delete with a rename to temp file.
|
||||||
|
*/
|
||||||
|
if(operation instanceof DeleteFileOperation)
|
||||||
|
{
|
||||||
|
DeleteFileOperation d = (DeleteFileOperation)operation;
|
||||||
|
|
||||||
|
|
||||||
|
if(logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("entering DELETE_SUBSTITUTED state: " + lockName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String tempName = ".shuffle" + d.getName();
|
||||||
|
|
||||||
|
deletes.put(d.getName(), tempName);
|
||||||
|
|
||||||
|
String[] paths = FileName.splitPath(d.getPath());
|
||||||
|
String currentFolder = paths[0];
|
||||||
|
|
||||||
|
RenameFileCommand r1 = new RenameFileCommand(d.getName(), tempName, d.getRootNodeRef(), d.getPath(), currentFolder + "\\" + tempName);
|
||||||
|
|
||||||
|
internalState = InternalState.DELETE_SUBSTITUTED;
|
||||||
|
|
||||||
|
return r1;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// anything else bomb out
|
||||||
|
if(logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("State error, expected a DELETE");
|
||||||
|
}
|
||||||
|
isComplete = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DELETE_SUBSTITUTED:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looking for a move operation of the deleted file
|
||||||
|
*/
|
||||||
|
if(operation instanceof MoveFileOperation)
|
||||||
|
{
|
||||||
|
MoveFileOperation m = (MoveFileOperation)operation;
|
||||||
|
|
||||||
|
String targetFile = m.getTo();
|
||||||
|
|
||||||
|
if(deletes.containsKey(targetFile))
|
||||||
|
{
|
||||||
|
String tempName = deletes.get(targetFile);
|
||||||
|
|
||||||
|
String[] paths = FileName.splitPath(m.getToPath());
|
||||||
|
String currentFolder = paths[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is where the scenario fires.
|
||||||
|
* a) Rename the temp file back to the targetFile
|
||||||
|
* b) Copy content from moved file
|
||||||
|
* c) Delete rather than move file
|
||||||
|
*/
|
||||||
|
logger.debug("scenario fires");
|
||||||
|
ArrayList<Command> commands = new ArrayList<Command>();
|
||||||
|
|
||||||
|
RenameFileCommand r1 = new RenameFileCommand(tempName, targetFile, m.getRootNodeRef(), currentFolder + "\\" + tempName, m.getToPath());
|
||||||
|
|
||||||
|
CopyContentCommand copyContent = new CopyContentCommand(m.getFrom(), targetFile, m.getRootNodeRef(), m.getFromPath(), m.getToPath());
|
||||||
|
|
||||||
|
DeleteFileCommand d1 = new DeleteFileCommand(m.getFrom(), m.getRootNodeRef(), m.getFromPath());
|
||||||
|
|
||||||
|
commands.add(r1);
|
||||||
|
commands.add(copyContent);
|
||||||
|
commands.add(d1);
|
||||||
|
|
||||||
|
logger.debug("Scenario complete");
|
||||||
|
isComplete = true;
|
||||||
|
|
||||||
|
return new CompoundCommand(commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isComplete()
|
||||||
|
{
|
||||||
|
return isComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Ranking getRanking()
|
||||||
|
{
|
||||||
|
return ranking;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRanking(Ranking ranking)
|
||||||
|
{
|
||||||
|
this.ranking = ranking;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "ScenarioTempDeleteShuffleInstance:" + lockName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(long timeout)
|
||||||
|
{
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeout()
|
||||||
|
{
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
}
|
@@ -22,11 +22,12 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.repo.batch.BatchProcessWorkProvider;
|
import org.alfresco.repo.batch.BatchProcessWorkProvider;
|
||||||
@@ -100,6 +101,7 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
|||||||
public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
||||||
{
|
{
|
||||||
private static final Log logger = LogFactory.getLog(HomeFolderProviderSynchronizer.class);
|
private static final Log logger = LogFactory.getLog(HomeFolderProviderSynchronizer.class);
|
||||||
|
private static final Log batchLogger = LogFactory.getLog(HomeFolderProviderSynchronizer.class+".batch");
|
||||||
|
|
||||||
private static final String GUEST_HOME_FOLDER_PROVIDER = "guestHomeFolderProvider";
|
private static final String GUEST_HOME_FOLDER_PROVIDER = "guestHomeFolderProvider";
|
||||||
private static final String BOOTSTRAP_HOME_FOLDER_PROVIDER = "bootstrapHomeFolderProvider";
|
private static final String BOOTSTRAP_HOME_FOLDER_PROVIDER = "bootstrapHomeFolderProvider";
|
||||||
@@ -225,6 +227,8 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
*
|
*
|
||||||
* Alternative approaches are possible, but the above has the advantage that
|
* Alternative approaches are possible, but the above has the advantage that
|
||||||
* nodes are not moved if they are already in their preferred location.
|
* nodes are not moved if they are already in their preferred location.
|
||||||
|
*
|
||||||
|
* Also needed to change the case of parent folders.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Using authorities rather than Person objects as they are much lighter
|
// Using authorities rather than Person objects as they are much lighter
|
||||||
@@ -279,9 +283,9 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
for (RunAsWorker worker: workers)
|
for (RunAsWorker worker: workers)
|
||||||
{
|
{
|
||||||
String name = worker.getName();
|
String name = worker.getName();
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isInfoEnabled())
|
||||||
{
|
{
|
||||||
logger.debug(" -- "+
|
logger.info(" -- "+
|
||||||
(TenantService.DEFAULT_DOMAIN.equals(tenantDomain)? "" : tenantDomain+" ")+
|
(TenantService.DEFAULT_DOMAIN.equals(tenantDomain)? "" : tenantDomain+" ")+
|
||||||
name+" --");
|
name+" --");
|
||||||
}
|
}
|
||||||
@@ -296,11 +300,11 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
new WorkProvider(authorities),
|
new WorkProvider(authorities),
|
||||||
threadCount, peoplePerTransaction,
|
threadCount, peoplePerTransaction,
|
||||||
null,
|
null,
|
||||||
logger, 100);
|
batchLogger, 100);
|
||||||
processor.process(worker, true);
|
processor.process(worker, true);
|
||||||
if (processor.getTotalErrors() > 0)
|
if (processor.getTotalErrors() > 0)
|
||||||
{
|
{
|
||||||
logger.debug(" -- Give up after error --");
|
logger.info(" -- Give up after error --");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,7 +323,11 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
{
|
{
|
||||||
public Set<String> execute() throws Exception
|
public Set<String> execute() throws Exception
|
||||||
{
|
{
|
||||||
return authorityService.getAllAuthorities(AuthorityType.USER);
|
// Returns a sorted set (using natural ordering) rather than a hashCode
|
||||||
|
// so that it is more obvious what the order is for processing users.
|
||||||
|
Set<String> result = new TreeSet<String>();
|
||||||
|
result.addAll(authorityService.getAllAuthorities(AuthorityType.USER));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return txnHelper.doInTransaction(restoreCallback, false, true);
|
return txnHelper.doInTransaction(restoreCallback, false, true);
|
||||||
@@ -414,17 +422,24 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
private String createTmpFolderName(NodeRef root)
|
private String createTmpFolderName(NodeRef root)
|
||||||
{
|
{
|
||||||
// Try a few times but then give up.
|
// Try a few times but then give up.
|
||||||
for (int i = 1; i <= 100; i++)
|
String temporary = "Temporary-";
|
||||||
|
int from = 1;
|
||||||
|
int to = 100;
|
||||||
|
for (int i = from; i <= to; i++)
|
||||||
{
|
{
|
||||||
String tmpFolderName = "Temporary"+i;
|
String tmpFolderName = temporary+i;
|
||||||
if (fileFolderService.searchSimple(root, tmpFolderName) == null)
|
if (fileFolderService.searchSimple(root, tmpFolderName) == null)
|
||||||
{
|
{
|
||||||
fileFolderService.create(root, tmpFolderName, ContentModel.TYPE_FOLDER);
|
fileFolderService.create(root, tmpFolderName, ContentModel.TYPE_FOLDER);
|
||||||
return tmpFolderName;
|
return tmpFolderName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new PersonException("Unable to create a temporty " +
|
String msg = "Unable to create a temporary " +
|
||||||
"folder into which home folders could be moved.");
|
"folder into which home folders will be moved. " +
|
||||||
|
"Tried creating " + temporary + from + " .. " + temporary + to +
|
||||||
|
". Remove these folders and try again.";
|
||||||
|
logger.error(" # "+msg);
|
||||||
|
throw new PersonException(msg);
|
||||||
}
|
}
|
||||||
}.doWork();
|
}.doWork();
|
||||||
}
|
}
|
||||||
@@ -480,18 +495,18 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
@Override
|
@Override
|
||||||
protected void handleInPreferredLocation()
|
protected void handleInPreferredLocation()
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isInfoEnabled())
|
||||||
{
|
{
|
||||||
logger.debug(" "+toPath(actualPath)+" is already in preferred location.");
|
logger.info(" # "+toPath(actualPath)+" is already in preferred location.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleSharedHomeProvider()
|
protected void handleSharedHomeProvider()
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isInfoEnabled())
|
||||||
{
|
{
|
||||||
logger.debug(" "+userName+" "+providerName+" creates shared home folders - These are not moved.");
|
logger.info(" # "+userName+" "+providerName+" creates shared home folders - These are not moved.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,36 +514,36 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
@Override
|
@Override
|
||||||
protected void handleOriginalSharedHomeProvider()
|
protected void handleOriginalSharedHomeProvider()
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isInfoEnabled())
|
||||||
{
|
{
|
||||||
logger.debug(" "+userName+" Original "+originalProviderName+" creates shared home folders - These are not moved.");
|
logger.info(" # "+userName+" Original "+originalProviderName+" creates shared home folders - These are not moved.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleNotAHomeFolderProvider2()
|
protected void handleNotAHomeFolderProvider2()
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isInfoEnabled())
|
||||||
{
|
{
|
||||||
logger.debug(" "+userName+" "+providerName+" for is not a HomeFolderProvider2.");
|
logger.info(" # "+userName+" "+providerName+" for is not a HomeFolderProvider2.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleSpecialHomeFolderProvider()
|
protected void handleSpecialHomeFolderProvider()
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isInfoEnabled())
|
||||||
{
|
{
|
||||||
logger.debug(" "+userName+" Original "+originalProviderName+" is an internal type - These are not moved.");
|
logger.info(" # "+userName+" Original "+originalProviderName+" is an internal type - These are not moved.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleHomeFolderNotSet()
|
protected void handleHomeFolderNotSet()
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isInfoEnabled())
|
||||||
{
|
{
|
||||||
logger.debug(" "+userName+" Home folder is not set - ignored");
|
logger.info(" # "+userName+" Home folder is not set - ignored");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.doWork();
|
}.doWork();
|
||||||
@@ -538,6 +553,11 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
* @return a String for debug a folder list.
|
* @return a String for debug a folder list.
|
||||||
*/
|
*/
|
||||||
private String toPath(List<String> folders)
|
private String toPath(List<String> folders)
|
||||||
|
{
|
||||||
|
return toPath(folders, (folders == null) ? 0 : folders.size()-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toPath(List<String> folders, int depth)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder("");
|
StringBuilder sb = new StringBuilder("");
|
||||||
if (folders != null)
|
if (folders != null)
|
||||||
@@ -549,8 +569,16 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
sb.append('/');
|
sb.append('/');
|
||||||
}
|
}
|
||||||
sb.append(folder);
|
sb.append(folder);
|
||||||
|
if (depth-- <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append('.');
|
||||||
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,20 +662,23 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
// parent folders should have been created.
|
// parent folders should have been created.
|
||||||
NodeRef newParent = createNewParentIfRequired(root, preferredPath);
|
NodeRef newParent = createNewParentIfRequired(root, preferredPath);
|
||||||
|
|
||||||
|
// If the preferred home folder already exists, append "-N"
|
||||||
|
homeFolderManager.modifyHomeFolderNameIfItExists(root, preferredPath);
|
||||||
String homeFolderName = preferredPath.get(preferredPath.size() - 1);
|
String homeFolderName = preferredPath.get(preferredPath.size() - 1);
|
||||||
|
|
||||||
// Throw our own FileExistsException before we get one that
|
|
||||||
// marks the transaction for rollback, as there is no point
|
|
||||||
// trying again.
|
|
||||||
if (nodeService.getChildByName(newParent, ContentModel.ASSOC_CONTAINS,
|
|
||||||
homeFolderName) != null)
|
|
||||||
{
|
|
||||||
throw new FileExistsException(newParent, homeFolderName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the old parent before we move anything.
|
// Get the old parent before we move anything.
|
||||||
NodeRef oldParent = nodeService.getPrimaryParent(homeFolder) .getParentRef();
|
NodeRef oldParent = nodeService.getPrimaryParent(homeFolder) .getParentRef();
|
||||||
|
|
||||||
|
// Log action
|
||||||
|
if (logger.isInfoEnabled())
|
||||||
|
{
|
||||||
|
logger.info(" mv "+toPath(actualPath)+
|
||||||
|
" "+ toPath(preferredPath)+
|
||||||
|
((providerName != null && !providerName.equals(originalProviderName))
|
||||||
|
? " # AND reset provider to "+providerName
|
||||||
|
: "") + ".");
|
||||||
|
}
|
||||||
|
|
||||||
// Perform the move
|
// Perform the move
|
||||||
homeFolder = fileFolderService.move(homeFolder, newParent,
|
homeFolder = fileFolderService.move(homeFolder, newParent,
|
||||||
homeFolderName).getNodeRef();
|
homeFolderName).getNodeRef();
|
||||||
@@ -662,16 +693,6 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
ContentModel.PROP_HOME_FOLDER_PROVIDER, providerName);
|
ContentModel.PROP_HOME_FOLDER_PROVIDER, providerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log action
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug(" mv "+toPath(actualPath)+
|
|
||||||
" "+ toPath(preferredPath)+
|
|
||||||
((providerName != null && !providerName.equals(originalProviderName))
|
|
||||||
? " AND reset provider to "+providerName
|
|
||||||
: "") + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tidy up
|
// Tidy up
|
||||||
removeEmptyParentFolders(oldParent, oldRoot);
|
removeEmptyParentFolders(oldParent, oldRoot);
|
||||||
}
|
}
|
||||||
@@ -679,7 +700,7 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
{
|
{
|
||||||
String message = "mv "+toPath(actualPath)+" "+toPath(preferredPath)+
|
String message = "mv "+toPath(actualPath)+" "+toPath(preferredPath)+
|
||||||
" failed as the target already existed.";
|
" failed as the target already existed.";
|
||||||
logger.error(" "+message);
|
logger.error(" # "+message);
|
||||||
throw new PersonException(message);
|
throw new PersonException(message);
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException e)
|
catch (FileNotFoundException e)
|
||||||
@@ -691,7 +712,7 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
throw new PersonException(message);
|
throw new PersonException(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NodeRef createNewParentIfRequired(NodeRef root, List<String> homeFolderPath)
|
private NodeRef createNewParentIfRequired(NodeRef root, List<String> homeFolderPath)
|
||||||
{
|
{
|
||||||
NodeRef parent = root;
|
NodeRef parent = root;
|
||||||
@@ -701,8 +722,13 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
String pathElement = homeFolderPath.get(i);
|
String pathElement = homeFolderPath.get(i);
|
||||||
NodeRef nodeRef = nodeService.getChildByName(parent,
|
NodeRef nodeRef = nodeService.getChildByName(parent,
|
||||||
ContentModel.ASSOC_CONTAINS, pathElement);
|
ContentModel.ASSOC_CONTAINS, pathElement);
|
||||||
|
String path = toPath(homeFolderPath, i);
|
||||||
if (nodeRef == null)
|
if (nodeRef == null)
|
||||||
{
|
{
|
||||||
|
if (logger.isInfoEnabled())
|
||||||
|
{
|
||||||
|
logger.info(" mkdir "+path);
|
||||||
|
}
|
||||||
parent = fileFolderService.create(parent, pathElement,
|
parent = fileFolderService.create(parent, pathElement,
|
||||||
ContentModel.TYPE_FOLDER).getNodeRef();
|
ContentModel.TYPE_FOLDER).getNodeRef();
|
||||||
}
|
}
|
||||||
@@ -714,7 +740,13 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
// there is no point trying again.
|
// there is no point trying again.
|
||||||
if (!fileFolderService.getFileInfo(nodeRef).isFolder())
|
if (!fileFolderService.getFileInfo(nodeRef).isFolder())
|
||||||
{
|
{
|
||||||
throw new FileExistsException(parent, null);
|
if (logger.isErrorEnabled())
|
||||||
|
{
|
||||||
|
logger.error(" # cannot create folder " + path +
|
||||||
|
" as content with the same name exists. " +
|
||||||
|
"Move the content and try again.");
|
||||||
|
}
|
||||||
|
throw new FileExistsException(parent, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
parent = nodeRef;
|
parent = nodeRef;
|
||||||
@@ -757,9 +789,9 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isInfoEnabled())
|
||||||
{
|
{
|
||||||
logger.debug(" rm "+toPath(root, nodeRef));
|
logger.info(" rm "+toPath(root, nodeRef));
|
||||||
}
|
}
|
||||||
nodeService.deleteNode(nodeRef);
|
nodeService.deleteNode(nodeRef);
|
||||||
}
|
}
|
||||||
@@ -983,84 +1015,226 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gathers and checks parent folder paths.
|
// Records the parents of the preferred folder paths (the leaf folder are not recorded)
|
||||||
|
// and checks actual paths against these.
|
||||||
private class ParentFolderStructure
|
private class ParentFolderStructure
|
||||||
{
|
{
|
||||||
// Sets of parent folders within each root node
|
// Parent folders within each root node
|
||||||
private Map<NodeRef, Set<List<String>>> folders = new HashMap<NodeRef, Set<List<String>>>();
|
private Map<NodeRef, RootFolder> folders = new HashMap<NodeRef, RootFolder>();
|
||||||
|
|
||||||
public void recordParentFolder(NodeRef root, List<String> path)
|
public void recordParentFolder(NodeRef root, List<String> path)
|
||||||
{
|
{
|
||||||
Set<List<String>> rootsFolders = getFolders(root);
|
RootFolder rootsFolders = getFolders(root);
|
||||||
synchronized(rootsFolders)
|
synchronized(rootsFolders)
|
||||||
{
|
{
|
||||||
// If parent is the root, all home folders clash
|
rootsFolders.add(path);
|
||||||
int parentSize = path.size() - 1;
|
|
||||||
if (parentSize == 0)
|
|
||||||
{
|
|
||||||
// We could optimise the code a little by clearing
|
|
||||||
// all other entries and putting a contains(null)
|
|
||||||
// check just inside the synchronized(rootsFolders)
|
|
||||||
// but it might be useful to have a complete lit of
|
|
||||||
// folders.
|
|
||||||
rootsFolders.add(null);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug(" Recorded root as parent");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (parentSize-- > 0)
|
|
||||||
{
|
|
||||||
List<String> parentPath = new ArrayList<String>();
|
|
||||||
for (int j = 0; j <= parentSize; j++)
|
|
||||||
{
|
|
||||||
parentPath.add(path.get(j));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()
|
|
||||||
&& !rootsFolders.contains(parentPath))
|
|
||||||
{
|
|
||||||
logger.debug(" Recorded parent: "
|
|
||||||
+ toPath(parentPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
rootsFolders.add(parentPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true} if the {@code path} is a parent folder
|
* Checks to see if there is a clash between the preferred paths and the
|
||||||
* or the parent folders includes the root itself. In
|
* existing folder structure. If there is a clash, the existing home folder
|
||||||
* the latter case all existing folders might clash
|
* (the leaf folder) is moved to a temporary structure. This allows any
|
||||||
* so must be moved out of the way.
|
* parent folders to be tidied up (if empty), so that the new preferred
|
||||||
|
* structure can be recreated.<p>
|
||||||
|
*
|
||||||
|
* 1. There is no clash if the path is null or empty.
|
||||||
|
*
|
||||||
|
* 2. There is a clash if there is a parent structure included the root
|
||||||
|
* folder itself.<p>
|
||||||
|
*
|
||||||
|
* 3. There is a clash if the existing path exists in the parent structure.
|
||||||
|
* This comparison ignores case as Alfresco does not allow duplicates
|
||||||
|
* regardless of case.<p>
|
||||||
|
*
|
||||||
|
* 4. There is a clash if any of the folders in the existing path don't
|
||||||
|
* match the case of the parent folders.
|
||||||
|
*
|
||||||
|
* 5. There is a clash there are different case versions of the parent
|
||||||
|
* folders themselves or other existing folders.
|
||||||
|
*
|
||||||
|
* When 4 takes place, we will end up with the first one we try to recreate
|
||||||
|
* being used for all.
|
||||||
*/
|
*/
|
||||||
public boolean clash(NodeRef root, List<String> path)
|
public boolean clash(NodeRef root, List<String> path)
|
||||||
{
|
{
|
||||||
Set<List<String>> rootsFolders = getFolders(root);
|
if (path == null || path.isEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RootFolder rootsFolders = getFolders(root);
|
||||||
synchronized(rootsFolders)
|
synchronized(rootsFolders)
|
||||||
{
|
{
|
||||||
return rootsFolders.contains(path) ||
|
return rootsFolders.clash(path);
|
||||||
rootsFolders.contains(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<List<String>> getFolders(NodeRef root)
|
private RootFolder getFolders(NodeRef root)
|
||||||
{
|
{
|
||||||
synchronized(folders)
|
synchronized(folders)
|
||||||
{
|
{
|
||||||
Set<List<String>> rootsFolders = folders.get(root);
|
RootFolder rootsFolders = folders.get(root);
|
||||||
if (rootsFolders == null)
|
if (rootsFolders == null)
|
||||||
{
|
{
|
||||||
rootsFolders = new HashSet<List<String>>();
|
rootsFolders = new RootFolder();
|
||||||
folders.put(root, rootsFolders);
|
folders.put(root, rootsFolders);
|
||||||
}
|
}
|
||||||
return rootsFolders;
|
return rootsFolders;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Records the parents of the preferred folder paths (the leaf folder are not recorded)
|
||||||
|
// and checks actual paths against these BUT only for a single root.
|
||||||
|
private class RootFolder extends Folder
|
||||||
|
{
|
||||||
|
private boolean includesRoot;
|
||||||
|
|
||||||
|
public RootFolder()
|
||||||
|
{
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a path (but not the leaf folder) if it does not already exist.
|
||||||
|
public void add(List<String> path)
|
||||||
|
{
|
||||||
|
if (!includesRoot)
|
||||||
|
{
|
||||||
|
int parentSize = path.size() - 1;
|
||||||
|
if (parentSize == 0)
|
||||||
|
{
|
||||||
|
includesRoot = true;
|
||||||
|
children = null; // can discard children as all home folders now clash.
|
||||||
|
if (logger.isInfoEnabled())
|
||||||
|
{
|
||||||
|
logger.info(" # Recorded root as parent - no need to record other parents as all home folders will clash");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add(path, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See description of {@link ParentFolderStructure#clash(NodeRef, List)}.<p>
|
||||||
|
*
|
||||||
|
* Performs check 2 and then calls {@link Folder#clash(List, int)} to
|
||||||
|
* perform 3, 4 and 5.
|
||||||
|
*/
|
||||||
|
public boolean clash(List<String> path)
|
||||||
|
{
|
||||||
|
// Checks 2.
|
||||||
|
return includesRoot ? false : clash(path, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Folder
|
||||||
|
{
|
||||||
|
// Case specific name of first folder added.
|
||||||
|
String name;
|
||||||
|
|
||||||
|
// Indicates if there is another preferred name that used different case.
|
||||||
|
boolean duplicateWithDifferentCase;
|
||||||
|
|
||||||
|
List<Folder> children;
|
||||||
|
|
||||||
|
public Folder(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a path (but not the leaf folder) if it does not already exist.
|
||||||
|
* @param path the full path to add
|
||||||
|
* @param depth the current depth into the path starting with 0.
|
||||||
|
*/
|
||||||
|
protected void add(List<String> path, int depth)
|
||||||
|
{
|
||||||
|
int parentSize = path.size() - 1;
|
||||||
|
String name = path.get(depth);
|
||||||
|
Folder child = getChild(name);
|
||||||
|
if (child == null)
|
||||||
|
{
|
||||||
|
child = new Folder(name);
|
||||||
|
if (children == null)
|
||||||
|
{
|
||||||
|
children = new LinkedList<Folder>();
|
||||||
|
}
|
||||||
|
children.add(child);
|
||||||
|
if (logger.isInfoEnabled())
|
||||||
|
{
|
||||||
|
logger.info(" " + toPath(path, depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!child.name.equals(name))
|
||||||
|
{
|
||||||
|
child.duplicateWithDifferentCase = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't add the leaf folder
|
||||||
|
if (++depth < parentSize)
|
||||||
|
{
|
||||||
|
add(path, depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See description of {@link ParentFolderStructure#clash(NodeRef, List)}.<p>
|
||||||
|
*
|
||||||
|
* Performs checks 3, 4 and 5 for a single level and then recursively checks
|
||||||
|
* lower levels.
|
||||||
|
*/
|
||||||
|
protected boolean clash(List<String> path, int depth)
|
||||||
|
{
|
||||||
|
String name = path.get(depth);
|
||||||
|
Folder child = getChild(name); // Uses equalsIgnoreCase
|
||||||
|
if (child == null)
|
||||||
|
{
|
||||||
|
// Negation of check 3.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (child.duplicateWithDifferentCase) // if there folders using different case!
|
||||||
|
{
|
||||||
|
// Check 5.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (!child.name.equals(name)) // if the case does not match
|
||||||
|
{
|
||||||
|
// Check 4.
|
||||||
|
child.duplicateWithDifferentCase = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a match (including case) has been made to the end of the path
|
||||||
|
if (++depth == path.size())
|
||||||
|
{
|
||||||
|
// Check 3.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check lower levels.
|
||||||
|
return clash(path, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the child folder with the specified name (ignores case).
|
||||||
|
*/
|
||||||
|
private Folder getChild(String name)
|
||||||
|
{
|
||||||
|
if (children != null)
|
||||||
|
{
|
||||||
|
for (Folder child: children)
|
||||||
|
{
|
||||||
|
if (name.equalsIgnoreCase(child.name))
|
||||||
|
{
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,8 +28,8 @@ import java.util.Arrays;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import javax.transaction.Status;
|
import javax.transaction.Status;
|
||||||
import javax.transaction.UserTransaction;
|
import javax.transaction.UserTransaction;
|
||||||
@@ -51,7 +51,6 @@ import org.alfresco.service.cmr.repository.NodeService;
|
|||||||
import org.alfresco.service.cmr.repository.Path;
|
import org.alfresco.service.cmr.repository.Path;
|
||||||
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
||||||
import org.alfresco.service.cmr.security.AuthorityService;
|
import org.alfresco.service.cmr.security.AuthorityService;
|
||||||
import org.alfresco.service.cmr.security.PersonService;
|
|
||||||
import org.alfresco.service.namespace.NamespaceService;
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
@@ -83,6 +82,7 @@ public class HomeFolderProviderSynchronizerTest
|
|||||||
private static AuthorityService authorityService;
|
private static AuthorityService authorityService;
|
||||||
private static TenantAdminService tenantAdminService;
|
private static TenantAdminService tenantAdminService;
|
||||||
private static TenantService tenantService;
|
private static TenantService tenantService;
|
||||||
|
private static UserNameMatcherImpl userNameMatcher;
|
||||||
private static PortableHomeFolderManager homeFolderManager;
|
private static PortableHomeFolderManager homeFolderManager;
|
||||||
private static RegexHomeFolderProvider largeHomeFolderProvider;
|
private static RegexHomeFolderProvider largeHomeFolderProvider;
|
||||||
private static String largeHomeFolderProviderName;
|
private static String largeHomeFolderProviderName;
|
||||||
@@ -109,6 +109,7 @@ public class HomeFolderProviderSynchronizerTest
|
|||||||
authorityService = (AuthorityService) applicationContext.getBean("authorityService");
|
authorityService = (AuthorityService) applicationContext.getBean("authorityService");
|
||||||
tenantAdminService = (TenantAdminService) applicationContext.getBean("tenantAdminService");
|
tenantAdminService = (TenantAdminService) applicationContext.getBean("tenantAdminService");
|
||||||
tenantService = (TenantService) applicationContext.getBean("tenantService");
|
tenantService = (TenantService) applicationContext.getBean("tenantService");
|
||||||
|
userNameMatcher = (UserNameMatcherImpl) applicationContext.getBean("userNameMatcher");
|
||||||
homeFolderManager = (PortableHomeFolderManager) applicationContext.getBean("homeFolderManager");
|
homeFolderManager = (PortableHomeFolderManager) applicationContext.getBean("homeFolderManager");
|
||||||
largeHomeFolderProvider = (RegexHomeFolderProvider) applicationContext.getBean("largeHomeFolderProvider");
|
largeHomeFolderProvider = (RegexHomeFolderProvider) applicationContext.getBean("largeHomeFolderProvider");
|
||||||
largeHomeFolderProviderName = largeHomeFolderProvider.getName();
|
largeHomeFolderProviderName = largeHomeFolderProvider.getName();
|
||||||
@@ -207,6 +208,7 @@ public class HomeFolderProviderSynchronizerTest
|
|||||||
trans.commit();
|
trans.commit();
|
||||||
trans = null;
|
trans = null;
|
||||||
AuthenticationUtil.clearCurrentSecurityContext();
|
AuthenticationUtil.clearCurrentSecurityContext();
|
||||||
|
userNameMatcher.setUserNamesAreCaseSensitive(false); // Put back the default
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<NodeRef> deleteNonAdminGuestUsers()
|
private Set<NodeRef> deleteNonAdminGuestUsers()
|
||||||
@@ -704,6 +706,8 @@ public class HomeFolderProviderSynchronizerTest
|
|||||||
@Test
|
@Test
|
||||||
public void testPathAlreadyInUseByContent() throws Exception
|
public void testPathAlreadyInUseByContent() throws Exception
|
||||||
{
|
{
|
||||||
|
System.out.println("testPathAlreadyInUseByContent: EXPECT TO SEE AN EXCEPTION IN THE LOG ======================== ");
|
||||||
|
|
||||||
createUser("", "fred");
|
createUser("", "fred");
|
||||||
createContent("", "fr");
|
createContent("", "fr");
|
||||||
|
|
||||||
@@ -731,7 +735,7 @@ public class HomeFolderProviderSynchronizerTest
|
|||||||
assertHomeFolderLocation("peter", "pe/peter");
|
assertHomeFolderLocation("peter", "pe/peter");
|
||||||
assertHomeFolderLocation("pe", "pe/pe");
|
assertHomeFolderLocation("pe", "pe/pe");
|
||||||
|
|
||||||
assertFalse("The Temporary1 folder should have been removed", exists("Temporary1"));
|
assertFalse("The Temporary-1 folder should have been removed", exists("Temporary-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -739,24 +743,26 @@ public class HomeFolderProviderSynchronizerTest
|
|||||||
{
|
{
|
||||||
createUser("", "fr");
|
createUser("", "fr");
|
||||||
createUser("", "fred");
|
createUser("", "fred");
|
||||||
createFolder("Temporary1");
|
createFolder("Temporary-1");
|
||||||
createFolder("Temporary2");
|
createFolder("Temporary-2");
|
||||||
createFolder("Temporary3");
|
createFolder("Temporary-3");
|
||||||
|
|
||||||
// Don't delete the temporary folder
|
// Don't delete the temporary folder
|
||||||
homeFolderProviderSynchronizer.setKeepEmptyParents("true");
|
homeFolderProviderSynchronizer.setKeepEmptyParents("true");
|
||||||
|
|
||||||
moveUserHomeFolders();
|
moveUserHomeFolders();
|
||||||
|
|
||||||
assertTrue("The existing Temporary1 folder should still exist", exists("Temporary1"));
|
assertTrue("The existing Temporary-1 folder should still exist", exists("Temporary-1"));
|
||||||
assertTrue("The existing Temporary2 folder should still exist", exists("Temporary2"));
|
assertTrue("The existing Temporary-2 folder should still exist", exists("Temporary-2"));
|
||||||
assertTrue("The existing Temporary3 folder should still exist", exists("Temporary3"));
|
assertTrue("The existing Temporary-3 folder should still exist", exists("Temporary-3"));
|
||||||
assertTrue("The existing Temporary4 folder should still exist", exists("Temporary4"));
|
assertTrue("The existing Temporary-4 folder should still exist", exists("Temporary-4"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testException() throws Exception
|
public void testException() throws Exception
|
||||||
{
|
{
|
||||||
|
System.out.println("testException: EXPECT TO SEE AN EXCEPTION IN THE LOG ======================== ");
|
||||||
|
|
||||||
// Force the need for a temporary folder
|
// Force the need for a temporary folder
|
||||||
createUser("", "fr");
|
createUser("", "fr");
|
||||||
createUser("", "fred");
|
createUser("", "fred");
|
||||||
@@ -764,7 +770,7 @@ public class HomeFolderProviderSynchronizerTest
|
|||||||
// Use up all possible temporary folder names
|
// Use up all possible temporary folder names
|
||||||
for (int i=1; i<=100; i++)
|
for (int i=1; i<=100; i++)
|
||||||
{
|
{
|
||||||
createFolder("Temporary"+i);
|
createFolder("Temporary-"+i);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveUserHomeFolders();
|
moveUserHomeFolders();
|
||||||
@@ -939,4 +945,38 @@ public class HomeFolderProviderSynchronizerTest
|
|||||||
assertHomeFolderLocation(tenant2, "fred", "fr/"+tenantService.getDomainUser("fred", tenant2));
|
assertHomeFolderLocation(tenant2, "fred", "fr/"+tenantService.getDomainUser("fred", tenant2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ALF-11535
|
||||||
|
@Test
|
||||||
|
public void testChangeParentFolderCase() throws Exception
|
||||||
|
{
|
||||||
|
// By default, user names are case sensitive
|
||||||
|
createUser("fr", "FRED");
|
||||||
|
moveUserHomeFolders();
|
||||||
|
assertHomeFolderLocation("FRED", "FR/FRED");
|
||||||
|
assertHomeFolderLocation("fred", "FR/FRED"); // Same user
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALF-11535
|
||||||
|
@Test
|
||||||
|
public void testCaseSensitiveUsers() throws Exception
|
||||||
|
{
|
||||||
|
userNameMatcher.setUserNamesAreCaseSensitive(true);
|
||||||
|
|
||||||
|
// Users are processed in a sorted order (natural ordering).
|
||||||
|
// The preferred parent folder structure of the first user
|
||||||
|
// is used where there is a clash between users.
|
||||||
|
|
||||||
|
// The following users are in their natural order.
|
||||||
|
createUser("Ab", "Abby");
|
||||||
|
createUser("TE", "TESS");
|
||||||
|
createUser("TE", "Tess");
|
||||||
|
createUser("Ab", "aBBY");
|
||||||
|
|
||||||
|
moveUserHomeFolders();
|
||||||
|
assertHomeFolderLocation("Abby", "Ab/Abby");
|
||||||
|
assertHomeFolderLocation("TESS", "TE/TESS");
|
||||||
|
assertHomeFolderLocation("Tess", "TE/Tess-1");
|
||||||
|
assertHomeFolderLocation("aBBY", "Ab/aBBY-1");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -72,6 +72,8 @@ public class PersonTest extends TestCase
|
|||||||
|
|
||||||
private TransactionService transactionService;
|
private TransactionService transactionService;
|
||||||
private PersonService personService;
|
private PersonService personService;
|
||||||
|
private UserNameMatcherImpl userNameMatcher;
|
||||||
|
|
||||||
private BehaviourFilter policyBehaviourFilter;
|
private BehaviourFilter policyBehaviourFilter;
|
||||||
private NodeService nodeService;
|
private NodeService nodeService;
|
||||||
private NodeRef rootNodeRef;
|
private NodeRef rootNodeRef;
|
||||||
@@ -93,6 +95,7 @@ public class PersonTest extends TestCase
|
|||||||
|
|
||||||
transactionService = (TransactionService) ctx.getBean("transactionService");
|
transactionService = (TransactionService) ctx.getBean("transactionService");
|
||||||
personService = (PersonService) ctx.getBean("personService");
|
personService = (PersonService) ctx.getBean("personService");
|
||||||
|
userNameMatcher = (UserNameMatcherImpl) ctx.getBean("userNameMatcher");
|
||||||
nodeService = (NodeService) ctx.getBean("nodeService");
|
nodeService = (NodeService) ctx.getBean("nodeService");
|
||||||
permissionService = (PermissionService) ctx.getBean("permissionService");
|
permissionService = (PermissionService) ctx.getBean("permissionService");
|
||||||
authorityService = (AuthorityService) ctx.getBean("authorityService");
|
authorityService = (AuthorityService) ctx.getBean("authorityService");
|
||||||
@@ -124,6 +127,7 @@ public class PersonTest extends TestCase
|
|||||||
@Override
|
@Override
|
||||||
protected void tearDown() throws Exception
|
protected void tearDown() throws Exception
|
||||||
{
|
{
|
||||||
|
userNameMatcher.setUserNamesAreCaseSensitive(false); // Put back the default
|
||||||
|
|
||||||
if ((testTX.getStatus() == Status.STATUS_ACTIVE) || (testTX.getStatus() == Status.STATUS_MARKED_ROLLBACK))
|
if ((testTX.getStatus() == Status.STATUS_ACTIVE) || (testTX.getStatus() == Status.STATUS_MARKED_ROLLBACK))
|
||||||
{
|
{
|
||||||
@@ -1125,9 +1129,7 @@ public class PersonTest extends TestCase
|
|||||||
final String TEST_PERSON_UPPER = TEST_PERSON_MIXED.toUpperCase();
|
final String TEST_PERSON_UPPER = TEST_PERSON_MIXED.toUpperCase();
|
||||||
final String TEST_PERSON_LOWER = TEST_PERSON_MIXED.toLowerCase();
|
final String TEST_PERSON_LOWER = TEST_PERSON_MIXED.toLowerCase();
|
||||||
|
|
||||||
UserNameMatcherImpl usernameMatcher = new UserNameMatcherImpl();
|
userNameMatcher.setUserNamesAreCaseSensitive(true);
|
||||||
usernameMatcher.setUserNamesAreCaseSensitive(true);
|
|
||||||
((PersonServiceImpl)personService).setUserNameMatcher(usernameMatcher); // case-sensitive
|
|
||||||
|
|
||||||
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
|
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
|
||||||
|
|
||||||
@@ -1217,9 +1219,6 @@ public class PersonTest extends TestCase
|
|||||||
}
|
}
|
||||||
// ignore - expected
|
// ignore - expected
|
||||||
}
|
}
|
||||||
|
|
||||||
usernameMatcher.setUserNamesAreCaseSensitive(false);
|
|
||||||
((PersonServiceImpl)personService).setUserNameMatcher(usernameMatcher); // case-insensitive
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testUpdateUserNameCase()
|
public void testUpdateUserNameCase()
|
||||||
@@ -1227,6 +1226,7 @@ public class PersonTest extends TestCase
|
|||||||
final String TEST_PERSON_UPPER = "TEST_PERSON_THREE";
|
final String TEST_PERSON_UPPER = "TEST_PERSON_THREE";
|
||||||
final String TEST_PERSON_LOWER = TEST_PERSON_UPPER.toLowerCase();
|
final String TEST_PERSON_LOWER = TEST_PERSON_UPPER.toLowerCase();
|
||||||
|
|
||||||
|
userNameMatcher.setUserNamesAreCaseSensitive(true);
|
||||||
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
|
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
|
||||||
|
|
||||||
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
||||||
|
@@ -353,43 +353,51 @@ public class PortableHomeFolderManager implements HomeFolderManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// If the preferred home folder already exists, append "-N"
|
||||||
|
NodeRef root = getRootPathNodeRef(provider);
|
||||||
List<String> homeFolderPath = provider.getHomeFolderPath(person);
|
List<String> homeFolderPath = provider.getHomeFolderPath(person);
|
||||||
|
modifyHomeFolderNameIfItExists(root, homeFolderPath);
|
||||||
FileInfo fileInfo;
|
|
||||||
|
|
||||||
// Test if it already exists
|
// Create folder
|
||||||
NodeRef existing = getExisting(provider, fileFolderService, homeFolderPath);
|
FileInfo fileInfo = createTree(provider, getRootPathNodeRef(provider), homeFolderPath,
|
||||||
if (existing != null)
|
provider.getTemplateNodeRef(), fileFolderService);
|
||||||
{
|
|
||||||
fileInfo = fileFolderService.getFileInfo(existing);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fileInfo = createTree(provider, getRootPathNodeRef(provider), homeFolderPath,
|
|
||||||
provider.getTemplateNodeRef(), fileFolderService);
|
|
||||||
}
|
|
||||||
NodeRef homeFolderNodeRef = fileInfo.getNodeRef();
|
NodeRef homeFolderNodeRef = fileInfo.getNodeRef();
|
||||||
return new HomeSpaceNodeRef(homeFolderNodeRef, HomeSpaceNodeRef.Status.CREATED);
|
return new HomeSpaceNodeRef(homeFolderNodeRef, HomeSpaceNodeRef.Status.CREATED);
|
||||||
}
|
}
|
||||||
return homeSpaceNodeRef;
|
return homeSpaceNodeRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NodeRef getExisting(HomeFolderProvider2 provider, FileFolderService fileFolderService,
|
/**
|
||||||
List<String> homeFolderPath)
|
* Modifies (if required) the leaf folder name in the {@code homeFolderPath} by
|
||||||
|
* appending {@code "-N"} (where N is an integer starting with 1), so that a
|
||||||
|
* new folder will be created.
|
||||||
|
* @param root folder.
|
||||||
|
* @param homeFolderPath the full path. Only the final element is used.
|
||||||
|
*/
|
||||||
|
public void modifyHomeFolderNameIfItExists(NodeRef root, List<String> homeFolderPath)
|
||||||
{
|
{
|
||||||
NodeRef existing;
|
int n = 0;
|
||||||
|
int last = homeFolderPath.size()-1;
|
||||||
|
String name = homeFolderPath.get(last);
|
||||||
|
String homeFolderName = name;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
FileInfo existingFileInfo = fileFolderService.resolveNamePath(getRootPathNodeRef(provider), homeFolderPath);
|
do
|
||||||
existing = existingFileInfo.getNodeRef();
|
{
|
||||||
|
if (n > 0)
|
||||||
|
{
|
||||||
|
homeFolderName = name+'-'+n;
|
||||||
|
homeFolderPath.set(last, homeFolderName);
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
} while (fileFolderService.resolveNamePath(root, homeFolderPath, false) != null);
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException fnfe)
|
catch (FileNotFoundException e)
|
||||||
{
|
{
|
||||||
existing = null;// home folder noderef doesn't exist yet
|
// Should not be thrown as call to resolveNamePath passes in false
|
||||||
}
|
}
|
||||||
return existing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* creates a tree of folder nodes based on the path elements provided.
|
* creates a tree of folder nodes based on the path elements provided.
|
||||||
*/
|
*/
|
||||||
|
@@ -80,6 +80,7 @@ import org.alfresco.service.cmr.security.AuthorityType;
|
|||||||
import org.alfresco.service.cmr.security.NoSuchPersonException;
|
import org.alfresco.service.cmr.security.NoSuchPersonException;
|
||||||
import org.alfresco.service.cmr.security.PermissionService;
|
import org.alfresco.service.cmr.security.PermissionService;
|
||||||
import org.alfresco.service.cmr.security.PersonService;
|
import org.alfresco.service.cmr.security.PersonService;
|
||||||
|
import org.alfresco.service.cmr.security.PublicServiceAccessService;
|
||||||
import org.alfresco.service.cmr.security.AuthorityService.AuthorityFilter;
|
import org.alfresco.service.cmr.security.AuthorityService.AuthorityFilter;
|
||||||
import org.alfresco.service.cmr.site.SiteInfo;
|
import org.alfresco.service.cmr.site.SiteInfo;
|
||||||
import org.alfresco.service.cmr.site.SiteService;
|
import org.alfresco.service.cmr.site.SiteService;
|
||||||
@@ -161,6 +162,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic
|
|||||||
private BehaviourFilter behaviourFilter;
|
private BehaviourFilter behaviourFilter;
|
||||||
private SitesPermissionCleaner sitesPermissionsCleaner;
|
private SitesPermissionCleaner sitesPermissionsCleaner;
|
||||||
private PolicyComponent policyComponent;
|
private PolicyComponent policyComponent;
|
||||||
|
private PublicServiceAccessService publicServiceAccessService;
|
||||||
|
|
||||||
private NamedObjectRegistry<CannedQueryFactory<NodeRef>> cannedQueryRegistry;
|
private NamedObjectRegistry<CannedQueryFactory<NodeRef>> cannedQueryRegistry;
|
||||||
|
|
||||||
@@ -327,6 +329,11 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic
|
|||||||
this.sitesPermissionsCleaner = sitesPermissionsCleaner;
|
this.sitesPermissionsCleaner = sitesPermissionsCleaner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPublicServiceAccessService(PublicServiceAccessService publicServiceAccessService)
|
||||||
|
{
|
||||||
|
this.publicServiceAccessService = publicServiceAccessService;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the registry of {@link CannedQueryFactory canned queries}
|
* Set the registry of {@link CannedQueryFactory canned queries}
|
||||||
*/
|
*/
|
||||||
@@ -385,13 +392,9 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic
|
|||||||
*/
|
*/
|
||||||
public boolean hasCreateSitePermissions()
|
public boolean hasCreateSitePermissions()
|
||||||
{
|
{
|
||||||
final NodeRef siteRoot = getSiteRoot();
|
// NOTE: see ALF-13580 - since 3.4.6 PermissionService.CONTRIBUTOR is no longer used as the default on the Sites folder
|
||||||
if (siteRoot == null)
|
// instead the ability to call createSite() and the Spring configured ACL is the mechanism used to protect access.
|
||||||
{
|
return (publicServiceAccessService.hasAccess("SiteService", "createSite", "", "", "", "", true) == AccessStatus.ALLOWED);
|
||||||
throw new SiteServiceException("No root sites folder exists");
|
|
||||||
}
|
|
||||||
boolean result = permissionService.hasPermission(siteRoot, PermissionService.CONTRIBUTOR).equals(AccessStatus.ALLOWED);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -432,11 +432,11 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo
|
|||||||
/**
|
/**
|
||||||
* Create tenant by restoring from a complete repository export. This is equivalent to a bootstrap import using restore-context.xml.
|
* Create tenant by restoring from a complete repository export. This is equivalent to a bootstrap import using restore-context.xml.
|
||||||
*/
|
*/
|
||||||
public void importTenant(String tenantDomain, final File directorySource, String rootContentStoreDir)
|
public void importTenant(final String tenantDomainIn, final File directorySource, String contentRoot)
|
||||||
{
|
{
|
||||||
tenantDomain = getTenantDomain(tenantDomain);
|
final String tenantDomain = getTenantDomain(tenantDomainIn);
|
||||||
|
|
||||||
initTenant(tenantDomain, rootContentStoreDir);
|
initTenant(tenantDomain, contentRoot);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@@ -14,25 +14,26 @@
|
|||||||
<bean id="spacesArchiveBootstrap-mt" parent="spacesArchiveBootstrap-base" singleton="false" />
|
<bean id="spacesArchiveBootstrap-mt" parent="spacesArchiveBootstrap-base" singleton="false" />
|
||||||
<bean id="spacesBootstrap-mt" parent="spacesBootstrap-base" singleton="false" />
|
<bean id="spacesBootstrap-mt" parent="spacesBootstrap-base" singleton="false" />
|
||||||
|
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<!-- MT Admin Service Implementation -->
|
<!-- MT Admin Service Implementation -->
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
<bean id="tenantAdminService" parent="baseMultiTAdminService" />
|
<bean id="tenantAdminService" parent="baseMultiTAdminService" class="org.alfresco.repo.tenant.MultiTAdminServiceImpl" />
|
||||||
|
|
||||||
<bean id="tenantInterpreter" class="org.alfresco.repo.tenant.TenantInterpreter" parent="interpreterBase">
|
<bean id="tenantInterpreter" class="org.alfresco.repo.tenant.TenantInterpreter" parent="interpreterBase">
|
||||||
<property name="tenantAdminService" ref="tenantAdminService"/>
|
<property name="tenantAdminService" ref="tenantAdminService"/>
|
||||||
<property name="tenantService" ref="tenantService"/>
|
<property name="tenantService" ref="tenantService"/>
|
||||||
<property name="authenticationService" ref="AuthenticationService"/>
|
<property name="authenticationService" ref="AuthenticationService"/>
|
||||||
<property name="baseAdminUsername"><value>${alfresco_user_store.adminusername}</value></property>
|
<property name="baseAdminUsername"><value>${alfresco_user_store.adminusername}</value></property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="tenantInterpreterHelp" class="org.alfresco.i18n.ResourceBundleBootstrapComponent">
|
<bean id="tenantInterpreterHelp" class="org.alfresco.i18n.ResourceBundleBootstrapComponent">
|
||||||
<property name="resourceBundles">
|
<property name="resourceBundles">
|
||||||
<list>
|
<list>
|
||||||
<value>alfresco.messages.tenant-interpreter-help</value>
|
<value>alfresco.messages.tenant-interpreter-help</value>
|
||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user