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);
}