diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-patch-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-patch-context.xml index eaad20fc4d..469a4ec2fe 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-patch-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-patch-context.xml @@ -63,7 +63,7 @@ - + @@ -76,7 +76,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -108,4 +108,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml index 197429a496..4937f3a39c 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml @@ -1479,4 +1479,49 @@ + + + ${spaces.store} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/dataset/DataSetServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/dataset/DataSetServiceImpl.java index 7db15e5327..f279699c9e 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/dataset/DataSetServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/dataset/DataSetServiceImpl.java @@ -6,7 +6,9 @@ import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -24,6 +26,7 @@ import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderServi import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; import org.alfresco.module.org_alfresco_module_rm.role.Role; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authority.RMAuthority; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -374,7 +377,7 @@ public class DataSetServiceImpl implements DataSetService, RecordsManagementMode // Create "all" role group for root node String allRoles = authorityService.createAuthority(AuthorityType.GROUP, allRoleShortName, - "All Roles", null); + "All Roles", new HashSet(Arrays.asList(RMAuthority.ZONE_APP_RM))); // Put all the role groups in it Set roles = filePlanRoleService.getRoles(rmRoot); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv21RolesPatch.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv21RolesPatch.java new file mode 100644 index 0000000000..d370c59b34 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv21RolesPatch.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2013 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.module.org_alfresco_module_rm.patch; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; +import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; +import org.alfresco.module.org_alfresco_module_rm.role.Role; +import org.alfresco.repo.module.AbstractModuleComponent; +import org.alfresco.repo.security.authority.RMAuthority; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.springframework.beans.factory.BeanNameAware; + +/** + * Adds the existing rm roles to a new zone "APP.RM" + * + * @author Tuna Aksoy + * @since 2.1 + */ +public class RMv21RolesPatch extends AbstractModuleComponent implements BeanNameAware +{ + private FilePlanService filePlanService; + private FilePlanRoleService filePlanRoleService; + private AuthorityService authorityService; + + public void setFilePlanService(FilePlanService filePlanService) + { + this.filePlanService = filePlanService; + } + + public void setFilePlanRoleService(FilePlanRoleService filePlanRoleService) + { + this.filePlanRoleService = filePlanRoleService; + } + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + @Override + protected void executeInternal() throws Throwable + { + Set filePlans = filePlanService.getFilePlans(); + for (NodeRef filePlan : filePlans) + { + boolean parentAddedToZone = false; + Set roles = filePlanRoleService.getRoles(filePlan); + for (Role role : roles) + { + String roleGroupName = role.getRoleGroupName(); + addAuthorityToZone(roleGroupName); + if (parentAddedToZone == false) + { + String allRolesGroup = authorityService.getName(AuthorityType.GROUP, "AllRoles" + filePlan.getId()); + addAuthorityToZone(allRolesGroup); + parentAddedToZone = true; + } + } + } + } + + private void addAuthorityToZone(String roleGroupName) + { + authorityService.addAuthorityToZones(roleGroupName, new HashSet(Arrays.asList(RMAuthority.ZONE_APP_RM))); + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java index e8f04cacb5..4d8b7f5f8f 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java @@ -22,6 +22,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -39,6 +40,7 @@ import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authority.RMAuthority; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -184,7 +186,7 @@ public class FilePlanRoleServiceImpl implements FilePlanRoleService, public NodeRef doWork() { // Create "all" role group for root node - String allRoles = authorityService.createAuthority(AuthorityType.GROUP, getAllRolesGroupShortName(rmRootNode), "All Roles", null); + String allRoles = authorityService.createAuthority(AuthorityType.GROUP, getAllRolesGroupShortName(rmRootNode), "All Roles", new HashSet(Arrays.asList(RMAuthority.ZONE_APP_RM))); // Set the permissions permissionService.setInheritParentPermissions(rmRootNode, false); @@ -607,7 +609,7 @@ public class FilePlanRoleServiceImpl implements FilePlanRoleService, // Create a group that relates to the records management role Set zones = new HashSet(2); zones.add(getZoneName(rmRootNode)); - zones.add(AuthorityService.ZONE_APP_DEFAULT); + zones.add(RMAuthority.ZONE_APP_RM); String roleGroup = authorityService.createAuthority(AuthorityType.GROUP, fullRoleName, roleDisplayLabel, zones); // Add the roleGroup to the "all" role group diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/BootstrapTestDataGet.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/BootstrapTestDataGet.java index dcc9d0d330..3738580214 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/BootstrapTestDataGet.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/BootstrapTestDataGet.java @@ -22,7 +22,9 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -40,6 +42,7 @@ import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderServi import org.alfresco.module.org_alfresco_module_rm.security.RecordsManagementSecurityService; import org.alfresco.module.org_alfresco_module_rm.security.Role; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authority.RMAuthority; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -70,15 +73,15 @@ public class BootstrapTestDataGet extends DeclarativeWebScript implements RecordsManagementModel, ApplicationContextAware { private static Log logger = LogFactory.getLog(BootstrapTestDataGet.class); - + private static final String ARG_SITE_NAME = "site"; private static final String ARG_IMPORT = "import"; - + private static final String XML_IMPORT = "alfresco/module/org_alfresco_module_rm/dod5015/DODExampleFilePlan.xml"; private static final String charsetName = "UTF-8"; - + private static final StoreRef SPACES_STORE = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); - + private NodeService nodeService; private SearchService searchService; private RecordsManagementService recordsManagementService; @@ -89,70 +92,70 @@ public class BootstrapTestDataGet extends DeclarativeWebScript private RecordsManagementSecurityService recordsManagementSecurityService; private AuthorityService authorityService; private RecordsManagementSearchBehaviour recordsManagementSearchBehaviour; - private DispositionService dispositionService; + private DispositionService dispositionService; private ApplicationContext applicationContext; - + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } - + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } - + public void setSearchService(SearchService searchService) { this.searchService = searchService; } - + public void setDispositionService(DispositionService dispositionService) { this.dispositionService = dispositionService; } - + public void setRecordsManagementService(RecordsManagementService recordsManagementService) { this.recordsManagementService = recordsManagementService; } - + public void setRecordsManagementActionService(RecordsManagementActionService recordsManagementActionService) { this.recordsManagementActionService = recordsManagementActionService; } - - public void setImporterService(ImporterService importerService) + + public void setImporterService(ImporterService importerService) { this.importerService = importerService; } - + public void setSiteService(SiteService siteService) { this.siteService = siteService; } - + public void setPermissionService(PermissionService permissionService) { this.permissionService = permissionService; } - + public void setAuthorityService(AuthorityService authorityService) { this.authorityService = authorityService; } - + public void setRecordsManagementSecurityService(RecordsManagementSecurityService recordsManagementSecurityService) { this.recordsManagementSecurityService = recordsManagementSecurityService; } - + public void setRecordsManagementSearchBehaviour(RecordsManagementSearchBehaviour searchBehaviour) { this.recordsManagementSearchBehaviour = searchBehaviour; } - + @Override public Map executeImpl(WebScriptRequest req, Status status, Cache cache) { @@ -162,14 +165,14 @@ public class BootstrapTestDataGet extends DeclarativeWebScript { importData = Boolean.parseBoolean(req.getParameter(ARG_IMPORT)); } - + // resolve rm site String siteName = RmSiteType.DEFAULT_SITE_NAME; if (req.getParameter(ARG_SITE_NAME) != null) { siteName = req.getParameter(ARG_SITE_NAME); } - + if (importData) { SiteInfo site = siteService.getSite(siteName); @@ -177,14 +180,14 @@ public class BootstrapTestDataGet extends DeclarativeWebScript { throw new AlfrescoRuntimeException("Records Management site does not exist: " + siteName); } - + // resolve documentLibrary (filePlan) container NodeRef filePlan = siteService.getContainer(siteName, RmSiteType.COMPONENT_DOCUMENT_LIBRARY); if (filePlan == null) { filePlan = siteService.createContainer(siteName, RmSiteType.COMPONENT_DOCUMENT_LIBRARY, TYPE_FILE_PLAN, null); } - + // import the RM test data ACP into the the provided filePlan node reference InputStream is = BootstrapTestDataGet.class.getClassLoader().getResourceAsStream(XML_IMPORT); if (is == null) @@ -203,31 +206,31 @@ public class BootstrapTestDataGet extends DeclarativeWebScript Location location = new Location(filePlan); importerService.importView(viewReader, location, null, null); } - + // Patch data - BootstrapTestDataGet.patchLoadedData(applicationContext, searchService, nodeService, recordsManagementService, + BootstrapTestDataGet.patchLoadedData(applicationContext, searchService, nodeService, recordsManagementService, recordsManagementActionService, permissionService, authorityService, recordsManagementSecurityService, recordsManagementSearchBehaviour, dispositionService); - + Map model = new HashMap(1, 1.0f); model.put("success", true); - + return model; } - + /** * Temp method to patch AMP'ed data - * + * * @param searchService * @param nodeService * @param recordsManagementService * @param recordsManagementActionService */ public static void patchLoadedData( final ApplicationContext applicationContext, - final SearchService searchService, - final NodeService nodeService, + final SearchService searchService, + final NodeService nodeService, final RecordsManagementService recordsManagementService, final RecordsManagementActionService recordsManagementActionService, final PermissionService permissionService, @@ -249,20 +252,20 @@ public class BootstrapTestDataGet extends DeclarativeWebScript logger.info("Updating permissions for rm root: " + rmRoot); permissionService.setInheritParentPermissions(rmRoot, false); } - + String allRoleShortName = "AllRoles" + rmRoot.getId(); String allRoleGroupName = authorityService.getName(AuthorityType.GROUP, allRoleShortName); - + if (authorityService.authorityExists(allRoleGroupName) == false) - { + { logger.info("Creating all roles group for root node: " + rmRoot.toString()); - + // Create "all" role group for root node - String allRoles = authorityService.createAuthority(AuthorityType.GROUP, - allRoleShortName, - "All Roles", - null); - + String allRoles = authorityService.createAuthority(AuthorityType.GROUP, + allRoleShortName, + "All Roles", + new HashSet(Arrays.asList(RMAuthority.ZONE_APP_RM))); + // Put all the role groups in it Set roles = recordsManagementSecurityService.getRoles(rmRoot); for (Role role : roles) @@ -270,22 +273,22 @@ public class BootstrapTestDataGet extends DeclarativeWebScript logger.info(" - adding role group " + role.getRoleGroupName() + " to all roles group"); authorityService.addAuthority(allRoles, role.getRoleGroupName()); } - + // Set the permissions permissionService.setPermission(rmRoot, allRoles, RMPermissionModel.READ_RECORDS, true); } } - + // Make sure all the containers do not inherit permissions ResultSet rs = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, "TYPE:\"rma:recordsManagementContainer\""); try { logger.info("Bootstraping " + rs.length() + " record containers ..."); - + for (NodeRef container : rs.getNodeRefs()) { String containerName = (String)nodeService.getProperty(container, ContentModel.PROP_NAME); - + // Set permissions if (permissionService.getInheritParentPermissions(container) == true) { @@ -298,24 +301,24 @@ public class BootstrapTestDataGet extends DeclarativeWebScript { rs.close(); } - + // fix up the test dataset to fire initial events for disposition schedules rs = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, "TYPE:\"rma:recordFolder\""); try { logger.info("Bootstraping " + rs.length() + " record folders ..."); - + for (NodeRef recordFolder : rs.getNodeRefs()) { String folderName = (String)nodeService.getProperty(recordFolder, ContentModel.PROP_NAME); - + // Set permissions if (permissionService.getInheritParentPermissions(recordFolder) == true) { logger.info("Updating permissions for record folder: " + folderName); permissionService.setInheritParentPermissions(recordFolder, false); } - + if (nodeService.hasAspect(recordFolder, ASPECT_DISPOSITION_LIFECYCLE) == false) { // See if the folder has a disposition schedule that needs to be applied @@ -328,7 +331,7 @@ public class BootstrapTestDataGet extends DeclarativeWebScript recordService.initialiseRecordFolder(recordFolder); } } - + // fixup the search behaviour aspect for the record folder logger.info("Setting up search aspect for record folder: " + folderName); recordManagementSearchBehaviour.fixupSearchAspect(recordFolder); @@ -338,12 +341,12 @@ public class BootstrapTestDataGet extends DeclarativeWebScript { rs.close(); } - + return null; } }; - + AuthenticationUtil.runAs(runAsWork, AuthenticationUtil.getAdminUserName()); - + } } \ No newline at end of file diff --git a/rm-server/source/java/org/alfresco/repo/security/authority/RMAuthority.java b/rm-server/source/java/org/alfresco/repo/security/authority/RMAuthority.java new file mode 100644 index 0000000000..db8106a536 --- /dev/null +++ b/rm-server/source/java/org/alfresco/repo/security/authority/RMAuthority.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2013 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.authority; + +/** + * Interface for defining constants + * + * @author Tuna Aksoy + * @since 2.1 + */ +public interface RMAuthority +{ + /** + * The default rm zone. + */ + public static String ZONE_APP_RM = "APP.RM"; +} diff --git a/rm-server/source/java/org/alfresco/repo/security/authority/RMAuthorityDAOImpl.java b/rm-server/source/java/org/alfresco/repo/security/authority/RMAuthorityDAOImpl.java new file mode 100644 index 0000000000..bb5c68cfd8 --- /dev/null +++ b/rm-server/source/java/org/alfresco/repo/security/authority/RMAuthorityDAOImpl.java @@ -0,0 +1,1408 @@ +/* + * 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.authority; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.query.CannedQuery; +import org.alfresco.query.CannedQueryFactory; +import org.alfresco.query.CannedQueryResults; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.domain.permissions.AclDAO; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.person.PersonServiceImpl; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.service.cmr.dictionary.DictionaryService; +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.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthorityService.AuthorityFilter; +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.cmr.security.PersonService.PersonInfo; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.ISO9075; +import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.SearchLanguageConversion; +import org.alfresco.util.registry.NamedObjectRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * FIXME !!!!
+ * This class overrides the original {@link AuthorityDAOImpl} class
+ * We need to modify two private methods in the original class
+ *
+ * addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type)
+ *
+ * and
+ *
+ * addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type, Pattern pattern)
+ *
+ * After changing the modifiers for those methods, this class can extend from the original one and those methods can
+ * overriden in this class. For the time being the whole class has been copied and the needed changes have been
+ * made directly in this class. + * + */ +public class RMAuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.BeforeDeleteNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy +{ + private static Log logger = LogFactory.getLog(AuthorityDAOImpl.class); + + private static final String CANNED_QUERY_AUTHS_LIST = "authsGetAuthoritiesCannedQueryFactory"; // see authority-services-context.xml + + private StoreRef storeRef; + + private NodeService nodeService; + + private NamespacePrefixResolver namespacePrefixResolver; + + private QName qnameAssocSystem; + + private QName qnameAssocAuthorities; + + private QName qnameAssocZones; + + private SearchService searchService; + + private DictionaryService dictionaryService; + + private PersonService personService; + + private TenantService tenantService; + + private SimpleCache, NodeRef> authorityLookupCache; + + private static final NodeRef NULL_NODEREF = new NodeRef("null", "null", "null"); + + private SimpleCache> userAuthorityCache; + + private SimpleCache, List> zoneAuthorityCache; + + private SimpleCache> childAuthorityCache; + + /** System Container ref cache (Tennant aware) */ + private Map systemContainerRefs = new ConcurrentHashMap(4); + + private AclDAO aclDao; + + private PolicyComponent policyComponent; + + /** The number of authorities in a zone to pre-cache, allowing quick generation of 'first n' results. */ + private int zoneAuthoritySampleSize = 10000; + + private NamedObjectRegistry> cannedQueryRegistry; + + private static final Collection SEARCHABLE_AUTHORITY_TYPES = new LinkedList(); + static + { + SEARCHABLE_AUTHORITY_TYPES.add(AuthorityType.ROLE); + SEARCHABLE_AUTHORITY_TYPES.add(AuthorityType.GROUP); + } + + public RMAuthorityDAOImpl() + { + super(); + } + + + /** + * Sets number of authorities in a zone to pre-cache, allowing quick generation of 'first n' results and adaption of + * search technique based on hit rate. + * + * @param zoneAuthoritySampleSize + * the zoneAuthoritySampleSize to set + */ + public void setZoneAuthoritySampleSize(int zoneAuthoritySampleSize) + { + this.zoneAuthoritySampleSize = zoneAuthoritySampleSize; + } + + public void setStoreUrl(String storeUrl) + { + this.storeRef = new StoreRef(storeUrl); + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) + { + this.namespacePrefixResolver = namespacePrefixResolver; + qnameAssocSystem = QName.createQName("sys", "system", namespacePrefixResolver); + qnameAssocAuthorities = QName.createQName("sys", "authorities", namespacePrefixResolver); + qnameAssocZones = QName.createQName("sys", "zones", namespacePrefixResolver); + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setAuthorityLookupCache(SimpleCache, NodeRef> authorityLookupCache) + { + this.authorityLookupCache = authorityLookupCache; + } + + public void setUserAuthorityCache(SimpleCache> userAuthorityCache) + { + this.userAuthorityCache = userAuthorityCache; + } + + public void setZoneAuthorityCache(SimpleCache, List> zoneAuthorityCache) + { + this.zoneAuthorityCache = zoneAuthorityCache; + } + + public void setChildAuthorityCache(SimpleCache> childAuthorityCache) + { + this.childAuthorityCache = childAuthorityCache; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setAclDAO(AclDAO aclDao) + { + this.aclDao = aclDao; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setCannedQueryRegistry(NamedObjectRegistry> cannedQueryRegistry) + { + this.cannedQueryRegistry = cannedQueryRegistry; + } + + + public boolean authorityExists(String name) + { + NodeRef ref = getAuthorityOrNull(name); + return ref != null; + } + + public void addAuthority(Collection parentNames, String childName) + { + Set parentRefs = new HashSet(parentNames.size() * 2); + AuthorityType authorityType = AuthorityType.getAuthorityType(childName); + boolean isUser = authorityType.equals(AuthorityType.USER); + boolean notUserOrGroup = !isUser && !authorityType.equals(AuthorityType.GROUP); + for (String parentName : parentNames) + { + NodeRef parentRef = getAuthorityOrNull(parentName); + if (parentRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + parentName); + } + if (notUserOrGroup + && !(authorityType.equals(AuthorityType.ROLE) && AuthorityType.getAuthorityType(parentName).equals( + AuthorityType.ROLE))) + { + throw new AlfrescoRuntimeException("Authorities of the type " + authorityType + + " may not be added to other authorities"); + } + removeCachedChildAuthorities(parentRef); + parentRefs.add(parentRef); + } + NodeRef childRef = getAuthorityOrNull(childName); + + if (childRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + childName); + } + + // Normalize the user name if necessary + if (isUser) + { + childName = (String) nodeService.getProperty(childRef, ContentModel.PROP_USERNAME); + } + + nodeService.addChild(parentRefs, childRef, ContentModel.ASSOC_MEMBER, QName.createQName("cm", childName, + namespacePrefixResolver)); + if (isUser) + { + userAuthorityCache.remove(childName); + } + else + { + userAuthorityCache.clear(); + } + } + + public void createAuthority(String name, String authorityDisplayName, Set authorityZones) + { + HashMap props = new HashMap(); + props.put(ContentModel.PROP_AUTHORITY_NAME, name); + props.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, authorityDisplayName); + NodeRef childRef; + NodeRef authorityContainerRef = getAuthorityContainer(); + childRef = nodeService.createNode(authorityContainerRef, ContentModel.ASSOC_CHILDREN, QName.createQName("cm", name, namespacePrefixResolver), + ContentModel.TYPE_AUTHORITY_CONTAINER, props).getChildRef(); + if (authorityZones != null) + { + Set zoneRefs = new HashSet(authorityZones.size() * 2); + String currentUserDomain = tenantService.getCurrentUserDomain(); + for (String authorityZone : authorityZones) + { + zoneRefs.add(getOrCreateZone(authorityZone)); + zoneAuthorityCache.remove(new Pair(currentUserDomain, authorityZone)); + } + zoneAuthorityCache.remove(new Pair(currentUserDomain, null)); + nodeService.addChild(zoneRefs, childRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", name, namespacePrefixResolver)); + } + authorityLookupCache.put(cacheKey(name), childRef); + } + + private NodeRef cacheKey(NodeRef nodeRef) + { + return tenantService.getName(nodeRef); + } + + private Pair cacheKey(String authorityName) + { + String tenantDomain = AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER ? tenantService.getDomain(authorityName) : tenantService.getCurrentUserDomain(); + return new Pair(tenantDomain, authorityName); + } + + public void deleteAuthority(String name) + { + NodeRef nodeRef = getAuthorityOrNull(name); + if (nodeRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + name); + } + String currentUserDomain = tenantService.getCurrentUserDomain(); + for (String authorityZone : getAuthorityZones(name)) + { + zoneAuthorityCache.remove(new Pair(currentUserDomain, authorityZone)); + } + zoneAuthorityCache.remove(new Pair(currentUserDomain, null)); + removeParentsFromChildAuthorityCache(nodeRef); + authorityLookupCache.remove(cacheKey(name)); + userAuthorityCache.clear(); + + nodeService.deleteNode(nodeRef); + } + + // Get authorities by type and/or zone (both cannot be null) + public PagingResults getAuthorities(AuthorityType type, String zoneName, String displayNameFilter, boolean sortByDisplayName, boolean sortAscending, PagingRequest pagingRequest) + { + ParameterCheck.mandatory("pagingRequest", pagingRequest); + + if ((type == null) && (zoneName == null)) + { + throw new IllegalArgumentException("Type and/or zoneName required - both cannot be null"); + } + + if ((zoneName == null) && (type.equals(AuthorityType.USER))) + { + return getUserAuthoritiesImpl(displayNameFilter, sortByDisplayName, sortAscending, pagingRequest); + } + + NodeRef containerRef = null; + if (zoneName != null) + { + containerRef = getZone(zoneName); + if (containerRef == null) + { + throw new UnknownAuthorityException("A zone was not found for " + zoneName); + } + } + else + { + containerRef = getAuthorityContainer(); + } + + return getAuthoritiesImpl(type, containerRef, displayNameFilter, sortByDisplayName, sortAscending, pagingRequest); + } + + private PagingResults getAuthoritiesImpl(AuthorityType type, NodeRef containerRef, String displayNameFilter, boolean sortByDisplayName, boolean sortAscending, PagingRequest pagingRequest) + { + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); + + if (type != null) + { + switch (type) + { + case GROUP: + case ROLE: + case USER: + // drop through + break; + default: + throw new UnsupportedOperationException("Unexpected authority type: "+type); + } + } + + // get canned query + GetAuthoritiesCannedQueryFactory getAuthoritiesCannedQueryFactory = (GetAuthoritiesCannedQueryFactory)cannedQueryRegistry.getNamedObject(CANNED_QUERY_AUTHS_LIST); + CannedQuery cq = getAuthoritiesCannedQueryFactory.getCannedQuery(type, containerRef, displayNameFilter, sortByDisplayName, sortAscending, pagingRequest); + + // execute canned query + final CannedQueryResults results = cq.execute(); + + PagingResults finalResults = new PagingResults() + { + @Override + public String getQueryExecutionId() + { + return results.getQueryExecutionId(); + } + @Override + public List getPage() + { + List auths = new ArrayList(results.getPageCount()); + for (AuthorityInfo authInfo : results.getPage()) + { + auths.add(authInfo.getAuthorityName()); + } + return auths; + } + @Override + public boolean hasMoreItems() + { + return results.hasMoreItems(); + } + @Override + public Pair getTotalResultCount() + { + return results.getTotalResultCount(); + } + }; + + if (start != null) + { + int cnt = finalResults.getPage().size(); + int skipCount = pagingRequest.getSkipCount(); + int maxItems = pagingRequest.getMaxItems(); + boolean hasMoreItems = finalResults.hasMoreItems(); + int pageNum = (skipCount / maxItems) + 1; + + logger.debug("getAuthoritiesByType: "+cnt+" items in "+(System.currentTimeMillis()-start)+" msecs [type="+type+",pageNum="+pageNum+",skip="+skipCount+",max="+maxItems+",hasMorePages="+hasMoreItems+",filter="+displayNameFilter+"]"); + } + + return finalResults; + } + + // delegate to PersonService.getPeople + private PagingResults getUserAuthoritiesImpl(String displayNameFilter, boolean sortByDisplayName, boolean sortAscending, PagingRequest pagingRequest) + { + List> filter = null; + if (displayNameFilter != null) + { + filter = new ArrayList>(); + filter.add(new Pair(ContentModel.PROP_USERNAME, displayNameFilter)); + } + + List> sort = null; + if (sortByDisplayName) + { + sort = new ArrayList>(); + sort.add(new Pair(ContentModel.PROP_USERNAME, sortAscending)); + } + + final PagingResults ppr = personService.getPeople(filter, true, sort, pagingRequest); + + List result = ppr.getPage(); + final List auths = new ArrayList(result.size()); + + for (PersonInfo person : result) + { + auths.add(person.getUserName()); + } + + return new PagingResults() + { + @Override + public String getQueryExecutionId() + { + return ppr.getQueryExecutionId(); + } + @Override + public List getPage() + { + return auths; + } + @Override + public boolean hasMoreItems() + { + return ppr.hasMoreItems(); + } + @Override + public Pair getTotalResultCount() + { + return ppr.getTotalResultCount(); + } + }; + } + + public Set getRootAuthorities(AuthorityType type, String zoneName) + { + NodeRef container = (zoneName == null ? getAuthorityContainer() : getZone(zoneName)); + if (container == null) + { + // The zone doesn't even exist so there are no root authorities + return Collections.emptySet(); + } + + return getRootAuthoritiesUnderContainer(container, type); + } + + public Set findAuthorities(AuthorityType type, String parentAuthority, boolean immediate, + String displayNamePattern, String zoneName) + { + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); + + Pattern pattern = displayNamePattern == null ? null : Pattern.compile(SearchLanguageConversion.convert( + SearchLanguageConversion.DEF_LUCENE, SearchLanguageConversion.DEF_REGEX, displayNamePattern), + Pattern.CASE_INSENSITIVE); + + // Use SQL to determine root authorities + Set rootAuthorities = null; + if (parentAuthority == null && immediate) + { + rootAuthorities = getRootAuthorities(type, zoneName); + if (pattern == null) + { + if (start != null) + { + logger.debug("findAuthorities (rootAuthories): "+rootAuthorities.size()+" items in "+(System.currentTimeMillis()-start)+" msecs [type="+type+",zone="+zoneName+"]"); + } + + return rootAuthorities; + } + } + + // Use a Lucene search for other criteria + Set authorities = new TreeSet(); + SearchParameters sp = new SearchParameters(); + sp.addStore(this.storeRef); + sp.setLanguage("lucene"); + StringBuilder query = new StringBuilder(500); + if (type == null || type == AuthorityType.USER) + { + if (type == null) + { + query.append("(("); + } + query.append("TYPE:\"").append(ContentModel.TYPE_PERSON).append("\""); + if (displayNamePattern != null) + { + query.append(" AND @").append( + AbstractLuceneQueryParser.escape("{" + ContentModel.PROP_USERNAME.getNamespaceURI() + "}" + + ISO9075.encode(ContentModel.PROP_USERNAME.getLocalName()))).append(":\"").append( + AbstractLuceneQueryParser.escape(displayNamePattern)).append("\""); + + } + if (type == null) + { + query.append(") OR ("); + } + } + if (type != AuthorityType.USER) + { + query.append("TYPE:\"").append(ContentModel.TYPE_AUTHORITY_CONTAINER).append("\""); + if (displayNamePattern != null) + { + query.append(" AND ("); + if (!displayNamePattern.startsWith("*")) + { + // Allow for the appropriate type prefix in the authority name + Collection authorityTypes = type == null ? SEARCHABLE_AUTHORITY_TYPES + : Collections.singleton(type); + boolean first = true; + for (AuthorityType subType: authorityTypes) + { + if (first) + { + first = false; + } + else + { + query.append(" OR "); + } + query.append("@").append( + AbstractLuceneQueryParser.escape("{" + ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI() + "}" + + ISO9075.encode(ContentModel.PROP_AUTHORITY_NAME.getLocalName()))).append(":\""); + query.append(getName(subType, AbstractLuceneQueryParser.escape(displayNamePattern))).append("\""); + + } + } + else + { + query.append("@").append( + AbstractLuceneQueryParser.escape("{" + ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI() + "}" + + ISO9075.encode(ContentModel.PROP_AUTHORITY_NAME.getLocalName()))).append(":\""); + query.append(getName(type, AbstractLuceneQueryParser.escape(displayNamePattern))).append("\""); + } + query.append(" OR @").append( + AbstractLuceneQueryParser.escape("{" + ContentModel.PROP_AUTHORITY_DISPLAY_NAME.getNamespaceURI() + "}" + + ISO9075.encode(ContentModel.PROP_AUTHORITY_DISPLAY_NAME.getLocalName()))).append( + ":\"").append(AbstractLuceneQueryParser.escape(displayNamePattern)).append("\")"); + } + if (type == null) + { + query.append("))"); + } + } + if (parentAuthority != null) + { + if(immediate) + { + // use PARENT + NodeRef parentAuthorityNodeRef = getAuthorityNodeRefOrNull(parentAuthority); + if(parentAuthorityNodeRef != null) + { + query.append(" AND PARENT:\"").append(AbstractLuceneQueryParser.escape(parentAuthorityNodeRef.toString())).append("\""); + } + else + { + throw new UnknownAuthorityException("An authority was not found for " + parentAuthority); + } + } + else + { + // use PATH + query.append(" AND PATH:\"/sys:system/sys:authorities/cm:").append(ISO9075.encode(parentAuthority)); + query.append("//*\""); + } + } + if (zoneName != null) + { + // Zones are all direct links to those within so it is safe to use PARENT to look them up + NodeRef zoneNodeRef = getZone(zoneName); + if (zoneNodeRef != null) + { + query.append(" AND PARENT:\"").append(AbstractLuceneQueryParser.escape(zoneNodeRef.toString())).append("\""); + } + else + { + throw new UnknownAuthorityException("A zone was not found for " + zoneName); + } + } + sp.setQuery(query.toString()); + sp.setMaxItems(100); + ResultSet rs = null; + try + { + rs = searchService.query(sp); + + for (ResultSetRow row : rs) + { + NodeRef nodeRef = row.getNodeRef(); + QName idProp = dictionaryService.isSubClass(nodeService.getType(nodeRef), + ContentModel.TYPE_AUTHORITY_CONTAINER) ? ContentModel.PROP_AUTHORITY_NAME + : ContentModel.PROP_USERNAME; + addAuthorityNameIfMatches(authorities, DefaultTypeConverter.INSTANCE.convert(String.class, nodeService + .getProperty(nodeRef, idProp)), type, pattern); + } + + // If we asked for root authorities, we must do an intersection with the set of root authorities + if (rootAuthorities != null) + { + authorities.retainAll(rootAuthorities); + } + + if (start != null) + { + logger.debug("findAuthorities: "+authorities.size()+" items in "+(System.currentTimeMillis()-start)+" msecs [type="+type+",zone="+zoneName+",parent="+parentAuthority+",immediate="+immediate+",filter="+displayNamePattern+"]"); + } + + return authorities; + } + finally + { + if (rs != null) + { + rs.close(); + } + } + } + + public Set getContainedAuthorities(AuthorityType type, String parentName, boolean immediate) + { + AuthorityType parentAuthorityType = AuthorityType.getAuthorityType(parentName); + if (parentAuthorityType == AuthorityType.USER) + { + // Users never contain other authorities + return Collections. emptySet(); + } + else + { + NodeRef nodeRef = getAuthorityOrNull(parentName); + if (nodeRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + parentName); + } + + Set authorities = new TreeSet(); + listAuthorities(type, nodeRef, authorities, false, !immediate, false); + return authorities; + } + } + + public void removeAuthority(String parentName, String childName) + { + NodeRef parentRef = getAuthorityOrNull(parentName); + if (parentRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + parentName); + } + NodeRef childRef = getAuthorityOrNull(childName); + if (childRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + childName); + } + nodeService.removeChild(parentRef, childRef); + removeCachedChildAuthorities(parentRef); + if (AuthorityType.getAuthorityType(childName) == AuthorityType.USER) + { + userAuthorityCache.remove(childName); + } + else + { + userAuthorityCache.clear(); + } + } + + public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) + { + // Optimize for the case where we want all the authorities that a user belongs to + if (!immediate && AuthorityType.getAuthorityType(name) == AuthorityType.USER) + { + // Get the unfiltered set of authorities from the cache or generate it + Set authorities = userAuthorityCache.get(name); + if (authorities == null) + { + authorities = new TreeSet(); + listAuthorities(null, name, authorities, true, true); + userAuthorityCache.put(name, authorities); + } + // If we wanted the unfiltered set we are done + if (type == null) + { + return authorities; + } + // Apply the filtering by type + Set filteredAuthorities = new TreeSet(); + for (String authority : authorities) + { + addAuthorityNameIfMatches(filteredAuthorities, authority, type); + } + return filteredAuthorities; + } + // Otherwise, crawl the DB for the answer + else + { + Set authorities = new TreeSet(); + listAuthorities(type, name, authorities, true, !immediate); + return authorities; + } + } + + public Set getContainingAuthoritiesInZone(AuthorityType type, String authority, final String zoneName, AuthorityFilter filter, int size) + { + // Retrieved the cached 'sample' of authorities in the zone + String currentUserDomain = tenantService.getCurrentUserDomain(); + Pair cacheKey = new Pair(currentUserDomain, zoneName); + List zoneAuthorities = zoneAuthorityCache.get(cacheKey); + final int maxToProcess = Math.max(size, zoneAuthoritySampleSize); + if (zoneAuthorities == null) + { + zoneAuthorities = AuthenticationUtil.runAs(new RunAsWork>() + { + @Override + public List doWork() throws Exception + { + NodeRef root = zoneName == null ? getAuthorityContainer() : getZone(zoneName); + if (root == null) + { + return Collections.emptyList(); + } + return nodeService.getChildAssocs(root, null, null, maxToProcess, false); + } + }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), currentUserDomain)); + zoneAuthorityCache.put(cacheKey, zoneAuthorities); + } + + // Now search each for the required authority. If the number of results is greater than or close to the size + // limit, then this will be the most efficient route + Set result = new TreeSet(); + final int maxResults = size > 0 ? size : Integer.MAX_VALUE; + int hits = 0, processed = 0; + for (ChildAssociationRef groupAssoc : zoneAuthorities) + { + String containing = groupAssoc.getQName().getLocalName(); + AuthorityType containingType = AuthorityType.getAuthorityType(containing); + processed++; + // Cache the authority by key, if appropriate + switch (containingType) + { + case USER: + case ADMIN: + case GUEST: + break; + default: + Pair containingKey = cacheKey(containing); + if (!authorityLookupCache.contains(containingKey)) + { + authorityLookupCache.put(containingKey, groupAssoc.getChildRef()); + } + } + if ((type == null || containingType == type) + && (authority == null || isAuthorityContained(groupAssoc.getChildRef(), authority)) + && (filter == null || filter.includeAuthority(containing))) + { + result.add(containing); + if (++hits == maxResults) + { + break; + } + } + + // If this top down search is not providing an adequate hit count then resort to a naiive unlimited search + if (processed >= maxToProcess) + { + Set unfilteredResult; + boolean filterZone; + if (authority == null) + { + unfilteredResult = new HashSet(getAuthorities(type, zoneName, null, false, true, new PagingRequest(0, filter == null ? maxResults : Integer.MAX_VALUE, null)).getPage()); + if (filter == null) + { + return unfilteredResult; + } + filterZone = false; + } + else + { + unfilteredResult = getContainingAuthorities(type, authority, false); + filterZone = zoneName != null; + } + Set newResult = new TreeSet(result); + int i=newResult.size(); + for (String container : unfilteredResult) + { + // Do not call the filter multiple times on the same result in case it is 'stateful' + if (!result.contains(container) && (filter == null || filter.includeAuthority(container)) + && (!filterZone || getAuthorityZones(container).contains(zoneName))) + { + newResult.add(container); + if (++i >= maxResults) + { + break; + } + } + } + result = newResult; + break; + } + } + return result; + } + + public String getShortName(String name) + { + AuthorityType type = AuthorityType.getAuthorityType(name); + if (type.isFixedString()) + { + return ""; + } + else if (type.isPrefixed()) + { + return name.substring(type.getPrefixString().length()); + } + else + { + return name; + } + } + + public String getName(AuthorityType type, String shortName) + { + if (type.isFixedString()) + { + return type.getFixedString(); + } + else if (type.isPrefixed()) + { + return type.getPrefixString() + shortName; + } + else + { + return shortName; + } + } + + private void addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type) + { + if (type == null || AuthorityType.getAuthorityType(authorityName).equals(type) && !getAuthorityZones(authorityName).contains("APP.RM")) + { + authorities.add(authorityName); + } + } + + private void addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type, Pattern pattern) + { + if (type == null || AuthorityType.getAuthorityType(authorityName).equals(type) && !getAuthorityZones(authorityName).contains("APP.RM")) + { + if (pattern == null) + { + authorities.add(authorityName); + } + else + { + if (pattern.matcher(getShortName(authorityName)).matches()) + { + authorities.add(authorityName); + } + else + { + String displayName = getAuthorityDisplayName(authorityName); + if (displayName != null && pattern.matcher(displayName).matches()) + { + authorities.add(authorityName); + } + } + } + } + } + + private void listAuthorities(AuthorityType type, String name, Set authorities, boolean parents, boolean recursive) + { + AuthorityType localType = AuthorityType.getAuthorityType(name); + if (localType.equals(AuthorityType.GUEST)) + { + // Nothing to do + } + else + { + NodeRef ref = getAuthorityOrNull(name); + + if (ref != null) + { + listAuthorities(type, ref, authorities, parents, recursive, false); + } + else if (!localType.equals(AuthorityType.USER)) + { + // Don't worry about missing person objects. It might be the system user or a user yet to be + // auto-created + throw new UnknownAuthorityException("An authority was not found for " + name); + } + } + } + + private void listAuthorities(AuthorityType type, NodeRef nodeRef, Set authorities, boolean parents, boolean recursive, boolean includeNode) + { + if (includeNode) + { + String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService + .getProperty(nodeRef, dictionaryService.isSubClass(nodeService.getType(nodeRef), + ContentModel.TYPE_AUTHORITY_CONTAINER) ? ContentModel.PROP_AUTHORITY_NAME + : ContentModel.PROP_USERNAME)); + addAuthorityNameIfMatches(authorities, authorityName, type); + } + + // Loop over children if we want immediate children or are in recursive mode + if (!includeNode || recursive) + { + if (parents) + { + List cars = nodeService.getParentAssocs(nodeRef, ContentModel.ASSOC_MEMBER, RegexQNamePattern.MATCH_ALL); + + for (ChildAssociationRef car : cars) + { + listAuthorities(type, car.getParentRef(), authorities, true, recursive, true); + } + } + else + { + List cars = getCachedChildAuthorities(nodeRef); + if (cars == null) + { + cars = nodeService.getChildAssocs(nodeRef, RegexQNamePattern.MATCH_ALL, + RegexQNamePattern.MATCH_ALL, false); + if (!cars.isEmpty() && cars.get(0).getTypeQName().equals(ContentModel.ASSOC_MEMBER)) + { + putCachedChildAuthorities(nodeRef, cars); + } + } + + // Take advantage of the fact that the authority name is on the child association + for (ChildAssociationRef car : cars) + { + String childName = car.getQName().getLocalName(); + AuthorityType childType = AuthorityType.getAuthorityType(childName); + addAuthorityNameIfMatches(authorities, childName, type); + if (recursive && childType != AuthorityType.USER) + { + listAuthorities(type, car.getChildRef(), authorities, false, true, false); + } + } + } + } + } + + + // Take advantage of the fact that the authority name is on the child association + public boolean isAuthorityContained(NodeRef authorityNodeRef, String authorityToFind) + { + List cars = getCachedChildAuthorities(authorityNodeRef); + if (cars == null) + { + cars = nodeService.getChildAssocs(authorityNodeRef, RegexQNamePattern.MATCH_ALL, + RegexQNamePattern.MATCH_ALL, false); + putCachedChildAuthorities(authorityNodeRef, cars); + } + + // Loop over children recursively to find authorityToFind + for (ChildAssociationRef car : cars) + { + String authorityName = car.getQName().getLocalName(); + if (authorityToFind.equals(authorityName) + || AuthorityType.getAuthorityType(authorityName) != AuthorityType.USER + && isAuthorityContained(car.getChildRef(), authorityToFind)) + { + return true; + } + } + return false; + } + + private void removeParentsFromChildAuthorityCache(NodeRef nodeRef) + { + for (ChildAssociationRef car: nodeService.getParentAssocs(nodeRef)) + { + NodeRef parentRef = car.getParentRef(); + if (dictionaryService.isSubClass(nodeService.getType(parentRef), ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + removeCachedChildAuthorities(parentRef); + } + } + } + + private NodeRef getAuthorityOrNull(final String name) + { + try + { + if (AuthorityType.getAuthorityType(name).equals(AuthorityType.USER)) + { + return personService.getPerson(name, false); + } + else if (AuthorityType.getAuthorityType(name).equals(AuthorityType.GUEST) || AuthorityType.getAuthorityType(name).equals(AuthorityType.ADMIN)) + { + return null; + } + else + { + Pair cacheKey = cacheKey(name); + NodeRef result = authorityLookupCache.get(cacheKey); + if (result == null) + { + List results = nodeService.getChildAssocs(getAuthorityContainer(), + ContentModel.ASSOC_CHILDREN, QName.createQName("cm", name, namespacePrefixResolver), false); + result = results.isEmpty() ? NULL_NODEREF :results.get(0).getChildRef(); + authorityLookupCache.put(cacheKey, result); + } + return result == NULL_NODEREF ? null : result; + } + } + catch (NoSuchPersonException e) + { + return null; + } + } + + /** + * @return Returns the authority container, which must exist + */ + private NodeRef getAuthorityContainer() + { + return getSystemContainer(qnameAssocAuthorities); + } + + /** + * @return Returns the zone container, which must exist + */ + private NodeRef getZoneContainer() + { + return getSystemContainer(qnameAssocZones); + } + + /** + * Return the system container for the specified assoc name. + * The containers are cached in a thread safe Tenant aware cache. + * + * @param assocQName + * + * @return System container, which must exist + */ + private NodeRef getSystemContainer(QName assocQName) + { + final String cacheKey = tenantService.getCurrentUserDomain() + assocQName.toString(); + NodeRef systemContainerRef = systemContainerRefs.get(cacheKey); + if (systemContainerRef == null) + { + NodeRef rootNodeRef = nodeService.getRootNode(this.storeRef); + List results = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocSystem, false); + if (results.size() == 0) + { + throw new AlfrescoRuntimeException("Required system path not found: " + qnameAssocSystem); + } + NodeRef sysNodeRef = results.get(0).getChildRef(); + results = nodeService.getChildAssocs(sysNodeRef, RegexQNamePattern.MATCH_ALL, assocQName, false); + if (results.size() == 0) + { + throw new AlfrescoRuntimeException("Required path not found: " + assocQName); + } + systemContainerRef = results.get(0).getChildRef(); + systemContainerRefs.put(cacheKey, systemContainerRef); + } + return systemContainerRef; + } + + public NodeRef getAuthorityNodeRefOrNull(String name) + { + return getAuthorityOrNull(name); + } + + public String getAuthorityName(NodeRef authorityRef) + { + String name = null; + if (nodeService.exists(authorityRef)) + { + QName type = nodeService.getType(authorityRef); + if (dictionaryService.isSubClass(type, ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + name = (String) nodeService.getProperty(authorityRef, ContentModel.PROP_AUTHORITY_NAME); + } + else if (dictionaryService.isSubClass(type, ContentModel.TYPE_PERSON)) + { + name = (String) nodeService.getProperty(authorityRef, ContentModel.PROP_USERNAME); + } + } + return name; + } + + public String getAuthorityDisplayName(String authorityName) + { + NodeRef ref = getAuthorityOrNull(authorityName); + if (ref == null) + { + return null; + } + Serializable value = nodeService.getProperty(ref, ContentModel.PROP_AUTHORITY_DISPLAY_NAME); + if (value == null) + { + return null; + } + return DefaultTypeConverter.INSTANCE.convert(String.class, value); + } + + public void setAuthorityDisplayName(String authorityName, String authorityDisplayName) + { + NodeRef ref = getAuthorityOrNull(authorityName); + if (ref == null) + { + return; + } + nodeService.setProperty(ref, ContentModel.PROP_AUTHORITY_DISPLAY_NAME, authorityDisplayName); + + } + + public NodeRef getOrCreateZone(String zoneName) + { + return getOrCreateZone(zoneName, true); + } + + private NodeRef getOrCreateZone(String zoneName, boolean create) + { + NodeRef zoneContainerRef = getZoneContainer(); + QName zoneQName = QName.createQName("cm", zoneName, namespacePrefixResolver); + List results = nodeService.getChildAssocs(zoneContainerRef, ContentModel.ASSOC_CHILDREN, zoneQName, false); + if (results.isEmpty()) + { + if (create) + { + HashMap props = new HashMap(); + props.put(ContentModel.PROP_NAME, zoneName); + return nodeService.createNode(zoneContainerRef, ContentModel.ASSOC_CHILDREN, zoneQName, ContentModel.TYPE_ZONE, props).getChildRef(); + } + else + { + return null; + } + } + else + { + return results.get(0).getChildRef(); + } + } + + public NodeRef getZone(String zoneName) + { + return getOrCreateZone(zoneName, false); + } + + public Set getAuthorityZones(String name) + { + Set zones = new TreeSet(); + NodeRef childRef = getAuthorityOrNull(name); + if (childRef == null) + { + return null; + } + List results = nodeService.getParentAssocs(childRef, ContentModel.ASSOC_IN_ZONE, RegexQNamePattern.MATCH_ALL); + if (results.isEmpty()) + { + return zones; + } + + for (ChildAssociationRef current : results) + { + NodeRef zoneRef = current.getParentRef(); + Serializable value = nodeService.getProperty(zoneRef, ContentModel.PROP_NAME); + if (value == null) + { + continue; + } + else + { + String zone = DefaultTypeConverter.INSTANCE.convert(String.class, value); + zones.add(zone); + } + } + return zones; + } + + public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type) + { + NodeRef zoneRef = getZone(zoneName); + if (zoneRef == null) + { + return Collections.emptySet(); + } + return new HashSet(getAuthoritiesImpl(type, zoneRef, null, false, false, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage()); + } + + public void addAuthorityToZones(String authorityName, Set zones) + { + if ((zones != null) && (zones.size() > 0)) + { + Set zoneRefs = new HashSet(zones.size() * 2); + for (String authorityZone : zones) + { + zoneRefs.add(getOrCreateZone(authorityZone)); + } + NodeRef authRef = getAuthorityOrNull(authorityName); + if (authRef != null) + { + // Normalize the user name if necessary + if (AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER) + { + authorityName = (String) nodeService.getProperty(authRef, ContentModel.PROP_USERNAME); + } + + nodeService.addChild(zoneRefs, authRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", authorityName, namespacePrefixResolver)); + } + } + } + + public void removeAuthorityFromZones(String authorityName, Set zones) + { + if ((zones != null) && (zones.size() > 0)) + { + NodeRef authRef = getAuthorityOrNull(authorityName); + List results = nodeService.getParentAssocs(authRef, ContentModel.ASSOC_IN_ZONE, RegexQNamePattern.MATCH_ALL); + for (ChildAssociationRef current : results) + { + NodeRef zoneRef = current.getParentRef(); + Serializable value = nodeService.getProperty(zoneRef, ContentModel.PROP_NAME); + if (value == null) + { + continue; + } + else + { + String testZone = DefaultTypeConverter.INSTANCE.convert(String.class, value); + if (zones.contains(testZone)) + { + nodeService.removeChildAssociation(current); + } + } + } + } + } + + private Set getRootAuthoritiesUnderContainer(NodeRef container, AuthorityType type) + { + if (type != null && type.equals(AuthorityType.USER)) + { + return Collections. emptySet(); + } + Collection childRefs = nodeService.getChildAssocsWithoutParentAssocsOfType(container, ContentModel.ASSOC_MEMBER); + Set authorities = new TreeSet(); + for (ChildAssociationRef childRef : childRefs) + { + addAuthorityNameIfMatches(authorities, childRef.getQName().getLocalName(), type); + } + return authorities; + } + + // Listen out for person removals so that we can clear cached authorities + public void beforeDeleteNode(NodeRef nodeRef) + { + userAuthorityCache.remove(getAuthorityName(nodeRef)); + removeParentsFromChildAuthorityCache(nodeRef); + } + + public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) + { + boolean isAuthority = dictionaryService.isSubClass(nodeService.getType(nodeRef), + ContentModel.TYPE_AUTHORITY_CONTAINER); + QName idProp = isAuthority ? ContentModel.PROP_AUTHORITY_NAME : ContentModel.PROP_USERNAME; + String authBefore = DefaultTypeConverter.INSTANCE.convert(String.class, before.get(idProp)); + if (authBefore == null) + { + // Node has just been created; nothing to do + return; + } + String authAfter = DefaultTypeConverter.INSTANCE.convert(String.class, after.get(idProp)); + if (!EqualsHelper.nullSafeEquals(authBefore, authAfter)) + { + if (AlfrescoTransactionSupport.getResource(PersonServiceImpl.KEY_ALLOW_UID_UPDATE) != null || authBefore.equalsIgnoreCase(authAfter)) + { + if (isAuthority) + { + if (authBefore != null) + { + // Fix any ACLs + aclDao.renameAuthority(authBefore, authAfter); + } + + // Fix primary association local name + QName newAssocQName = QName.createQName("cm", authAfter, namespacePrefixResolver); + ChildAssociationRef assoc = nodeService.getPrimaryParent(nodeRef); + nodeService.moveNode(nodeRef, assoc.getParentRef(), assoc.getTypeQName(), newAssocQName); + + // Fix other non-case sensitive parent associations + QName oldAssocQName = QName.createQName("cm", authBefore, namespacePrefixResolver); + newAssocQName = QName.createQName("cm", authAfter, namespacePrefixResolver); + for (ChildAssociationRef parent : nodeService.getParentAssocs(nodeRef)) + { + if (!parent.isPrimary() && parent.getQName().equals(oldAssocQName)) + { + nodeService.removeChildAssociation(parent); + nodeService.addChild(parent.getParentRef(), parent.getChildRef(), parent.getTypeQName(), + newAssocQName); + } + } + authorityLookupCache.clear(); + + // Cache is out of date + userAuthorityCache.clear(); + } + else + { + userAuthorityCache.remove(authBefore); + } + removeParentsFromChildAuthorityCache(nodeRef); + } + else + { + throw new UnsupportedOperationException("The name of an authority can not be changed"); + } + } + } + + public void init() + { + // Listen out for person removals so that we can clear cached authorities + this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), ContentModel.TYPE_PERSON, new JavaBehaviour( + this, "beforeDeleteNode")); + // Listen out for updates to persons and authority containers to handle renames + this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), ContentModel.TYPE_AUTHORITY, new JavaBehaviour( + this, "onUpdateProperties")); + } + + private List getCachedChildAuthorities(NodeRef parentNodeRef) + { + return childAuthorityCache.get(cacheKey(parentNodeRef)); + } + + private void removeCachedChildAuthorities(NodeRef parentNodeRef) + { + childAuthorityCache.remove(cacheKey(parentNodeRef)); + } + + private void putCachedChildAuthorities(NodeRef parentNodeRef, List children) + { + childAuthorityCache.put(cacheKey(parentNodeRef), children); + } +}