Java tutorial
/* * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. */ package org.alfresco.repo.security.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.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.query.CannedQuery; import org.alfresco.query.CannedQueryFactory; import org.alfresco.query.CannedQueryResults; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.domain.permissions.AclDAO; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser; import org.alfresco.repo.security.SEIPTenantIntegration; 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 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<Pair<String, String>, NodeRef> authorityLookupCache; private static final NodeRef NULL_NODEREF = new NodeRef("null", "null", "null"); private SimpleCache<String, Map<String, Set<String>>> userAuthorityCache; private SimpleCache<Pair<String, String>, List<ChildAssociationRef>> zoneAuthorityCache; private SimpleCache<NodeRef, List<ChildAssociationRef>> childAuthorityCache; private SimpleCache<String, BridgeTable<String>> authorityBridgeTableByTenantCache; /** System Container ref cache (Tennant aware) */ private Map<String, NodeRef> systemContainerRefs = new ConcurrentHashMap<String, NodeRef>(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<CannedQueryFactory> cannedQueryRegistry; private AuthorityBridgeDAO authorityBridgeDAO; private boolean useBridgeTable = true; private static final Collection<AuthorityType> SEARCHABLE_AUTHORITY_TYPES = new LinkedList<AuthorityType>(); 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<Pair<String, String>, NodeRef> authorityLookupCache) { this.authorityLookupCache = authorityLookupCache; } public void setUserAuthorityCache(SimpleCache<String, Map<String, Set<String>>> userAuthorityCache) { this.userAuthorityCache = userAuthorityCache; } public void setZoneAuthorityCache( SimpleCache<Pair<String, String>, List<ChildAssociationRef>> zoneAuthorityCache) { this.zoneAuthorityCache = zoneAuthorityCache; } public void setChildAuthorityCache(SimpleCache<NodeRef, List<ChildAssociationRef>> childAuthorityCache) { this.childAuthorityCache = childAuthorityCache; } public void setAuthorityBridgeTableByTenantCache( SimpleCache<String, BridgeTable<String>> 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<CannedQueryFactory> 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<String> parentNames, String childName) { Set<NodeRef> parentRefs = new HashSet<NodeRef>(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) { getUserAuthorityCache().remove(childName); } else { getUserAuthorityCache().clear(); authorityBridgeTableByTenantCache.clear(); } } private Map<String, Set<String>> getUserAuthorityCache() { String tenantId = SEIPTenantIntegration.getTenantId(); Map<String, Set<String>> userAuthCachePerTenant = userAuthorityCache.get(tenantId); if (userAuthCachePerTenant == null) { userAuthCachePerTenant = new LinkedHashMap<String, Set<String>>(); userAuthorityCache.put(tenantId, userAuthCachePerTenant); } return userAuthCachePerTenant; } public void createAuthority(String name, String authorityDisplayName, Set<String> authorityZones) { HashMap<QName, Serializable> props = new HashMap<QName, Serializable>(); 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<NodeRef> zoneRefs = new HashSet<NodeRef>(authorityZones.size() * 2); String currentUserDomain = tenantService.getCurrentUserDomain(); for (String authorityZone : authorityZones) { zoneRefs.add(getOrCreateZone(authorityZone)); zoneAuthorityCache.remove(new Pair<String, String>(currentUserDomain, authorityZone)); } zoneAuthorityCache.remove(new Pair<String, String>(currentUserDomain, null)); nodeService.addChild(zoneRefs, childRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", name, namespacePrefixResolver)); } authorityLookupCache.put(cacheKey(name), childRef); } private Pair<String, String> cacheKey(String authorityName) { String tenantDomain = AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER ? tenantService.getDomain(authorityName) : tenantService.getCurrentUserDomain(); return new Pair<String, String>(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<String, String>(currentUserDomain, authorityZone)); } zoneAuthorityCache.remove(new Pair<String, String>(currentUserDomain, null)); removeParentsFromChildAuthorityCache(nodeRef); authorityLookupCache.remove(cacheKey(name)); getUserAuthorityCache().clear(); authorityBridgeTableByTenantCache.clear(); nodeService.deleteNode(nodeRef); } // Get authorities by type and/or zone (both cannot be null) public PagingResults<String> 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<String> 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<AuthorityInfo> cq = getAuthoritiesCannedQueryFactory.getCannedQuery(type, containerRef, displayNameFilter, sortByDisplayName, sortAscending, pagingRequest); // execute canned query final CannedQueryResults<AuthorityInfo> results = cq.execute(); PagingResults<String> finalResults = new PagingResults<String>() { @Override public String getQueryExecutionId() { return results.getQueryExecutionId(); } @Override public List<String> getPage() { List<String> auths = new ArrayList<String>(results.getPageCount()); for (AuthorityInfo authInfo : results.getPage()) { auths.add(authInfo.getAuthorityName()); } return auths; } @Override public boolean hasMoreItems() { return results.hasMoreItems(); } @Override public Pair<Integer, Integer> 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<String> getUserAuthoritiesImpl(String displayNameFilter, boolean sortByDisplayName, boolean sortAscending, PagingRequest pagingRequest) { List<Pair<QName, String>> filter = null; if (displayNameFilter != null) { filter = new ArrayList<Pair<QName, String>>(); filter.add(new Pair<QName, String>(ContentModel.PROP_USERNAME, displayNameFilter)); } List<Pair<QName, Boolean>> sort = null; if (sortByDisplayName) { sort = new ArrayList<Pair<QName, Boolean>>(); sort.add(new Pair<QName, Boolean>(ContentModel.PROP_USERNAME, sortAscending)); } final PagingResults<PersonInfo> ppr = personService.getPeople(filter, true, sort, pagingRequest); List<PersonInfo> result = ppr.getPage(); final List<String> auths = new ArrayList<String>(result.size()); for (PersonInfo person : result) { auths.add(person.getUserName()); } return new PagingResults<String>() { @Override public String getQueryExecutionId() { return ppr.getQueryExecutionId(); } @Override public List<String> getPage() { return auths; } @Override public boolean hasMoreItems() { return ppr.hasMoreItems(); } @Override public Pair<Integer, Integer> getTotalResultCount() { return ppr.getTotalResultCount(); } }; } public Set<String> 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<String> 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<String> 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<String> authorities = new TreeSet<String>(); 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<AuthorityType> 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<String> getContainedAuthorities(AuthorityType type, String parentName, boolean immediate) { AuthorityType parentAuthorityType = AuthorityType.getAuthorityType(parentName); if (parentAuthorityType == AuthorityType.USER) { // Users never contain other authorities return Collections.<String>emptySet(); } else { NodeRef nodeRef = getAuthorityOrNull(parentName); if (nodeRef == null) { throw new UnknownAuthorityException("An authority was not found for " + parentName); } Set<String> authorities = new TreeSet<String>(); 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) { getUserAuthorityCache().remove(childName); } else { getUserAuthorityCache().clear(); authorityBridgeTableByTenantCache.clear(); } } private BridgeTable<String> getBridgeTable() { String tenant = tenantService.getCurrentUserDomain(); BridgeTable<String> bridgeTable = authorityBridgeTableByTenantCache.get(tenant); if (bridgeTable == null) { List<AuthorityBridgeLink> links = authorityBridgeDAO.getAuthorityBridgeLinks(); bridgeTable = new BridgeTable<String>(); for (AuthorityBridgeLink link : links) { bridgeTable.addLink(link.getParentName(), link.getChildName()); } authorityBridgeTableByTenantCache.put(tenant, bridgeTable); } return bridgeTable; } private void listAuthoritiesByBridgeTable(Set<String> authorities, String name) { BridgeTable<String> bridgeTable = getBridgeTable(); AuthorityType type = AuthorityType.getAuthorityType(name); switch (type) { case ADMIN: case GUEST: case USER: case EVERYONE: NodeRef authRef = getAuthorityOrNull(name); List<AuthorityBridgeLink> 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<String> 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<String> authorities = getUserAuthorityCache().get(name); if (authorities == null) { authorities = new TreeSet<String>(); if (useBridgeTable) { listAuthoritiesByBridgeTable(authorities, name); } else { listAuthorities(null, name, authorities, true, true); } getUserAuthorityCache().put(name, authorities); } // If we wanted the unfiltered set we are done if (type == null) { return authorities; } // Apply the filtering by type Set<String> filteredAuthorities = new TreeSet<String>(); for (String authority : authorities) { addAuthorityNameIfMatches(filteredAuthorities, authority, type); } return filteredAuthorities; } // Otherwise, crawl the DB for the answer else { Set<String> authorities = new TreeSet<String>(); listAuthorities(type, name, authorities, true, !immediate); return authorities; } } public Set<String> 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<String, String> cacheKey = new Pair<String, String>(currentUserDomain, zoneName); List<ChildAssociationRef> zoneAuthorities = zoneAuthorityCache.get(cacheKey); final int maxToProcess = Math.max(size, zoneAuthoritySampleSize); if (zoneAuthorities == null) { zoneAuthorities = AuthenticationUtil.runAs(new RunAsWork<List<ChildAssociationRef>>() { @Override public List<ChildAssociationRef> 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<String> result = new TreeSet<String>(); 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<String, String> 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<String> unfilteredResult; boolean filterZone; if (authority == null) { unfilteredResult = new HashSet<String>(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<String> newResult = new TreeSet<String>(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<String> authorities, String authorityName, AuthorityType type) { if (type == null || AuthorityType.getAuthorityType(authorityName).equals(type)) { authorities.add(authorityName); } } private void addAuthorityNameIfMatches(Set<String> 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<String> 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<String> 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<ChildAssociationRef> cars = nodeService.getParentAssocs(nodeRef, ContentModel.ASSOC_MEMBER, RegexQNamePattern.MATCH_ALL); for (ChildAssociationRef car : cars) { listAuthorities(type, car.getParentRef(), authorities, true, recursive, true); } } else { List<ChildAssociationRef> 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<ChildAssociationRef> 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<String, String> cacheKey = cacheKey(name); NodeRef result = authorityLookupCache.get(cacheKey); if (result == null) { List<ChildAssociationRef> 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, <b>which must exist</b> */ private NodeRef getAuthorityContainer() { return getSystemContainer(qnameAssocAuthorities); } /** * @return Returns the zone container, <b>which must exist</b> */ 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, <b>which must exist</b> */ 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<ChildAssociationRef> 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<ChildAssociationRef> results = nodeService.getChildAssocs(zoneContainerRef, ContentModel.ASSOC_CHILDREN, zoneQName, false); if (results.isEmpty()) { if (create) { HashMap<QName, Serializable> props = new HashMap<QName, Serializable>(); 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<String> getAuthorityZones(String name) { Set<String> zones = new TreeSet<String>(); NodeRef childRef = getAuthorityOrNull(name); if (childRef == null) { return null; } List<ChildAssociationRef> 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<String> getAllAuthoritiesInZone(String zoneName, AuthorityType type) { NodeRef zoneRef = getZone(zoneName); if (zoneRef == null) { return Collections.emptySet(); } return new HashSet<String>( getAuthoritiesImpl(type, zoneRef, null, false, false, new PagingRequest(0, Integer.MAX_VALUE, null)) .getPage()); } public void addAuthorityToZones(String authorityName, Set<String> zones) { if ((zones != null) && (zones.size() > 0)) { Set<NodeRef> zoneRefs = new HashSet<NodeRef>(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<String> zones) { if ((zones != null) && (zones.size() > 0)) { NodeRef authRef = getAuthorityOrNull(authorityName); List<ChildAssociationRef> 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<String> getRootAuthoritiesUnderContainer(NodeRef container, AuthorityType type) { if (type != null && type.equals(AuthorityType.USER)) { return Collections.<String>emptySet(); } Collection<ChildAssociationRef> childRefs = nodeService.getChildAssocsWithoutParentAssocsOfType(container, ContentModel.ASSOC_MEMBER); Set<String> authorities = new TreeSet<String>(); 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) { getUserAuthorityCache().remove(getAuthorityName(nodeRef)); removeParentsFromChildAuthorityCache(nodeRef); } public void onUpdateProperties(NodeRef nodeRef, Map<QName, Serializable> before, Map<QName, Serializable> 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 getUserAuthorityCache().clear(); } else { getUserAuthorityCache().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")); } }