diff --git a/config/alfresco/authority-services-context.xml b/config/alfresco/authority-services-context.xml
index 77e255b0fc..b329b69dd6 100644
--- a/config/alfresco/authority-services-context.xml
+++ b/config/alfresco/authority-services-context.xml
@@ -25,6 +25,9 @@
+
+
+
diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java
index 6effe3c69f..813886f84b 100644
--- a/source/java/org/alfresco/repo/avm/AVMNodeService.java
+++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java
@@ -949,6 +949,28 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi
NodeRef childRef,
QName assocTypeQName,
QName qname) throws InvalidNodeRefException
+ {
+ return addChild(Collections.singletonList(parentRef), childRef, assocTypeQName, qname).get(0);
+ }
+
+ /**
+ * Associates a given child node with a given collection of parents. All nodes must belong to the same store.
+ *
+ *
+ *
+ * @param parentRefs
+ * @param childRef
+ * @param assocTypeQName the qualified name of the association type as defined in the datadictionary
+ * @param qname the qualified name of the association
+ * @return Returns a reference to the newly created child association
+ * @throws InvalidNodeRefException if the parent or child nodes could not be found
+ * @throws CyclicChildRelationshipException if the child partakes in a cyclic relationship after the add
+ */
+ public List addChild(
+ Collection parentRefs,
+ NodeRef childRef,
+ QName assocTypeQName,
+ QName qname) throws InvalidNodeRefException
{
Pair childVersionPath = AVMNodeConverter.ToAVMVersionPath(childRef);
AVMNodeDescriptor child = fAVMService.lookup(childVersionPath.getFirst(),
@@ -957,24 +979,29 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi
{
throw new InvalidNodeRefException(childVersionPath.getSecond() + " not found.", childRef);
}
- Pair parentVersionPath = AVMNodeConverter.ToAVMVersionPath(parentRef);
- if (parentVersionPath.getFirst() >= 0)
+
+ List childAssociationRefs = new ArrayList(parentRefs.size());
+ for (NodeRef parentRef : parentRefs)
{
- throw new InvalidNodeRefException("Read Only.", parentRef);
- }
- try
- {
- fAVMService.link(parentVersionPath.getSecond(), qname.getLocalName(), child);
- ChildAssociationRef newChild =
- new ChildAssociationRef(assocTypeQName, parentRef, qname,
- AVMNodeConverter.ToNodeRef(-1,
- AVMNodeConverter.ExtendAVMPath(parentVersionPath.getSecond(), qname.getLocalName())));
- return newChild;
- }
- catch (AVMException e)
- {
- throw new InvalidNodeRefException("Could not link.", childRef);
+ Pair parentVersionPath = AVMNodeConverter.ToAVMVersionPath(parentRef);
+ if (parentVersionPath.getFirst() >= 0)
+ {
+ throw new InvalidNodeRefException("Read Only.", parentRef);
+ }
+ try
+ {
+ fAVMService.link(parentVersionPath.getSecond(), qname.getLocalName(), child);
+ ChildAssociationRef newChild = new ChildAssociationRef(assocTypeQName, parentRef, qname,
+ AVMNodeConverter.ToNodeRef(-1, AVMNodeConverter.ExtendAVMPath(parentVersionPath.getSecond(),
+ qname.getLocalName())));
+ childAssociationRefs.add(newChild);
+ }
+ catch (AVMException e)
+ {
+ throw new InvalidNodeRefException("Could not link.", childRef);
+ }
}
+ return childAssociationRefs;
}
/**
diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
index d6d723b890..06b2346752 100644
--- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
@@ -852,48 +852,63 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// invokeOnDeleteNode(childParentAssocRef, childNodeType, childNodeQNames, true);
}
}
-
+
public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName)
{
- Pair parentNodePair = getNodePairNotNull(parentRef);
- Long parentNodeId = parentNodePair.getFirst();
+ return addChild(Collections.singletonList(parentRef), childRef, assocTypeQName, assocQName).get(0);
+ }
+
+ public List addChild(Collection parentRefs, NodeRef childRef, QName assocTypeQName, QName assocQName)
+ {
+ // Get the node's name, if present
Pair childNodePair = getNodePairNotNull(childRef);
Long childNodeId = childNodePair.getFirst();
-
- // Invoke policy behaviours
- invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName, false);
-
- // Get the node's name, if present
Map childNodeProperties = nodeDaoService.getNodeProperties(childNodePair.getFirst());
String childNodeName = extractNameProperty(childNodeProperties);
-
- // make the association
- Pair childAssocPair = nodeDaoService.newChildAssoc(
- parentNodeId,
- childNodeId,
- false,
- assocTypeQName,
- assocQName,
- childNodeName);
- ChildAssociationRef childAssocRef = childAssocPair.getSecond();
- // ensure name uniqueness
- setChildNameUnique(childAssocPair, childNodePair);
- NodeRef childNodeRef = childAssocRef.getChildRef();
+
+ List childAssociationRefs = new ArrayList(parentRefs.size());
+ List> parentNodePairs = new ArrayList>(parentRefs.size());
+ for (NodeRef parentRef : parentRefs)
+ {
+ Pair parentNodePair = getNodePairNotNull(parentRef);
+ Long parentNodeId = parentNodePair.getFirst();
+ parentNodePairs.add(parentNodePair);
+
+ // Invoke policy behaviours
+ invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName, false);
+
+ // make the association
+ Pair childAssocPair = nodeDaoService.newChildAssoc(parentNodeId, childNodeId,
+ false, assocTypeQName, assocQName, childNodeName);
+ // ensure name uniqueness
+ setChildNameUnique(childAssocPair, childNodePair);
+
+ childAssociationRefs.add(childAssocPair.getSecond());
+ }
// check that the child addition of the child has not created a cyclic relationship
// this functionality is provided for free in getPath
- getPaths(childNodeRef, false);
+ getPaths(childRef, false);
// Invoke policy behaviours
- invokeOnCreateChildAssociation(childAssocRef, false);
+ for (ChildAssociationRef childAssocRef : childAssociationRefs)
+ {
+ invokeOnCreateChildAssociation(childAssocRef, false);
+ }
// Add missing aspects
- addMissingAspects(parentNodePair, assocTypeQName);
+ for (Pair parentNodePair : parentNodePairs)
+ {
+ addMissingAspects(parentNodePair, assocTypeQName);
+ }
// Index
- nodeIndexer.indexCreateChildAssociation(childAssocRef);
+ for (ChildAssociationRef childAssocRef : childAssociationRefs)
+ {
+ nodeIndexer.indexCreateChildAssociation(childAssocRef);
+ }
- return childAssocRef;
+ return childAssociationRefs;
}
public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java
index 37ea38488c..42ad11619a 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java
@@ -24,21 +24,21 @@
*/
package org.alfresco.repo.security.authority;
+import java.util.Collection;
import java.util.Set;
import org.alfresco.service.cmr.repository.NodeRef;
-import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
public interface AuthorityDAO
{
/**
- * Add an authority to another.
+ * Add a child authority to the given parent authorities
*
- * @param parentName
+ * @param parentNames
* @param childName
*/
- void addAuthority(String parentName, String childName);
+ void addAuthority(Collection parentNames, String childName);
/**
* Create an authority.
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
index fc7b15d3fe..3677b10718 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
@@ -149,26 +149,34 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
return ref != null;
}
- public void addAuthority(String parentName, String childName)
+ public void addAuthority(Collection parentNames, String childName)
{
- NodeRef parentRef = getAuthorityOrNull(parentName);
- if (parentRef == null)
- {
- throw new UnknownAuthorityException("An authority was not found for " + parentName);
- }
+ Set parentRefs = new HashSet(parentNames.size() * 2);
AuthorityType authorityType = AuthorityType.getAuthorityType(childName);
- if (!authorityType.equals(AuthorityType.USER)
- && !authorityType.equals(AuthorityType.GROUP)
- && !(authorityType.equals(AuthorityType.ROLE) && AuthorityType.getAuthorityType(parentName).equals(AuthorityType.ROLE)))
+ boolean notUserOrGroup = !authorityType.equals(AuthorityType.USER) && !authorityType.equals(AuthorityType.GROUP);
+ for (String parentName : parentNames)
{
- throw new AlfrescoRuntimeException("Authorities of the type " + authorityType + " may not be added to other authorities");
+ 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");
+ }
+ parentRefs.add(parentRef);
}
NodeRef childRef = getAuthorityOrNull(childName);
if (childRef == null)
{
throw new UnknownAuthorityException("An authority was not found for " + childName);
}
- nodeService.addChild(parentRef, childRef, ContentModel.ASSOC_MEMBER, QName.createQName("cm", childName, namespacePrefixResolver));
+ nodeService.addChild(parentRefs, childRef, ContentModel.ASSOC_MEMBER, QName.createQName("cm", childName,
+ namespacePrefixResolver));
authorityLookupCache.clear();
}
@@ -183,10 +191,12 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
ContentModel.TYPE_AUTHORITY_CONTAINER, props).getChildRef();
if (authorityZones != null)
{
+ Set zoneRefs = new HashSet(authorityZones.size() * 2);
for (String authorityZone : authorityZones)
{
- nodeService.addChild(getOrCreateZone(authorityZone), childRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", name, namespacePrefixResolver));
+ zoneRefs.add(getOrCreateZone(authorityZone));
}
+ nodeService.addChild(zoneRefs, childRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", name, namespacePrefixResolver));
}
authorityLookupCache.clear();
}
@@ -607,15 +617,15 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
{
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)
{
- for (String zone : zones)
- {
- // Add the person to an authentication zone (corresponding to an external user registry)
- // Let's preserve case on this child association
- nodeService.addChild(getOrCreateZone(zone), authRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", authorityName, namespacePrefixResolver));
- }
+ nodeService.addChild(zoneRefs, authRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", authorityName, namespacePrefixResolver));
}
}
}
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java
index f188ae3fbd..ce8ad64667 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java
@@ -1,141 +1,150 @@
-/*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
-
- * This program 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 General Public License for more details.
-
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
- * As a special exception to the terms and conditions of version 2.0 of
- * the GPL, you may redistribute this Program in connection with Free/Libre
- * and Open Source Software ("FLOSS") applications as described in Alfresco's
- * FLOSS exception. You should have recieved a copy of the text describing
- * the FLOSS exception, and it is also available here:
- * http://www.alfresco.com/legal/licensing"
- */
-package org.alfresco.repo.security.authority;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.alfresco.model.ContentModel;
-import org.alfresco.repo.security.authentication.AuthenticationUtil;
-import org.alfresco.repo.security.permissions.PermissionServiceSPI;
-import org.alfresco.repo.tenant.TenantService;
-import org.alfresco.service.cmr.repository.NodeRef;
-import org.alfresco.service.cmr.repository.NodeService;
-import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
-import org.alfresco.service.cmr.security.AuthenticationService;
-import org.alfresco.service.cmr.security.AuthorityService;
-import org.alfresco.service.cmr.security.AuthorityType;
-import org.alfresco.service.cmr.security.PermissionService;
-import org.alfresco.service.cmr.security.PersonService;
-import org.springframework.beans.factory.InitializingBean;
-
-/**
- * The default implementation of the authority service.
- *
- * @author Andy Hind
- */
-public class AuthorityServiceImpl implements AuthorityService, InitializingBean
-{
- private static Set DEFAULT_ZONES = new HashSet();
-
- private PersonService personService;
-
- private NodeService nodeService;
-
- private TenantService tenantService;
-
- private AuthorityDAO authorityDAO;
-
- private AuthenticationService authenticationService;
-
- private PermissionServiceSPI permissionServiceSPI;
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.security.authority;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.security.permissions.PermissionServiceSPI;
+import org.alfresco.repo.security.person.UserNameMatcher;
+import org.alfresco.repo.tenant.TenantService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
+import org.alfresco.service.cmr.security.AuthenticationService;
+import org.alfresco.service.cmr.security.AuthorityService;
+import org.alfresco.service.cmr.security.AuthorityType;
+import org.alfresco.service.cmr.security.PermissionService;
+import org.alfresco.service.cmr.security.PersonService;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * The default implementation of the authority service.
+ *
+ * @author Andy Hind
+ */
+public class AuthorityServiceImpl implements AuthorityService, InitializingBean
+{
+ private static Set DEFAULT_ZONES = new HashSet();
+
+ private PersonService personService;
+
+ private NodeService nodeService;
+
+ private TenantService tenantService;
+
+ private AuthorityDAO authorityDAO;
+
+ private UserNameMatcher userNameMatcher;
+
+ private AuthenticationService authenticationService;
+
+ private PermissionServiceSPI permissionServiceSPI;
+
+ private Set adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY);
+
+ private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY);
+
+ private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES);
+
+ private Set adminGroups = Collections.emptySet();
- private Set adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY);
-
- private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY);
-
- private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES);
-
- private Set adminGroups = Collections.emptySet();
-
private Set guestGroups = Collections.emptySet();
- static
- {
- DEFAULT_ZONES.add(AuthorityService.ZONE_APP_DEFAULT);
- DEFAULT_ZONES.add(AuthorityService.ZONE_AUTH_ALFRESCO);
- }
-
- public AuthorityServiceImpl()
- {
- super();
- }
-
- public void setNodeService(NodeService nodeService)
- {
- this.nodeService = nodeService;
- }
-
- public void setTenantService(TenantService tenantService)
- {
- this.tenantService = tenantService;
- }
-
- public void setPersonService(PersonService personService)
- {
- this.personService = personService;
- }
-
- public void setAuthorityDAO(AuthorityDAO authorityDAO)
- {
- this.authorityDAO = authorityDAO;
- }
-
- public void setAuthenticationService(AuthenticationService authenticationService)
- {
- this.authenticationService = authenticationService;
- }
-
- public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI)
- {
- this.permissionServiceSPI = permissionServiceSPI;
- }
+ static
+ {
+ DEFAULT_ZONES.add(AuthorityService.ZONE_APP_DEFAULT);
+ DEFAULT_ZONES.add(AuthorityService.ZONE_AUTH_ALFRESCO);
+ }
+
+ public AuthorityServiceImpl()
+ {
+ super();
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setTenantService(TenantService tenantService)
+ {
+ this.tenantService = tenantService;
+ }
+
+ public void setPersonService(PersonService personService)
+ {
+ this.personService = personService;
+ }
+
+ public void setAuthorityDAO(AuthorityDAO authorityDAO)
+ {
+ this.authorityDAO = authorityDAO;
+ }
+
+ public void setUserNameMatcher(UserNameMatcher userNameMatcher)
+ {
+ this.userNameMatcher = userNameMatcher;
+ }
+
+ public void setAuthenticationService(AuthenticationService authenticationService)
+ {
+ this.authenticationService = authenticationService;
+ }
+
+ public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI)
+ {
+ this.permissionServiceSPI = permissionServiceSPI;
+ }
+
+ public void setAdminGroups(Set adminGroups)
+ {
+ this.adminGroups = adminGroups;
+ }
- public void setAdminGroups(Set adminGroups)
- {
- this.adminGroups = adminGroups;
- }
-
public void setGuestGroups(Set guestGroups)
{
this.guestGroups = guestGroups;
}
- public void afterPropertiesSet() throws Exception
- {
- // Fully qualify the admin group names
- if (!this.adminGroups.isEmpty())
- {
- Set adminGroups = new HashSet(this.adminGroups.size());
- for (String group : this.adminGroups)
- {
- adminGroups.add(getName(AuthorityType.GROUP, group));
- }
- this.adminGroups = adminGroups;
- }
+ public void afterPropertiesSet() throws Exception
+ {
+ // Fully qualify the admin group names
+ if (!this.adminGroups.isEmpty())
+ {
+ Set adminGroups = new HashSet(this.adminGroups.size());
+ for (String group : this.adminGroups)
+ {
+ adminGroups.add(getName(AuthorityType.GROUP, group));
+ }
+ this.adminGroups = adminGroups;
+ }
// Fully qualify the guest group names
if (!this.guestGroups.isEmpty())
{
@@ -146,28 +155,28 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean
}
this.guestGroups = guestGroups;
}
- }
-
- public boolean hasAdminAuthority()
- {
- String currentUserName = AuthenticationUtil.getRunAsUser();
-
- // Determine whether the administrator role is mapped to this user or one of their groups
- return ((currentUserName != null) && getAuthoritiesForUser(currentUserName).contains(PermissionService.ADMINISTRATOR_AUTHORITY));
- }
-
- public boolean isAdminAuthority(String authorityName)
- {
- String canonicalName = personService.getUserIdentifier(authorityName);
- if (canonicalName == null)
- {
- canonicalName = authorityName;
- }
-
- // Determine whether the administrator role is mapped to this user or one of their groups
- return getAuthoritiesForUser(canonicalName).contains(PermissionService.ADMINISTRATOR_AUTHORITY);
- }
-
+ }
+
+ public boolean hasAdminAuthority()
+ {
+ String currentUserName = AuthenticationUtil.getRunAsUser();
+
+ // Determine whether the administrator role is mapped to this user or one of their groups
+ return ((currentUserName != null) && getAuthoritiesForUser(currentUserName).contains(PermissionService.ADMINISTRATOR_AUTHORITY));
+ }
+
+ public boolean isAdminAuthority(String authorityName)
+ {
+ String canonicalName = personService.getUserIdentifier(authorityName);
+ if (canonicalName == null)
+ {
+ canonicalName = authorityName;
+ }
+
+ // Determine whether the administrator role is mapped to this user or one of their groups
+ return getAuthoritiesForUser(canonicalName).contains(PermissionService.ADMINISTRATOR_AUTHORITY);
+ }
+
public boolean hasGuestAuthority()
{
String currentUserName = AuthenticationUtil.getRunAsUser();
@@ -188,75 +197,40 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean
return getAuthoritiesForUser(canonicalName).contains(PermissionService.GUEST_AUTHORITY);
}
- public Set getAuthorities()
- {
- String currentUserName = AuthenticationUtil.getRunAsUser();
- return getAuthoritiesForUser(currentUserName);
- }
-
- public Set getAuthoritiesForUser(String currentUserName)
- {
- Set authorities = new HashSet(64);
-
- authorities.addAll(getContainingAuthorities(null, currentUserName, false));
-
- // Work out mapped roles
-
+ public Set getAuthorities()
+ {
+ String currentUserName = AuthenticationUtil.getRunAsUser();
+ return getAuthoritiesForUser(currentUserName);
+ }
+
+ public Set getAuthoritiesForUser(String currentUserName)
+ {
+ Set authorities = new HashSet(64);
+
+ authorities.addAll(getContainingAuthorities(null, currentUserName, false));
+
+ // Work out mapped roles
+
// Check named guest and admin users
- Set adminUsers = this.authenticationService.getDefaultAdministratorUserNames();
+ Set adminUsers = this.authenticationService.getDefaultAdministratorUserNames();
Set guestUsers = this.authenticationService.getDefaultGuestUserNames();
- // note: for multi-tenancy, this currently relies on a naming convention which assumes that all tenant admins will
- // have the same base name as the default non-tenant specific admin. Typically "admin" is the default required admin user,
- // although, if for example "bob" is also listed as an admin then all tenant-specific bob's will also have admin authority
- String currentUserBaseName = tenantService.getBaseNameUser(currentUserName);
-
- boolean isAdminUser = false;
- boolean isGuestUser = false;
- if (tenantService.isEnabled())
- {
- // note: for multi-tenancy, this currently relies on a naming convention which assumes that all tenant admins will
- // have the same base name as the default non-tenant specific admin. Typically "admin" is the default required admin user,
- // although, if for example "bob" is also listed as an admin then all tenant-specific bob's will also have admin authority
-
- for (String adminUser : adminUsers)
- {
- if (adminUser.equals(currentUserName) || tenantService.getBaseNameUser(adminUser).equals(currentUserBaseName))
- {
- isAdminUser = true;
- break;
- }
- }
- if (!isAdminUser)
- {
- for (String guestUser : guestUsers)
- {
- if (guestUser.equals(currentUserName) || tenantService.getBaseNameUser(guestUser).equals(currentUserBaseName))
- {
- isGuestUser = true;
- break;
- }
- }
- }
- }
- else
- {
- isAdminUser = (adminUsers.contains(currentUserName) || adminUsers.contains(currentUserBaseName));
- isGuestUser = (guestUsers.contains(currentUserName) || guestUsers.contains(currentUserBaseName));
- }
-
+ // Check for name matches using MT + case sensitivity rules
+ boolean isAdminUser = containsMatch(adminUsers, currentUserName);
+ boolean isGuestUser = containsMatch(guestUsers, currentUserName);
+
// Check if any of the user's groups are listed as admin groups
- if (!isAdminUser && !adminGroups.isEmpty())
- {
- for (String authority : authorities)
- {
- if (adminGroups.contains(authority) || adminGroups.contains(tenantService.getBaseNameUser(authority)))
- {
- isAdminUser = true;
- break;
- }
- }
- }
+ if (!isAdminUser && !adminGroups.isEmpty())
+ {
+ for (String authority : authorities)
+ {
+ if (adminGroups.contains(authority) || adminGroups.contains(tenantService.getBaseNameUser(authority)))
+ {
+ isAdminUser = true;
+ break;
+ }
+ }
+ }
// Check if any of the user's groups are listed as guest groups
if (!isAdminUser && !isGuestUser && !guestGroups.isEmpty())
{
@@ -269,272 +243,308 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean
}
}
}
-
+
// Give admin user's the ADMINISTRATOR authorities
- if (isAdminUser)
- {
- authorities.addAll(adminSet);
- }
+ if (isAdminUser)
+ {
+ authorities.addAll(adminSet);
+ }
// Give all non-guest users the ALL authorities
if (!isGuestUser)
- {
- authorities.addAll(allSet);
- }
+ {
+ authorities.addAll(allSet);
+ }
else
{
authorities.addAll(guestSet);
}
- return authorities;
- }
-
- public Set getAllAuthorities(AuthorityType type)
- {
- Set authorities = new HashSet();
- switch (type)
- {
- case ADMIN:
- authorities.addAll(adminSet);
- break;
- case EVERYONE:
- authorities.addAll(allSet);
- break;
- case GUEST:
- authorities.addAll(guestSet);
- break;
- case GROUP:
- authorities.addAll(authorityDAO.getAllAuthorities(type));
- break;
- case OWNER:
- break;
- case ROLE:
- authorities.addAll(authorityDAO.getAllAuthorities(type));
- break;
- case USER:
- for (NodeRef personRef : personService.getAllPeople())
- {
- authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef,
- ContentModel.PROP_USERNAME)));
- }
- break;
- default:
- break;
- }
- return authorities;
- }
-
-
-
- public Set findAuthorities(AuthorityType type, String namePattern)
- {
- return findAuthoritiesInZone(type, namePattern, null);
- }
-
-
- public Set findAuthoritiesByShortName(AuthorityType type, String shortNamePattern)
- {
- String fullNamePattern = getName(type, shortNamePattern);
- return findAuthorities(type, fullNamePattern);
- }
-
- public void addAuthority(String parentName, String childName)
- {
- authorityDAO.addAuthority(parentName, childName);
- }
-
- private void checkTypeIsMutable(AuthorityType type)
- {
- if((type == AuthorityType.GROUP) || (type == AuthorityType.ROLE))
- {
- return;
- }
- else
- {
- throw new AuthorityException("Trying to modify a fixed authority");
- }
- }
-
- public String createAuthority(AuthorityType type, String shortName)
- {
- return createAuthority(type, shortName, shortName, getDefaultZones());
- }
-
- public void deleteAuthority(String name)
- {
- deleteAuthority(name, false);
- }
-
- public void deleteAuthority(String name, boolean cascade)
- {
- AuthorityType type = AuthorityType.getAuthorityType(name);
- checkTypeIsMutable(type);
- if (cascade)
- {
- for (String child : getContainedAuthorities(type, name, true))
- {
- deleteAuthority(child, true);
- }
- }
- authorityDAO.deleteAuthority(name);
- permissionServiceSPI.deletePermissions(name);
- }
-
- public Set getAllRootAuthorities(AuthorityType type)
- {
- return authorityDAO.getAllRootAuthorities(type);
- }
-
- public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate)
- {
- return authorityDAO.getContainedAuthorities(type, name, immediate);
- }
-
- public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate)
- {
- return authorityDAO.getContainingAuthorities(type, name, immediate);
- }
-
- public String getName(AuthorityType type, String shortName)
- {
- if (type.isFixedString())
- {
- return type.getFixedString();
- }
- else if (type.isPrefixed())
- {
- return type.getPrefixString() + shortName;
- }
- else
- {
- return shortName;
- }
- }
-
- 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 void removeAuthority(String parentName, String childName)
- {
- authorityDAO.removeAuthority(parentName, childName);
- }
-
- public boolean authorityExists(String name)
- {
- return authorityDAO.authorityExists(name);
- }
-
- public String createAuthority(AuthorityType type, String shortName, String authorityDisplayName,
- Set authorityZones)
- {
- checkTypeIsMutable(type);
- String name = getName(type, shortName);
- authorityDAO.createAuthority(name, authorityDisplayName, authorityZones);
- return name;
- }
-
- public String getAuthorityDisplayName(String name)
- {
- String displayName = authorityDAO.getAuthorityDisplayName(name);
- if(displayName == null)
- {
- displayName = getShortName(name);
- }
- return displayName;
- }
-
- public void setAuthorityDisplayName(String authorityName, String authorityDisplayName)
- {
- AuthorityType type = AuthorityType.getAuthorityType(authorityName);
- checkTypeIsMutable(type);
- authorityDAO.setAuthorityDisplayName(authorityName, authorityDisplayName);
- }
-
- public Set getAuthorityZones(String name)
- {
- return authorityDAO.getAuthorityZones(name);
- }
-
- public NodeRef getOrCreateZone(String zoneName)
- {
- return authorityDAO.getOrCreateZone(zoneName);
- }
-
- public NodeRef getZone(String zoneName)
- {
- return authorityDAO.getZone(zoneName);
- }
-
- public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type)
- {
- return authorityDAO.getAllAuthoritiesInZone(zoneName, type);
- }
-
- public void addAuthorityToZones(String authorityName, Set zones)
- {
- authorityDAO.addAuthorityToZones(authorityName, zones);
-
- }
-
- public void removeAuthorityFromZones(String authorityName, Set zones)
- {
- authorityDAO.removeAuthorityFromZones(authorityName, zones);
- }
-
- public Set getDefaultZones()
- {
- return DEFAULT_ZONES;
- }
-
- public Set getAllRootAuthoritiesInZone(String zoneName, AuthorityType type)
- {
- return authorityDAO.getAllRootAuthoritiesInZone(zoneName, type);
- }
-
- public Set findAuthoritiesByShortNameInZone(AuthorityType type, String shortNamePattern, String zone)
- {
- String fullNamePattern = getName(type, shortNamePattern);
- return findAuthoritiesInZone(type, fullNamePattern, zone);
- }
-
- public Set findAuthoritiesInZone(AuthorityType type, String namePattern, String zone)
- {
- Set authorities = new HashSet();
- switch (type)
- {
- case ADMIN:
- case EVERYONE:
- case GUEST:
- throw new UnsupportedOperationException();
- case GROUP:
- Set zones = null;
- if(zone != null)
- {
- zones = Collections.singleton(zone);
- }
- authorities.addAll(authorityDAO.findAuthorities(type, namePattern, zones));
- break;
- case OWNER:
- case ROLE:
- throw new UnsupportedOperationException();
- case USER:
- throw new UnsupportedOperationException();
- default:
- break;
- }
- return authorities;
- }
-}
+ return authorities;
+ }
+
+ public Set getAllAuthorities(AuthorityType type)
+ {
+ Set authorities = new HashSet();
+ switch (type)
+ {
+ case ADMIN:
+ authorities.addAll(adminSet);
+ break;
+ case EVERYONE:
+ authorities.addAll(allSet);
+ break;
+ case GUEST:
+ authorities.addAll(guestSet);
+ break;
+ case GROUP:
+ authorities.addAll(authorityDAO.getAllAuthorities(type));
+ break;
+ case OWNER:
+ break;
+ case ROLE:
+ authorities.addAll(authorityDAO.getAllAuthorities(type));
+ break;
+ case USER:
+ for (NodeRef personRef : personService.getAllPeople())
+ {
+ authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef,
+ ContentModel.PROP_USERNAME)));
+ }
+ break;
+ default:
+ break;
+ }
+ return authorities;
+ }
+
+
+
+ public Set findAuthorities(AuthorityType type, String namePattern)
+ {
+ return findAuthoritiesInZone(type, namePattern, null);
+ }
+
+
+ public Set findAuthoritiesByShortName(AuthorityType type, String shortNamePattern)
+ {
+ String fullNamePattern = getName(type, shortNamePattern);
+ return findAuthorities(type, fullNamePattern);
+ }
+
+ public void addAuthority(String parentName, String childName)
+ {
+ addAuthority(Collections.singleton(parentName), childName);
+ }
+
+ public void addAuthority(Collection parentNames, String childName)
+ {
+ authorityDAO.addAuthority(parentNames, childName);
+ }
+
+ private boolean containsMatch(Set names, String name)
+ {
+ String baseName = this.tenantService.getBaseNameUser(name);
+ if (this.tenantService.isEnabled())
+ {
+ // note: for multi-tenancy, this currently relies on a naming convention which assumes that all tenant
+ // admins will have the same base name as the default non-tenant specific admin. Typically "admin" is the
+ // default required admin user, although, if for example "bob" is also listed as an admin then all
+ // tenant-specific bob's will also have admin authority
+ for (String candidate : names)
+ {
+ if (this.userNameMatcher.matches(candidate, name)
+ || this.userNameMatcher.matches(this.tenantService.getBaseNameUser(candidate), baseName))
+ {
+ return true;
+ }
+ }
+ }
+ else
+ {
+ for (String candidate : names)
+ {
+ if (this.userNameMatcher.matches(candidate, name) || this.userNameMatcher.matches(candidate, baseName))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void checkTypeIsMutable(AuthorityType type)
+ {
+ if((type == AuthorityType.GROUP) || (type == AuthorityType.ROLE))
+ {
+ return;
+ }
+ else
+ {
+ throw new AuthorityException("Trying to modify a fixed authority");
+ }
+ }
+
+ public String createAuthority(AuthorityType type, String shortName)
+ {
+ return createAuthority(type, shortName, shortName, getDefaultZones());
+ }
+
+ public void deleteAuthority(String name)
+ {
+ deleteAuthority(name, false);
+ }
+
+ public void deleteAuthority(String name, boolean cascade)
+ {
+ AuthorityType type = AuthorityType.getAuthorityType(name);
+ checkTypeIsMutable(type);
+ if (cascade)
+ {
+ for (String child : getContainedAuthorities(type, name, true))
+ {
+ deleteAuthority(child, true);
+ }
+ }
+ authorityDAO.deleteAuthority(name);
+ permissionServiceSPI.deletePermissions(name);
+ }
+
+ public Set getAllRootAuthorities(AuthorityType type)
+ {
+ return authorityDAO.getAllRootAuthorities(type);
+ }
+
+ public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate)
+ {
+ return authorityDAO.getContainedAuthorities(type, name, immediate);
+ }
+
+ public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate)
+ {
+ return authorityDAO.getContainingAuthorities(type, name, immediate);
+ }
+
+ public String getName(AuthorityType type, String shortName)
+ {
+ if (type.isFixedString())
+ {
+ return type.getFixedString();
+ }
+ else if (type.isPrefixed())
+ {
+ return type.getPrefixString() + shortName;
+ }
+ else
+ {
+ return shortName;
+ }
+ }
+
+ 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 void removeAuthority(String parentName, String childName)
+ {
+ authorityDAO.removeAuthority(parentName, childName);
+ }
+
+ public boolean authorityExists(String name)
+ {
+ return authorityDAO.authorityExists(name);
+ }
+
+ public String createAuthority(AuthorityType type, String shortName, String authorityDisplayName,
+ Set authorityZones)
+ {
+ checkTypeIsMutable(type);
+ String name = getName(type, shortName);
+ authorityDAO.createAuthority(name, authorityDisplayName, authorityZones);
+ return name;
+ }
+
+ public String getAuthorityDisplayName(String name)
+ {
+ String displayName = authorityDAO.getAuthorityDisplayName(name);
+ if(displayName == null)
+ {
+ displayName = getShortName(name);
+ }
+ return displayName;
+ }
+
+ public void setAuthorityDisplayName(String authorityName, String authorityDisplayName)
+ {
+ AuthorityType type = AuthorityType.getAuthorityType(authorityName);
+ checkTypeIsMutable(type);
+ authorityDAO.setAuthorityDisplayName(authorityName, authorityDisplayName);
+ }
+
+ public Set getAuthorityZones(String name)
+ {
+ return authorityDAO.getAuthorityZones(name);
+ }
+
+ public NodeRef getOrCreateZone(String zoneName)
+ {
+ return authorityDAO.getOrCreateZone(zoneName);
+ }
+
+ public NodeRef getZone(String zoneName)
+ {
+ return authorityDAO.getZone(zoneName);
+ }
+
+ public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type)
+ {
+ return authorityDAO.getAllAuthoritiesInZone(zoneName, type);
+ }
+
+ public void addAuthorityToZones(String authorityName, Set zones)
+ {
+ authorityDAO.addAuthorityToZones(authorityName, zones);
+
+ }
+
+ public void removeAuthorityFromZones(String authorityName, Set zones)
+ {
+ authorityDAO.removeAuthorityFromZones(authorityName, zones);
+ }
+
+ public Set getDefaultZones()
+ {
+ return DEFAULT_ZONES;
+ }
+
+ public Set getAllRootAuthoritiesInZone(String zoneName, AuthorityType type)
+ {
+ return authorityDAO.getAllRootAuthoritiesInZone(zoneName, type);
+ }
+
+ public Set findAuthoritiesByShortNameInZone(AuthorityType type, String shortNamePattern, String zone)
+ {
+ String fullNamePattern = getName(type, shortNamePattern);
+ return findAuthoritiesInZone(type, fullNamePattern, zone);
+ }
+
+ public Set findAuthoritiesInZone(AuthorityType type, String namePattern, String zone)
+ {
+ Set authorities = new HashSet();
+ switch (type)
+ {
+ case ADMIN:
+ case EVERYONE:
+ case GUEST:
+ throw new UnsupportedOperationException();
+ case GROUP:
+ Set zones = null;
+ if(zone != null)
+ {
+ zones = Collections.singleton(zone);
+ }
+ authorities.addAll(authorityDAO.findAuthorities(type, namePattern, zones));
+ break;
+ case OWNER:
+ case ROLE:
+ throw new UnsupportedOperationException();
+ case USER:
+ throw new UnsupportedOperationException();
+ default:
+ break;
+ }
+ return authorities;
+ }
+}
diff --git a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java
index 55644e2a28..cc27f6f315 100644
--- a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java
@@ -24,6 +24,7 @@
*/
package org.alfresco.repo.security.authority;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -204,7 +205,12 @@ public class SimpleAuthorityServiceImpl implements AuthorityService
{
}
-
+
+ public void addAuthority(Collection parentNames, String childName)
+ {
+
+ }
+
public String createAuthority(AuthorityType type, String shortName)
{
return "";
diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
index cc568a38c2..569393ddfa 100644
--- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
+++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
@@ -25,12 +25,16 @@
package org.alfresco.repo.security.sync;
import java.text.DateFormat;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.TreeSet;
import org.alfresco.model.ContentModel;
@@ -55,7 +59,6 @@ import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.AbstractLifecycleBean;
-import org.alfresco.util.Pair;
import org.alfresco.util.PropertyMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -365,17 +368,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
boolean requiresNew = splitTxns
|| AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY;
- int personsProcessed = syncPersonsWithPlugin(id, plugin, requiresNew, visitedZoneIds,
- allZoneIds);
- int groupsProcessed = syncGroupsWithPlugin(id, plugin, force, requiresNew, visitedZoneIds,
- allZoneIds);
- if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
- {
- ChainingUserRegistrySynchronizer.logger
- .info("Finished synchronizing users and groups with user registry '" + id + "'");
- ChainingUserRegistrySynchronizer.logger.info(personsProcessed + " user(s) and "
- + groupsProcessed + " group(s) processed");
- }
+ syncWithPlugin(id, plugin, force, requiresNew, visitedZoneIds, allZoneIds);
}
}
catch (NoSuchBeanDefinitionException e)
@@ -431,29 +424,31 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
}
/**
- * Synchronizes changes only to local users (persons) with a {@link UserRegistry} for a particular zone.
+ * Synchronizes local groups and users with a {@link UserRegistry} for a particular zone, optionally handling
+ * deletions.
*
* @param zone
- * the zone id. This identifier is used to tag all created users, so that in the future we can tell those
- * that have been deleted from the registry.
+ * the zone id. This identifier is used to tag all created groups and users, so that in the future we can
+ * tell those that have been deleted from the registry.
* @param userRegistry
* the user registry for the zone.
+ * @param force
+ * true
if user and group deletions are to be processed.
* @param splitTxns
* Can the modifications to Alfresco be split across multiple transactions for maximum performance? If
- * true
, users and groups are created/updated in batches of 10 for increased performance. If
+ * true
, users and groups are created/updated in batches for increased performance. If
* false
, all users and groups are processed in the current transaction. This is required if
* calling synchronously (e.g. in response to an authentication event in the same transaction).
* @param visitedZoneIds
* the set of zone ids already processed. These zones have precedence over the current zone when it comes
- * to user name 'collisions'. If a user is queried that already exists locally but is tagged with one of
- * the zones in this set, then it will be ignored as this zone has lower priority.
+ * to group name 'collisions'. If a user or group is queried that already exists locally but is tagged
+ * with one of the zones in this set, then it will be ignored as this zone has lower priority.
* @param allZoneIds
* the set of all zone ids in the authentication chain. Helps us work out whether the zone information
- * recorded against a user is invalid for the current authentication chain and whether the user needs to
- * be 're-zoned'.
- * @return the number of users processed
+ * recorded against a user or group is invalid for the current authentication chain and whether the user
+ * or group needs to be 're-zoned'.
*/
- private int syncPersonsWithPlugin(final String zone, UserRegistry userRegistry, boolean splitTxns,
+ private void syncWithPlugin(final String zone, UserRegistry userRegistry, boolean force, boolean splitTxns,
final Set visitedZoneIds, final Set allZoneIds)
{
// Create a prefixed zone ID for use with the authority service
@@ -462,9 +457,314 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
// The set of zones we associate with new objects (default plus registry specific)
final Set zoneSet = getZones(zoneId);
- final long lastModifiedMillis = getMostRecentUpdateTime(
- ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId);
- final Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
+ long lastModifiedMillis = getMostRecentUpdateTime(
+ ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId);
+ Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
+
+ if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
+ {
+ if (lastModified == null)
+ {
+ ChainingUserRegistrySynchronizer.logger.info("Retrieving all groups from user registry '" + zone + "'");
+ }
+ else
+ {
+ ChainingUserRegistrySynchronizer.logger.info("Retrieving groups changed since "
+ + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zone + "'");
+ }
+ }
+
+ // Get current set of known authorities
+ Set allZoneAuthorities = this.retryingTransactionHelper.doInTransaction(
+ new RetryingTransactionCallback>()
+ {
+ public Set execute() throws Throwable
+ {
+ return ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(zoneId,
+ null);
+ }
+ }, false, splitTxns);
+
+ // First, analyze the group structure. Create maps of authorities to their parents for associations to create
+ // and delete. Also deal with 'overlaps' with other zones in the authentication chain.
+ final BatchProcessor groupProcessor = new BatchProcessor(
+ this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getGroups(lastModified),
+ zone + " Group Analysis", this.loggingInterval, this.workerThreads, 20);
+ class Analyzer implements Worker
+ {
+ private final Set allZoneAuthorities;
+ private final Set groupsToCreate = new TreeSet();
+ private final Map> groupAssocsToCreate = new TreeMap>();
+ private final Map> groupAssocsToDelete = new TreeMap>();
+ private long latestTime;
+
+ public Analyzer(final Set allZoneAuthorities, final long latestTime)
+ {
+ this.allZoneAuthorities = allZoneAuthorities;
+ this.latestTime = latestTime;
+ }
+
+ public long getLatestTime()
+ {
+ return this.latestTime;
+ }
+
+ public Set getGroupsToCreate()
+ {
+ return this.groupsToCreate;
+ }
+
+ public Map> getGroupAssocsToCreate()
+ {
+ return this.groupAssocsToCreate;
+ }
+
+ public Map> getGroupAssocsToDelete()
+ {
+ return this.groupAssocsToDelete;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.security.sync.BatchProcessor.Worker#getIdentifier(java.lang.Object)
+ */
+ public String getIdentifier(NodeDescription entry)
+ {
+ return entry.getSourceId();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.security.sync.BatchProcessor.Worker#process(java.lang.Object)
+ */
+ public void process(NodeDescription group) throws Throwable
+ {
+ PropertyMap groupProperties = group.getProperties();
+ String groupName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_NAME);
+ String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName);
+ Set groupZones = ChainingUserRegistrySynchronizer.this.authorityService
+ .getAuthorityZones(groupName);
+
+ if (groupZones == null)
+ {
+ // The group did not exist at all
+ addAssociations(groupName, group.getChildAssociations(), false);
+ }
+ else
+ {
+ // Check whether the group is in any of the authentication chain zones
+ Set intersection = new TreeSet(groupZones);
+ intersection.retainAll(allZoneIds);
+ if (intersection.isEmpty())
+ {
+ // The group exists, but not in a zone that's in the authentication chain. May be due to
+ // upgrade or zone changes. Let's re-zone them
+ if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
+ {
+ ChainingUserRegistrySynchronizer.logger.warn("Updating group '" + groupShortName
+ + "'. This group will in future be assumed to originate from user registry '"
+ + zone + "'.");
+ }
+ ChainingUserRegistrySynchronizer.this.authorityService.removeAuthorityFromZones(groupName,
+ groupZones);
+ ChainingUserRegistrySynchronizer.this.authorityService.addAuthorityToZones(groupName, zoneSet);
+ }
+ if (groupZones.contains(zoneId) || intersection.isEmpty())
+ {
+ // The group already existed in this zone or no valid zone: update the group
+ updateAssociations(group, groupName);
+ }
+ else
+ {
+ // Check whether the group is in any of the higher priority authentication chain zones
+ intersection.retainAll(visitedZoneIds);
+ if (!intersection.isEmpty())
+ {
+ // A group that exists in a different zone with higher precedence
+ return;
+ }
+ // The group existed, but in a zone with lower precedence
+ if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
+ {
+ ChainingUserRegistrySynchronizer.logger
+ .warn("Recreating occluded group '"
+ + groupShortName
+ + "'. This group was previously created through synchronization with a lower priority user registry.");
+ }
+ ChainingUserRegistrySynchronizer.this.authorityService.deleteAuthority(groupName);
+ // create the group
+ addAssociations(groupName, group.getChildAssociations(), false);
+ }
+ }
+
+ synchronized (this)
+ {
+ // Maintain the last modified date
+ Date groupLastModified = group.getLastModified();
+ if (groupLastModified != null)
+ {
+ this.latestTime = Math.max(this.latestTime, groupLastModified.getTime());
+ }
+ }
+ }
+
+ private synchronized void updateAssociations(NodeDescription group, String groupName)
+ {
+ Set oldChildren = ChainingUserRegistrySynchronizer.this.authorityService
+ .getContainedAuthorities(null, groupName, true);
+ Set newChildren = group.getChildAssociations();
+ Set toDelete = new TreeSet(oldChildren);
+ Set toAdd = new TreeSet(newChildren);
+ toDelete.removeAll(newChildren);
+ toAdd.removeAll(oldChildren);
+ addAssociations(groupName, toAdd, true);
+ deleteAssociations(groupName, toDelete);
+ }
+
+ private synchronized void addAssociations(String groupName, Set children, boolean existed)
+ {
+ this.allZoneAuthorities.add(groupName);
+ if (!existed)
+ {
+ this.groupsToCreate.add(groupName);
+ }
+ // Add an entry for the parent itself, in case it is a root group
+ Set parents = this.groupAssocsToCreate.get(groupName);
+ if (parents == null)
+ {
+ parents = new TreeSet();
+ this.groupAssocsToCreate.put(groupName, parents);
+ }
+ for (String child : children)
+ {
+ parents = this.groupAssocsToCreate.get(child);
+ if (parents == null)
+ {
+ parents = new TreeSet();
+ this.groupAssocsToCreate.put(child, parents);
+ }
+ parents.add(groupName);
+ }
+ }
+
+ private synchronized void deleteAssociations(String groupName, Set children)
+ {
+ for (String child : children)
+ {
+ // Make sure each child features as a key in the creation map
+ addAssociations(child, Collections. emptySet(), true);
+
+ Set parents = this.groupAssocsToDelete.get(child);
+ if (parents == null)
+ {
+ parents = new TreeSet();
+ this.groupAssocsToDelete.put(child, parents);
+ }
+ parents.add(groupName);
+ }
+ }
+ }
+
+ Analyzer groupAnalyzer = new Analyzer(allZoneAuthorities, lastModifiedMillis);
+ int groupProcessedCount = groupProcessor.process(groupAnalyzer, splitTxns);
+ final Map> groupAssocsToCreate = groupAnalyzer.getGroupAssocsToCreate();
+ final Map> groupAssocsToDelete = groupAnalyzer.getGroupAssocsToDelete();
+
+ // Prune our set of authorities according to deletions
+ Set deletionCandidates = null;
+ if (force)
+ {
+ deletionCandidates = new TreeSet(allZoneAuthorities);
+ userRegistry.processDeletions(deletionCandidates);
+ allZoneAuthorities.removeAll(deletionCandidates);
+ groupAssocsToCreate.keySet().removeAll(deletionCandidates);
+ groupAssocsToDelete.keySet().removeAll(deletionCandidates);
+ }
+
+ // Sort the group associations in depth-first order (root groups first)
+ Map> sortedGroupAssociations = new LinkedHashMap>(groupAssocsToCreate
+ .size() * 2);
+ List authorityPath = new ArrayList(5);
+ for (String authority : groupAssocsToCreate.keySet())
+ {
+ if (allZoneAuthorities.contains(authority))
+ {
+ authorityPath.add(authority);
+ visitGroupAssociations(authorityPath, allZoneAuthorities, groupAssocsToCreate, sortedGroupAssociations);
+ authorityPath.clear();
+ }
+ }
+
+ // Add the groups and their parent associations in depth-first order
+ final Set groupsToCreate = groupAnalyzer.getGroupsToCreate();
+ BatchProcessor>> groupCreator = new BatchProcessor>>(
+ this.retryingTransactionHelper, this.applicationEventPublisher, sortedGroupAssociations.entrySet(),
+ zone + " Group Creation and Association", this.loggingInterval, 1, 20);
+ groupCreator.process(new Worker>>()
+ {
+
+ public String getIdentifier(Map.Entry> entry)
+ {
+ return entry.getKey() + " " + entry.getValue();
+ }
+
+ public void process(Map.Entry> entry) throws Throwable
+ {
+ Set parents = entry.getValue();
+ String child = entry.getKey();
+
+ if (groupsToCreate.contains(child))
+ {
+ String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child);
+ if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
+ {
+ ChainingUserRegistrySynchronizer.logger.debug("Creating group '" + groupShortName + "'");
+ }
+ // create the group
+ ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType
+ .getAuthorityType(child), groupShortName, groupShortName, zoneSet);
+ }
+ if (!parents.isEmpty())
+ {
+ if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
+ {
+ for (String groupName : parents)
+ {
+ ChainingUserRegistrySynchronizer.logger.debug("Adding '"
+ + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child)
+ + "' to group '"
+ + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName)
+ + "'");
+ }
+ }
+ ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, child);
+ }
+ Set parentsToDelete = groupAssocsToDelete.get(child);
+ if (parentsToDelete != null && !parentsToDelete.isEmpty())
+ {
+ for (String parent : parentsToDelete)
+ {
+ if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
+ {
+ ChainingUserRegistrySynchronizer.logger
+ .debug("Removing '"
+ + ChainingUserRegistrySynchronizer.this.authorityService
+ .getShortName(child)
+ + "' from group '"
+ + ChainingUserRegistrySynchronizer.this.authorityService
+ .getShortName(parent) + "'");
+ }
+ ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, child);
+ }
+ }
+ }
+ }, splitTxns);
+
+ // Process persons and their parent associations
+
+ lastModifiedMillis = getMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE,
+ zoneId);
+ lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{
if (lastModified == null)
@@ -479,10 +779,15 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
}
final BatchProcessor personProcessor = new BatchProcessor(
this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getPersons(lastModified),
- zone + " User Creation", this.loggingInterval, this.workerThreads, 10);
- class CreationWorker implements Worker
+ zone + " User Creation and Association", this.loggingInterval, this.workerThreads, 10);
+ class PersonWorker implements Worker
{
- private long latestTime = lastModifiedMillis;
+ private long latestTime;
+
+ public PersonWorker(final long latestTime)
+ {
+ this.latestTime = latestTime;
+ }
public long getLatestTime()
{
@@ -562,6 +867,42 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
ChainingUserRegistrySynchronizer.this.personService.createPerson(personProperties, zoneSet);
}
}
+ // Maintain associations
+ Set parents = groupAssocsToCreate.get(personName);
+ if (parents != null && !parents.isEmpty())
+ {
+ if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
+ {
+ for (String groupName : parents)
+ {
+ ChainingUserRegistrySynchronizer.logger.debug("Adding '"
+ + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(personName)
+ + "' to group '"
+ + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName)
+ + "'");
+ }
+ }
+ ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, personName);
+ }
+ Set parentsToDelete = groupAssocsToDelete.get(personName);
+ if (parentsToDelete != null && !parentsToDelete.isEmpty())
+ {
+ for (String parent : parentsToDelete)
+ {
+ if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
+ {
+ ChainingUserRegistrySynchronizer.logger
+ .debug("Removing '"
+ + ChainingUserRegistrySynchronizer.this.authorityService
+ .getShortName(personName)
+ + "' from group '"
+ + ChainingUserRegistrySynchronizer.this.authorityService
+ .getShortName(parent) + "'");
+ }
+ ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, personName);
+ }
+ }
+
synchronized (this)
{
// Maintain the last modified date
@@ -574,293 +915,22 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
}
}
- CreationWorker creations = new CreationWorker();
- int processedCount = personProcessor.process(creations, splitTxns);
- long latestTime = creations.getLatestTime();
- if (latestTime != -1)
- {
- setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId,
- latestTime, splitTxns);
- }
+ PersonWorker persons = new PersonWorker(lastModifiedMillis);
+ int personProcessedCount = personProcessor.process(persons, splitTxns);
- // Remember we have visited this zone
- visitedZoneIds.add(zoneId);
-
- return processedCount;
- }
-
- /**
- * Synchronizes local groups with a {@link UserRegistry} for a particular zone and also handles deletions of local
- * groups and users.
- *
- * @param zone
- * the zone id. This identifier is used to tag all created groups, so that in the future we can tell
- * those that have been deleted from the registry.
- * @param userRegistry
- * the user registry for the zone.
- * @param force
- * true
if user and group deletions are to be processed.
- * @param splitTxns
- * Can the modifications to Alfresco be split across multiple transactions for maximum performance? If
- * true
, users and groups are created/updated in batches of 10 for increased performance. If
- * false
, all users and groups are processed in the current transaction. This is required if
- * calling synchronously (e.g. in response to an authentication event in the same transaction).
- * @param visitedZoneIds
- * the set of zone ids already processed. These zones have precedence over the current zone when it comes
- * to group name 'collisions'. If a group is queried that already exists locally but is tagged with one
- * of the zones in this set, then it will be ignored as this zone has lower priority.
- * @param allZoneIds
- * the set of all zone ids in the authentication chain. Helps us work out whether the zone information
- * recorded against a group is invalid for the current authentication chain and whether the group needs
- * to be 're-zoned'.
- * @return the number of groups processed
- */
- private int syncGroupsWithPlugin(final String zone, UserRegistry userRegistry, boolean force, boolean splitTxns,
- final Set visitedZoneIds, final Set allZoneIds)
- {
- // Create a prefixed zone ID for use with the authority service
- final String zoneId = AuthorityService.ZONE_AUTH_EXT_PREFIX + zone;
-
- // The set of zones we associate with new objects (default plus registry specific)
- final Set zoneSet = getZones(zoneId);
-
- final long lastModifiedMillis = getMostRecentUpdateTime(
- ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId);
- final Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
-
- if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
- {
- if (lastModified == null)
- {
- ChainingUserRegistrySynchronizer.logger.info("Retrieving all groups from user registry '" + zone + "'");
- }
- else
- {
- ChainingUserRegistrySynchronizer.logger.info("Retrieving groups changed since "
- + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zone + "'");
- }
- }
-
- // Get current set of known authorities
- Set deletionCandidates = this.retryingTransactionHelper.doInTransaction(
- new RetryingTransactionCallback>()
- {
- public Set execute() throws Throwable
- {
- return ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(zoneId,
- null);
- }
- }, false, splitTxns);
-
- final BatchProcessor groupProcessor = new BatchProcessor(
- this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getGroups(lastModified,
- deletionCandidates, force), zone + " Group Creation", this.loggingInterval, this.workerThreads,
- 20);
- class CreationWorker implements Worker
- {
- private final Set> groupAssocsToCreate = new TreeSet>(
- new Comparator>()
- {
-
- public int compare(Pair o1, Pair o2)
- {
- int result = o1.getFirst().compareTo(o2.getFirst());
- if (result == 0)
- {
- return o1.getSecond().compareTo(o2.getSecond());
- }
- return result;
- }
- });
- private long latestTime = lastModifiedMillis;
-
- public long getLatestTime()
- {
- return this.latestTime;
- }
-
- public Set> getGroupAssocsToCreate()
- {
- return this.groupAssocsToCreate;
- }
-
- /*
- * (non-Javadoc)
- * @see org.alfresco.repo.security.sync.BatchProcessor.Worker#getIdentifier(java.lang.Object)
- */
- public String getIdentifier(NodeDescription entry)
- {
- return entry.getSourceId();
- }
-
- /*
- * (non-Javadoc)
- * @see org.alfresco.repo.security.sync.BatchProcessor.Worker#process(java.lang.Object)
- */
- public void process(NodeDescription group) throws Throwable
- {
- PropertyMap groupProperties = group.getProperties();
- String groupName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_NAME);
- String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName);
- Set groupZones = ChainingUserRegistrySynchronizer.this.authorityService
- .getAuthorityZones(groupName);
-
- if (groupZones == null)
- {
- // The group did not exist at all
- if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
- {
- ChainingUserRegistrySynchronizer.logger.debug("Creating group '" + groupShortName + "'");
- }
- // create the group
- ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType
- .getAuthorityType(groupName), groupShortName, (String) groupProperties
- .get(ContentModel.PROP_AUTHORITY_DISPLAY_NAME), zoneSet);
- Set children = group.getChildAssociations();
- if (!children.isEmpty())
- {
- synchronized (this)
- {
- for (String child : children)
- {
- this.groupAssocsToCreate.add(new Pair(groupName, child));
- }
- }
- }
- }
- else
- {
- // Check whether the group is in any of the authentication chain zones
- Set intersection = new TreeSet(groupZones);
- intersection.retainAll(allZoneIds);
- if (intersection.isEmpty())
- {
- // The group exists, but not in a zone that's in the authentication chain. May be due to
- // upgrade or zone changes. Let's re-zone them
- if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
- {
- ChainingUserRegistrySynchronizer.logger.warn("Updating group '" + groupShortName
- + "'. This group will in future be assumed to originate from user registry '"
- + zone + "'.");
- }
- ChainingUserRegistrySynchronizer.this.authorityService.removeAuthorityFromZones(groupName,
- groupZones);
- ChainingUserRegistrySynchronizer.this.authorityService.addAuthorityToZones(groupName, zoneSet);
- }
- if (groupZones.contains(zoneId) || intersection.isEmpty())
- {
- // The group already existed in this zone or no valid zone: update the group
- Set oldChildren = ChainingUserRegistrySynchronizer.this.authorityService
- .getContainedAuthorities(null, groupName, true);
- Set newChildren = group.getChildAssociations();
- Set toDelete = new TreeSet(oldChildren);
- Set toAdd = new TreeSet(newChildren);
- toDelete.removeAll(newChildren);
- toAdd.removeAll(oldChildren);
- if (!toAdd.isEmpty())
- {
- synchronized (this)
- {
- for (String child : toAdd)
- {
- this.groupAssocsToCreate.add(new Pair(groupName, child));
- }
- }
- }
- for (String child : toDelete)
- {
- if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
- {
- ChainingUserRegistrySynchronizer.logger.debug("Removing '"
- + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child)
- + "' from group '" + groupShortName + "'");
- }
- ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(groupName, child);
- }
- }
- else
- {
- // Check whether the group is in any of the higher priority authentication chain zones
- intersection.retainAll(visitedZoneIds);
- if (!intersection.isEmpty())
- {
- // A group that exists in a different zone with higher precedence
- return;
- }
- // The group existed, but in a zone with lower precedence
- if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
- {
- ChainingUserRegistrySynchronizer.logger
- .warn("Recreating occluded group '"
- + groupShortName
- + "'. This group was previously created through synchronization with a lower priority user registry.");
- }
- ChainingUserRegistrySynchronizer.this.authorityService.deleteAuthority(groupName);
- // create the group
- ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType
- .getAuthorityType(groupName), groupShortName, (String) groupProperties
- .get(ContentModel.PROP_AUTHORITY_DISPLAY_NAME), zoneSet);
- Set children = group.getChildAssociations();
- if (!children.isEmpty())
- {
- synchronized (this)
- {
- for (String child : children)
- {
- this.groupAssocsToCreate.add(new Pair(groupName, child));
- }
- }
- }
- }
- }
-
- synchronized (this)
- {
- // Maintain the last modified date
- Date groupLastModified = group.getLastModified();
- if (groupLastModified != null)
- {
- this.latestTime = Math.max(this.latestTime, groupLastModified.getTime());
- }
- }
- }
- }
-
- CreationWorker creations = new CreationWorker();
- int processedCount = groupProcessor.process(creations, splitTxns);
- long latestTime = creations.getLatestTime();
+ // Only now that the whole tree has been processed is it safe to persist the last modified dates
+ long latestTime = groupAnalyzer.getLatestTime();
if (latestTime != -1)
{
setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, latestTime,
splitTxns);
}
-
- // Add the new associations, now that we have created everything
- BatchProcessor> groupAssocProcessor = new BatchProcessor>(
- this.retryingTransactionHelper, this.applicationEventPublisher, creations.getGroupAssocsToCreate(),
- zone + " Group Association Creation", this.loggingInterval, this.workerThreads, 20);
- groupAssocProcessor.process(new Worker>()
+ latestTime = persons.getLatestTime();
+ if (latestTime != -1)
{
-
- public String getIdentifier(Pair entry)
- {
- return entry.toString();
- }
-
- public void process(Pair entry) throws Throwable
- {
- String groupName = entry.getFirst();
- String child = entry.getSecond();
- if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
- {
- ChainingUserRegistrySynchronizer.logger.debug("Adding '"
- + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child)
- + "' to group '"
- + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName) + "'");
- }
- ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(groupName, child);
- }
- }, splitTxns);
+ setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId,
+ latestTime, splitTxns);
+ }
// Delete authorities if we have complete information for the zone
if (force)
@@ -868,8 +938,20 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
BatchProcessor authorityDeletionProcessor = new BatchProcessor(
this.retryingTransactionHelper, this.applicationEventPublisher, deletionCandidates, zone
+ " Authority Deletion", this.loggingInterval, this.workerThreads, 10);
- processedCount += authorityDeletionProcessor.process(new Worker()
+ class AuthorityDeleter implements Worker
{
+ private int personProcessedCount;
+ private int groupProcessedCount;
+
+ public int getPersonProcessedCount()
+ {
+ return this.personProcessedCount;
+ }
+
+ public int getGroupProcessedCount()
+ {
+ return this.groupProcessedCount;
+ }
public String getIdentifier(String entry)
{
@@ -885,6 +967,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
ChainingUserRegistrySynchronizer.logger.debug("Deleting user '" + authority + "'");
}
ChainingUserRegistrySynchronizer.this.personService.deletePerson(authority);
+ synchronized (this)
+ {
+ this.personProcessedCount++;
+ }
}
else
{
@@ -895,15 +981,76 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
+ "'");
}
ChainingUserRegistrySynchronizer.this.authorityService.deleteAuthority(authority);
+ synchronized (this)
+ {
+ this.groupProcessedCount++;
+ }
}
}
- }, splitTxns);
+ }
+ AuthorityDeleter authorityDeleter = new AuthorityDeleter();
+ authorityDeletionProcessor.process(authorityDeleter, splitTxns);
+ groupProcessedCount += authorityDeleter.getGroupProcessedCount();
+ personProcessedCount += authorityDeleter.getPersonProcessedCount();
}
// Remember we have visited this zone
visitedZoneIds.add(zoneId);
- return processedCount;
+ if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
+ {
+ ChainingUserRegistrySynchronizer.logger.info("Finished synchronizing users and groups with user registry '"
+ + zone + "'");
+ ChainingUserRegistrySynchronizer.logger.info(personProcessedCount + " user(s) and " + groupProcessedCount
+ + " group(s) processed");
+ }
+ }
+
+ /**
+ * Visits the last authority in the given list by recursively visiting its parents in associationsOld and then
+ * adding the authority to associationsNew. Used to sort associationsOld into 'depth-first' order.
+ *
+ * @param authorityPath
+ * The authority to visit, preceeded by all its descendants. Allows detection of cyclic child
+ * associations.
+ * @param allAuthorities
+ * the set of all known authorities
+ * @param associationsOld
+ * the association map to sort
+ * @param associationsNew
+ * the association map to add to in depth first order
+ */
+ private void visitGroupAssociations(List authorityPath, Set allAuthorities,
+ Map> associationsOld, Map> associationsNew)
+ {
+ String authorityName = authorityPath.get(authorityPath.size() - 1);
+ if (!associationsNew.containsKey(authorityName))
+ {
+ Set associations = associationsOld.get(authorityName);
+
+ if (!associations.isEmpty())
+ {
+ // Filter out associations to unknown parent authorities
+ associations.retainAll(allAuthorities);
+ int insertIndex = authorityPath.size();
+ for (String parentAuthority : associations)
+ {
+ // Prevent cyclic paths
+ if (!authorityPath.contains(parentAuthority))
+ {
+ authorityPath.add(parentAuthority);
+ visitGroupAssociations(authorityPath, allAuthorities, associationsOld, associationsNew);
+ authorityPath.remove(insertIndex);
+ }
+ }
+ }
+
+ // Omit associations from users from this map, as they will be processed separately
+ if (AuthorityType.getAuthorityType(authorityName) != AuthorityType.USER)
+ {
+ associationsNew.put(authorityName, associations);
+ }
+ }
}
/**
diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java
index 4657d483d8..0d4940ba14 100644
--- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java
+++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java
@@ -631,25 +631,29 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
{
return this.zoneId;
}
+
+
- /*
- * (non-Javadoc)
- * @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date, java.util.Set, boolean)
+ /* (non-Javadoc)
+ * @see org.alfresco.repo.security.sync.UserRegistry#processDeletions(java.util.Set)
*/
- public Collection getGroups(Date modifiedSince, Set candidateAuthoritiesForDeletion,
- boolean prune)
+ public void processDeletions(Set candidateAuthoritiesForDeletion)
{
- if (prune)
+ for (NodeDescription person : this.persons)
{
- for (NodeDescription person : this.persons)
- {
- candidateAuthoritiesForDeletion.remove(person.getProperties().get(ContentModel.PROP_USERNAME));
- }
- for (NodeDescription group : this.groups)
- {
- candidateAuthoritiesForDeletion.remove(group.getProperties().get(ContentModel.PROP_AUTHORITY_NAME));
- }
+ candidateAuthoritiesForDeletion.remove(person.getProperties().get(ContentModel.PROP_USERNAME));
}
+ for (NodeDescription group : this.groups)
+ {
+ candidateAuthoritiesForDeletion.remove(group.getProperties().get(ContentModel.PROP_AUTHORITY_NAME));
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date)
+ */
+ public Collection getGroups(Date modifiedSince)
+ {
return this.groups;
}
diff --git a/source/java/org/alfresco/repo/security/sync/UserRegistry.java b/source/java/org/alfresco/repo/security/sync/UserRegistry.java
index f45fb649c9..58da7b888c 100644
--- a/source/java/org/alfresco/repo/security/sync/UserRegistry.java
+++ b/source/java/org/alfresco/repo/security/sync/UserRegistry.java
@@ -50,22 +50,23 @@ public interface UserRegistry
public Collection getPersons(Date modifiedSince);
/**
- * Gets descriptions of all the groups in the user registry or all those changed since a certain date. Group
- * associations should be restricted to those in the given set of known authorities. Optionally this set is 'pruned'
- * to contain only those authorities that no longer exist in the user registry, i.e. the deletion candidates.
+ * Gets descriptions of all the groups in the user registry or all those changed since a certain date.
*
* @param modifiedSince
* if non-null, then only descriptions of groups modified since this date should be returned; if
* null
then descriptions of all groups should be returned.
- * @param knownAuthorities
- * the current set of known authorities
- * @param prune
- * should this set be 'pruned' so that it contains only those authorities that do not exist in the
- * registry, i.e. the deletion candidates?
* @return a {@link Collection} of {@link NodeDescription}s of all the groups in the user registry or all those
* changed since a certain date. The description properties should correspond to those of an Alfresco
* authority node.
*/
- public Collection getGroups(Date modifiedSince, Set knownAuthorities, boolean prune);
+ public Collection getGroups(Date modifiedSince);
+ /**
+ * Retrieves the complete set of known users and groups from the user registry and removes them from the set of
+ * candidate local authorities to be deleted.
+ *
+ * @param candidateAuthoritiesForDeletion
+ * the candidate authorities for deletion
+ */
+ public void processDeletions(final Set candidateAuthoritiesForDeletion);
}
diff --git a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java
index 86e4180c31..7f65dc2397 100644
--- a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java
+++ b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java
@@ -480,14 +480,11 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial
return new PersonCollection(modifiedSince);
}
- /**
- * Retrieves the complete set of known users and groups from the LDAP directory and removes them from the set of
- * candidate local authorities to be deleted.
- *
- * @param candidateAuthoritiesForDeletion
- * the candidate authorities for deletion
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.security.sync.UserRegistry#processDeletions(java.util.Set)
*/
- private void processDeletions(final Set candidateAuthoritiesForDeletion)
+ public void processDeletions(final Set candidateAuthoritiesForDeletion)
{
processQuery(new SearchCallback()
{
@@ -562,20 +559,8 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial
* (non-Javadoc)
* @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date)
*/
- public Collection getGroups(Date modifiedSince, final Set candidateAuthoritiesForDeletion,
- boolean prune)
+ public Collection getGroups(Date modifiedSince)
{
- // Take the given set of authorities as a starting point for the set of all authorities
- final Set allAuthorities = new TreeSet(candidateAuthoritiesForDeletion);
-
- // If required, work out what authority deletions are required by pruning down the deletion set and the set of
- // all authorities
- if (prune)
- {
- processDeletions(candidateAuthoritiesForDeletion);
- allAuthorities.removeAll(candidateAuthoritiesForDeletion);
- }
-
// Work out whether the user and group trees are disjoint. This may allow us to optimize reverse DN
// resolution.
final LdapName groupDistinguishedNamePrefix;
@@ -636,7 +621,6 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial
group = new NodeDescription(result.getNameInNamespace());
group.getProperties().put(ContentModel.PROP_AUTHORITY_NAME, gid);
lookup.put(gid, group);
- allAuthorities.add(gid);
}
else if (LDAPUserRegistry.this.errorOnDuplicateGID)
{
@@ -799,12 +783,6 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial
LDAPUserRegistry.logger.debug("Found " + lookup.size());
}
- // Post-process the group associations to filter out those that point to excluded users or groups (now that we
- // know the full set of groups)
- for (NodeDescription group : lookup.values())
- {
- group.getChildAssociations().retainAll(allAuthorities);
- }
return lookup.values();
}
diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java
index 7139c57d4c..24b194f08b 100644
--- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java
+++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java
@@ -234,6 +234,18 @@ public class NodeServiceImpl implements NodeService, VersionModel
throw new UnsupportedOperationException(MSG_UNSUPPORTED);
}
+ /**
+ * @throws UnsupportedOperationException always
+ */
+ public List addChild(Collection parentRefs,
+ NodeRef childRef,
+ QName assocTypeQName,
+ QName qname) throws InvalidNodeRefException
+ {
+ // This operation is not supported for a version store
+ throw new UnsupportedOperationException(MSG_UNSUPPORTED);
+ }
+
/**
* @throws UnsupportedOperationException always
*/
diff --git a/source/java/org/alfresco/service/cmr/repository/NodeService.java b/source/java/org/alfresco/service/cmr/repository/NodeService.java
index d22704a3a0..d003ae02fd 100644
--- a/source/java/org/alfresco/service/cmr/repository/NodeService.java
+++ b/source/java/org/alfresco/service/cmr/repository/NodeService.java
@@ -324,6 +324,26 @@ public interface NodeService
QName assocTypeQName,
QName qname) throws InvalidNodeRefException;
+ /**
+ * Associates a given child node with a given collection of parents. All nodes must belong to the same store.
+ *
+ *
+ *
+ * @param parentRefs
+ * @param childRef
+ * @param assocTypeQName the qualified name of the association type as defined in the datadictionary
+ * @param qname the qualified name of the association
+ * @return Returns a reference to the newly created child association
+ * @throws InvalidNodeRefException if the parent or child nodes could not be found
+ * @throws CyclicChildRelationshipException if the child partakes in a cyclic relationship after the add
+ */
+ @Auditable(key = Auditable.Key.ARG_0 ,parameters = {"parentRefs", "childRef", "assocTypeQName", "qname"})
+ public List addChild(
+ Collection parentRefs,
+ NodeRef childRef,
+ QName assocTypeQName,
+ QName qname) throws InvalidNodeRefException;
+
/**
* Severs all parent-child relationships between two nodes.
*
diff --git a/source/java/org/alfresco/service/cmr/security/AuthorityService.java b/source/java/org/alfresco/service/cmr/security/AuthorityService.java
index 6fd74fa9cb..6442841087 100644
--- a/source/java/org/alfresco/service/cmr/security/AuthorityService.java
+++ b/source/java/org/alfresco/service/cmr/security/AuthorityService.java
@@ -24,6 +24,7 @@
*/
package org.alfresco.service.cmr.security;
+import java.util.Collection;
import java.util.Set;
import org.alfresco.service.Auditable;
@@ -215,6 +216,18 @@ public interface AuthorityService
@Auditable(parameters = {"parentName", "childName"})
public void addAuthority(String parentName, String childName);
+ /**
+ * Set a given child authority to be included by the given parent authorities. For example, adding a
+ * group to groups or adding a user to groups.
+ *
+ * @param parentNames -
+ * the full name string identifier for the parents.
+ * @param childName -
+ * the string identifier for the child.
+ */
+ @Auditable(parameters = {"parentNames", "childName"})
+ public void addAuthority(Collection parentNames, String childName);
+
/**
* Remove an authority as a member of another authority. The child authority
* will still exist. If the child authority was not created as a root