diff --git a/config/alfresco/authority-services-context.xml b/config/alfresco/authority-services-context.xml index f36a751aeb..5bff0f571f 100644 --- a/config/alfresco/authority-services-context.xml +++ b/config/alfresco/authority-services-context.xml @@ -54,7 +54,7 @@ - + ${spaces.store} @@ -97,6 +97,22 @@ + + + + + + + + ${authority.useBridgeTable} + + + + + + + + @@ -106,9 +122,8 @@ - - - + + diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index c5c80a225d..a2145f9cfb 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -140,7 +140,18 @@ + + + + + + + + + + + diff --git a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml index b2cd233f20..1a6e901bf1 100644 --- a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml +++ b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml @@ -84,6 +84,8 @@ Inbound settings from iBatis + + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-authorities-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-authorities-common-SqlMap.xml index 9451d2a4be..b16134c55f 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-authorities-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-authorities-common-SqlMap.xml @@ -16,6 +16,11 @@ + + + + + @@ -43,4 +48,33 @@ assoc.parent_node_id = #{parentNodeId} + + + + + + \ No newline at end of file diff --git a/config/alfresco/ibatis/org.hibernate.dialect.MySQLInnoDBDialect/query-authorities-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.MySQLInnoDBDialect/query-authorities-common-SqlMap.xml new file mode 100644 index 0000000000..3fa0885550 --- /dev/null +++ b/config/alfresco/ibatis/org.hibernate.dialect.MySQLInnoDBDialect/query-authorities-common-SqlMap.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index c5c30cc5b7..32052dd628 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -1033,9 +1033,16 @@ cache.immutableSingletonSharedCache.maxItems=12000 cache.remoteAlfrescoTicketService.ticketsCache.maxItems=1000 cache.contentDiskDriver.fileInfoCache.maxItems=1000 cache.globalConfigSharedCache.maxItems=1000 +cache.authorityBridgeTableByTenantSharedCache.maxItems=10 # # Download Service Limits, in bytes # download.maxContentSize=2152852358 + +# +# Use bridge tables for caching authority evaluation. +# +authority.useBridgeTable=true + diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml index 0f7081154b..db9cf051e4 100644 --- a/config/alfresco/tx-cache-context.xml +++ b/config/alfresco/tx-cache-context.xml @@ -219,7 +219,20 @@ + + + + + + + org.alfresco.authorityBridgeTableByTenantTransactionalCache + + + + + + diff --git a/source/java/org/alfresco/repo/security/authority/AbstractAuthorityBridgeDAO.java b/source/java/org/alfresco/repo/security/authority/AbstractAuthorityBridgeDAO.java new file mode 100644 index 0000000000..e422d26bc1 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AbstractAuthorityBridgeDAO.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.security.authority; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; + +/** + * @author Andy + */ +public abstract class AbstractAuthorityBridgeDAO implements AuthorityBridgeDAO +{ + + private NodeDAO nodeDAO; + + private QNameDAO qnameDAO; + + private TenantService tenantService; + + /** + * @param nodeDAO + * the nodeDAO to set + */ + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + /** + * @param qnameDAO + * the qnameDAO to set + */ + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + /** + * @param tenantService + * the tenantService to set + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.authority.AuthorityBridgeDAO#getAuthorityBridgeLinks() + */ + @Override + public List getAuthorityBridgeLinks() + { + Long authorityContainerTypeQNameId = Long.MIN_VALUE; + Pair authorityContainerTypeQNamePair = qnameDAO.getQName(ContentModel.TYPE_AUTHORITY_CONTAINER); + if (authorityContainerTypeQNamePair != null) + { + authorityContainerTypeQNameId = authorityContainerTypeQNamePair.getFirst(); + } + + Long memberAssocQNameId = Long.MIN_VALUE; + Pair memberAssocQNamePair = qnameDAO.getQName(ContentModel.ASSOC_MEMBER); + if (memberAssocQNamePair != null) + { + memberAssocQNameId = memberAssocQNamePair.getFirst(); + } + + Long authorityNameQNameId = Long.MIN_VALUE; + Pair authorityNameQNamePair = qnameDAO.getQName(ContentModel.PROP_AUTHORITY_NAME); + if (authorityNameQNamePair != null) + { + authorityNameQNameId = authorityNameQNamePair.getFirst(); + } + + // Get tenenat specifc store id + Long storeId = Long.MIN_VALUE; + StoreRef tenantSpecificStoreRef = tenantService.getName(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + if (tenantSpecificStoreRef != null) + { + storeId = nodeDAO.getStore(tenantSpecificStoreRef).getFirst(); + } + + return selectAuthorityBridgeLinks(authorityContainerTypeQNameId, memberAssocQNameId, authorityNameQNameId, storeId); + } + + + + /* (non-Javadoc) + * @see org.alfresco.repo.security.authority.AuthorityBridgeDAO#getDirectAuthoritiesForUser(java.lang.String) + */ + @Override + public List getDirectAuthoritiesForUser(NodeRef authRef) + { + Long authorityContainerTypeQNameId = Long.MIN_VALUE; + Pair authorityContainerTypeQNamePair = qnameDAO.getQName(ContentModel.TYPE_AUTHORITY_CONTAINER); + if (authorityContainerTypeQNamePair != null) + { + authorityContainerTypeQNameId = authorityContainerTypeQNamePair.getFirst(); + } + + Long memberAssocQNameId = Long.MIN_VALUE; + Pair memberAssocQNamePair = qnameDAO.getQName(ContentModel.ASSOC_MEMBER); + if (memberAssocQNamePair != null) + { + memberAssocQNameId = memberAssocQNamePair.getFirst(); + } + + Long authorityNameQNameId = Long.MIN_VALUE; + Pair authorityNameQNamePair = qnameDAO.getQName(ContentModel.PROP_AUTHORITY_NAME); + if (authorityNameQNamePair != null) + { + authorityNameQNameId = authorityNameQNamePair.getFirst(); + } + + // Get tenenat specifc store id + Long storeId = Long.MIN_VALUE; + StoreRef tenantSpecificStoreRef = tenantService.getName(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + if (tenantSpecificStoreRef != null) + { + storeId = nodeDAO.getStore(tenantSpecificStoreRef).getFirst(); + } + + Pair pair = (authRef == null) ? null : nodeDAO.getNodePair(authRef); + + return selectDirectAuthoritiesForUser(authorityContainerTypeQNameId, memberAssocQNameId, authorityNameQNameId, storeId, (pair == null) ? -1L : pair.getFirst()); + } + + /** + * @param authorityContainerTypeQNameId + * @param memberAssocQNameId + * @param authorityNameQNameId + * @param storeId + * @param user + * @return + */ + protected abstract List selectDirectAuthoritiesForUser(Long authorityContainerTypeQNameId, Long memberAssocQNameId, Long authorityNameQNameId, Long storeId, + Long nodeId); + + /** + * @param authorityContainerTypeQNameId + * @param memberAssocQNameId + * @param authorityNameQNameId + * @param storeId + * @return + */ + protected abstract List selectAuthorityBridgeLinks(Long authorityContainerTypeQNameId, Long memberAssocQNameId, Long authorityNameQNameId, Long storeId); + +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityBridgeDAO.java b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeDAO.java new file mode 100644 index 0000000000..96235cb3da --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeDAO.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.security.authority; + +import java.util.List; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @author Andy + * + */ +public interface AuthorityBridgeDAO +{ + List getAuthorityBridgeLinks(); + + List getDirectAuthoritiesForUser(NodeRef authRef); +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityBridgeDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeDAOImpl.java new file mode 100644 index 0000000000..89e45dc1f4 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeDAOImpl.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.security.authority; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.mybatis.spring.SqlSessionTemplate; + +/** + * @author Andy + */ +public class AuthorityBridgeDAOImpl extends AbstractAuthorityBridgeDAO +{ + private static final String QUERY_SELECT_GET_AUTHORITY_BRIDGE_ENTRIES = "alfresco.query.authorities.select_GetAuthorityBridgeEntries"; + + private static final String QUERY_SELECT_GET_DIRECT_AUTHORITIES_FOR_UESR = "alfresco.query.authorities.select_GetDirectAuthoritiesForUser"; + + private Log logger = LogFactory.getLog(getClass()); + + private SqlSessionTemplate template; + + public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) + { + this.template = sqlSessionTemplate; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.authority.AbstractAuthorityBridgeDAO#selectAuthorityBridgeLinks(java.lang.Long, + * java.lang.Long, java.lang.Long, java.lang.Long) + */ + @Override + protected List selectAuthorityBridgeLinks(Long authorityContainerTypeQNameId, Long memberAssocQNameId, Long authorityNameQNameId, Long storeId) + { + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); + + AuthorityBridgeParametersEntity authorityBridgeParametersEntity = new AuthorityBridgeParametersEntity(authorityContainerTypeQNameId, memberAssocQNameId, authorityNameQNameId, storeId); + + List links = (List) template.selectList(QUERY_SELECT_GET_AUTHORITY_BRIDGE_ENTRIES, authorityBridgeParametersEntity); + + if (start != null) + { + logger.debug("Authority bridge query: "+links.size()+" in "+(System.currentTimeMillis()-start)+" msecs"); + } + + return links; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.security.authority.AbstractAuthorityBridgeDAO#selectDirectAuthoritiesForUser(java.lang.Long, java.lang.Long, java.lang.Long, java.lang.Long, java.lang.String) + */ + @Override + protected List selectDirectAuthoritiesForUser(Long authorityContainerTypeQNameId, Long memberAssocQNameId, Long authorityNameQNameId, Long storeId, + Long nodeId) + { + + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); + + AuthorityBridgeParametersEntity authorityBridgeParametersEntity = new AuthorityBridgeParametersEntity(authorityContainerTypeQNameId, memberAssocQNameId, authorityNameQNameId, storeId, nodeId); + + List links = (List) template.selectList(QUERY_SELECT_GET_DIRECT_AUTHORITIES_FOR_UESR, authorityBridgeParametersEntity); + + if (start != null) + { + logger.debug("Direct authority: "+links.size()+" in "+(System.currentTimeMillis()-start)+" msecs"); + } + + return links; + } + +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityBridgeLink.java b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeLink.java new file mode 100644 index 0000000000..7e6c10e760 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeLink.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2012 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; + +/** + * @author Andy + * + */ +public class AuthorityBridgeLink +{ + private String childName; + + private String parentName; + + /** + * @return the childName + */ + public String getChildName() + { + return childName; + } + + /** + * @param childName the childName to set + */ + public void setChildName(String childName) + { + this.childName = childName; + } + + /** + * @return the parentName + */ + public String getParentName() + { + return parentName; + } + + /** + * @param parentName the parentName to set + */ + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + + +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityBridgeParametersEntity.java b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeParametersEntity.java new file mode 100644 index 0000000000..c6cb1fc62c --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityBridgeParametersEntity.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2005-2012 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; + +/** + * @author Andy + * + */ +public class AuthorityBridgeParametersEntity +{ + Long typeQNameId; + + Long storeId; + + Long childAssocTypeQNameId; + + Long authorityNameQNameId; + + Long nodeId; + + public AuthorityBridgeParametersEntity() + { + + } + + public AuthorityBridgeParametersEntity(Long typeQNameId, Long childAssocTypeQNameId, Long authorityNameQNameId, Long storeId) + { + this.typeQNameId = typeQNameId; + this.childAssocTypeQNameId = childAssocTypeQNameId; + this.storeId = storeId; + this.authorityNameQNameId = authorityNameQNameId; + } + + public AuthorityBridgeParametersEntity(Long typeQNameId, Long childAssocTypeQNameId, Long authorityNameQNameId, Long storeId, Long nodeId) + { + this(typeQNameId, childAssocTypeQNameId, authorityNameQNameId, storeId); + this.nodeId = nodeId; + } + + /** + * @return the typeQNameId + */ + public Long getTypeQNameId() + { + return typeQNameId; + } + + /** + * @param typeQNameId the typeQNameId to set + */ + public void setTypeQNameId(Long typeQNameId) + { + this.typeQNameId = typeQNameId; + } + + /** + * @return the storeId + */ + public Long getStoreId() + { + return storeId; + } + + /** + * @param storeId the storeId to set + */ + public void setStoreId(Long storeId) + { + this.storeId = storeId; + } + + /** + * @return the childAssocTypeQNameId + */ + public Long getChildAssocTypeQNameId() + { + return childAssocTypeQNameId; + } + + /** + * @param childAssocTypeQNameId the childAssocTypeQNameId to set + */ + public void setChildAssocTypeQNameId(Long childAssocTypeQNameId) + { + this.childAssocTypeQNameId = childAssocTypeQNameId; + } + + /** + * @return the authorityNameQNameId + */ + public Long getAuthorityNameQNameId() + { + return authorityNameQNameId; + } + + /** + * @param authorityNameQNameId the authorityNameQNameId to set + */ + public void setAuthorityNameQNameId(Long authorityNameQNameId) + { + this.authorityNameQNameId = authorityNameQNameId; + } + + /** + * @return the childName + */ + public Long getNodeId() + { + return nodeId; + } + + /** + * @param childName the childName to set + */ + public void setNodeId(Long nodeId) + { + this.nodeId = nodeId; + } + + +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOBridgeTableImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOBridgeTableImpl.java new file mode 100644 index 0000000000..1261573f04 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOBridgeTableImpl.java @@ -0,0 +1,1465 @@ +/* + * 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.concurrent.locks.ReentrantReadWriteLock; +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.BridgeTable; +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; + +public class AuthorityDAOBridgeTableImpl implements AuthorityDAO, NodeServicePolicies.BeforeDeleteNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy +{ + private static Log logger = LogFactory.getLog(AuthorityDAOBridgeTableImpl.class); + + private static final String CANNED_QUERY_AUTHS_LIST = "authsGetAuthoritiesCannedQueryFactory"; // see authority-services-context.xml + + private static final String CANNED_QUERY_AUTH_BRIDGE_LINK_LIST = "authsGetAuthorityBridgeCannedQueryFactory"; // 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; + + private SimpleCache> authorityBridgeTableByTenantCache; + + /** 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 AuthorityBridgeDAO authorityBridgeDAO; + + private boolean useBridgeTable = true; + + private static final Collection SEARCHABLE_AUTHORITY_TYPES = new LinkedList(); + + static + { + SEARCHABLE_AUTHORITY_TYPES.add(AuthorityType.ROLE); + SEARCHABLE_AUTHORITY_TYPES.add(AuthorityType.GROUP); + } + + public AuthorityDAOBridgeTableImpl() + { + 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 setAuthorityBridgeTableByTenantCache(SimpleCache> authorityBridgeTableByTenantCache) + { + this.authorityBridgeTableByTenantCache = authorityBridgeTableByTenantCache; + } + + /** + * @param useBridgeTable the useBridgeTable to set + */ + public void setUseBridgeTable(boolean useBridgeTable) + { + this.useBridgeTable = useBridgeTable; + } + + + 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; + } + + /** + * @param authorityBridgeDAO the authorityBridgeDAO to set + */ + public void setAuthorityBridgeDAO(AuthorityBridgeDAO authorityBridgeDAO) + { + this.authorityBridgeDAO = authorityBridgeDAO; + } + + + 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"); + } + childAuthorityCache.remove(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(); + authorityBridgeTableByTenantCache.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 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(); + authorityBridgeTableByTenantCache.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); + childAuthorityCache.remove(parentRef); + if (AuthorityType.getAuthorityType(childName) == AuthorityType.USER) + { + userAuthorityCache.remove(childName); + } + else + { + userAuthorityCache.clear(); + authorityBridgeTableByTenantCache.clear(); + } + } + + private BridgeTable getBridgeTable() + { + String tenant = tenantService.getCurrentUserDomain(); + BridgeTable bridgeTable = authorityBridgeTableByTenantCache.get(tenant); + if(bridgeTable == null) + { + List links = authorityBridgeDAO.getAuthorityBridgeLinks(); + bridgeTable = new BridgeTable(); + for(AuthorityBridgeLink link : links) + { + bridgeTable.addLink(link.getParentName(), link.getChildName()); + } + authorityBridgeTableByTenantCache.put(tenant, bridgeTable); + } + return bridgeTable; + } + + private void listAuthoritiesByBridgeTable(Set authorities, String name) + { + BridgeTable bridgeTable = getBridgeTable(); + + AuthorityType type = AuthorityType.getAuthorityType(name); + switch(type) + { + case ADMIN: + case GUEST: + case USER: + case EVERYONE: + NodeRef authRef = getAuthorityOrNull(name); + List parents = authorityBridgeDAO.getDirectAuthoritiesForUser(authRef); + for(AuthorityBridgeLink parent : parents) + { + authorities.add(parent.getParentName()); + authorities.addAll(bridgeTable.getAncestors(parent.getParentName())); + } + break; + case GROUP: + case OWNER: + case ROLE: + authorities.addAll(bridgeTable.getAncestors(name)); + break; + } + } + + 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(); + if(useBridgeTable) + { + listAuthoritiesByBridgeTable(authorities, name); + } + else + { + 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)) + { + authorities.add(authorityName); + } + } + + private void addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type, Pattern pattern) + { + if (type == null || AuthorityType.getAuthorityType(authorityName).equals(type)) + { + 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 = childAuthorityCache.get(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)) + { + childAuthorityCache.put(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 = childAuthorityCache.get(authorityNodeRef); + if (cars == null) + { + cars = nodeService.getChildAssocs(authorityNodeRef, RegexQNamePattern.MATCH_ALL, + RegexQNamePattern.MATCH_ALL, false); + childAuthorityCache.put(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)) + { + childAuthorityCache.remove(parentRef); + } + } + } + + private NodeRef getAuthorityOrNull(String name) + { + try + { + if (AuthorityType.getAuthorityType(name).equals(AuthorityType.USER)) + { + return personService.getPerson(name, false); + } + else if (AuthorityType.getAuthorityType(name).equals(AuthorityType.GUEST)) + { + return personService.getPerson(name, false); + } + else if (AuthorityType.getAuthorityType(name).equals(AuthorityType.ADMIN)) + { + return personService.getPerson(name, false); + } + 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(); + authorityBridgeTableByTenantCache.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")); + } +} diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index 2d5182d5d8..02cd8ac436 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -1036,22 +1036,20 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic private List listSitesImpl(final String userName, int size) { final int maxResults = size > 0 ? size : 1000; + Set containingAuthorities = authorityService.getContainingAuthorities(AuthorityType.GROUP, userName, false); final Set siteNames = new TreeSet(); - authorityService.getContainingAuthoritiesInZone(AuthorityType.GROUP, userName, AuthorityService.ZONE_APP_SHARE, new AuthorityFilter(){ - @Override - public boolean includeAuthority(String authority) + for(String authority : containingAuthorities) + { + if (siteNames.size() < maxResults) { - if (siteNames.size() < maxResults) + String siteName = resolveSite(authority); + if (siteName != null) { - String siteName = resolveSite(authority); - if (siteName == null) - { - return false; - } - return siteNames.add(siteName); + siteNames.add(siteName); } - return false; - }}, maxResults); + } + } + if (siteNames.isEmpty()) { return Collections.emptyList(); diff --git a/source/java/org/alfresco/repo/site/SiteServiceTestHuge.java b/source/java/org/alfresco/repo/site/SiteServiceTestHuge.java index 32b2d05360..d7da90ab11 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceTestHuge.java +++ b/source/java/org/alfresco/repo/site/SiteServiceTestHuge.java @@ -944,21 +944,21 @@ public class SiteServiceTestHuge // allocateUsersToGroups(NUM_USERS, NUM_GROUPS, Allocation.ALL_TO_EACH); // } -// @Test -// public void testInit() throws Exception -// { -// createUsers(NUM_USERS); -// createGroups(NUM_GROUPS); -// allocateUsersToGroups(NUM_USERS, NUM_GROUPS, Allocation.ALL_TO_EACH); -// -// createSites(NUM_SITES, NUM_USERS, 0, OnFailure.KEEP_GOING); -// -// int blockSize = 10; -// for (int siteId = getNextSiteToAddGroupTo(1); siteId <= NUM_SITES; siteId += blockSize) -// { -// allocateGroupToSite(siteId, NUM_USERS, ADMIN_USER, OnFailure.KEEP_GOING, blockSize); -// } -// } + @Test + public void testInit() throws Exception + { + createUsers(NUM_USERS); + createGroups(NUM_GROUPS); + allocateUsersToGroups(NUM_USERS, NUM_GROUPS, Allocation.ALL_TO_EACH); + + createSites(NUM_SITES, NUM_USERS, 0, OnFailure.KEEP_GOING); + + int blockSize = 10; + for (int siteId = getNextSiteToAddGroupTo(1); siteId <= NUM_SITES; siteId += blockSize) + { + allocateGroupToSite(siteId, NUM_USERS, ADMIN_USER, OnFailure.KEEP_GOING, blockSize); + } + } // ------------------ Test to load data from cmd line -------------------- diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 33f381e422..480852b387 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -747,8 +747,7 @@ public class WorkflowServiceImpl implements WorkflowService // Expand authorities to include associated groups (and parent groups) List authorities = new ArrayList(); authorities.add(authority); - Set parents = authorityService.getContainingAuthoritiesInZone(AuthorityType.GROUP, authority, - null, null, 100); + Set parents = authorityService.getContainingAuthorities(AuthorityType.GROUP, authority, false); authorities.addAll(parents); // Retrieve pooled tasks for authorities (from each of the registered @@ -758,7 +757,10 @@ public class WorkflowServiceImpl implements WorkflowService for (String id : ids) { TaskComponent component = registry.getTaskComponent(id); - tasks.addAll(component.getPooledTasks(authorities)); + for(int i = 0; i < authorities.size(); i+=1000) + { + tasks.addAll(component.getPooledTasks(authorities.subList(i*1000, ((i+1)*1000) > authorities.size() ? authorities.size() : ((i+1)*1000)))); + } } return Collections.unmodifiableList(tasks); }