Merged V3.2 to HEAD

15099: Do not allow referential integrity problems in user registry data (dangling references to users in groups) fail the whole sync operation. Just warn and continue.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@15100 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Dave Ward
2009-07-08 09:59:43 +00:00
parent fd14602b9d
commit a929a7fd74
2 changed files with 114 additions and 55 deletions

View File

@@ -196,38 +196,39 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
Set<String> visitedZoneIds = new TreeSet<String>(); Set<String> visitedZoneIds = new TreeSet<String>();
for (String id : this.applicationContextManager.getInstanceIds()) for (String id : this.applicationContextManager.getInstanceIds())
{ {
StringBuilder builder = new StringBuilder(32);
builder.append(AuthorityService.ZONE_AUTH_EXT_PREFIX);
builder.append(id);
String zoneId = builder.toString();
ApplicationContext context = this.applicationContextManager.getApplicationContext(id); ApplicationContext context = this.applicationContextManager.getApplicationContext(id);
try try
{ {
UserRegistry plugin = (UserRegistry) context.getBean(this.sourceBeanName); UserRegistry plugin = (UserRegistry) context.getBean(this.sourceBeanName);
if (!(plugin instanceof ActivateableBean) || ((ActivateableBean) plugin).isActive()) if (!(plugin instanceof ActivateableBean) || ((ActivateableBean) plugin).isActive())
{ {
ChainingUserRegistrySynchronizer.logger.info("Synchronizing users and groups with user registry '" if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
+ id + "'"); {
if (force) ChainingUserRegistrySynchronizer.logger
.info("Synchronizing users and groups with user registry '" + id + "'");
}
if (force && ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
{ {
ChainingUserRegistrySynchronizer.logger ChainingUserRegistrySynchronizer.logger
.warn("Forced synchronization with user registry '" .warn("Forced synchronization with user registry '"
+ id + id
+ "'; some users and groups previously created by synchronization with this user registry may be removed."); + "'; some users and groups previously created by synchronization with this user registry may be removed.");
} }
int personsProcessed = syncPersonsWithPlugin(zoneId, plugin, force, visitedZoneIds); int personsProcessed = syncPersonsWithPlugin(id, plugin, force, visitedZoneIds);
int groupsProcessed = syncGroupsWithPlugin(zoneId, plugin, force, visitedZoneIds); int groupsProcessed = syncGroupsWithPlugin(id, plugin, force, visitedZoneIds);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{
ChainingUserRegistrySynchronizer.logger ChainingUserRegistrySynchronizer.logger
.info("Finished synchronizing users and groups with user registry '" + zoneId + "'"); .info("Finished synchronizing users and groups with user registry '" + id + "'");
ChainingUserRegistrySynchronizer.logger.info(personsProcessed + " user(s) and " + groupsProcessed ChainingUserRegistrySynchronizer.logger.info(personsProcessed + " user(s) and "
+ " group(s) processed"); + groupsProcessed + " group(s) processed");
}
} }
} }
catch (NoSuchBeanDefinitionException e) catch (NoSuchBeanDefinitionException e)
{ {
// Ignore and continue // Ignore and continue
} }
visitedZoneIds.add(zoneId);
} }
} }
@@ -264,7 +265,7 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
/** /**
* Synchronizes local users (persons) with a {@link UserRegistry} for a particular zone. * Synchronizes local users (persons) with a {@link UserRegistry} for a particular zone.
* *
* @param zoneId * @param zone
* the zone id. This identifier is used to tag all created users, so that in the future we can tell those * 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. * that have been deleted from the registry.
* @param userRegistry * @param userRegistry
@@ -278,21 +279,26 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
* the zones in this set, then it will be ignored as this zone has lower priority. * the zones in this set, then it will be ignored as this zone has lower priority.
* @return the number of users processed * @return the number of users processed
*/ */
private int syncPersonsWithPlugin(String zoneId, UserRegistry userRegistry, boolean force, private int syncPersonsWithPlugin(String zone, UserRegistry userRegistry, boolean force, Set<String> visitedZoneIds)
Set<String> visitedZoneIds)
{ {
// Create a prefixed zone ID for use with the authority service
String zoneId = AuthorityService.ZONE_AUTH_EXT_PREFIX + zone;
int processedCount = 0; int processedCount = 0;
long lastModifiedMillis = force ? -1L : getMostRecentUpdateTime( long lastModifiedMillis = force ? -1L : getMostRecentUpdateTime(
ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId); ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId);
Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{
if (lastModified == null) if (lastModified == null)
{ {
ChainingUserRegistrySynchronizer.logger.info("Retrieving all users from user registry '" + zoneId + "'"); ChainingUserRegistrySynchronizer.logger.info("Retrieving all users from user registry '" + zone + "'");
} }
else else
{ {
ChainingUserRegistrySynchronizer.logger.info("Retrieving users changed since " ChainingUserRegistrySynchronizer.logger.info("Retrieving users changed since "
+ DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zoneId + "'"); + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zone + "'");
}
} }
Iterator<NodeDescription> persons = userRegistry.getPersons(lastModified); Iterator<NodeDescription> persons = userRegistry.getPersons(lastModified);
Set<String> personsToDelete = this.authorityService.getAllAuthoritiesInZone(zoneId, AuthorityType.USER); Set<String> personsToDelete = this.authorityService.getAllAuthoritiesInZone(zoneId, AuthorityType.USER);
@@ -304,7 +310,10 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
if (personsToDelete.remove(personName)) if (personsToDelete.remove(personName))
{ {
// The person already existed in this zone: update the person // The person already existed in this zone: update the person
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{
ChainingUserRegistrySynchronizer.logger.info("Updating user '" + personName + "'"); ChainingUserRegistrySynchronizer.logger.info("Updating user '" + personName + "'");
}
this.personService.setPersonProperties(personName, personProperties); this.personService.setPersonProperties(personName, personProperties);
} }
else else
@@ -320,17 +329,23 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
continue; continue;
} }
// The person existed, but in a zone with lower precedence // The person existed, but in a zone with lower precedence
if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
{
ChainingUserRegistrySynchronizer.logger ChainingUserRegistrySynchronizer.logger
.warn("Recreating occluded user '" .warn("Recreating occluded user '"
+ personName + personName
+ "'. This user was previously created manually or through synchronization with a lower priority user registry."); + "'. This user was previously created manually or through synchronization with a lower priority user registry.");
}
this.personService.deletePerson(personName); this.personService.deletePerson(personName);
} }
else else
{ {
// The person did not exist at all // The person did not exist at all
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{
ChainingUserRegistrySynchronizer.logger.info("Creating user '" + personName + "'"); ChainingUserRegistrySynchronizer.logger.info("Creating user '" + personName + "'");
} }
}
this.personService.createPerson(personProperties, getZones(zoneId)); this.personService.createPerson(personProperties, getZones(zoneId));
} }
// Increment the count of processed people // Increment the count of processed people
@@ -347,8 +362,11 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
if (force && !personsToDelete.isEmpty()) if (force && !personsToDelete.isEmpty())
{ {
for (String personName : personsToDelete) for (String personName : personsToDelete)
{
if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
{ {
ChainingUserRegistrySynchronizer.logger.warn("Deleting user '" + personName + "'"); ChainingUserRegistrySynchronizer.logger.warn("Deleting user '" + personName + "'");
}
this.personService.deletePerson(personName); this.personService.deletePerson(personName);
processedCount++; processedCount++;
} }
@@ -360,13 +378,16 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
lastModifiedMillis); lastModifiedMillis);
} }
// Remember we have visited this zone
visitedZoneIds.add(zoneId);
return processedCount; return processedCount;
} }
/** /**
* Synchronizes local groups (authorities) with a {@link UserRegistry} for a particular zone. * Synchronizes local groups (authorities) with a {@link UserRegistry} for a particular zone.
* *
* @param zoneId * @param zone
* the zone id. This identifier is used to tag all created groups, so that in the future we can tell * 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. * those that have been deleted from the registry.
* @param userRegistry * @param userRegistry
@@ -380,20 +401,26 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
* of the zones in this set, then it will be ignored as this zone has lower priority. * of the zones in this set, then it will be ignored as this zone has lower priority.
* @return the number of groups processed * @return the number of groups processed
*/ */
private int syncGroupsWithPlugin(String zoneId, UserRegistry userRegistry, boolean force, Set<String> visitedZoneIds) private int syncGroupsWithPlugin(String zone, UserRegistry userRegistry, boolean force, Set<String> visitedZoneIds)
{ {
// Create a prefixed zone ID for use with the authority service
String zoneId = AuthorityService.ZONE_AUTH_EXT_PREFIX + zone;
int processedCount = 0; int processedCount = 0;
long lastModifiedMillis = force ? -1L : getMostRecentUpdateTime( long lastModifiedMillis = force ? -1L : getMostRecentUpdateTime(
ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId); ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId);
Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{
if (lastModified == null) if (lastModified == null)
{ {
ChainingUserRegistrySynchronizer.logger.info("Retrieving all groups from user registry '" + zoneId + "'"); ChainingUserRegistrySynchronizer.logger.info("Retrieving all groups from user registry '" + zone + "'");
} }
else else
{ {
ChainingUserRegistrySynchronizer.logger.info("Retrieving groups changed since " ChainingUserRegistrySynchronizer.logger.info("Retrieving groups changed since "
+ DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zoneId + "'"); + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zone + "'");
}
} }
Iterator<NodeDescription> groups = userRegistry.getGroups(lastModified); Iterator<NodeDescription> groups = userRegistry.getGroups(lastModified);
@@ -418,10 +445,13 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
groupAssocsToCreate.put(groupName, toAdd); groupAssocsToCreate.put(groupName, toAdd);
} }
for (String child : toDelete) for (String child : toDelete)
{
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{ {
ChainingUserRegistrySynchronizer.logger.info("Removing '" ChainingUserRegistrySynchronizer.logger.info("Removing '"
+ this.authorityService.getShortName(child) + "' from group '" + this.authorityService.getShortName(child) + "' from group '"
+ this.authorityService.getShortName(groupName) + "'"); + this.authorityService.getShortName(groupName) + "'");
}
this.authorityService.removeAuthority(groupName, child); this.authorityService.removeAuthority(groupName, child);
} }
} }
@@ -438,16 +468,22 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
continue; continue;
} }
// The group existed, but in a zone with lower precedence // The group existed, but in a zone with lower precedence
if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
{
ChainingUserRegistrySynchronizer.logger ChainingUserRegistrySynchronizer.logger
.warn("Recreating occluded group '" .warn("Recreating occluded group '"
+ groupShortName + groupShortName
+ "'. This group was previously created manually or through synchronization with a lower priority user registry."); + "'. This group was previously created manually or through synchronization with a lower priority user registry.");
}
this.authorityService.deleteAuthority(groupName); this.authorityService.deleteAuthority(groupName);
} }
else else
{
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{ {
ChainingUserRegistrySynchronizer.logger.info("Creating group '" + groupShortName + "'"); ChainingUserRegistrySynchronizer.logger.info("Creating group '" + groupShortName + "'");
} }
}
// create the group // create the group
this.authorityService.createAuthority(AuthorityType.getAuthorityType(groupName), groupShortName, this.authorityService.createAuthority(AuthorityType.getAuthorityType(groupName), groupShortName,
@@ -476,10 +512,26 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
for (String child : entry.getValue()) for (String child : entry.getValue())
{ {
String groupName = entry.getKey(); String groupName = entry.getKey();
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{
ChainingUserRegistrySynchronizer.logger.info("Adding '" + this.authorityService.getShortName(child) ChainingUserRegistrySynchronizer.logger.info("Adding '" + this.authorityService.getShortName(child)
+ "' to group '" + this.authorityService.getShortName(groupName) + "'"); + "' to group '" + this.authorityService.getShortName(groupName) + "'");
}
try
{
this.authorityService.addAuthority(groupName, child); this.authorityService.addAuthority(groupName, child);
} }
catch (Exception e)
{
// Let's not allow referential integrity problems (dangling references) kill the whole process
if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
{
ChainingUserRegistrySynchronizer.logger.warn("Failed to add '"
+ this.authorityService.getShortName(child) + "' to group '"
+ this.authorityService.getShortName(groupName) + "'", e);
}
}
}
} }
@@ -487,9 +539,12 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
if (force && !groupsToDelete.isEmpty()) if (force && !groupsToDelete.isEmpty())
{ {
for (String group : groupsToDelete) for (String group : groupsToDelete)
{
if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
{ {
ChainingUserRegistrySynchronizer.logger.warn("Deleting group '" ChainingUserRegistrySynchronizer.logger.warn("Deleting group '"
+ this.authorityService.getShortName(group) + "'"); + this.authorityService.getShortName(group) + "'");
}
this.authorityService.deleteAuthority(group); this.authorityService.deleteAuthority(group);
processedCount++; processedCount++;
} }
@@ -501,6 +556,9 @@ public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronize
lastModifiedMillis); lastModifiedMillis);
} }
// Remember we have visited this zone
visitedZoneIds.add(zoneId);
return processedCount; return processedCount;
} }

View File

@@ -220,7 +220,8 @@ public class ChainingUserRegistrySynchronizerTest extends BaseSpringTest
/** /**
* Tests a forced update of the test users and groups. Also tests that groups and users that previously existed in * Tests a forced update of the test users and groups. Also tests that groups and users that previously existed in
* Z2 get moved when they appear in Z1. The layout is as follows * Z2 get moved when they appear in Z1. Also tests that 'dangling references' to removed users (U4, U5) do not cause
* any problems. The layout is as follows
* *
* <pre> * <pre>
* Z1 * Z1
@@ -252,7 +253,7 @@ public class ChainingUserRegistrySynchronizerTest extends BaseSpringTest
newPerson("U1", "somenewemail@alfresco.com"), newPerson("U3"), newPerson("U6") newPerson("U1", "somenewemail@alfresco.com"), newPerson("U3"), newPerson("U6")
}, new NodeDescription[] }, new NodeDescription[]
{ {
newGroup("G2", "U1", "U3", "U6"), newGroup("G6", "U3", "G7"), newGroup("G7") newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U4", "U5")
})); }));
this.synchronizer.synchronize(true); this.synchronizer.synchronize(true);
assertExists("Z1", "U2"); assertExists("Z1", "U2");