mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-07 18:25:23 +00:00
29057: ALF-9491: Bitrock 7.2.2 29063: ALF-8766 Concatenated strings in EN webclient file 29066: Merge DEV/DEV/BELARUS/V3.4-BUG-FIX-2011_07_13 to DEV/V3.4-BUG-FIX 29010: ALF-7396: Japanese- Untranslated 29072: HomeFolderProvider work - Changes as a result of Dave Ward's comments (HomeFolderManager not fully done as there is a spring issue with using NodeService, FileFolderService, fileFolderService, SearchService or searchService) 29074: ALF-7637 - Share displays incorrect folder contents after copy-on-outbound rule against working copy 29075: ALF-8406 - Configuring the datalist display for sub-types does not work 29082: ALF-6847 translation: "Collega" should be reverted to English: "Link" as per term list. 29087: ALF-5717 property names for wcm quickstart website-model had an invalid format or did not end in .description or .title 29093: Merge V3.3 to DEV/V3.4-BUG-FIX (28596) 28596: Remove dependency between subsystems and all the object factories in the parent context! - Do not allow eager initialization when looking up parent post processors - Removes circular dependencies from sysAdmin subsystem 29094: Merge HEAD to DEV/V3.4-BUG-FIX () 28892: Broke circular references between NodeService beans, NodeIndexer, Lucene and back to NodeService. - NodeIndexer is now bootstrapped to pull out reference to the Lucene beans 29100: Revert Merge V3.3 to DEV/V3.4-BUG-FIX (28596) Caused RepositoryStartupTest to fail 28596: Remove dependency between subsystems and all the object factories in the parent context! - Do not allow eager initialization when looking up parent post processors - Removes circular dependencies from sysAdmin subsystem 29102: ALF-9048: Make apply_amps.bat work from its installed location 29103: ALF-8746: Restored Japanese choice format translations 29104: Merged V3.3 to V3.4-BUG-FIX (Reinstated this revision as it is required) 28596: Remove dependency between subsystems and all the object factories in the parent context! - Do not allow eager initialization when looking up parent post processors - Removes circular dependencies from sysAdmin subsystem 29105: Use org.springframework.aop.target.LazyInitTargetSource in the NodeService public proxy to break a circular dependency 29106: Make PersonService interact with HomeFolderManager via a lazy proxy to prevent another circular dependency - Simple HomeFolderManager interface created - Implementation class renamed to PortableHomeFolderManager - Removed TODOs from authentication-services-context.xml 29107: Forgot to remove the serviceRegistry dependency from homeFolderManager 29108: ALF-9529: Installer memory consumption and startup time improvements - Bitrock discover the for loop! 29109: ALF-9530: Postgres installed as Windows service should run as a postgres user, not System - Fix from Bitrock 29118: Fix for ALF-6737 - It's impossible to view any version of the wiki page if it was renamed with native characters 29119: Fix for ALF-5787 - strings extracted for L10N in Web form creation help text 29124: ALF-9530: Follow up fix from Bitrock 29126: Fix for ALF-8344 - Incorrect message is displayed while recover deleted file 29127: Fix for ALF-9445 - French - Share, translation on Transfer Target configuration 29129: ALF-9476: Make FTPS work on IBM JDK 29133: Fix failing DictionaryRestApiTest 29136: Fix build issues from 29104: - run as system when creating home folders (PortableHomeFolderManager) - re-factored onCreateNode out of PortableHomeFolderManager into PersonServiceImpl - re-factored property PortableHomeFolderManager.enableHomeFolderCreationAsPeopleAreCreated to PersonServiceImpl.homeFolderCreationEager 29137: Fix for ALF-8831 - Internal error occurs in My Tasks Webscripts component 29138: Fix for ALF-8765 - Layout is displaced if translated string occupies more than 1 line 29140: Fix for ALF-8668 - Deleting author account causes Failed to load the forum posts 29142: - PortableHomeFolderManager: Moved code to run as System into PersonServiceImpl so that one must have a valid authority to call the publick makeHomeFolder method. The authority should already be valid if called via PersonServiceImpl. - Removed unused policyBehaviourFilter property from PersonServiceImpl 29146: ALF-8701: partially translated string in html-upload.get_fr 29147: ALF-8727: DE - changes to Root Category 29149: ALF-8731: DE - Wiki changes (space before full stop) 29152: ALF-9503: Add space after colon in strings in file wdr-messages.properties 29153: Fixed ALF-7899: association.ftl does not render when showTargetLink=true in workflow 29165: ALF-8749: on submit action properties in wcn-workflow-messages.properties 29166: Fix for ALF-6220 - Language pack - .ftl localization 29167: ALF-9550 - Typos in new section of webclient.properties 29169: Fix for ALF-7844 - W3C: Impossible to activate 'Choose from popular tags in this site' link by Enter/Space keys 29170: Merge V3.4-TEAM to V3.4-BUG-FIX (3.4.4) 27471: Fix for ALF-8150 - check for visibility before applying focus to element for IE. 29171: Fixes: ALF-8922, removes date formatting from API (now returns ISO8601) and instead formats it on the client, using L10N strings. 29172: Fix for ALF-2023 - Repository Action - Copy item to specific space doesn not include content. The option to 'deep copy' is now exposed in the UI for Run Action and Rules in Explorer. 29173: Fix for ALF-1446 - Sorting of inline descendants is not observed 29175: ALF-241 - The item is not coppied via 'Paste All' in Shelf when 'Details' page is opened 29177: Fix for ALF-9520 - confusing sample config. Reordered sample config file as suggested. 29178: Fixed ALF-6400: GERMAN: Explorer mouse over hints for TinyMCE are not localized Fixed ALF-5766: ALL translations errors in Explorer - Calendars are not localizable for content based on webforms 29202: Merge DEV/BELARUS/V3.4-BUG-FIX-2011_04_12 to V3.4-BUG-FIX (3.4.4) 27836: ALF-8524: CLONE - Sharepoint doesn't work with HTTPS Changes in url links required for HTTPS support. 29203: Restored removal of postgresCreateSymLinksLinuxBuildingFromWindows tag (32 bit Linux) from revision 26582 29211: Fix for ALF-1051 - It is impossible to find link by tag from link details page 29212: Fix for ALF-5301 - TinyMCE is replacing carriage return with white spaces 29250: Latest L10N update for all languages (DE, ES, FR, IT, JA) from Gloria (based on r29172) 29253: L10N Update from Gloria 29270: Fixed ALF-516: Unable to add content/delete tables in webform content when using FireFox 29271: Update from Gloria 29272: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_07_13 to BRANCHES/DEV/V3.4-BUG-FIX: (with minor modification) 29223: ALF-7619: When document A has an association with a document B editing A's properties fails if user has no permission to edit B 29274: ALF-9517 Incorrect behaviour of versions on Copy action. Version is 0.1 rather than 1.0 29283: Resolve ALF-8273: Valid datetime value cannot be parsed by CMIS AtomPub interface 29284: Update from Gloria 29286: ALF-9596: Merged PATCHES/V3.4.1 to V3.4-BUG-FIX 28150: ALF-8607: Detailed debug logging when out of sync transaction detected by index checker / tracker 28177: ALF-8607: Corrections to debug logging in AbstractReindexComponent 28213: ALF-8607: Further corrections to debug logging in AbstractReindexComponent - Log attributes from indexes, rather than nodeService properties 28341: ALF-8607: Stop index checker from 'lying' - isTxnPresentInIndex() call must be made in a new transaction in order to get a database view in sync with the current indexes 28352: ALF-8607: Revisit transaction delineation. Nested transaction only required in checkTxnsImpl() 28403: ALF-8607: Merged PATCHES/V3.3.4 to PATCHES/V3.4.1 27823: ALF-7237: Index tracker needs to perform a cascade reindex on updated nodes in order to cope with node moves 28406: ALF-8607: Improvement to FTS fix. Prevent FTS from restoring documents that have been deleted! 28412: ALF-8607: Invalidate properties and aspects as well as parent assocs when stale cache entry dected during transaction tracking 28427: ALF-8607: Prevent NPE with bad NodeRef in ADMLuceneIndexerImpl.createDocumentsImpl() 28705: ALF-8607: Validate transaction IDs when fetching parent associations - Compare the cached child node transaction ID against one fetched from the DB - Stops us from pairing up the cached node for an older or newer transaction with the wrong parent associations 28707: ALF-8607: Merged PATCHES/V3.3.4 to PATCHES/V3.4.1 28588: ALF-7237: Prevent FTS from ever wiping out a document that still exists and ignore duplicates 28708: ALF-8607: Make FTS capable of recovering from cache concurrency issues by using a RetryingTransactionHelper and better exception handling. - Also avoids skipping the entire batch when the reindexing of a particular document fails. 28710: ALF-8607: Corrected transaction delineation 28753: ALF-8607: Prevent errors caused by AbstractReindexComponent diagnostics trying to parse FTSREF document IDs as NodeRefs (which they aren't!) 28755: ALF-8607: When 'failing over' during FTS indexing, don't bother adding a FTS status document so we don't get stuck in a loop with a problematic document 28815: ALF-8607: Do two way validation of cached / fetched nodes and their parent associations to avoid skew - Should resolve problem of tracking moves to the archive store and moves in general 28862: ALF-8607: Lucene indexers now support 'read through' behaviour for FTS and Index tracking batches - Small discrete read only transactions used to read each reindexed node from the database / cache - Avoids cache 'drift' and 'skew' after long running indexing transactions 28863: ALF-8607: Missing file 28869: ALF-8607: isTxnPresentInIndex() needs to 'read through' so index tracker and checker don't pollute the cache 28872: ALF-8607: Optimization to prevent constant writing to AVM indexes whilst 'ticking over'. 28950: ALF-8607: Improved logic in AbstractReindexComponent.isTxnPresentInIndex() so that we can reliably cope with multi-store transactions (e.g. archive store + spaces store) - Due to FTS, the txn ID may have 'drifted in' to one store but not the other so we must validate all stores in the txn 29098: ALF-8607: Use getNodeRefStatus as a cache validation point for reindexing 'read through' transactions - Guarantees that FTS reindexed node will see correct state (well if we had consistent read behaviour it would!) - Removes stale nodeRef -> ID mappings (e.g. when original node moved to archive store and substituted with deleted node) - Inexplicably seems to produce a ~30x speedup in performance tests on MySQL! Appears to remove a contention point. More investigation required to find out what! 29287: ALF-9598: Merged PATCHES/V3.4.1 to V3.4-BUG-FIX 28653: ALF-9189: More efficient usage of IndexReaders to avoid huge memory churn with large indexes - A single reading thread could block out all other reading threads because a write lock is obtained whilst constructing a set of FilterIndexReaderByStringId readers and all deletions across all indexes have to be evaluated. We now cache a FilterIndexReaderByStringId for each 'layer' of the index so that we get some reuse. We also defer evaluation of deletions to AFTER the write lock is returned and in some cases never have to evaluate the deletions at all. - When merging deletions we now make use of a cached index reader for locating the documents, and only resort to a new reader if deletions have to be performed. Hopefully this will mean that the reader for the largest indexes, containing the least recently used stuff, will get left alone most of the time. 28690: ALF-9189: Corrections to previous fix - Forgot to remove non-lazy reader initialization - Fixed NPE - Reinstated correct looping behaviour - each processed delta must be considered as one of the indexes to search for the next processed delta 29099: ALF-9189: Avoid having to allocate a byte array full of number ones for all occurrences of a term to 'fake' norms. - Severe Lucene memory hog during FTS 29262: ALF-9189: Fixed memory leak during index tracking / reindexing and further memory leak regression - Fixed up Lucene refcounting again - remember to propagate through decrefs on ReferenceCounting readers - Refined ALF-9189 fix to guarantee mainreader clean up - Remember to flush the delta during reindexing / tracking - Some extra trace diagnostics to help 29288: ALF-9600: Merged PATCHES/V3.4.1 to V3.4-BUG_FIX 28876: ALF-9041: Merged HEAD to PATCHES/V3.4.1 28850: Latest SpringSurf libs - Fix to SSO connector passing empty username 29289: ALF-8241: assemble-tomcat populates endorsed directory with xalan.jar and serializer.jar and Bitrock installer installs these too 29291: Merged DEV/SWIFT to V3.4-BUG-FIX (3.4.4) - already merged to HEAD as part of a larger merge 26104: RM: Remove incomplete and unnecessary unit test 29302: Fix for ALF-8885 - Unable to paste item due to system error:null git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29325 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1067 lines
41 KiB
Java
1067 lines
41 KiB
Java
/*
|
|
* 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.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.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.<p>
|
|
*
|
|
* By default no action is taken unless the the global property
|
|
* {@code home_folder_provider_synchronizer.enabled=true}.<p>
|
|
*
|
|
* 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=<providerBeanName>}.<p>
|
|
*
|
|
* <b>Warning:</b> 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.<p>
|
|
*
|
|
* @author Alan Davis
|
|
*/
|
|
public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
|
|
{
|
|
private static final Log logger = LogFactory.getLog(HomeFolderProviderSynchronizer.class);
|
|
|
|
private static final String GUEST_HOME_FOLDER_PROVIDER = "guestHomeFolderProvider";
|
|
private static final String BOOTSTRAP_HOME_FOLDER_PROVIDER = "bootstrapHomeFolderProvider";
|
|
|
|
private final TransactionService transactionService;
|
|
private final AuthorityService authorityService;
|
|
private final PersonService personService;
|
|
private final FileFolderService fileFolderService;
|
|
private final NodeService nodeService;
|
|
private final PortableHomeFolderManager homeFolderManager;
|
|
private final TenantAdminService tenantAdminService;
|
|
|
|
private boolean enabled;
|
|
private String overrideHomeFolderProviderName;
|
|
private boolean keepEmptyParents;
|
|
|
|
public HomeFolderProviderSynchronizer(
|
|
TransactionService transactionService,
|
|
AuthorityService authorityService, PersonService personService,
|
|
FileFolderService fileFolderService, NodeService nodeService,
|
|
PortableHomeFolderManager homeFolderManager,
|
|
TenantAdminService tenantAdminService)
|
|
{
|
|
this.transactionService = transactionService;
|
|
this.authorityService = authorityService;
|
|
this.personService = personService;
|
|
this.fileFolderService = fileFolderService;
|
|
this.nodeService = nodeService;
|
|
this.homeFolderManager = homeFolderManager;
|
|
this.tenantAdminService = tenantAdminService;
|
|
}
|
|
|
|
public void setEnabled(String enabled)
|
|
{
|
|
this.enabled = "true".equalsIgnoreCase(enabled);
|
|
}
|
|
|
|
private boolean enabled()
|
|
{
|
|
return enabled;
|
|
}
|
|
|
|
public void setOverrideHomeFolderProviderName(String overrideHomeFolderProviderName)
|
|
{
|
|
this.overrideHomeFolderProviderName = overrideHomeFolderProviderName;
|
|
}
|
|
|
|
private String getOverrideHomeFolderProviderName()
|
|
{
|
|
return overrideHomeFolderProviderName;
|
|
}
|
|
|
|
public void setKeepEmptyParents(String keepEmptyParents)
|
|
{
|
|
this.keepEmptyParents = "true".equalsIgnoreCase(keepEmptyParents);
|
|
}
|
|
|
|
private boolean keepEmptyParents()
|
|
{
|
|
return keepEmptyParents;
|
|
}
|
|
|
|
@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<Tenant> 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<String> authorities = getAllAuthoritiesInTxn(systemUserName);
|
|
final ParentFolderStructure parentFolderStructure = new ParentFolderStructure();
|
|
final Map<NodeRef,String> tmpFolders = new HashMap<NodeRef,String>();
|
|
|
|
// 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<NodeRef> processor = new BatchProcessor<NodeRef>(
|
|
"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<String> getAllAuthoritiesInTxn(final String systemUserName)
|
|
{
|
|
return AuthenticationUtil.runAs(new RunAsWork<Set<String>>()
|
|
{
|
|
public Set<String> doWork() throws Exception
|
|
{
|
|
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
|
RetryingTransactionCallback<Set<String>> restoreCallback =
|
|
new RetryingTransactionCallback<Set<String>>()
|
|
{
|
|
public Set<String> 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<NodeRef,String> 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<String>();
|
|
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<String> 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<String> 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<String> 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<String> path = new ArrayList<String>();
|
|
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<String> preferredPath, NodeRef oldRoot, String providerName,
|
|
String originalProviderName, List<String> 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<String> 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<NodeRef>
|
|
{
|
|
private static final int BATCH_SIZE = 100;
|
|
|
|
private final VmShutdownListener vmShutdownLister = new VmShutdownListener("getHomeFolderProviderSynchronizerWorkProvider");
|
|
private final Iterator<String> iterator;
|
|
private final int size;
|
|
|
|
public WorkProvider(Set<String> authorities)
|
|
{
|
|
iterator = authorities.iterator();
|
|
size = authorities.size();
|
|
}
|
|
|
|
@Override
|
|
public synchronized int getTotalEstimatedWorkSize()
|
|
{
|
|
return size;
|
|
}
|
|
|
|
@Override
|
|
public synchronized Collection<NodeRef> getNextWork()
|
|
{
|
|
if (vmShutdownLister.isVmShuttingDown())
|
|
{
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
|
RetryingTransactionCallback<Collection<NodeRef>> restoreCallback = new RetryingTransactionCallback<Collection<NodeRef>>()
|
|
{
|
|
public Collection<NodeRef> execute() throws Exception
|
|
{
|
|
Collection<NodeRef> results = new ArrayList<NodeRef>(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<NodeRef>
|
|
{
|
|
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<Object> runAsWork = new RunAsWork<Object>()
|
|
{
|
|
@Override
|
|
public Object doWork() throws Exception
|
|
{
|
|
RunAsWorker.this.doWork(person);
|
|
return null;
|
|
}
|
|
};
|
|
AuthenticationUtil.runAs(runAsWork, userName);
|
|
}
|
|
|
|
public abstract void doWork(NodeRef person) throws Exception;
|
|
|
|
public String getName()
|
|
{
|
|
return name;
|
|
}
|
|
};
|
|
|
|
// Obtains home folder provider and path information with call backs.
|
|
private abstract class HomeFolderHandler
|
|
{
|
|
protected final NodeRef person;
|
|
protected final String overrideProviderName;
|
|
|
|
protected NodeRef homeFolder;
|
|
protected String userName;
|
|
protected String originalProviderName;
|
|
protected String providerName;
|
|
protected HomeFolderProvider2 provider;
|
|
protected NodeRef root;
|
|
protected List<String> preferredPath;
|
|
protected List<String> actualPath;
|
|
protected NodeRef originalRoot;
|
|
|
|
public HomeFolderHandler(NodeRef person, String overrideProviderName)
|
|
{
|
|
this.person = person;
|
|
this.overrideProviderName =
|
|
(overrideProviderName == null || overrideProviderName.trim().isEmpty())
|
|
? null
|
|
: overrideProviderName;
|
|
}
|
|
|
|
public void doWork()
|
|
{
|
|
homeFolder = DefaultTypeConverter.INSTANCE.convert(NodeRef.class,
|
|
nodeService.getProperty(person, ContentModel.PROP_HOMEFOLDER));
|
|
userName = DefaultTypeConverter.INSTANCE.convert(
|
|
String.class, nodeService.getProperty(person, ContentModel.PROP_USERNAME));
|
|
|
|
if (homeFolder != null)
|
|
{
|
|
originalProviderName = DefaultTypeConverter.INSTANCE.convert(String.class,
|
|
nodeService.getProperty(person, ContentModel.PROP_HOME_FOLDER_PROVIDER));
|
|
if (!BOOTSTRAP_HOME_FOLDER_PROVIDER.equals(originalProviderName) &&
|
|
!GUEST_HOME_FOLDER_PROVIDER.equals(originalProviderName))
|
|
{
|
|
providerName = overrideProviderName != null
|
|
? overrideProviderName
|
|
: originalProviderName;
|
|
provider = homeFolderManager.getHomeFolderProvider2(providerName);
|
|
|
|
if (provider != null)
|
|
{
|
|
root = homeFolderManager.getRootPathNodeRef(provider);
|
|
preferredPath = provider.getHomeFolderPath(person);
|
|
|
|
if (preferredPath == null || preferredPath.isEmpty())
|
|
{
|
|
handleSharedHomeProvider();
|
|
}
|
|
else
|
|
{
|
|
originalRoot = null;
|
|
HomeFolderProvider2 originalProvider = homeFolderManager.getHomeFolderProvider2(originalProviderName);
|
|
List<String> originalPreferredPath = null;
|
|
if (originalProvider != null)
|
|
{
|
|
originalRoot = homeFolderManager.getRootPathNodeRef(originalProvider);
|
|
originalPreferredPath = originalProvider.getHomeFolderPath(person);
|
|
}
|
|
|
|
if (originalProvider != null &&
|
|
(originalPreferredPath == null || originalPreferredPath.isEmpty()))
|
|
{
|
|
handleOriginalSharedHomeProvider();
|
|
}
|
|
else
|
|
{
|
|
actualPath = getRelativePath(root, homeFolder);
|
|
|
|
if (preferredPath.equals(actualPath))
|
|
{
|
|
handleInPreferredLocation();
|
|
}
|
|
else
|
|
{
|
|
handleNotInPreferredLocation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
handleNotAHomeFolderProvider2();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
handleSpecialHomeFolderProvider();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
handleHomeFolderNotSet();
|
|
}
|
|
}
|
|
|
|
protected abstract void handleInPreferredLocation();
|
|
|
|
protected abstract void handleNotInPreferredLocation();
|
|
|
|
protected void handleSharedHomeProvider()
|
|
{
|
|
}
|
|
|
|
protected void handleOriginalSharedHomeProvider()
|
|
{
|
|
}
|
|
|
|
protected void handleNotAHomeFolderProvider2()
|
|
{
|
|
}
|
|
|
|
protected void handleSpecialHomeFolderProvider()
|
|
{
|
|
}
|
|
|
|
protected void handleHomeFolderNotSet()
|
|
{
|
|
}
|
|
}
|
|
|
|
// Gathers and checks parent folder paths.
|
|
private class ParentFolderStructure
|
|
{
|
|
// Sets of parent folders within each root node
|
|
private Map<NodeRef, Set<List<String>>> folders = new HashMap<NodeRef, Set<List<String>>>();
|
|
|
|
public void recordParentFolder(NodeRef root, List<String> path)
|
|
{
|
|
Set<List<String>> rootsFolders = getFolders(root);
|
|
synchronized(rootsFolders)
|
|
{
|
|
// If parent is the root, all home folders clash
|
|
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
|
|
* or the parent folders includes the root itself. In
|
|
* the latter case all existing folders might clash
|
|
* so must be moved out of the way.
|
|
*/
|
|
public boolean clash(NodeRef root, List<String> path)
|
|
{
|
|
Set<List<String>> rootsFolders = getFolders(root);
|
|
synchronized(rootsFolders)
|
|
{
|
|
return rootsFolders.contains(path) ||
|
|
rootsFolders.contains(null);
|
|
}
|
|
}
|
|
|
|
private Set<List<String>> getFolders(NodeRef root)
|
|
{
|
|
synchronized(folders)
|
|
{
|
|
Set<List<String>> rootsFolders = folders.get(root);
|
|
if (rootsFolders == null)
|
|
{
|
|
rootsFolders = new HashSet<List<String>>();
|
|
folders.put(root, rootsFolders);
|
|
}
|
|
return rootsFolders;
|
|
}
|
|
}
|
|
}
|
|
}
|