diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml
index 4aec5ecd87..6184cae3e4 100644
--- a/config/alfresco/authentication-services-context.xml
+++ b/config/alfresco/authentication-services-context.xml
@@ -375,13 +375,22 @@
-
${home.folder.creation.eager}
+
+
+
+
+
+
+
-
-
+
+
+
@@ -393,8 +402,47 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${spaces.user_homes.regex.key}
+
+
+ ${spaces.user_homes.regex.pattern}
+
+
+ ${spaces.user_homes.regex.group_order}
+
+
+
+
+
+ /${spaces.company_home.childname}
@@ -402,7 +450,8 @@
-
+
@@ -417,12 +466,8 @@
-
-
-
-
-
-
+
+ /${spaces.company_home.childname}/${spaces.guest_home.childname}
@@ -436,9 +481,12 @@
-
+
-
+
@@ -460,7 +508,8 @@
-
+
@@ -474,42 +523,34 @@
-
-
-
-
-
-
+
+ /${spaces.company_home.childname}${spaces.store}
-
-
-
-
-
-
-
-
+
+ /${spaces.company_home.childname}/${spaces.user_homes.childname}${spaces.store}
-
-
+
+
+
+
+ /${spaces.company_home.childname}/${spaces.user_homes.childname}
-
-
+
+ ${spaces.store}
-
diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml
index 9be01982b6..4ffb89842b 100644
--- a/config/alfresco/bootstrap-context.xml
+++ b/config/alfresco/bootstrap-context.xml
@@ -686,6 +686,18 @@
${system.usages.enabled}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/alfresco/model/transferModel.xml b/config/alfresco/model/transferModel.xml
index 8647b5ed03..df5d8b35a2 100644
--- a/config/alfresco/model/transferModel.xml
+++ b/config/alfresco/model/transferModel.xml
@@ -175,7 +175,7 @@
Can this resource be enabled/disabled.
- Is this enabled.
+ Enabledd:booleantrue
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index 02ce8c2d86..38227e8776 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -384,6 +384,9 @@ spaces.wcm.childname=app:wcm
spaces.wcm_content_forms.childname=app:wcm_forms
spaces.content_forms.childname=app:forms
spaces.user_homes.childname=app:user_homes
+spaces.user_homes.regex.key=userName
+spaces.user_homes.regex.pattern=
+spaces.user_homes.regex.group_order=
spaces.sites.childname=st:sites
spaces.templates.email.invite.childname=cm:invite
spaces.templates.email.activities.childname=cm:activities
@@ -399,6 +402,7 @@ spaces.extension_webscripts.childname=cm:extensionwebscripts
spaces.models.childname=app:models
spaces.workflow.definitions.childname=app:workflow_defs
+
# ADM VersionStore Configuration
version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore
version.store.version2Store=workspace://version2Store
diff --git a/config/alfresco/subsystems/Authentication/ldap-ad/ldap-ad-authentication.properties b/config/alfresco/subsystems/Authentication/ldap-ad/ldap-ad-authentication.properties
index 05df62c1aa..b7af1b1393 100644
--- a/config/alfresco/subsystems/Authentication/ldap-ad/ldap-ad-authentication.properties
+++ b/config/alfresco/subsystems/Authentication/ldap-ad/ldap-ad-authentication.properties
@@ -99,7 +99,7 @@ ldap.synchronization.userEmailAttributeName=mail
ldap.synchronization.userOrganizationalIdAttributeName=company
# The default home folder provider to use for people created via LDAP import
-ldap.synchronization.defaultHomeFolderProvider=userHomesHomeFolderProvider
+ldap.synchronization.defaultHomeFolderProvider=largeHomeFolderProvider
# The attribute on LDAP group objects to map to the authority name property in Alfresco
ldap.synchronization.groupIdAttributeName=cn
diff --git a/config/alfresco/subsystems/Authentication/ldap/ldap-authentication.properties b/config/alfresco/subsystems/Authentication/ldap/ldap-authentication.properties
index 2caf6a30cf..882eb80e9a 100644
--- a/config/alfresco/subsystems/Authentication/ldap/ldap-authentication.properties
+++ b/config/alfresco/subsystems/Authentication/ldap/ldap-authentication.properties
@@ -105,7 +105,7 @@ ldap.synchronization.userEmailAttributeName=mail
ldap.synchronization.userOrganizationalIdAttributeName=o
# The default home folder provider to use for people created via LDAP import
-ldap.synchronization.defaultHomeFolderProvider=userHomesHomeFolderProvider
+ldap.synchronization.defaultHomeFolderProvider=largeHomeFolderProvider
# The attribute on LDAP group objects to map to the authority name property in Alfresco
ldap.synchronization.groupIdAttributeName=cn
diff --git a/source/java/org/alfresco/repo/admin/patch/impl/FixUserQNamesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/FixUserQNamesPatch.java
index 0578708d18..fb229f03e5 100644
--- a/source/java/org/alfresco/repo/admin/patch/impl/FixUserQNamesPatch.java
+++ b/source/java/org/alfresco/repo/admin/patch/impl/FixUserQNamesPatch.java
@@ -33,6 +33,7 @@ import org.alfresco.repo.batch.BatchProcessor;
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
import org.alfresco.repo.domain.qname.QNameDAO;
import org.alfresco.repo.importer.ImporterBootstrap;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.rule.RuleService;
@@ -110,18 +111,22 @@ public class FixUserQNamesPatch extends AbstractPatch implements ApplicationEven
20,
this.applicationEventPublisher, logger, 1000);
+ final String runAsUser = AuthenticationUtil.getRunAsUser();
+
int updated = batchProcessor.process(new BatchProcessWorker()
{
public void beforeProcess() throws Throwable
{
// Disable rules
ruleService.disableRules();
+ AuthenticationUtil.setRunAsUser(runAsUser);
}
public void afterProcess() throws Throwable
{
// Enable rules
ruleService.enableRules();
+ AuthenticationUtil.clearCurrentSecurityContext();
}
public String getIdentifier(ChildAssociationRef entry)
diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
index 2429a3ad4f..5355be1caa 100644
--- a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
+++ b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
@@ -480,7 +480,14 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab
{
logger.debug("[getMessageInternal] " + this);
}
- AbstractMimeMessage mes = (AbstractMimeMessage) messages.get(uid).getMimeMessage();
+ SimpleStoredMessage storedMessage = messages.get(uid);
+ if (storedMessage == null)
+ {
+ messagesCache.remove(uid);
+ msnCache.remove(uid);
+ return null;
+ }
+ AbstractMimeMessage mes = (AbstractMimeMessage) storedMessage.getMimeMessage();
FileInfo mesInfo = mes.getMessageInfo();
Date modified = (Date) serviceRegistry.getNodeService().getProperty(mesInfo.getNodeRef(), ContentModel.PROP_MODIFIED);
@@ -1181,7 +1188,10 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab
if (nodeService.hasAspect(folderNodeRef, ImapModel.ASPECT_IMAP_FOLDER))
{
modifDate = ((Long) nodeService.getProperty(folderNodeRef, ImapModel.PROP_UIDVALIDITY));
- return (modifDate - YEAR_2005) / 1000;
+ // we need tens part of the second at least, because
+ // we should avoid issues when several changes were completed within a second.
+ // so, divide by 100 instead of 1000. see ImapServiceImplCacheTest#testRepoBehaviourWithFoldersCache()
+ return (modifDate - YEAR_2005) / 100;
}
}
return new Long(0);
diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
index f9ab9508aa..ab9c494b46 100644
--- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
+++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
@@ -100,6 +100,8 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.extensions.surf.util.I18NUtil;
import org.springframework.util.FileCopyUtils;
+import com.icegreen.greenmail.imap.ImapConstants;
+
/**
* @author Dmitry Vaserin
* @author Arseny Kovalchuk
@@ -216,6 +218,11 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
this.foldersCache = foldersCache;
}
+ public SimpleCache getFoldersCache()
+ {
+ return foldersCache;
+ }
+
public FileFolderService getFileFolderService()
{
return fileFolderService;
@@ -792,11 +799,13 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
/**
* Check whether resultFolder is stale
*/
+ /*
if(resultFolder.isStale())
{
logger.debug("folder is stale");
resultFolder = null;
}
+ */
}
if (resultFolder == null)
@@ -841,7 +850,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
NodeRef targetNode = fileFolderService.searchSimple(nodeRef, folderNames[i]);
- if (targetNode == null)
+ if (i == 0 && targetNode == null)
{
resultFolder = new AlfrescoImapFolder(user.getQualifiedMailboxName(), serviceRegistry);
if (logger.isDebugEnabled())
@@ -1294,6 +1303,20 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
logger.debug("listMailboxes returning size:" + result.size());
+ StringBuilder prefix = new StringBuilder(128);
+ prefix.append(ImapConstants.USER_NAMESPACE)
+ .append(AlfrescoImapConst.HIERARCHY_DELIMITER)
+ .append(user.getQualifiedMailboxName())
+ .append(AlfrescoImapConst.HIERARCHY_DELIMITER);
+ int prefixLength = prefix.length();
+
+ for(AlfrescoImapFolder folder : result)
+ {
+ String cacheKey = folder.getFullName().substring(prefixLength + 1, folder.getFullName().length() - 1);
+ logger.debug("[listMailboxes] Adding the cache entry : " + cacheKey);
+ foldersCache.put(cacheKey, folder);
+ }
+
return result;
}
@@ -2150,6 +2173,38 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
}
}
+
+ public void beforeDeleteNode(NodeRef nodeRef)
+ {
+
+ NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef();
+ if (ContentModel.TYPE_FOLDER.equals(nodeService.getType(nodeRef)))
+ {
+ // If a node is a folder, we need to remove its' cache with its' children cache as well
+ invalidateFolderCacheByNodeRef(nodeRef, true);
+ }
+ else if (ContentModel.TYPE_CONTENT.equals(nodeService.getType(nodeRef)))
+ {
+ // If a node is a content, it is simpler to remove its' parent cache
+ // to avoid of deal with folder messages cache
+ invalidateFolderCacheByNodeRef(parentNodeRef, false);
+ }
+ else
+ {
+ return;
+ }
+ // Add a listener once, when a lots of messsages were created/moved into the folder
+ if (AlfrescoTransactionSupport.getResource(UIDVALIDITY_LISTENER_ALREADY_BOUND) == null)
+ {
+ AlfrescoTransactionSupport.bindListener(new UidValidityTransactionListener(parentNodeRef, nodeService));
+ AlfrescoTransactionSupport.bindResource(UIDVALIDITY_LISTENER_ALREADY_BOUND, true);
+ }
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("[beforeDeleteNode] Node " + nodeRef + " going to be removed. UIDVALIDITY will be changed for " + parentNodeRef);
+ }
+ }
+
private class UidValidityTransactionListener extends TransactionListenerAdapter
{
@@ -2202,7 +2257,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
}
if (logger.isDebugEnabled())
{
- logger.debug("UIDVALIDITY was modified");
+ logger.debug("UIDVALIDITY was modified for " + folderNodeRef);
}
return modifDate;
}
@@ -2212,6 +2267,69 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
}
+
+ private void invalidateFolderCacheByNodeRef(NodeRef folderNodeRef, boolean invalidateChildren)
+ {
+ if (logger.isDebugEnabled())
+ {
+ if (invalidateChildren)
+ {
+ logger.debug("[invalidateFolderCacheByNodeRef] Invalidate cache entries for " + folderNodeRef);
+ }
+ else
+ {
+ logger.debug("[invalidateFolderCacheByNodeRef] Invalidate cache entries for " + folderNodeRef + " and children");
+ }
+ }
+
+ if (invalidateChildren)
+ {
+ SimpleCache foldersCache = getFoldersCache();
+ List toRemove = new LinkedList();
+ for(Serializable name : foldersCache.getKeys())
+ {
+ AlfrescoImapFolder folder = (AlfrescoImapFolder) foldersCache.get(name);
+ if (folderNodeRef.equals(folder.getFolderInfo().getNodeRef()))
+ {
+ toRemove.add(name);
+ break;
+ }
+ }
+ if (toRemove.size() > 0)
+ {
+ String rootName = (String) toRemove.get(0);
+ for(Serializable name : foldersCache.getKeys())
+ {
+ if (((String) name).startsWith(rootName))
+ {
+ toRemove.add(name);
+ }
+ }
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Caches to invalidate: " + toRemove.toString());
+ }
+ for(Serializable name : toRemove)
+ {
+ foldersCache.remove(name);
+ }
+ }
+ }
+ else
+ {
+ SimpleCache foldersCache = getFoldersCache();
+ for(Serializable name : foldersCache.getKeys())
+ {
+ AlfrescoImapFolder folder = (AlfrescoImapFolder) foldersCache.get(name);
+ if (folderNodeRef.equals(folder.getFolderInfo().getNodeRef()))
+ {
+ foldersCache.remove(name);
+ break;
+ }
+ }
+ }
+ }
+
/**
* Return true if provided nodeRef is in Sites/.../documentlibrary
*/
diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImplCacheTest.java b/source/java/org/alfresco/repo/imap/ImapServiceImplCacheTest.java
new file mode 100644
index 0000000000..c82b21c60b
--- /dev/null
+++ b/source/java/org/alfresco/repo/imap/ImapServiceImplCacheTest.java
@@ -0,0 +1,224 @@
+package org.alfresco.repo.imap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory;
+import org.alfresco.repo.model.filefolder.FileFolderServiceImpl;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.repository.ContentService;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.cmr.security.MutableAuthenticationService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.config.RepositoryFolderConfigBean;
+import org.springframework.context.ApplicationContext;
+
+import com.icegreen.greenmail.store.SimpleStoredMessage;
+
+/**
+ * Unit test for cache implementation in the ImapServiceImpl. Based on ImapServiceImplTest, but
+ * we need this separate test because we need to get transactions to commit to trigger behaviours in ImapServiceImpl.
+ *
+ * @author ArsenyKo
+ */
+public class ImapServiceImplCacheTest extends TestCase
+{
+ private static final String USER_NAME = "admin";
+ private static final String USER_PASSWORD = "admin";
+
+ private static final String TEST_IMAP_FOLDER_NAME = "aaa";
+
+ private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
+ private NodeService nodeService;
+ private MutableAuthenticationService authenticationService;
+ private SearchService searchService;
+ private NamespaceService namespaceService;
+ private FileFolderService fileFolderService;
+ private ContentService contentService;
+
+ private ImapService imapService;
+
+ private NodeRef testImapFolderNodeRef;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry");
+ nodeService = serviceRegistry.getNodeService();
+ authenticationService = serviceRegistry.getAuthenticationService();
+ imapService = serviceRegistry.getImapService();
+ searchService = serviceRegistry.getSearchService();
+ namespaceService = serviceRegistry.getNamespaceService();
+ fileFolderService = serviceRegistry.getFileFolderService();
+ contentService = serviceRegistry.getContentService();
+
+ authenticationService.authenticate(USER_NAME, USER_PASSWORD.toCharArray());
+
+ String storePath = "workspace://SpacesStore";
+ String companyHomePathInStore = "/app:company_home";
+
+ StoreRef storeRef = new StoreRef(storePath);
+
+ NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef);
+
+ List nodeRefs = searchService.selectNodes(storeRootNodeRef, companyHomePathInStore, null, namespaceService, false);
+ NodeRef companyHomeNodeRef = nodeRefs.get(0);
+
+ ChildApplicationContextFactory imap = (ChildApplicationContextFactory) ctx.getBean("imap");
+ ApplicationContext imapCtx = imap.getApplicationContext();
+ ImapServiceImpl imapServiceImpl = (ImapServiceImpl)imapCtx.getBean("imapService");
+
+ // Creating IMAP test folder for IMAP root
+ LinkedList folders = new LinkedList();
+ folders.add(TEST_IMAP_FOLDER_NAME);
+ FileFolderServiceImpl.makeFolders(fileFolderService, companyHomeNodeRef, folders, ContentModel.TYPE_FOLDER);
+
+ // Setting IMAP root
+ RepositoryFolderConfigBean imapHome = new RepositoryFolderConfigBean();
+ imapHome.setStore(storePath);
+ imapHome.setRootPath(companyHomePathInStore);
+ imapHome.setFolderPath(TEST_IMAP_FOLDER_NAME);
+ imapServiceImpl.setImapHome(imapHome);
+
+ // Starting IMAP
+ imapServiceImpl.startup();
+
+ nodeRefs = searchService.selectNodes(storeRootNodeRef,
+ companyHomePathInStore + "/" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + TEST_IMAP_FOLDER_NAME,
+ null,
+ namespaceService,
+ false);
+ testImapFolderNodeRef = nodeRefs.get(0);
+
+ }
+
+ public void tearDown() throws Exception
+ {
+ fileFolderService.delete(testImapFolderNodeRef);
+ }
+
+
+ public void testRepoBehaviourWithFoldersCache() throws Exception
+ {
+ AlfrescoImapUser localUser = new AlfrescoImapUser(USER_NAME + "@alfresco.com", USER_NAME, USER_PASSWORD);
+ String folderName = "ALF9361";
+ String mailbox = "Alfresco IMAP" + AlfrescoImapConst.HIERARCHY_DELIMITER +
+ TEST_IMAP_FOLDER_NAME + AlfrescoImapConst.HIERARCHY_DELIMITER +
+ folderName;
+ int contentItemsCount = 3;
+ // Create a tree like ALF9361/ALF9361_0/sub_0
+ // Mailbox path with default mount point should be like 'Alfresco IMAP/aaa/ALF9361/ALF9361_0/sub_0
+ FileInfo localRootFolder = fileFolderService.create(testImapFolderNodeRef, folderName, ContentModel.TYPE_FOLDER);
+ List subFolders = new ArrayList(10);
+ for(int i = 0; i < 3; i++)
+ {
+ String childMailbox = folderName + "_" + i;
+ FileInfo subFolder = fileFolderService.create(localRootFolder.getNodeRef(), childMailbox, ContentModel.TYPE_FOLDER);
+ for(int j = 0; j < 3; j++)
+ {
+ String subChildMailbox = "sub_" + j;
+ fileFolderService.create(subFolder.getNodeRef(), subChildMailbox, ContentModel.TYPE_FOLDER);
+ }
+ subFolders.add(subFolder);
+ }
+ // Create content within 'Alfresco IMAP/aaa/ALF9361'
+ createTestContent(localRootFolder, contentItemsCount);
+ // Load the cache
+ imapService.listMailboxes(localUser, "*");
+ imapService.listSubscribedMailboxes(localUser, "*");
+ // Get the folder to examine
+ AlfrescoImapFolder folder = imapService.getFolder(localUser, mailbox);
+ // Check the folder exist via IMAP
+ assertNotNull("Folder wasn't successfully gotten from IMAP", folder);
+ assertEquals(contentItemsCount, folder.getMessageCount());
+ // Check UIDVALIDITY
+ long uidValidityBefore = folder.getUidValidity();
+ // Delete first childMailbox 'ALF9361/ALF9361_0'
+ //System.out.println(" --------------------- DELETE FOLDER --------------------");
+ //System.out.println(" Parent " + localRootFolder.getNodeRef());
+ fileFolderService.delete(subFolders.get(0).getNodeRef());
+ // Get the folder once more and check it was changed since child was removed
+ folder = imapService.getFolder(localUser, mailbox);
+ // Content count should be the same since we havn't deleted a content yet
+ assertEquals(contentItemsCount, folder.getMessageCount());
+ long uidValidity = folder.getUidValidity();
+ assertTrue("UIDVALIDITY wasn't incremented", (uidValidity - uidValidityBefore) > 0);
+ uidValidityBefore = uidValidity;
+ // Try to get deleted child
+ try
+ {
+ String subFolderName = mailbox + AlfrescoImapConst.HIERARCHY_DELIMITER + folderName + "_0";
+ folder = imapService.getFolder(localUser, subFolderName);
+ fail("The folder still in the cache");
+ }
+ catch (RuntimeException e)
+ {
+ // expected
+ }
+ // Try to get deleted sub child. If the cache wasn't invalidated we will get it
+ // But it should be connected to AlfrescoImapFolder.isStale() method.
+ // ArsenyKo: I think we should avoid repo API invocation like isStale...
+ try
+ {
+ String subSubFolderName = mailbox + AlfrescoImapConst.HIERARCHY_DELIMITER + mailbox + "_0" + AlfrescoImapConst.HIERARCHY_DELIMITER + "sub_0";
+ folder = imapService.getFolder(localUser, subSubFolderName);
+ fail("The folder still in the cache");
+ }
+ catch (RuntimeException e)
+ {
+ // expected
+ }
+ // Do manipulations with a content in the folder to check the cache behaviour
+ folder = imapService.getFolder(localUser, mailbox);
+ SimpleStoredMessage message = folder.getMessages().get(0);
+ AbstractMimeMessage alfrescoMessage = (AbstractMimeMessage) message.getMimeMessage();
+ long uid = message.getUid();
+ //System.out.println(" --------------------- DELETE FILE --------------------");
+ //System.out.println(" Parent " + folder.getFolderInfo().getNodeRef());
+ // Delete a content
+ fileFolderService.delete(alfrescoMessage.getMessageInfo().getNodeRef());
+ // Get a folder once again. We expect that the folder would be retrieved from the repo,
+ // since its' cache should be invalidated
+ folder = imapService.getFolder(localUser, mailbox);
+ // Get UIDVALIDITY. It should be changed, since we removed a message form the mailbox.
+ uidValidity = folder.getUidValidity();
+ assertTrue("UIDVALIDITY wasn't incremented", (uidValidity - uidValidityBefore) > 0);
+ // Additional check whether messages cache is valid. Messages cache should be recreated
+ //with the new inctance of AlfrescoImapMessage
+ assertTrue("Messages cache is stale", contentItemsCount > folder.getMessageCount());
+ long[] uids = folder.getMessageUids();
+ Arrays.sort(uids);
+ assertFalse("Messages msn cache is stale", Arrays.binarySearch(uids, uid) > 0);
+ assertNull("Message is still in the messages cache", folder.getMessage(uid));
+ //System.out.println(" --------------------- THE END --------------------");
+ fileFolderService.delete(localRootFolder.getNodeRef());
+
+ }
+
+ private List createTestContent(FileInfo parent, int count)
+ {
+ List result = new ArrayList(count);
+ for(int i = 0; i < count; i++)
+ {
+ FileInfo contentItem = fileFolderService.create(parent.getNodeRef(), "content_" + i, ContentModel.TYPE_CONTENT, ContentModel.ASSOC_CONTAINS);
+ ContentWriter contentWriter = contentService.getWriter(contentItem.getNodeRef(), ContentModel.PROP_CONTENT, false);
+ contentWriter.setEncoding("UTF-8");
+ contentWriter.putContent("TEST" + i);
+ }
+ return result;
+ }
+
+
+}
diff --git a/source/java/org/alfresco/repo/processor/ScriptServiceImpl.java b/source/java/org/alfresco/repo/processor/ScriptServiceImpl.java
index 835519742b..dbf2fd2493 100644
--- a/source/java/org/alfresco/repo/processor/ScriptServiceImpl.java
+++ b/source/java/org/alfresco/repo/processor/ScriptServiceImpl.java
@@ -441,8 +441,14 @@ public class ScriptServiceImpl implements ScriptService
// add the well known node wrapper objects
model.put("companyhome", companyHome);
- model.put("userhome", userHome);
- model.put("person", person);
+ if (userHome!= null)
+ {
+ model.put("userhome", userHome);
+ }
+ if (person != null)
+ {
+ model.put("person", person);
+ }
if (script != null)
{
model.put("script", script);
diff --git a/source/java/org/alfresco/repo/security/SecurityTestSuite.java b/source/java/org/alfresco/repo/security/SecurityTestSuite.java
index 9e806a7ff2..55474eee8b 100644
--- a/source/java/org/alfresco/repo/security/SecurityTestSuite.java
+++ b/source/java/org/alfresco/repo/security/SecurityTestSuite.java
@@ -18,9 +18,11 @@
*/
package org.alfresco.repo.security;
+import junit.framework.JUnit4TestAdapter;
import junit.framework.Test;
import junit.framework.TestSuite;
+import org.alfresco.repo.audit.access.AccessAuditorTest;
import org.alfresco.repo.ownable.impl.OwnableServiceTest;
import org.alfresco.repo.security.authentication.AuthenticationBootstrapTest;
import org.alfresco.repo.security.authentication.AuthenticationTest;
@@ -37,6 +39,7 @@ import org.alfresco.repo.security.permissions.impl.acegi.ACLEntryAfterInvocation
import org.alfresco.repo.security.permissions.impl.acegi.ACLEntryVoterTest;
import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSetTest;
import org.alfresco.repo.security.permissions.impl.model.PermissionModelTest;
+import org.alfresco.repo.security.person.HomeFolderProviderSynchronizerTest;
import org.alfresco.repo.security.person.PersonTest;
/**
@@ -74,6 +77,8 @@ public class SecurityTestSuite extends TestSuite
suite.addTestSuite(OwnableServiceTest.class);
suite.addTestSuite(ReadPermissionTest.class);
+ suite.addTest(new JUnit4TestAdapter(HomeFolderProviderSynchronizerTest.class));
+
return suite;
}
}
diff --git a/source/java/org/alfresco/repo/security/person/AbstractHomeFolderProvider.java b/source/java/org/alfresco/repo/security/person/AbstractHomeFolderProvider.java
index e127e5c735..d4793860e2 100644
--- a/source/java/org/alfresco/repo/security/person/AbstractHomeFolderProvider.java
+++ b/source/java/org/alfresco/repo/security/person/AbstractHomeFolderProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -19,17 +19,12 @@
package org.alfresco.repo.security.person;
import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import org.alfresco.model.ContentModel;
-import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
-import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.util.PropertyCheck;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
@@ -38,6 +33,9 @@ import org.springframework.beans.factory.InitializingBean;
* Common support for creating home folders This is hooked into node creation events from Person type objects via the
* homeFolderManager. Provider must all be wired up to the homeFolderManager.
*
+ * @deprecated
+ * Depreciated since 4.0. {@link AbstractHomeFolderProvider2} should now be used.
+ *
* @author Andy Hind
*/
public abstract class AbstractHomeFolderProvider implements HomeFolderProvider, BeanNameAware, InitializingBean
@@ -58,40 +56,34 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
private StoreRef storeRef;
/**
- * Service registry to get hold of public services (so taht actions are audited)
+ * Service registry to get hold of public services (so that actions are audited)
*/
private ServiceRegistry serviceRegistry;
- /**
- * Tenant service - required for MT-enabled environment, else optional
- */
- private TenantService tenantService;
-
/**
* The path to a folder
*/
private String path;
- /**
- * Cache the result of the path look up.
- */
- private Map pathNodeRefs; // MT-aware
-
/**
* The owner to set on creation of a home folder (if unset this will be the uid).
*/
private String ownerOnCreate;
- private PermissionsManager onCreatePermissionsManager;
+ /**
+ * PermissionsManager used on creating the home folder
+ */
+ private PermissionsManager onCreatePermissionsManager;
- private PermissionsManager onReferencePermissionsManager;
+ /**
+ * PermissionsManager used on referencing the home folder
+ */
+ private PermissionsManager onReferencePermissionsManager;
- public AbstractHomeFolderProvider()
- {
- super();
-
- pathNodeRefs = new ConcurrentHashMap();
- }
+ /**
+ * Adaptor for this instance to be a HomeFolderProvider2
+ */
+ private V2Adaptor v2Adaptor = new V2Adaptor(this);
/**
* Register with the homeFolderManagewr
@@ -99,13 +91,9 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
public void afterPropertiesSet() throws Exception
{
PropertyCheck.mandatory(this, "homeFolderManager", homeFolderManager);
- homeFolderManager.addProvider(this);
+ homeFolderManager.addProvider(v2Adaptor);
}
- // === //
- // IOC //
- // === //
-
/**
* Get the home folder manager.
*/
@@ -116,7 +104,6 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
/**
* Set the home folder manager.
- *
* @param homeFolderManager
*/
public void setHomeFolderManager(HomeFolderManager homeFolderManager)
@@ -127,6 +114,7 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
/**
* Get the provider name
*/
+ @Override
public String getName()
{
return name;
@@ -135,6 +123,7 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
/**
* The provider name is taken from the bean name
*/
+ @Override
public void setBeanName(String name)
{
this.name = name;
@@ -153,7 +142,14 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
*/
public void setPath(String path)
{
+ boolean reset = this.path != null;
this.path = path;
+
+ // If a reset need to clear caches
+ if (reset)
+ {
+ homeFolderManager.clearCaches(v2Adaptor);
+ }
}
/**
@@ -201,7 +197,7 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
*/
public void setTenantService(TenantService tenantService)
{
- this.tenantService = tenantService;
+ // keep class signature but no longer use value
}
/**
@@ -212,10 +208,26 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
this.onCreatePermissionsManager = onCreatePermissionsManager;
}
+ /**
+ * Gets the PermissionsManager used on creating the home folder
+ */
+ public PermissionsManager getOnCreatePermissionsManager()
+ {
+ return onCreatePermissionsManager;
+ }
+
public void setOnReferencePermissionsManager(PermissionsManager onReferencePermissionsManager)
{
this.onReferencePermissionsManager = onReferencePermissionsManager;
}
+
+ /**
+ * Gets the PermissionsManager used on referencing the home folder
+ */
+ public PermissionsManager getOnReferencePermissionsManager()
+ {
+ return onReferencePermissionsManager;
+ }
/**
* Set the authority to use as the owner of all home folder nodes.
@@ -225,34 +237,28 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
this.ownerOnCreate = ownerOnCreate;
}
+ /**
+ * Get the authority to use as the owner of all home folder nodes.
+ */
+ public String getOwnerOnCreate()
+ {
+ return ownerOnCreate;
+ }
+
/**
* Cache path to node resolution
*/
protected NodeRef getPathNodeRef()
{
- String tenantDomain = (tenantService != null ? tenantService.getCurrentUserDomain() : TenantService.DEFAULT_DOMAIN);
-
- NodeRef pathNodeRef = pathNodeRefs.get(tenantDomain);
- if (pathNodeRef == null)
- {
- pathNodeRef = resolvePath(path);
- pathNodeRefs.put(tenantDomain, pathNodeRef);
- }
- return pathNodeRef;
+ return homeFolderManager.getRootPathNodeRef(v2Adaptor);
}
/**
- * Utility metho to resolve paths to nodes.
+ * Utility method to resolve paths to nodes.
*/
protected NodeRef resolvePath(String pathToResolve)
{
- List refs = serviceRegistry.getSearchService().selectNodes(serviceRegistry.getNodeService().getRootNode(storeRef), pathToResolve, null,
- serviceRegistry.getNamespaceService(), false);
- if (refs.size() != 1)
- {
- throw new IllegalStateException("Non-unique path: found : " + pathToResolve + " " + refs.size());
- }
- return refs.get(0);
+ return homeFolderManager.resolvePath(v2Adaptor, pathToResolve);
}
/**
@@ -260,69 +266,98 @@ public abstract class AbstractHomeFolderProvider implements HomeFolderProvider,
*/
public void onCreateNode(ChildAssociationRef childAssocRef)
{
- AuthenticationUtil.RunAsWork action = new OnCreateNode(childAssocRef);
- AuthenticationUtil.runAs(action, AuthenticationUtil.getSystemUserName());
+ homeFolderManager.homeFolderCreateAndSetPermissions(v2Adaptor, childAssocRef.getChildRef());
}
/**
- * Abstract implementation to find/create the approriate home space.
+ * Abstract implementation to find/create the appropriate home space.
*/
protected abstract HomeSpaceNodeRef getHomeFolder(NodeRef person);
-
+
/**
- * Helper class to encapsulate the createion settinhg permissions etc
- *
- * @author Andy Hind
+ * Get adaptor for this instance to be a HomeFolderProvider2
*/
- private class OnCreateNode implements AuthenticationUtil.RunAsWork
+ protected V2Adaptor getV2Adaptor()
{
- ChildAssociationRef childAssocRef;
-
- OnCreateNode(ChildAssociationRef childAssocRef)
+ return v2Adaptor;
+ }
+
+ /**
+ * Adaptor to the HomeFolderProvider2 interface.
+ */
+ public class V2Adaptor implements HomeFolderProvider2
+ {
+ AbstractHomeFolderProvider abstractHomeFolderProvider;
+
+ public V2Adaptor(AbstractHomeFolderProvider abstractHomeFolderProvider)
{
- this.childAssocRef = childAssocRef;
+ this.abstractHomeFolderProvider = abstractHomeFolderProvider;
+ abstractHomeFolderProvider.v2Adaptor = this;
}
- public NodeRef doWork() throws Exception
+ @Override
+ public String getName()
{
+ return abstractHomeFolderProvider.getName();
+ }
- // Find person
- NodeRef personNodeRef = childAssocRef.getChildRef();
- // Get home folder
- HomeSpaceNodeRef homeFolder = getHomeFolder(personNodeRef);
- // If it exists
- if (homeFolder.getNodeRef() != null)
- {
- // Get uid and keep
- String uid = DefaultTypeConverter.INSTANCE.convert(String.class, serviceRegistry.getNodeService().getProperty(personNodeRef, ContentModel.PROP_USERNAME));
+ @Override
+ public String getStoreUrl()
+ {
+ return abstractHomeFolderProvider.getStoreRef().toString();
+ }
- // If created or found then set (other wise it was already set correctly)
- if (homeFolder.getStatus() != HomeSpaceNodeRef.Status.VALID)
- {
- serviceRegistry.getNodeService().setProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER, homeFolder.getNodeRef());
- }
+ @Override
+ public String getRootPath()
+ {
+ return abstractHomeFolderProvider.getPath();
+ }
- String ownerToSet = ownerOnCreate == null ? uid : ownerOnCreate;
- // If created..
- if (homeFolder.getStatus() == HomeSpaceNodeRef.Status.CREATED)
- {
- if (onCreatePermissionsManager != null)
- {
- onCreatePermissionsManager.setPermissions(homeFolder.getNodeRef(), ownerToSet, uid);
- }
- }
- else
- {
- if (onReferencePermissionsManager != null)
- {
- onReferencePermissionsManager.setPermissions(homeFolder.getNodeRef(), ownerToSet, uid);
- }
- }
-
- }
- return homeFolder.getNodeRef();
+ @Override
+ public List getHomeFolderPath(NodeRef person)
+ {
+ return (abstractHomeFolderProvider instanceof UIDBasedHomeFolderProvider)
+ ? ((UIDBasedHomeFolderProvider)abstractHomeFolderProvider).getHomeFolderPath(person)
+ : null;
+ }
+ @Override
+ public NodeRef getTemplateNodeRef()
+ {
+ return (abstractHomeFolderProvider instanceof UIDBasedHomeFolderProvider)
+ ? ((UIDBasedHomeFolderProvider)abstractHomeFolderProvider).getTemplateNodeRef()
+ : null;
+ }
+
+ @Override
+ public String getOwner()
+ {
+ return abstractHomeFolderProvider.getOwnerOnCreate();
+ }
+
+ @Override
+ public PermissionsManager getOnCreatePermissionsManager()
+ {
+ return abstractHomeFolderProvider.getOnReferencePermissionsManager();
+ }
+
+ @Override
+ public PermissionsManager getOnReferencePermissionsManager()
+ {
+ return abstractHomeFolderProvider.getOnReferencePermissionsManager();
+ }
+
+ @Override
+ public HomeSpaceNodeRef getHomeFolder(NodeRef person)
+ {
+ return abstractHomeFolderProvider.getHomeFolder(person);
+ }
+
+ // The old way to create the home folder, so must still call it in case
+ // the method is overridden
+ public void onCreateNode(ChildAssociationRef childAssocRef)
+ {
+ abstractHomeFolderProvider.onCreateNode(childAssocRef);
}
}
-
}
diff --git a/source/java/org/alfresco/repo/security/person/AbstractHomeFolderProvider2.java b/source/java/org/alfresco/repo/security/person/AbstractHomeFolderProvider2.java
new file mode 100644
index 0000000000..09c458c7a8
--- /dev/null
+++ b/source/java/org/alfresco/repo/security/person/AbstractHomeFolderProvider2.java
@@ -0,0 +1,209 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.security.person;
+
+import java.util.List;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.util.PropertyCheck;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * Abstract class that implements {@link HomeFolderProvider2} which
+ * works with the {@link HomeFolderManager} (which performs most of
+ * the work) to create home folders in custom locations.
+ *
+ * @author Alan Davis
+ */
+public abstract class AbstractHomeFolderProvider2 implements
+ HomeFolderProvider2, BeanNameAware, InitializingBean
+{
+ /**
+ * The provider name
+ */
+ private String name;
+
+ /**
+ * The home folder manager
+ */
+ private HomeFolderManager homeFolderManager;
+
+ /**
+ * The store URL.
+ */
+ private String storeUrl;
+
+ /**
+ * The path to the root folder
+ */
+ private String rootPath;
+
+ /**
+ * Set the authority to use as the owner of all home folder nodes.
+ * May be {@code null}.
+ */
+ private String owner;
+
+ /**
+ * PermissionsManager used on creating the home folder
+ */
+ private PermissionsManager onCreatePermissionsManager;
+
+ /**
+ * PermissionsManager used on referencing the home folder
+ */
+ private PermissionsManager onReferencePermissionsManager;
+
+ /**
+ * Register with the homeFolderManagewr
+ */
+ public void afterPropertiesSet() throws Exception
+ {
+ PropertyCheck.mandatory(this, "homeFolderManager", homeFolderManager);
+ homeFolderManager.addProvider(this);
+ }
+
+ /**
+ * Get the home folder manager.
+ */
+ protected HomeFolderManager getHomeFolderManager()
+ {
+ return homeFolderManager;
+ }
+
+ /**
+ * Set the home folder manager.
+ * @param homeFolderManager
+ */
+ public void setHomeFolderManager(HomeFolderManager homeFolderManager)
+ {
+ this.homeFolderManager = homeFolderManager;
+ }
+
+ /**
+ * Get the provider name
+ */
+ @Override
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * The provider name is taken from the bean name
+ */
+ @Override
+ public void setBeanName(String name)
+ {
+ this.name = name;
+ }
+
+ /**
+ * Get the path of the root folder
+ */
+ @Override
+ public String getRootPath()
+ {
+ return rootPath;
+ }
+
+ /**
+ * Set the path of the root folder
+ */
+ public void setRootPath(String rootPath)
+ {
+ boolean reset = this.rootPath != null;
+ this.rootPath = rootPath;
+
+ // If a reset need to clear caches
+ if (reset)
+ {
+ homeFolderManager.clearCaches(this);
+ }
+ }
+
+ @Override
+ public String getStoreUrl()
+ {
+ return storeUrl;
+ }
+
+ /**
+ * Set the store URL.
+ */
+ public void setStoreUrl(String storeUrl)
+ {
+ this.storeUrl = storeUrl;
+ }
+
+ /**
+ * Sets the PermissionsManager used on creating the home folder
+ */
+ public void setOnCreatePermissionsManager(PermissionsManager onCreatePermissionsManager)
+ {
+ this.onCreatePermissionsManager = onCreatePermissionsManager;
+ }
+
+ @Override
+ public PermissionsManager getOnCreatePermissionsManager()
+ {
+ return onCreatePermissionsManager;
+ }
+
+ /**
+ * Sets the PermissionsManager used on referencing the home folder
+ */
+ public void setOnReferencePermissionsManager(PermissionsManager onReferencePermissionsManager)
+ {
+ this.onReferencePermissionsManager = onReferencePermissionsManager;
+ }
+
+ @Override
+ public PermissionsManager getOnReferencePermissionsManager()
+ {
+ return onReferencePermissionsManager;
+ }
+
+ /**
+ * Set the authority to use as the owner of all home folder nodes.
+ */
+ public void setOwner(String owner)
+ {
+ this.owner = owner;
+ }
+
+ @Override
+ public String getOwner()
+ {
+ return owner;
+ }
+
+ @Override
+ public List getHomeFolderPath(NodeRef person)
+ {
+ return null;
+ }
+
+ @Override
+ public NodeRef getTemplateNodeRef()
+ {
+ return null;
+ }
+}
diff --git a/source/java/org/alfresco/repo/security/person/BootstrapHomeFolderProvider.java b/source/java/org/alfresco/repo/security/person/BootstrapHomeFolderProvider.java
index b4c043555d..b8062a9b69 100644
--- a/source/java/org/alfresco/repo/security/person/BootstrapHomeFolderProvider.java
+++ b/source/java/org/alfresco/repo/security/person/BootstrapHomeFolderProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -23,17 +23,16 @@ import org.alfresco.service.cmr.repository.NodeRef;
/**
* Provider to use in the boostrap process - does nothing
*
- * Probably not required as behaviour/policies are disabled during normal import.
+ * Thought to be probably not required as behaviour/policies are
+ * disabled during normal import, but is used for 'admin' and 'guest'
*
* @author Andy Hind
*/
-public class BootstrapHomeFolderProvider extends AbstractHomeFolderProvider
+public class BootstrapHomeFolderProvider extends AbstractHomeFolderProvider2
{
-
@Override
- protected HomeSpaceNodeRef getHomeFolder(NodeRef person)
+ public HomeSpaceNodeRef getHomeFolder(NodeRef person)
{
return new HomeSpaceNodeRef(null, HomeSpaceNodeRef.Status.VALID);
}
-
}
diff --git a/source/java/org/alfresco/repo/security/person/ExistingPathBasedHomeFolderProvider.java b/source/java/org/alfresco/repo/security/person/ExistingPathBasedHomeFolderProvider.java
index b84eb15253..db24169f3a 100644
--- a/source/java/org/alfresco/repo/security/person/ExistingPathBasedHomeFolderProvider.java
+++ b/source/java/org/alfresco/repo/security/person/ExistingPathBasedHomeFolderProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -18,35 +18,20 @@
*/
package org.alfresco.repo.security.person;
-import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.NodeRef;
-import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
/**
- * Set a home space from a simple path.
+ * HomeFolderProvider that simply uses the root path for the home folder.
+ *
+ * @deprecated
+ * Depreciated since 4.0. {@link ExistingPathBasedHomeFolderProvider2} should now be used.
*
* @author Andy Hind
*/
public class ExistingPathBasedHomeFolderProvider extends AbstractHomeFolderProvider
{
-
- public ExistingPathBasedHomeFolderProvider()
- {
- super();
- }
-
protected HomeSpaceNodeRef getHomeFolder(NodeRef person)
{
- NodeRef existingHomeFolder = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, getServiceRegistry().getNodeService().getProperty(
- person, ContentModel.PROP_HOMEFOLDER));
- if (existingHomeFolder == null)
- {
- return new HomeSpaceNodeRef(getPathNodeRef(), HomeSpaceNodeRef.Status.REFERENCED);
- }
- else
- {
- return new HomeSpaceNodeRef(existingHomeFolder, HomeSpaceNodeRef.Status.VALID);
- }
+ return getHomeFolderManager().getHomeFolder(getV2Adaptor(), person, true);
}
-
}
diff --git a/source/java/org/alfresco/repo/security/person/ExistingPathBasedHomeFolderProvider2.java b/source/java/org/alfresco/repo/security/person/ExistingPathBasedHomeFolderProvider2.java
new file mode 100644
index 0000000000..2c2c3d7f58
--- /dev/null
+++ b/source/java/org/alfresco/repo/security/person/ExistingPathBasedHomeFolderProvider2.java
@@ -0,0 +1,36 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.security.person;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+/**
+ * HomeFolderProvider that simply uses the root path for the home folder.
+ * Generally it is a better idea to give each user their own home folder.
+ *
+ * @author Alan Davis
+ */
+public class ExistingPathBasedHomeFolderProvider2 extends AbstractHomeFolderProvider2
+{
+ @Override
+ public HomeSpaceNodeRef getHomeFolder(NodeRef person)
+ {
+ return getHomeFolderManager().getHomeFolder(this, person, true);
+ }
+}
diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderManager.java b/source/java/org/alfresco/repo/security/person/HomeFolderManager.java
index c853931452..1dc7da544f 100644
--- a/source/java/org/alfresco/repo/security/person/HomeFolderManager.java
+++ b/source/java/org/alfresco/repo/security/person/HomeFolderManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -18,15 +18,27 @@
*/
package org.alfresco.repo.security.person;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.tenant.TenantService;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileFolderUtil;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
@@ -34,26 +46,43 @@ import org.alfresco.service.namespace.QName;
/**
* Manage home folder creation by binding to events from the cm:person type.
*
- * @author Andy Hind
+ * @author Andy Hind,
+ * Alan Davis (support v1 and v2 HomeFolderProviders - code from
+ * v1 HomeFolderProviders moved into HomeFolderManager).
*/
public class HomeFolderManager implements NodeServicePolicies.OnCreateNodePolicy
{
-
private PolicyComponent policyComponent;
private NodeService nodeService;
private boolean enableHomeFolderCreationAsPeopleAreCreated = false;
+ private ServiceRegistry serviceRegistry;
+
+ private TenantService tenantService;
+
/**
* A default provider
*/
- private HomeFolderProvider defaultProvider;
+ private HomeFolderProvider2 defaultProvider;
/**
- * Providers that have registered and are looken up by name (== bean name)
+ * Original Providers (now depreciated) that have registered and are looked up by bean name.
*/
- private Map providers = new HashMap();
+ @SuppressWarnings("deprecation")
+ private Map v1Providers = new HashMap();
+
+ /**
+ * Providers that have registered and are looked up by bean name.
+ */
+ private Map v2Providers = new HashMap();
+
+ /**
+ * Cache the result of the path look up.
+ */
+ private Map> rootPathNodeRefMaps =
+ new ConcurrentHashMap>();
/**
* Bind the class behaviour to this implementation
@@ -90,21 +119,65 @@ public class HomeFolderManager implements NodeServicePolicies.OnCreateNodePolicy
this.nodeService = nodeService;
}
+ /**
+ * Set the service registry.
+ */
+ public void setServiceRegistry(ServiceRegistry serviceRegistry)
+ {
+ this.serviceRegistry = serviceRegistry;
+ }
+
+ /**
+ * Set the tenant service
+ */
+ public void setTenantService(TenantService tenantService)
+ {
+ this.tenantService = tenantService;
+ }
+
/**
* Register a home folder provider.
*
* @param provider
*/
+ @SuppressWarnings("deprecation")
public void addProvider(HomeFolderProvider provider)
{
- providers.put(provider.getName(), provider);
+ v1Providers.put(provider.getName(), provider);
+ }
+
+ /**
+ * Register a home folder provider.
+ *
+ * @param provider
+ */
+ public void addProvider(HomeFolderProvider2 provider)
+ {
+ v2Providers.put(provider.getName(), provider);
+ }
+
+ /**
+ * Returns the version 1 HomeFolderProvider with the given name.
+ */
+ @SuppressWarnings("deprecation")
+ public HomeFolderProvider getHomeFolderProvider1(String providerName)
+ {
+ return v1Providers.get(providerName);
+ }
+
+ /**
+ * Returns the version 2 HomeFolderProvider2 with the given name.
+ */
+ public HomeFolderProvider2 getHomeFolderProvider2(String providerName)
+ {
+ return v2Providers.get(providerName);
}
/**
* Set the default home folder provider (user which none is specified or when one is not found)
* @param defaultProvider
*/
- public void setDefaultProvider(HomeFolderProvider defaultProvider)
+ public void setDefaultProvider(HomeFolderProvider2 defaultProvider)
{
this.defaultProvider = defaultProvider;
}
@@ -123,22 +196,302 @@ public class HomeFolderManager implements NodeServicePolicies.OnCreateNodePolicy
/**
* Find the provider and call.
*/
+ @SuppressWarnings("deprecation")
public void makeHomeFolder(ChildAssociationRef childAssocRef)
{
- HomeFolderProvider provider = defaultProvider;
- String providerName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(childAssocRef
+ HomeFolderProvider2 v2Provider = defaultProvider;
+ HomeFolderProvider v1Provider = null;
+ String providerName = DefaultTypeConverter.INSTANCE.convert(
+ String.class, nodeService.getProperty(childAssocRef
.getChildRef(), ContentModel.PROP_HOME_FOLDER_PROVIDER));
if (providerName != null)
{
- provider = providers.get(providerName);
- if (provider == null)
+ v2Provider = getHomeFolderProvider2(providerName);
+ if (v2Provider == null)
{
- provider = defaultProvider;
+ v1Provider = getHomeFolderProvider1(providerName);
+ if (v1Provider == null)
+ {
+ v2Provider = defaultProvider;
+ }
}
}
- if (provider != null)
+ else
{
- provider.onCreateNode(childAssocRef);
+ providerName = defaultProvider.getName();
+ nodeService.setProperty(childAssocRef.getChildRef(),
+ ContentModel.PROP_HOME_FOLDER_PROVIDER, providerName);
+ }
+ if (v2Provider != null)
+ {
+ // If a V2Adaptor we still must call onCreateNode just like a
+ // v1 HomeFolderProvider in case it has been overridden
+ if (v2Provider instanceof AbstractHomeFolderProvider.V2Adaptor)
+ {
+ ((AbstractHomeFolderProvider.V2Adaptor)v2Provider).onCreateNode(childAssocRef);
+ }
+ else
+ {
+ homeFolderCreateAndSetPermissions(v2Provider, childAssocRef.getChildRef());
+ }
+ }
+ else if (v1Provider != null)
+ {
+ v1Provider.onCreateNode(childAssocRef);
+ }
+ }
+
+ void homeFolderCreateAndSetPermissions(HomeFolderProvider2 provider, NodeRef personNodeRef)
+ {
+ AuthenticationUtil.RunAsWork action =
+ new RunAsCreateAndSetPermissions(provider, personNodeRef);
+ AuthenticationUtil.runAs(action, AuthenticationUtil.getSystemUserName());
+ }
+
+ /**
+ * Helper class to encapsulate the creation and setting permissions etc
+ */
+ private class RunAsCreateAndSetPermissions implements AuthenticationUtil.RunAsWork
+ {
+ NodeRef personNodeRef;
+ HomeFolderProvider2 provider;
+
+ RunAsCreateAndSetPermissions(HomeFolderProvider2 provider, NodeRef personNodeRef)
+ {
+ this.personNodeRef = personNodeRef;
+ this.provider = provider;
+ }
+
+ public NodeRef doWork() throws Exception
+ {
+ // Get home folder
+ HomeSpaceNodeRef homeFolder = provider.getHomeFolder(personNodeRef);
+
+ // If it exists
+ if (homeFolder.getNodeRef() != null)
+ {
+ // Get uid and keep
+ String uid = DefaultTypeConverter.INSTANCE.convert(String.class,
+ serviceRegistry.getNodeService().getProperty(
+ personNodeRef, ContentModel.PROP_USERNAME));
+
+ // If created or found then set (other wise it was already set correctly)
+ if (homeFolder.getStatus() != HomeSpaceNodeRef.Status.VALID)
+ {
+ serviceRegistry.getNodeService().setProperty(
+ personNodeRef, ContentModel.PROP_HOMEFOLDER, homeFolder.getNodeRef());
+ }
+
+ final String providerSuppliedOwner = provider.getOwner();
+ String owner = (providerSuppliedOwner == null) ? uid : providerSuppliedOwner;
+ // If created..
+ if (homeFolder.getStatus() == HomeSpaceNodeRef.Status.CREATED)
+ {
+ PermissionsManager onCreatePermissionsManager =
+ provider.getOnCreatePermissionsManager();
+ if (onCreatePermissionsManager != null)
+ {
+ onCreatePermissionsManager.setPermissions(
+ homeFolder.getNodeRef(), owner, uid);
+ }
+ }
+ else
+ {
+ PermissionsManager onReferencePermissionsManager =
+ provider.getOnReferencePermissionsManager();
+ if (onReferencePermissionsManager != null)
+ {
+ onReferencePermissionsManager.setPermissions(
+ homeFolder.getNodeRef(), owner, uid);
+ }
+ }
+ }
+ return homeFolder.getNodeRef();
+ }
+ }
+
+ private StoreRef getStoreRef(HomeFolderProvider2 provider)
+ {
+ // Could check to see if provider is a V2Adaptor to avoid
+ // object creation, but there is little point.
+ return new StoreRef(provider.getStoreUrl());
+ }
+
+ /**
+ * Helper method for {@link HomeFolderProvider2.getHomeFolder} (so that it
+ * does not need its own NodeService) that returns a person property value.
+ */
+ public String getPersonProperty(NodeRef person, QName name)
+ {
+ String value = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(person, name));
+
+ if(value == null || value.length() == 0)
+ {
+ throw new PersonException("Can not create a home folder when the "+name+" property is null or empty");
+ }
+ return value;
+ }
+
+ void clearCaches(HomeFolderProvider2 provider)
+ {
+ getRootPathNodeRefMap(provider).clear();
+ }
+
+ NodeRef getRootPathNodeRef(HomeFolderProvider2 provider)
+ {
+ String rootPath = provider.getRootPath();
+ String tenantDomain = (tenantService != null ? tenantService.getCurrentUserDomain() : TenantService.DEFAULT_DOMAIN);
+ Map rootPathNodeRefMap = getRootPathNodeRefMap(provider);
+ NodeRef rootPathNodeRef = rootPathNodeRefMap.get(tenantDomain);
+ if (rootPathNodeRef == null)
+ {
+ // ok with race condition for initial construction
+ rootPathNodeRef = resolvePath(provider, rootPath);
+ rootPathNodeRefMap.put(tenantDomain, rootPathNodeRef);
+ }
+ return rootPathNodeRef;
+ }
+
+ private Map getRootPathNodeRefMap(HomeFolderProvider2 provider)
+ {
+ String name = provider.getName();
+ Map rootPathNodeRefMap = rootPathNodeRefMaps.get(name);
+ if (rootPathNodeRefMap == null)
+ {
+ // ok with race condition for initial construction
+ rootPathNodeRefMap = new ConcurrentHashMap();
+ rootPathNodeRefMaps.put(name, rootPathNodeRefMap);
+ }
+ return rootPathNodeRefMap;
+ }
+
+ /**
+ * Utility method to resolve paths to nodes.
+ */
+ NodeRef resolvePath(HomeFolderProvider2 provider, String pathToResolve)
+ {
+ List refs = serviceRegistry.getSearchService().selectNodes(
+ serviceRegistry.getNodeService().getRootNode(getStoreRef(provider)),
+ pathToResolve, null,
+ serviceRegistry.getNamespaceService(), false);
+ if (refs.size() != 1)
+ {
+ throw new IllegalStateException("Non-unique path: found : " +
+ pathToResolve + " " + refs.size());
+ }
+ return refs.get(0);
+ }
+
+ /**
+ * Helper method for {@link HomeFolderProvider2.getHomeFolder(NodeRef)}
+ * implementations to return a {@link HomeSpaceNodeRef}
+ * @param referenceRootNode indicates that a reference to the root node
+ * should be returned if the home folder property on the person
+ * has not yet been set.
+ */
+ public HomeSpaceNodeRef getHomeFolder(HomeFolderProvider2 provider, NodeRef person, boolean referenceRootNode)
+ {
+ HomeSpaceNodeRef homeSpaceNodeRef = null;
+ NodeRef existingHomeFolder = DefaultTypeConverter.INSTANCE.convert(
+ NodeRef.class, serviceRegistry.getNodeService().getProperty(
+ person, ContentModel.PROP_HOMEFOLDER));
+ if (existingHomeFolder != null)
+ {
+ homeSpaceNodeRef = new HomeSpaceNodeRef(existingHomeFolder,
+ HomeSpaceNodeRef.Status.VALID);
+ }
+ else if (referenceRootNode)
+ {
+ homeSpaceNodeRef = new HomeSpaceNodeRef(getRootPathNodeRef(provider),
+ HomeSpaceNodeRef.Status.REFERENCED);
+ }
+ else
+ {
+ FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
+ List homeFolderPath = provider.getHomeFolderPath(person);
+
+ FileInfo fileInfo;
+
+ // Test if it already exists
+ NodeRef existing = getExisting(provider, fileFolderService, homeFolderPath);
+ if (existing != null)
+ {
+ fileInfo = fileFolderService.getFileInfo(existing);
+ }
+ else
+ {
+ fileInfo = createTree(provider, getRootPathNodeRef(provider), homeFolderPath,
+ provider.getTemplateNodeRef(), fileFolderService);
+ }
+ NodeRef homeFolderNodeRef = fileInfo.getNodeRef();
+ return new HomeSpaceNodeRef(homeFolderNodeRef, HomeSpaceNodeRef.Status.CREATED);
+ }
+ return homeSpaceNodeRef;
+ }
+
+ private NodeRef getExisting(HomeFolderProvider2 provider, FileFolderService fileFolderService,
+ List homeFolderPath)
+ {
+ NodeRef existing;
+ try
+ {
+ FileInfo existingFileInfo = fileFolderService.resolveNamePath(getRootPathNodeRef(provider), homeFolderPath);
+ existing = existingFileInfo.getNodeRef();
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ existing = null;// home folder noderef doesn't exist yet
+ }
+ return existing;
+ }
+
+ /**
+ * creates a tree of folder nodes based on the path elements provided.
+ */
+ private FileInfo createTree(HomeFolderProvider2 provider, NodeRef root,
+ List homeFolderPath, NodeRef templateNodeRef,
+ FileFolderService fileFolderService)
+ {
+ NodeRef newParent = createNewParentIfRequired(root, homeFolderPath, fileFolderService);
+ String homeFolderName = homeFolderPath.get(homeFolderPath.size()-1);
+ FileInfo fileInfo;
+ if (templateNodeRef == null)
+ {
+ fileInfo = fileFolderService.create(
+ newParent,
+ homeFolderName,
+ ContentModel.TYPE_FOLDER);
+ }
+ else
+ {
+ try
+ {
+ fileInfo = fileFolderService.copy(
+ templateNodeRef,
+ newParent,
+ homeFolderName);
+ }
+ catch (FileNotFoundException e)
+ {
+ throw new PersonException("Invalid template to create home space");
+ }
+ }
+ return fileInfo;
+ }
+
+ private NodeRef createNewParentIfRequired(NodeRef root,
+ List homeFolderPath, FileFolderService fileFolderService)
+ {
+ if (homeFolderPath.size() > 1)
+ {
+ List parentPath = new ArrayList(homeFolderPath);
+ parentPath.remove(parentPath.size()-1);
+ return FileFolderUtil.makeFolders(fileFolderService, root,
+ parentPath, ContentModel.TYPE_FOLDER).getNodeRef();
+ }
+ else
+ {
+ return root;
}
}
}
diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderProvider.java b/source/java/org/alfresco/repo/security/person/HomeFolderProvider.java
index 508f2760ed..d977395b61 100644
--- a/source/java/org/alfresco/repo/security/person/HomeFolderProvider.java
+++ b/source/java/org/alfresco/repo/security/person/HomeFolderProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -23,6 +23,9 @@ import org.alfresco.repo.node.NodeServicePolicies;
/**
* Interface for home folder providers.
*
+ * @deprecated
+ * Depreciated since 4.0. {@link HomeFolderProvider2} should now be used.
+ *
* @author Andy Hind
*/
public interface HomeFolderProvider extends NodeServicePolicies.OnCreateNodePolicy
diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderProvider2.java b/source/java/org/alfresco/repo/security/person/HomeFolderProvider2.java
new file mode 100644
index 0000000000..36900ccf3d
--- /dev/null
+++ b/source/java/org/alfresco/repo/security/person/HomeFolderProvider2.java
@@ -0,0 +1,101 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.security.person;
+
+import java.util.List;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+/**
+ * Interface for home folder providers. Instances work with the
+ * {@link HomeFolderManager} (which performs most of the work)
+ * to allow it to create home folders in custom locations.
+ *
+ * The home folder may be a simple structure where all users share a root folder (See
+ * {@link ExistingPathBasedHomeFolderProvider2}), or all home folders are in the same root
+ * folder (See {@link UsernameHomeFolderProvider}) or in a tree of sub folders to
+ * avoids any single directory containing too many home directories which might cause
+ * performance issues (See {@link RegexHomeFolderProvider}).
+ *
+ * If the HomeFolderProvider is changed, home folders may be
+ * moved by using the {@link HomeFolderProviderSynchronizer} which optionally runs on
+ * restart.
+ *
+ * @author Andy Hind, Alan Davis (support v1 and v2 HomeFolderProviders)
+ */
+public interface HomeFolderProvider2
+{
+ /**
+ * Get the name of the provider (the bean name).
+ */
+ String getName();
+
+ /**
+ * Get the URL String of the node store that will be used.
+ */
+ String getStoreUrl();
+
+ /**
+ * Get the root path in the store under which all home folders will be located.
+ */
+ String getRootPath();
+
+ /**
+ * Returns a preferred path (a list of folder names) for the home folder relative to
+ * the root path. If all users share the root, the returned value should be an empty
+ * List or {@code null}. When all users have their own folder under the root
+ * there should be just one element in the List. Multiple elements should be returned
+ * when a nested folder structure is preferred.
+ * @param person NodeRef from which a property (normally the userName) is used as a
+ * hash key to create a nested directory structure.
+ * @return the path to be used.
+ */
+ List getHomeFolderPath(NodeRef person);
+
+ /**
+ * Returns a node to copy (a template) for the home folder.
+ * Only used by HomeFolderProviders that create home folders rather
+ * than just reference existing folders.
+ * @return the node to copy or {@code null} if not required.
+ */
+ NodeRef getTemplateNodeRef();
+
+ /**
+ * Set the authority to use as the owner of all home folder nodes.
+ * If {@code null} the {@link ContentModel.PROP_USERNAME} value of
+ * the person is used.
+ */
+ String getOwner();
+
+ /**
+ * Gets the PermissionsManager used on creating the home folder
+ */
+ PermissionsManager getOnCreatePermissionsManager();
+
+ /**
+ * Gets the PermissionsManager used on referencing the home folder
+ */
+ PermissionsManager getOnReferencePermissionsManager();
+
+ /**
+ * Callback from {@link HomeFolderManager} to locate or create a home folder.
+ * Implementations normally call {@link HomeFolderManager.getHomeFolder}.
+ */
+ HomeSpaceNodeRef getHomeFolder(NodeRef person);
+}
diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java
new file mode 100644
index 0000000000..9d7dfd6e07
--- /dev/null
+++ b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java
@@ -0,0 +1,1051 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.security.person;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.batch.BatchProcessWorkProvider;
+import org.alfresco.repo.batch.BatchProcessor;
+import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorkerAdaptor;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.tenant.Tenant;
+import org.alfresco.repo.tenant.TenantAdminService;
+import org.alfresco.repo.tenant.TenantService;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.cmr.model.FileExistsException;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileNotFoundException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.Path;
+import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
+import org.alfresco.service.cmr.security.AuthorityService;
+import org.alfresco.service.cmr.security.AuthorityType;
+import org.alfresco.service.cmr.security.NoSuchPersonException;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.VmShutdownListener;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.extensions.surf.util.AbstractLifecycleBean;
+
+/**
+ * Called on startup to move (synchronise) home folders to the preferred
+ * location defined by their {@link HomeFolderProvider2} or extend the
+ * now depreciated {@link AbstractHomeFolderProvider}. Only users that
+ * use a HomeFolderProvider2 that don't provide a shared home
+ * folder (all user are given the same home folder) will be moved. This
+ * allows existing home directories to be moved to reflect changes in
+ * policy related to the location of home directories. Originally created
+ * for ALF-7797 which related to the need to move large numbers of
+ * existing home directories created via an LDAP import into a hierarchical
+ * folder structure with fewer home folder in each.
+ *
+ * By default no action is taken unless the the global property
+ * {@code home_folder_provider_synchronizer.enabled=true}.
+ *
+ * The home folders for internal users (such as {@code admin} and {@code
+ * guest}) that use {@code guestHomeFolderProvider} or {@code
+ * bootstrapHomeFolderProvider} are not moved, nor are any users that use
+ * {@link HomeFolderProviders} create shared home folders (all user are
+ * given the same home folder).
+ *
+ * It is also possible change the HomeFolderProvider used by all other
+ * users by setting the global property
+ * {@code home_folder_provider_synchronizer.override_provider=}.
+ *
+ * Warning: The LDAP synchronise process overwrites the home folder
+ * provider property. This is not an issue as long as the root path of
+ * the overwriting provider is the same as the overwritten provider or is
+ * not an ancestor of any of the existing home folders. This is important
+ * because the root directory value is used by this class to tidy up empty
+ * 'parent' folders under the root when a home folders are moved elsewhere.
+ * If you have any concerns that this may not be true, set the global
+ * property {@code home_folder_provider_synchronizer.keep_empty_parents=true}
+ * and tidy up any empty folders manually. Typically users created by the
+ * LDAP sync process are all placed under the same root folder so there
+ * will be no parent folders anyway.
+ *
+ * @author Alan Davis
+ */
+public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
+{
+ private static final Log logger = LogFactory.getLog(HomeFolderProviderSynchronizer.class);
+
+ private static final String ENABLED_PROPERTY_NAME = "home_folder_provider_synchronizer.enabled";
+ private static final String OVERRIDE_PROPERTY_NAME = "home_folder_provider_synchronizer.override_provider";
+ private static final String KEEP_EMPTY_PARENTS_PROPERTY_NAME = "home_folder_provider_synchronizer.keep_empty_parents";
+
+ private static final String GUEST_HOME_FOLDER_PROVIDER = "guestHomeFolderProvider";
+ private static final String BOOTSTRAP_HOME_FOLDER_PROVIDER = "bootstrapHomeFolderProvider";
+
+ private final Properties properties;
+ private final TransactionService transactionService;
+ private final AuthorityService authorityService;
+ private final PersonService personService;
+ private final FileFolderService fileFolderService;
+ private final NodeService nodeService;
+ private final HomeFolderManager homeFolderManager;
+ private final TenantAdminService tenantAdminService;
+
+ public HomeFolderProviderSynchronizer(Properties properties,
+ TransactionService transactionService,
+ AuthorityService authorityService, PersonService personService,
+ FileFolderService fileFolderService, NodeService nodeService,
+ HomeFolderManager homeFolderManager,
+ TenantAdminService tenantAdminService)
+ {
+ this.properties = properties;
+ this.transactionService = transactionService;
+ this.authorityService = authorityService;
+ this.personService = personService;
+ this.fileFolderService = fileFolderService;
+ this.nodeService = nodeService;
+ this.homeFolderManager = homeFolderManager;
+ this.tenantAdminService = tenantAdminService;
+ }
+
+ private boolean enabled()
+ {
+ return "true".equalsIgnoreCase(properties.getProperty(ENABLED_PROPERTY_NAME));
+ }
+
+ private String getOverrideHomeFolderProviderName()
+ {
+ return properties.getProperty(OVERRIDE_PROPERTY_NAME);
+ }
+
+ private boolean keepEmptyParents()
+ {
+ return "true".equalsIgnoreCase(properties.getProperty(KEEP_EMPTY_PARENTS_PROPERTY_NAME));
+ }
+
+ @Override
+ protected void onShutdown(ApplicationEvent event)
+ {
+ // do nothing
+ }
+
+ @Override
+ protected void onBootstrap(ApplicationEvent event)
+ {
+ if (enabled())
+ {
+ final String overrideProviderName = getOverrideHomeFolderProviderName();
+
+ // Scan users in default and each Tenant
+ String systemUserName = AuthenticationUtil.getSystemUserName();
+ scanPeople(systemUserName, TenantService.DEFAULT_DOMAIN, overrideProviderName);
+
+ if (tenantAdminService.isEnabled())
+ {
+ List tenants = tenantAdminService.getAllTenants();
+ for (Tenant tenant : tenants)
+ {
+ if (tenant.isEnabled())
+ {
+ systemUserName = tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain());
+ scanPeople(systemUserName, tenant.getTenantDomain(), overrideProviderName);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Scans all {@code person} people objects and checks their home folder is located according
+ * to the person's home folder provider preferred default location.
+ * @param systemUserName String the system user name with the tenant-specific ID attached.
+ * @param tenantDomain String name of the tenant domain. Used to restrict the which people
+ * are processed.
+ * @param overrideProvider String the bean name of a HomeFolderProvider to be used
+ * in place of the all home folders existing providers. If {@code null}
+ * the existing provider is used.
+ */
+ private void scanPeople(final String systemUserName, String tenantDomain, final String overrideProvider)
+ {
+ /*
+ * To avoid problems with existing home folders that are located in locations
+ * that will be needed by 'parent' folders, we need a 4 phase process.
+ * Consider the following user names and required structure. There would be a
+ * problem with the username 'ab'.
+ *
+ * abc --> ab/abc
+ * def /abd
+ * abd /ab
+ * ab de/def
+ *
+ * 1. Record which parent folders are needed
+ * 2. Move any home folders which overlap with parent folders to a temporary folder
+ * 3. Create parent folder structure. Done in a single thread before the move of
+ * home folders to avoid race conditions
+ * 4. Move home folders if required
+ *
+ * Alternative approaches are possible, but the above has the advantage that
+ * nodes are not moved if they are already in their preferred location.
+ */
+
+ // Using authorities rather than Person objects as they are much lighter
+ final Set authorities = getAllAuthoritiesInTxn(systemUserName);
+ final ParentFolderStructure parentFolderStructure = new ParentFolderStructure();
+ final Map tmpFolders = new HashMap();
+
+ // Define the phases
+ final String createParentFoldersPhaseName = "createParentFolders";
+ RunAsWorker[] workers = new RunAsWorker[]
+ {
+ new RunAsWorker(systemUserName, "calculateParentFolderStructure")
+ {
+ @Override
+ public void doWork(NodeRef person) throws Exception
+ {
+ calculateParentFolderStructure(
+ parentFolderStructure, person, overrideProvider);
+ }
+ },
+
+ new RunAsWorker(systemUserName, "moveHomeFolderThatClashesWithParentFolderStructure")
+ {
+ @Override
+ public void doWork(NodeRef person) throws Exception
+ {
+ moveHomeFolderThatClashesWithParentFolderStructure(
+ parentFolderStructure, tmpFolders, person, overrideProvider);
+ }
+ },
+
+ new RunAsWorker(systemUserName, createParentFoldersPhaseName)
+ {
+ @Override
+ public void doWork(NodeRef person) throws Exception
+ {
+ createParentFolders(person, overrideProvider);
+ }
+ },
+
+ new RunAsWorker(systemUserName, "moveHomeFolderIfRequired")
+ {
+ @Override
+ public void doWork(NodeRef person) throws Exception
+ {
+ moveHomeFolderIfRequired(person, overrideProvider);
+ }
+ }
+ };
+
+ // Run the phases
+ for (RunAsWorker worker: workers)
+ {
+ String name = worker.getName();
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" -- "+
+ (TenantService.DEFAULT_DOMAIN.equals(tenantDomain)? "" : tenantDomain+" ")+
+ name+" --");
+ }
+
+ int threadCount = (name.equals(createParentFoldersPhaseName)) ? 1 : 2;
+ int peoplePerTransaction = 20;
+
+ // Use 2 threads, 20 person objects per transaction. Log every 100 entries.
+ BatchProcessor processor = new BatchProcessor(
+ "HomeFolderProviderSynchronizer",
+ transactionService.getRetryingTransactionHelper(),
+ new WorkProvider(authorities),
+ threadCount, peoplePerTransaction,
+ null,
+ logger, 100);
+ processor.process(worker, true);
+ if (processor.getTotalErrors() > 0)
+ {
+ logger.debug(" -- Give up after error --");
+ break;
+ }
+ }
+ }
+
+ // Can only use authorityService.getAllAuthorities(...) in a transaction.
+ private Set getAllAuthoritiesInTxn(final String systemUserName)
+ {
+ return AuthenticationUtil.runAs(new RunAsWork>()
+ {
+ public Set doWork() throws Exception
+ {
+ RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
+ RetryingTransactionCallback> restoreCallback =
+ new RetryingTransactionCallback>()
+ {
+ public Set execute() throws Exception
+ {
+ return authorityService.getAllAuthorities(AuthorityType.USER);
+ }
+ };
+ return txnHelper.doInTransaction(restoreCallback, false, true);
+ }
+ }, systemUserName);
+ }
+
+ /**
+ * Work out the preferred parent folder structure so we will be able to work out if any
+ * existing home folders clash.
+ */
+ private ParentFolderStructure calculateParentFolderStructure(
+ final ParentFolderStructure parentFolderStructure,
+ NodeRef person, String overrideProviderName)
+ {
+ new HomeFolderHandler(person, overrideProviderName)
+ {
+ @Override
+ protected void handleNotInPreferredLocation()
+ {
+ recordParentFolder();
+ }
+
+ @Override
+ protected void handleInPreferredLocation()
+ {
+ recordParentFolder();
+ }
+
+ private void recordParentFolder()
+ {
+ parentFolderStructure.recordParentFolder(root, preferredPath);
+ }
+ }.doWork();
+
+ return parentFolderStructure;
+ }
+
+ /**
+ * Move any home folders (to a temporary folder) that clash with the
+ * new parent folder structure.
+ */
+ private void moveHomeFolderThatClashesWithParentFolderStructure(
+ final ParentFolderStructure parentFolderStructure,
+ final Map tmpFolders,
+ NodeRef person, String overrideProviderName)
+ {
+ new HomeFolderHandler(person, overrideProviderName)
+ {
+ @Override
+ protected void handleNotInPreferredLocation()
+ {
+ moveToTmpIfClash();
+ }
+
+ @Override
+ protected void handleInPreferredLocation()
+ {
+ moveToTmpIfClash();
+ }
+
+ private void moveToTmpIfClash()
+ {
+ if (parentFolderStructure.clash(root, actualPath))
+ {
+ String tmpFolder = getTmpFolderName(root);
+ preferredPath = new ArrayList();
+ preferredPath.add(tmpFolder);
+ preferredPath.addAll(actualPath);
+
+ // - providerName parameter is set to null as we don't want the
+ // "homeFolderProvider" reset
+ moveHomeFolder(person, homeFolder, root, preferredPath, originalRoot,
+ null, originalProviderName, actualPath);
+ }
+ }
+
+ private String getTmpFolderName(NodeRef root)
+ {
+ synchronized(tmpFolders)
+ {
+ String tmpFolder = tmpFolders.get(root);
+ if (tmpFolder == null)
+ {
+ tmpFolder = createTmpFolderName(root);
+ tmpFolders.put(root, tmpFolder);
+ }
+ return tmpFolder;
+ }
+ }
+
+ private String createTmpFolderName(NodeRef root)
+ {
+ // Try a few times but then give up.
+ for (int i = 1; i <= 100; i++)
+ {
+ String tmpFolderName = "Temporary"+i;
+ if (fileFolderService.searchSimple(root, tmpFolderName) == null)
+ {
+ fileFolderService.create(root, tmpFolderName, ContentModel.TYPE_FOLDER);
+ return tmpFolderName;
+ }
+ }
+ throw new PersonException("Unable to create a temporty " +
+ "folder into which home folders could be moved.");
+ }
+ }.doWork();
+ }
+
+ /**
+ * Creates the new home folder structure, before we move home folders so that
+ * we don't have race conditions that result in unnecessary retries.
+ * @param parentFolderStructure
+ */
+ private void createParentFolders(NodeRef person, String overrideProviderName)
+ {
+ // We could short cut this process and build all the home folder from the
+ // ParentFolderStructure in the calling method, but that would complicate
+ // the code a little more and might result in transaction size problems.
+ // For now lets loop through all the person objects.
+
+ new HomeFolderHandler(person, overrideProviderName)
+ {
+ @Override
+ protected void handleNotInPreferredLocation()
+ {
+ createNewParentIfRequired(root, preferredPath);
+ }
+
+ @Override
+ protected void handleInPreferredLocation()
+ {
+ // do nothing
+ }
+ }.doWork();
+ }
+
+ /**
+ * If the home folder has been created but is not in its preferred location, the home folder
+ * is moved. Empty parent folder's under the old root are only removed if the old root is
+ * known and {@code home_folder_provider_synchronizer.keep_empty_parents=true} has not been
+ * set.
+ * @param person Person to be checked.
+ * @param overrideProviderName String name of a provider to use in place of
+ * the one currently used. Ignored if {@code null}.
+ */
+ private void moveHomeFolderIfRequired(NodeRef person, String overrideProviderName)
+ {
+ new HomeFolderHandler(person, overrideProviderName)
+ {
+ @Override
+ protected void handleNotInPreferredLocation()
+ {
+ moveHomeFolder(person, homeFolder, root, preferredPath, originalRoot,
+ providerName, originalProviderName, actualPath);
+ }
+
+ @Override
+ protected void handleInPreferredLocation()
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" "+toPath(actualPath)+" is already in preferred location.");
+ }
+ }
+
+ @Override
+ protected void handleSharedHomeProvider()
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" "+userName+" "+providerName+" creates shared home folders - These are not moved.");
+ }
+ }
+
+
+ @Override
+ protected void handleOriginalSharedHomeProvider()
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" "+userName+" Original "+originalProviderName+" creates shared home folders - These are not moved.");
+ }
+ }
+
+ @Override
+ protected void handleNotAHomeFolderProvider2()
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" "+userName+" "+providerName+" for is not a HomeFolderProvider2.");
+ }
+ }
+
+ @Override
+ protected void handleSpecialHomeFolderProvider()
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" "+userName+" Original "+originalProviderName+" is an internal type - These are not moved.");
+ }
+ }
+
+ @Override
+ protected void handleHomeFolderNotSet()
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" "+userName+" Home folder is not set - ignored");
+ }
+ }
+ }.doWork();
+ }
+
+ /**
+ * @return a String for debug a folder list.
+ */
+ private String toPath(List folders)
+ {
+ StringBuilder sb = new StringBuilder("");
+ if (folders != null)
+ {
+ for (String folder : folders)
+ {
+ if (sb.length() > 0)
+ {
+ sb.append('/');
+ }
+ sb.append(folder);
+ }
+ }
+ return sb.toString();
+ }
+
+ private String toPath(NodeRef root, NodeRef leaf)
+ {
+ StringBuilder sb = new StringBuilder("");
+ List path = getRelativePath(root, leaf);
+ if (path != null)
+ {
+ for (String folder : path)
+ {
+ if (sb.length() > 0)
+ {
+ sb.append('/');
+ }
+ sb.append(folder);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @return the relative 'path' (a list of folder names) of the {@code homeFolder}
+ * from the {@code root} or {@code null} if the homeFolder is not under the root
+ * or is the root.
+ */
+ private List getRelativePath(NodeRef root, NodeRef homeFolder)
+ {
+ if (root == null || homeFolder == null)
+ {
+ return null;
+ }
+
+ Path rootPath = nodeService.getPath(root);
+ Path homeFolderPath = nodeService.getPath(homeFolder);
+ int rootSize = rootPath.size();
+ int homeFolderSize = homeFolderPath.size();
+ if (rootSize >= homeFolderSize)
+ {
+ return null;
+ }
+
+ // Check homeFolder is under root
+ for (int i=0; i < rootSize; i++)
+ {
+ if (!rootPath.get(i).equals(homeFolderPath.get(i)))
+ {
+ return null;
+ }
+ }
+
+ // Build up path of sub folders
+ List path = new ArrayList();
+ for (int i = rootSize; i < homeFolderSize; i++)
+ {
+ Path.Element element = homeFolderPath.get(i);
+ if (!(element instanceof Path.ChildAssocElement))
+ {
+ return null;
+ }
+ QName folderQName = ((Path.ChildAssocElement) element).getRef().getQName();
+ path.add(folderQName.getLocalName());
+ }
+ return path;
+ }
+
+ /**
+ * Move an existing home folder from one location to another,
+ * removing empty parent folders and reseting homeFolder and
+ * homeFolderProvider properties.
+ */
+ private void moveHomeFolder(NodeRef person, NodeRef homeFolder, NodeRef root,
+ List preferredPath, NodeRef oldRoot, String providerName,
+ String originalProviderName, List actualPath)
+ {
+ try
+ {
+ // Create the new parent folder (if required)
+ // Code still here for completeness, but should be okay
+ // as the temporary folder will have been created and any
+ // parent folders should have been created.
+ NodeRef newParent = createNewParentIfRequired(root, preferredPath);
+
+ 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.
+ NodeRef oldParent = nodeService.getPrimaryParent(homeFolder) .getParentRef();
+
+ // Perform the move
+ homeFolder = fileFolderService.move(homeFolder, newParent,
+ homeFolderName).getNodeRef();
+
+ // Reset the homeFolder property
+ nodeService.setProperty(person, ContentModel.PROP_HOMEFOLDER, homeFolder);
+
+ // Change provider name
+ if (providerName != null && !providerName.equals(originalProviderName))
+ {
+ nodeService.setProperty(person,
+ 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
+ removeEmptyParentFolders(oldParent, oldRoot);
+ }
+ catch (FileExistsException e)
+ {
+ String message = "mv "+toPath(actualPath)+" "+toPath(preferredPath)+
+ " failed as the target already existed.";
+ logger.error(" "+message);
+ throw new PersonException(message);
+ }
+ catch (FileNotFoundException e)
+ {
+ // This should not happen unless there is a coding error
+ String message = "mv "+toPath(actualPath)+" "+toPath(preferredPath)+
+ " failed as source did not exist.";
+ logger.error(" "+message);
+ throw new PersonException(message);
+ }
+ }
+
+ private NodeRef createNewParentIfRequired(NodeRef root, List homeFolderPath)
+ {
+ NodeRef parent = root;
+ int len = homeFolderPath.size() - 1;
+ for (int i = 0; i < len; i++)
+ {
+ String pathElement = homeFolderPath.get(i);
+ NodeRef nodeRef = nodeService.getChildByName(parent,
+ ContentModel.ASSOC_CONTAINS, pathElement);
+ if (nodeRef == null)
+ {
+ parent = fileFolderService.create(parent, pathElement,
+ ContentModel.TYPE_FOLDER).getNodeRef();
+ }
+ else
+ {
+ // Throw our own FileExistsException before we get an
+ // exception when we cannot create a sub-folder under
+ // a non folder that marks the transaction rollback, as
+ // there is no point trying again.
+ if (!fileFolderService.getFileInfo(nodeRef).isFolder())
+ {
+ throw new FileExistsException(parent, null);
+ }
+
+ parent = nodeRef;
+ }
+ }
+ return parent;
+ }
+
+ /**
+ * Removes the parent folder if it is empty and its parents up to but not
+ * including the root.
+ */
+ private void removeEmptyParentFolders(NodeRef parent, NodeRef root)
+ {
+ // Parent folders we have created don't have an owner, were as
+ // home folders do, hence the 3rd test (just in case) as we really
+ // don't want to delete empty home folders.
+ if (root != null &&
+ !keepEmptyParents() &&
+ nodeService.getProperty(parent, ContentModel.PROP_OWNER) == null)
+ {
+ // Do nothing if root is not an ancestor of parent.
+ NodeRef nodeRef = parent;
+ while (!root.equals(nodeRef))
+ {
+ if (nodeRef == null)
+ {
+ return;
+ }
+ nodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef();
+ }
+
+ // Remove any empty nodes.
+ while (!root.equals(parent))
+ {
+ nodeRef = parent;
+ parent = nodeService.getPrimaryParent(parent).getParentRef();
+
+ if (!nodeService.getChildAssocs(nodeRef).isEmpty())
+ {
+ return;
+ }
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" rm "+toPath(root, nodeRef));
+ }
+ nodeService.deleteNode(nodeRef);
+ }
+ }
+ }
+
+ // BatchProcessWorkProvider returns batches of 100 person objects from lightweight authorities.
+ private class WorkProvider implements BatchProcessWorkProvider
+ {
+ private static final int BATCH_SIZE = 100;
+
+ private final VmShutdownListener vmShutdownLister = new VmShutdownListener("getHomeFolderProviderSynchronizerWorkProvider");
+ private final Iterator iterator;
+ private final int size;
+
+ public WorkProvider(Set authorities)
+ {
+ iterator = authorities.iterator();
+ size = authorities.size();
+ }
+
+ @Override
+ public synchronized int getTotalEstimatedWorkSize()
+ {
+ return size;
+ }
+
+ @Override
+ public synchronized Collection getNextWork()
+ {
+ if (vmShutdownLister.isVmShuttingDown())
+ {
+ return Collections.emptyList();
+ }
+
+ RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
+ RetryingTransactionCallback> restoreCallback = new RetryingTransactionCallback>()
+ {
+ public Collection execute() throws Exception
+ {
+ Collection results = new ArrayList(BATCH_SIZE);
+ while (results.size() < BATCH_SIZE && iterator.hasNext())
+ {
+ String userName = iterator.next();
+ try
+ {
+ NodeRef person = personService.getPerson(userName, false);
+ results.add(person);
+ }
+ catch (NoSuchPersonException e)
+ {
+ if (logger.isTraceEnabled())
+ {
+ logger.trace("The user "+userName+" no longer exists - ignored.");
+ }
+ }
+ }
+ return results;
+ }
+ };
+ return txnHelper.doInTransaction(restoreCallback, false, true);
+ }
+ }
+
+ // BatchProcessWorker that runs work as another user.
+ private abstract class RunAsWorker extends BatchProcessWorkerAdaptor
+ {
+ final String userName;
+ final String name;
+
+ public RunAsWorker(String userName, String name)
+ {
+ this.userName = userName;
+ this.name = name;
+ }
+
+ public void process(final NodeRef person) throws Throwable
+ {
+ RunAsWork