diff --git a/config/alfresco-global.properties.sample b/config/alfresco-global.properties.sample
index 6f6be43b95..3645ace537 100644
--- a/config/alfresco-global.properties.sample
+++ b/config/alfresco-global.properties.sample
@@ -34,10 +34,14 @@
#db.url=jdbc:oracle:thin:@localhost:1521:alfresco
#
-# SQLServer connection (note you must enable TCP protocol on fixed port 1433)
+# SQLServer connection
+# Requires jTDS driver version 1.2.5 and SNAPSHOT isolation mode
+# Enable TCP protocol on fixed port 1433
+# Prepare the database with:
+# ALTER DATABASE alfresco SET ALLOW_SNAPSHOT_ISOLATION ON;
#
-#db.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
-#db.url=jdbc:sqlserver://localhost:1433;databaseName=alfresco
+#db.driver=net.sourceforge.jtds.jdbc.Driver
+#db.url=jdbc:jtds:sqlserver://localhost:1433/alfresco
#db.txn.isolation=4096
#
diff --git a/config/alfresco/audit-services-context.xml b/config/alfresco/audit-services-context.xml
index ad13cddd1d..866c5b4fa3 100644
--- a/config/alfresco/audit-services-context.xml
+++ b/config/alfresco/audit-services-context.xml
@@ -49,7 +49,7 @@
- alfresco/auditConfig.xml
+ classpath:alfresco/auditConfig.xml
diff --git a/source/java/org/alfresco/repo/audit/AuditConfiguration.java b/source/java/org/alfresco/repo/audit/AuditConfiguration.java
index e290943008..63443e98c2 100644
--- a/source/java/org/alfresco/repo/audit/AuditConfiguration.java
+++ b/source/java/org/alfresco/repo/audit/AuditConfiguration.java
@@ -35,8 +35,8 @@ public interface AuditConfiguration
InputStream getInputStream();
/**
- * Return path of the XML
+ * Return last modified time of the XML
* @return path
*/
- String getPath();
+ long getLastModified();
}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/audit/AuditConfigurationImpl.java b/source/java/org/alfresco/repo/audit/AuditConfigurationImpl.java
index 2d72f3ceef..4f5ebee488 100644
--- a/source/java/org/alfresco/repo/audit/AuditConfigurationImpl.java
+++ b/source/java/org/alfresco/repo/audit/AuditConfigurationImpl.java
@@ -14,28 +14,34 @@
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
+ * FLOSS exception. You should have recieved a copy of the text describing
* along with Alfresco. If not, see .
*/
package org.alfresco.repo.audit;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
/**
* A class to read the audit configuration from the class path
*
* @author Andy Hind
*/
-public class AuditConfigurationImpl implements AuditConfiguration
+public class AuditConfigurationImpl implements AuditConfiguration, ResourceLoaderAware
{
private static Log logger = LogFactory.getLog(AuditConfigurationImpl.class);
+ private static long STARTUP_TIME = System.currentTimeMillis();
private String config;
+ private ResourceLoader resourceLoader;
+
/**
* Default constructor
*
@@ -55,25 +61,45 @@ public class AuditConfigurationImpl implements AuditConfiguration
this.config = config;
}
+ private Resource getResource()
+ {
+ return this.resourceLoader.getResource(config);
+ }
+
public InputStream getInputStream()
{
- InputStream is = null;
try
{
- is = new FileInputStream(getPath());
+ return getResource().getInputStream();
}
- catch (FileNotFoundException e)
+ catch (IOException e)
{
- if (logger.isWarnEnabled())
- {
- logger.warn("File not found: " + getPath());
- }
+ logger.warn("Unable to resolve " + config + " as input stream", e);
+ return null;
}
- return is;
}
-
- public String getPath()
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.springframework.context.ResourceLoaderAware#setResourceLoader(org.springframework.core.io.ResourceLoader)
+ */
+ public void setResourceLoader(ResourceLoader resourceLoader)
{
- return this.getClass().getClassLoader().getResource(config).getPath();
+ this.resourceLoader = resourceLoader;
+ }
+
+ public long getLastModified()
+ {
+ try
+ {
+ return getResource().getFile().lastModified();
+ }
+ catch (IOException e)
+ {
+ // Not all resources can be resolved to files on the filesystem. If this is the case, just return the time
+ // the server was last started
+ return STARTUP_TIME;
+ }
}
}
diff --git a/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java b/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java
index 513f362bb4..aad9895a0a 100644
--- a/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java
+++ b/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java
@@ -19,7 +19,6 @@
package org.alfresco.repo.audit.hibernate;
import java.io.BufferedInputStream;
-import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
@@ -362,8 +361,7 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO,
{
if (contentStore instanceof FileContentStore)
{
- File currFile = new File(auditInfo.getAuditConfiguration().getPath());
- long currTimestamp = currFile.lastModified();
+ long currTimestamp = auditInfo.getAuditConfiguration().getLastModified();
long timestamp = ((FileContentStore)contentStore).getReader(auditConfig.getConfigURL()).getLastModified();
if (timestamp < currTimestamp)
{
diff --git a/source/java/org/alfresco/repo/management/subsystems/AbstractChainedSubsystemTest.java b/source/java/org/alfresco/repo/management/subsystems/AbstractChainedSubsystemTest.java
new file mode 100644
index 0000000000..9b76d9fdd3
--- /dev/null
+++ b/source/java/org/alfresco/repo/management/subsystems/AbstractChainedSubsystemTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2010 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 received 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.management.subsystems;
+
+import junit.framework.TestCase;
+
+/**
+ * Uses package level protection to allow us to sneak inside chained subsystems for test purposes.
+ *
+ * @author dward
+ */
+public abstract class AbstractChainedSubsystemTest extends TestCase
+{
+
+ public ChildApplicationContextFactory getChildApplicationContextFactory(DefaultChildApplicationContextManager childApplicationContextManager, String id)
+ {
+ DefaultChildApplicationContextManager.ApplicationContextManagerState state = (DefaultChildApplicationContextManager.ApplicationContextManagerState)childApplicationContextManager.getState(true);
+ return state.getApplicationContextFactory(id);
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
index 0ea7c2f99f..81e9c56299 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
@@ -888,23 +888,38 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
ContentModel.TYPE_AUTHORITY_CONTAINER);
QName idProp = isAuthority ? ContentModel.PROP_AUTHORITY_NAME : ContentModel.PROP_USERNAME;
String authBefore = DefaultTypeConverter.INSTANCE.convert(String.class, before.get(idProp));
+ if (authBefore == null)
+ {
+ // Node has just been created; nothing to do
+ return;
+ }
String authAfter = DefaultTypeConverter.INSTANCE.convert(String.class, after.get(idProp));
if (!EqualsHelper.nullSafeEquals(authBefore, authAfter))
{
- if ((authBefore == null) || authBefore.equalsIgnoreCase(authAfter))
+ if (authBefore.equalsIgnoreCase(authAfter))
{
if (isAuthority)
{
// Fix any ACLs
aclDao.updateAuthority(authBefore, authAfter);
- // Fix primary association local name
- // Unfortunately all the zone and group associations will still be bust!
+ // Fix primary association local name
QName newAssocQName = QName.createQName("cm", authAfter, namespacePrefixResolver);
ChildAssociationRef assoc = nodeService.getPrimaryParent(nodeRef);
nodeService.moveNode(nodeRef, assoc.getParentRef(), assoc.getTypeQName(), newAssocQName);
- // We can't be totally sure which tenant domain we need to target so clear the noderef cache
+ // Fix other non-case sensitive parent associations
+ QName oldAssocQName = QName.createQName("cm", authBefore, namespacePrefixResolver);
+ newAssocQName = QName.createQName("cm", authAfter, namespacePrefixResolver);
+ for (ChildAssociationRef parent : nodeService.getParentAssocs(nodeRef))
+ {
+ if (!parent.isPrimary() && parent.getQName().equals(oldAssocQName))
+ {
+ nodeService.removeChildAssociation(parent);
+ nodeService.addChild(parent.getParentRef(), parent.getChildRef(), parent.getTypeQName(),
+ newAssocQName);
+ }
+ }
authorityLookupCache.clear();
// Cache is out of date
diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
index 54c8e8b8db..a925b7367a 100644
--- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
@@ -355,8 +355,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, searchUserName.toLowerCase()),
false);
allRefs = new LinkedHashSet(childRefs.size() * 2);
- // add to cache
- personCache.put(cacheKey, allRefs);
for (ChildAssociationRef childRef : childRefs)
{
@@ -382,6 +380,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
else if (refs.size() == 1)
{
returnRef = refs.get(0);
+
+ // Don't bother caching unless we get a result that doesn't need duplicate processing
+ personCache.put(cacheKey, allRefs);
}
return returnRef;
}
@@ -411,6 +412,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
}
private static final String KEY_POST_TXN_DUPLICATES = "PersonServiceImpl.KEY_POST_TXN_DUPLICATES";
+ private static final String KEY_ALLOW_UID_UPDATE = "PersonServiceImpl.KEY_ALLOW_UID_UPDATE";
/**
* Get the txn-bound usernames that need cleaning up
@@ -455,6 +457,8 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
if (duplicateMode.equalsIgnoreCase(SPLIT))
{
+ // Allow UIDs to be updated in this transaction
+ AlfrescoTransactionSupport.bindResource(KEY_ALLOW_UID_UPDATE, Boolean.TRUE);
split(postTxnDuplicates);
s_logger.info("Split duplicate person objects");
}
@@ -497,13 +501,15 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
private NodeRef findBest(List refs)
{
+ // Given that we might not have audit attributes, use the assumption that the node ID increases to sort the
+ // nodes
if (lastIsBest)
{
- Collections.sort(refs, new CreationDateComparator(nodeService, false));
+ Collections.sort(refs, new NodeIdComparator(nodeService, false));
}
else
{
- Collections.sort(refs, new CreationDateComparator(nodeService, true));
+ Collections.sort(refs, new NodeIdComparator(nodeService, true));
}
NodeRef fallBack = null;
@@ -775,6 +781,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
{
nodeService.deleteNode(personNodeRef);
}
+ personCache.remove(userName.toLowerCase());
}
public Set getAllPeople()
@@ -845,6 +852,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
{
NodeRef personRef = childAssocRef.getChildRef();
String username = (String) this.nodeService.getProperty(personRef, ContentModel.PROP_USERNAME);
+ personCache.remove(username.toLowerCase());
permissionsManager.setPermissions(personRef, username, username);
// Make sure there is an authority entry - with a DB constraint for uniqueness
@@ -936,13 +944,13 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
return null;
}
- public static class CreationDateComparator implements Comparator
+ public static class NodeIdComparator implements Comparator
{
private NodeService nodeService;
boolean ascending;
- CreationDateComparator(NodeService nodeService, boolean ascending)
+ NodeIdComparator(NodeService nodeService, boolean ascending)
{
this.nodeService = nodeService;
this.ascending = ascending;
@@ -950,14 +958,14 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
public int compare(NodeRef first, NodeRef second)
{
- Date firstDate = DefaultTypeConverter.INSTANCE.convert(Date.class, nodeService.getProperty(first, ContentModel.PROP_CREATED));
- Date secondDate = DefaultTypeConverter.INSTANCE.convert(Date.class, nodeService.getProperty(second, ContentModel.PROP_CREATED));
+ Long firstId = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(first, ContentModel.PROP_NODE_DBID));
+ Long secondId = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(second, ContentModel.PROP_NODE_DBID));
- if (firstDate != null)
+ if (firstId != null)
{
- if (secondDate != null)
+ if (secondId != null)
{
- return firstDate.compareTo(secondDate) * (ascending ? 1 : -1);
+ return firstId.compareTo(secondId) * (ascending ? 1 : -1);
}
else
{
@@ -966,7 +974,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
}
else
{
- if (secondDate != null)
+ if (secondId != null)
{
return ascending ? 1 : -1;
}
@@ -996,22 +1004,39 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
public void onUpdateProperties(NodeRef nodeRef, Map before, Map after)
{
String uidBefore = DefaultTypeConverter.INSTANCE.convert(String.class, before.get(ContentModel.PROP_USERNAME));
+ if (uidBefore == null)
+ {
+ // Node has just been created; nothing to do
+ return;
+ }
String uidAfter = DefaultTypeConverter.INSTANCE.convert(String.class, after.get(ContentModel.PROP_USERNAME));
if (!EqualsHelper.nullSafeEquals(uidBefore, uidAfter))
{
- if ((uidBefore == null) || uidBefore.equalsIgnoreCase(uidAfter))
+ // Only allow UID update if we are in the special split processing txn or we are just changing case
+ if (AlfrescoTransactionSupport.getResource(KEY_ALLOW_UID_UPDATE) != null || uidBefore.equalsIgnoreCase(uidAfter))
{
// Fix any ACLs
aclDao.updateAuthority(uidBefore, uidAfter);
+
// Fix primary association local name
QName newAssocQName = QName.createQName("cm", uidAfter.toLowerCase(), namespacePrefixResolver);
ChildAssociationRef assoc = nodeService.getPrimaryParent(nodeRef);
nodeService.moveNode(nodeRef, assoc.getParentRef(), assoc.getTypeQName(), newAssocQName);
- // Fix cache
- if (uidBefore != null)
+
+ // Fix other non-case sensitive parent associations
+ QName oldAssocQName = QName.createQName("cm", uidBefore, namespacePrefixResolver);
+ newAssocQName = QName.createQName("cm", uidAfter, namespacePrefixResolver);
+ for (ChildAssociationRef parent : nodeService.getParentAssocs(nodeRef))
{
- personCache.remove(uidBefore.toLowerCase());
+ if (!parent.isPrimary() && parent.getQName().equals(oldAssocQName))
+ {
+ nodeService.removeChildAssociation(parent);
+ nodeService.addChild(parent.getParentRef(), parent.getChildRef(), parent.getTypeQName(), newAssocQName);
+ }
}
+
+ // Fix cache
+ personCache.remove(uidBefore.toLowerCase());
}
else
{
diff --git a/source/java/org/alfresco/repo/security/person/PersonTest.java b/source/java/org/alfresco/repo/security/person/PersonTest.java
index 6ed238868a..4641235a60 100644
--- a/source/java/org/alfresco/repo/security/person/PersonTest.java
+++ b/source/java/org/alfresco/repo/security/person/PersonTest.java
@@ -21,15 +21,19 @@ package org.alfresco.repo.security.person;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import junit.framework.Assert;
+
import org.alfresco.model.ContentModel;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
@@ -726,4 +730,94 @@ public class PersonTest extends BaseSpringTest
}
}, true, true);
}
+
+ public void testSplitDuplicates()
+ {
+ testProcessDuplicates(true);
+
+ // Test out the SplitPersonCleanupBootstrapBean for removal of the duplicates
+ SplitPersonCleanupBootstrapBean splitPersonBean = new SplitPersonCleanupBootstrapBean();
+ splitPersonBean.setNodeService(nodeService);
+ splitPersonBean.setPersonService(personService);
+ splitPersonBean.setTransactionService(transactionService);
+ Assert.assertEquals(9, splitPersonBean.removePeopleWithGUIDBasedIds());
+
+ }
+
+ public void testDeleteDuplicates()
+ {
+ testProcessDuplicates(false);
+ }
+
+ private void testProcessDuplicates(final boolean split)
+ {
+ // Kill the annoying Spring-managed txn
+ super.setComplete();
+ super.endTransaction();
+
+ // Set the duplicate processing mode
+ ((PersonServiceImpl) personService).setDuplicateMode(split ? "SPLIT" : "DELETE");
+
+ final String duplicateUserName = GUID.generate();
+ final NodeRef[] duplicates = transactionService.getRetryingTransactionHelper().doInTransaction(
+ new RetryingTransactionCallback()
+ {
+
+ public NodeRef[] execute() throws Throwable
+ {
+ NodeRef[] duplicates = new NodeRef[10];
+
+ // Generate a first person node
+ Map properties = createDefaultProperties(duplicateUserName, "firstName", "lastName", "email@orgId", "orgId", null);
+ duplicates[0] = personService.createPerson(properties);
+ ChildAssociationRef container = nodeService.getPrimaryParent(duplicates[0]);
+ List parents = nodeService.getParentAssocs(duplicates[0]);
+
+ // Generate some duplicates
+ for (int i = 1; i < duplicates.length; i++)
+ {
+ // Create the node with the same parent assocs
+ duplicates[i] = nodeService.createNode(container.getParentRef(), container.getTypeQName(),
+ container.getQName(), ContentModel.TYPE_PERSON, properties).getChildRef();
+ for (ChildAssociationRef parent : parents)
+ {
+ if (!parent.isPrimary())
+ {
+ nodeService.addChild(parent.getParentRef(), duplicates[i], parent.getTypeQName(),
+ parent.getQName());
+ }
+ }
+ }
+ // With the default settings, the last created node should be the one that wins
+ assertEquals(duplicates[duplicates.length - 1], personService.getPerson(duplicateUserName));
+ return duplicates;
+ }
+ }, false, true);
+
+ // Check the duplicates were processed appropriately in the previous transaction
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ for (int i = 0; i < duplicates.length - 1; i++)
+ {
+ if (split)
+ {
+ String newUserName = (String) nodeService
+ .getProperty(duplicates[i], ContentModel.PROP_USERNAME);
+ assertNotSame(duplicateUserName, newUserName);
+ }
+ else
+ {
+ assertFalse(nodeService.exists(duplicates[i]));
+ }
+ }
+
+ // Get rid of the non-split person
+ assertTrue(personService.personExists(duplicateUserName));
+ personService.deletePerson(duplicateUserName);
+ return null;
+ }
+ }, false, true);
+ }
}
diff --git a/source/java/org/alfresco/repo/security/person/SplitPersonCleanupBootstrapBean.java b/source/java/org/alfresco/repo/security/person/SplitPersonCleanupBootstrapBean.java
index 183db7c1b2..2f7ab5f6e8 100644
--- a/source/java/org/alfresco/repo/security/person/SplitPersonCleanupBootstrapBean.java
+++ b/source/java/org/alfresco/repo/security/person/SplitPersonCleanupBootstrapBean.java
@@ -19,8 +19,11 @@
package org.alfresco.repo.security.person;
import java.util.Set;
+import java.util.TreeSet;
import org.alfresco.model.ContentModel;
+import org.alfresco.repo.batch.BatchProcessor;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
@@ -78,14 +81,14 @@ public class SplitPersonCleanupBootstrapBean extends AbstractLifecycleBean
*
* @return
*/
- private int removePeopleWithGUIDBasedIds()
+ protected int removePeopleWithGUIDBasedIds()
{
- Integer count = transactionService.getRetryingTransactionHelper().doInTransaction(
- new RetryingTransactionCallback()
+ Set uidsToRemove = transactionService.getRetryingTransactionHelper().doInTransaction(
+ new RetryingTransactionCallback>()
{
- public Integer execute() throws Exception
+ public Set execute() throws Exception
{
- int count = 0;
+ Set uidsToRemove = new TreeSet();
// A GUID should be 36 chars
Set people = personService.getAllPeople();
@@ -95,21 +98,56 @@ public class SplitPersonCleanupBootstrapBean extends AbstractLifecycleBean
person, ContentModel.PROP_USERNAME));
if (isUIDWithGUID(uid))
{
- // Delete via the person service to get the correct tidy up
- personService.deletePerson(uid);
+ uidsToRemove.add(uid);
if (log.isDebugEnabled())
{
- log.debug("... removed person with uid " + uid);
+ log.debug("... will remove person with uid " + uid);
}
- log.info("... removed person with uid " + uid);
- count++;
}
}
- return count;
+ return uidsToRemove;
}
});
- return count.intValue();
+
+ if (uidsToRemove.isEmpty())
+ {
+ return 0;
+ }
+
+ // Process the duplicate persons in small batches
+ BatchProcessor batchProcessor = new BatchProcessor("Split Person Removal", transactionService
+ .getRetryingTransactionHelper(), uidsToRemove, 2, 10, getApplicationContext(), log, 100);
+ batchProcessor.process(new BatchProcessor.BatchProcessWorker()
+ {
+
+ public String getIdentifier(String entry)
+ {
+ return entry;
+ }
+
+ public void beforeProcess() throws Throwable
+ {
+ // Authenticate as system
+ String systemUsername = AuthenticationUtil.getSystemUserName();
+ AuthenticationUtil.setFullyAuthenticatedUser(systemUsername);
+ }
+
+ public void afterProcess() throws Throwable
+ {
+ }
+
+ public void process(String entry) throws Throwable
+ {
+ // Delete via the person service to get the correct tidy up
+ personService.deletePerson(entry);
+ if (log.isDebugEnabled())
+ {
+ log.debug("... removed person with uid " + entry);
+ }
+ }
+ }, true);
+ return uidsToRemove.size();
}