Alan Davis 86f9dfea17 Merge DEV/V3.4-BUG-FIX to HEAD (28799-28800,28864,28875,28879,28916,28941,28956,28970,28993)
28993: HomeFolderProviderSynchronizer: Provider used for LDAP to continue to use username as home folder name directly under .../app:company_home/app:user_homes
   Property spaces.user_homes.regex.pattern now set to "" rather than "^(..)" which would have given a single level hash structure.
   28970: Merge DEV/ALAN/HOME_FOLDER to DEV/V3.4-BUG-FIX
      28947: - Introduction of version 2 HomeFolderProvider2 and re-factor of version 1
        so that the code used to create the folders is now in the HomeFolderManager.
      - Re-factor homeFolderProviderSynchronizer to handle HomeFolderProvider2.
      - Addition of AbstractHomeFolderProvider.V2Adaptor to allow external providers
        that extend AbstractHomeFolderPathProvider to be handled by
        homeFolderProviderSynchronizer.
      28860: Minor change to class comment and removed unused imports
      28858: ALF-4727 Hashed home folder provider added and used by default for LDAP sync users  - based on Romain Guinot work.
      ALF-7797 HomeFolderProviderSynchronizer added to move existing users (normally those added by LDAP sync) into location preferred by home folder provider.
      - HomeFolderProviderSynchronizer bug fixes
        - tenant accounts supported for first time
        - addition of a phase to create parent folders before moving home folder to avoid a race condition
        - check for conditions that would result in FileExistExceptions as we don't want a the transaction to be discarded as this results in retries.  
      - HomeFolderProviderSynchronizerTest integration test including tenant services
      - HomeFolderManager now sets the HOME_FOLDER_PROVIDER if it uses a default when HOME_FOLDER_PROVIDER is not set.
      - AbstractHomeFolderProvider clears cache when path reset as it will be invalid.
      - UIDBasedHomeFolderProvider.createNewParent creates its own mutable List as the one passed in may not be mutable.
      28580: Save code changes - added comments to do with LDAP syn overriding the HFP value and related to this added a global property to keep empty parent folders. 
      28347: HomeFolderProviderSynchronizer
      - issue to do with new run of sync having created a user via UI that has a home folder as one of the parent folders.
      - issue to do with catching exception when creating temporary folder - transaction is gone
      - give up if error in any phase
      28298: Addition of HomeFolderPathProvider (based on Romain's work) and addition of HomeFolderProviderSynchronizer.
   28956: Merged DEV to V3.4-BUG-FIX
      ALF-9428: Multitenancy users not preserved after upgrade from 3.2.2.7 to 3.4.2
                - Provide correct RunAs context in FixUserQNamesPatch batching.
   28941: ALF-9361 : CLONE -sync Flat IMAP client with Alfresco is slow and inaccurate
   28916: ALF-9421 The AlfrescoJavaScript action now includes company home in the JavaScript scope.
   28879: Fixed ALF-9296: Alfresco Dashboard: Impossible to approve/reject task from My Tasks dashlet on My Alfresco
   28875: Fixed ALF-6329: SPANISH - Share, translation on Transfer Target configuration
   28864: Message:
   ALF-9430: RuntimeExec waitForCompletion logic is obscure
    - Only a single flag 'isCompleted'
    - Set flag in try-finally
    - Added notify()
   However, the wait() code doesn't, in practice, get called because the waitForCompletion is synchronized with the run()
   and is called a while after the reading thread is triggered.  So the logic is less obscure and safer for the finally.
   28800: File for rev 28799: ALF-9240
   28799: Merged DEV to V3.4-BUG-FIX
      28797: ALF-9240: Issue with adding an aspect with large multivalued list
             Added unit test to stress, but could not reproduce

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28995 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2011-07-13 16:47:03 +00:00

498 lines
18 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.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;
/**
* Manage home folder creation by binding to events from the cm:person type.
*
* @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 HomeFolderProvider2 defaultProvider;
/**
* Original Providers (now depreciated) that have registered and are looked up by bean name.
*/
@SuppressWarnings("deprecation")
private Map<String, HomeFolderProvider> v1Providers = new HashMap<String, HomeFolderProvider>();
/**
* Providers that have registered and are looked up by bean name.
*/
private Map<String, HomeFolderProvider2> v2Providers = new HashMap<String, HomeFolderProvider2>();
/**
* Cache the result of the path look up.
*/
private Map<String, Map<String, NodeRef>> rootPathNodeRefMaps =
new ConcurrentHashMap<String, Map<String, NodeRef>>();
/**
* Bind the class behaviour to this implementation
*/
public void init() throws Exception
{
if (enableHomeFolderCreationAsPeopleAreCreated)
{
policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), ContentModel.TYPE_PERSON, new JavaBehaviour(this, "onCreateNode"));
}
}
public void setEnableHomeFolderCreationAsPeopleAreCreated(boolean enableHomeFolderCreationAsPeopleAreCreated)
{
this.enableHomeFolderCreationAsPeopleAreCreated = enableHomeFolderCreationAsPeopleAreCreated;
}
/**
* Set the policy component.
*
* @param policyComponent
*/
public void setPolicyComponent(PolicyComponent policyComponent)
{
this.policyComponent = policyComponent;
}
/**
* Set the node service.
* @param nodeService
*/
public void setNodeService(NodeService nodeService)
{
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)
{
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(HomeFolderProvider2 defaultProvider)
{
this.defaultProvider = defaultProvider;
}
/**
* Find the provider and call if eager home folder creation is enabled.
*/
public void onCreateNode(ChildAssociationRef childAssocRef)
{
if (enableHomeFolderCreationAsPeopleAreCreated)
{
makeHomeFolder(childAssocRef);
}
}
/**
* Find the provider and call.
*/
@SuppressWarnings("deprecation")
public void makeHomeFolder(ChildAssociationRef 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)
{
v2Provider = getHomeFolderProvider2(providerName);
if (v2Provider == null)
{
v1Provider = getHomeFolderProvider1(providerName);
if (v1Provider == null)
{
v2Provider = defaultProvider;
}
}
}
else
{
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<NodeRef> 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>
{
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<String, NodeRef> 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<String, NodeRef> getRootPathNodeRefMap(HomeFolderProvider2 provider)
{
String name = provider.getName();
Map<String, NodeRef> rootPathNodeRefMap = rootPathNodeRefMaps.get(name);
if (rootPathNodeRefMap == null)
{
// ok with race condition for initial construction
rootPathNodeRefMap = new ConcurrentHashMap<String, NodeRef>();
rootPathNodeRefMaps.put(name, rootPathNodeRefMap);
}
return rootPathNodeRefMap;
}
/**
* Utility method to resolve paths to nodes.
*/
NodeRef resolvePath(HomeFolderProvider2 provider, String pathToResolve)
{
List<NodeRef> 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<String> 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<String> 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<String> 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<String> homeFolderPath, FileFolderService fileFolderService)
{
if (homeFolderPath.size() > 1)
{
List<String> parentPath = new ArrayList<String>(homeFolderPath);
parentPath.remove(parentPath.size()-1);
return FileFolderUtil.makeFolders(fileFolderService, root,
parentPath, ContentModel.TYPE_FOLDER).getNodeRef();
}
else
{
return root;
}
}
}