From eddc7efe0c89694861019a1337f8d3bc653e6913 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Thu, 30 Jun 2011 11:36:51 +0000 Subject: [PATCH] Merged V3.4-BUG-FIX to HEAD 28650: Merged DEV/TEMPORARY to V3.4-BUG-FIX 28637: ALF-5601: WCM Reviewer should be able to modify 'Launch Date' of the review item. Set "wcmwf:launchDate" to read-only on "submitpendingTask". 28697: Fix for ALF-2711 - Fix to handle incorrect (negative size!) content length headers sent by Adobe Flash when uploading files over 2GB. 28702: Merged DEV to V3.4-BUG-FIX 28693: ALF-9314: Unable to add to multi-valued properties via AVM Console The node property value of Collection type must be set within square braces as a comma separated values without spaces. E.g. [aaa,bbb,ccc] 28718: Merged PATCHES/V3.4.2 to V3.4-BUG-FIX 28569: ALF-9253 / ALF-9166: 'A valid SecureContext was not provided in the RequestContext' exception on startup following upgrade to 3.4.1 28618: ALF-8385 / ALF-9364: Merged DEV/TEMPORARY to PATCHES/V3.4.2 28565: ALF-5887 Addition of RenameUser command line toolContext - PersonServiceImpl should not disable normal behaviour when handling duplicate Person NodeRefs as the userAuthorityCache does not get updated correctly - Tool (base class for Import, Export and RenameUser command line tools) should not automatically login if setLogin(false) has been called. 28719: Merged V3.4 to V3.4-BUG-FIX 28648: ALF-9103: Remove obsolete (and mis-spelled) use-old-dm-alcs-context.xml.sample 28701: Corrected library for - Fix for ALF-7860 - Regression: Close button doesn't work in Node Browser git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28721 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../use-old-dm-alcs-context.xml.sample | 53 -- .../alfresco/rendition-services-context.xml | 1 + .../org/alfresco/repo/avm/AVMInterpreter.java | 2 +- .../RenditionDefinitionPersisterImpl.java | 33 +- .../security/authority/AuthorityDAOImpl.java | 4 +- .../security/person/PersonServiceImpl.java | 41 +- .../repo/security/person/PersonTest.java | 81 ++- .../java/org/alfresco/tools/RenameUser.java | 561 ++++++++++++++++++ .../org/alfresco/tools/RenameUserTest.java | 223 +++++++ source/java/org/alfresco/tools/Tool.java | 10 +- 10 files changed, 915 insertions(+), 94 deletions(-) delete mode 100644 config/alfresco/extension/use-old-dm-alcs-context.xml.sample create mode 100644 source/java/org/alfresco/tools/RenameUser.java create mode 100644 source/java/org/alfresco/tools/RenameUserTest.java diff --git a/config/alfresco/extension/use-old-dm-alcs-context.xml.sample b/config/alfresco/extension/use-old-dm-alcs-context.xml.sample deleted file mode 100644 index 3d0e802b3f..0000000000 --- a/config/alfresco/extension/use-old-dm-alcs-context.xml.sample +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - \ No newline at end of file diff --git a/config/alfresco/rendition-services-context.xml b/config/alfresco/rendition-services-context.xml index 742606deb9..85735d07ed 100644 --- a/config/alfresco/rendition-services-context.xml +++ b/config/alfresco/rendition-services-context.xml @@ -58,6 +58,7 @@ + diff --git a/source/java/org/alfresco/repo/avm/AVMInterpreter.java b/source/java/org/alfresco/repo/avm/AVMInterpreter.java index 35d465af07..0b274e091f 100644 --- a/source/java/org/alfresco/repo/avm/AVMInterpreter.java +++ b/source/java/org/alfresco/repo/avm/AVMInterpreter.java @@ -958,7 +958,7 @@ public class AVMInterpreter private static String[] getCSVArray(String valueString) { - String[] elements = valueString.split(",\\s+"); + String[] elements = valueString.split(","); if (elements.length == 0) { diff --git a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java index 0cbf825c47..33bf5f0750 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java +++ b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java @@ -27,6 +27,7 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ActionModel; import org.alfresco.repo.action.RuntimeActionService; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.CompositeAction; import org.alfresco.service.cmr.rendition.RenditionDefinition; @@ -59,6 +60,7 @@ public class RenditionDefinitionPersisterImpl implements RenditionDefinitionPers /* Injected services */ private NodeService nodeService; private RuntimeActionService runtimeActionService; + private BehaviourFilter behaviourFilter; /** * Injects the NodeService bean. @@ -79,7 +81,13 @@ public class RenditionDefinitionPersisterImpl implements RenditionDefinitionPers { this.runtimeActionService = runtimeActionService; } - + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + public List loadRenditionDefinitions() { checkRenderingActionRootNodeExists(); @@ -147,12 +155,23 @@ public class RenditionDefinitionPersisterImpl implements RenditionDefinitionPers public void saveRenditionDefinition(RenditionDefinition renderingAction) { NodeRef actionNodeRef = findOrCreateActionNode(renderingAction); - - // TODO Serialize using JSON content instead. - // The current serialization mechanism creates a complex content model - // structure which is verbose and a JSON-based approach using a simplified - // content model perhaps could offer performance improvements. - runtimeActionService.saveActionImpl(actionNodeRef, renderingAction); + + // ALF-9166 describes a problem whereby versionable saved rendition definition nodes cause problems on upgrade. + // This appears to be due to a rule defined on Company Home. The behaviour suppression below is a workaround for that issue. + try + { + behaviourFilter.disableBehaviour(actionNodeRef, ContentModel.ASPECT_VERSIONABLE); + + // TODO Serialize using JSON content instead. + // The current serialization mechanism creates a complex content model + // structure which is verbose and a JSON-based approach using a simplified + // content model perhaps could offer performance improvements. + runtimeActionService.saveActionImpl(actionNodeRef, renderingAction); + } + finally + { + behaviourFilter.enableBehaviour(actionNodeRef, ContentModel.ASPECT_VERSIONABLE); + } } public void deleteRenditionDefinition(RenditionDefinition renderingAction) diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java index bf294d6978..9a47684d76 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java @@ -44,7 +44,9 @@ import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser; +import org.alfresco.repo.security.person.PersonServiceImpl; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -1108,7 +1110,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor String authAfter = DefaultTypeConverter.INSTANCE.convert(String.class, after.get(idProp)); if (!EqualsHelper.nullSafeEquals(authBefore, authAfter)) { - if (authBefore.equalsIgnoreCase(authAfter)) + if (AlfrescoTransactionSupport.getResource(PersonServiceImpl.KEY_ALLOW_UID_UPDATE) != null || authBefore.equalsIgnoreCase(authAfter)) { if (isAuthority) { diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index a72d574afc..94d5a56e46 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -120,7 +120,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per private static final String SYSTEM_USAGE_WARN_LIMIT_USERS_EXCEEDED_VERBOSE = "system.usage.err.limit_users_exceeded_verbose"; 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"; + public static final String KEY_ALLOW_UID_UPDATE = "PersonServiceImpl.KEY_ALLOW_UID_UPDATE"; private static final String KEY_USERS_CREATED = "PersonServiceImpl.KEY_USERS_CREATED"; private StoreRef storeRef; @@ -603,34 +603,25 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { public Object execute() throws Throwable { - try + if (duplicateMode.equalsIgnoreCase(SPLIT)) { - policyBehaviourFilter.disableBehaviour(ContentModel.TYPE_PERSON); - - if (duplicateMode.equalsIgnoreCase(SPLIT)) - { logger.info("Splitting " + postTxnDuplicates.size() + " duplicate person objects."); - // Allow UIDs to be updated in this transaction - AlfrescoTransactionSupport.bindResource(KEY_ALLOW_UID_UPDATE, Boolean.TRUE); - split(postTxnDuplicates); + // Allow UIDs to be updated in this transaction + AlfrescoTransactionSupport.bindResource(KEY_ALLOW_UID_UPDATE, Boolean.TRUE); + split(postTxnDuplicates); logger.info("Split " + postTxnDuplicates.size() + " duplicate person objects."); - } - else if (duplicateMode.equalsIgnoreCase(DELETE)) - { - delete(postTxnDuplicates); - logger.info("Deleted duplicate person objects"); - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("Duplicate person objects exist"); - } - } } - finally + else if (duplicateMode.equalsIgnoreCase(DELETE)) { - policyBehaviourFilter.enableBehaviour(ContentModel.TYPE_PERSON); + delete(postTxnDuplicates); + logger.info("Deleted duplicate person objects"); + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Duplicate person objects exist"); + } } // Done @@ -644,7 +635,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { for (NodeRef nodeRef : toDelete) { - nodeService.deleteNode(nodeRef); + deletePerson(nodeRef); } } diff --git a/source/java/org/alfresco/repo/security/person/PersonTest.java b/source/java/org/alfresco/repo/security/person/PersonTest.java index 24028cc9b9..4958b51210 100644 --- a/source/java/org/alfresco/repo/security/person/PersonTest.java +++ b/source/java/org/alfresco/repo/security/person/PersonTest.java @@ -58,6 +58,7 @@ import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService.PersonInfo; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.tools.RenameUser; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.EqualsHelper; import org.alfresco.util.GUID; @@ -79,11 +80,6 @@ public class PersonTest extends TestCase private MutableAuthenticationDao authenticationDAO; private UserTransaction testTX; - public PersonTest() - { - super(); - } - public void setUp() throws Exception { AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); @@ -1388,4 +1384,79 @@ public class PersonTest extends TestCase personService.createPerson(properties); personService.notifyPerson(userName, "abc"); } + + public void testRenameUser() throws Exception + { + // Note: RenameUserTest contains unit tests. + + // End the Spring-managed txn + testTX.commit(); + + final String username = AuthenticationUtil.getAdminUserName(); + + final String oldUsername = GUID.generate(); + final String newUsername = oldUsername+GUID.generate(); + + // Create a person + final NodeRef person = transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // Tidy up failed runs + if (personService.personExists(oldUsername)) + { + personService.deletePerson(oldUsername); + } + if (personService.personExists(newUsername)) + { + personService.deletePerson(newUsername); + } + + // Generate a person node + Map properties = createDefaultProperties(oldUsername, "firstName", "lastName", "email@orgId", "orgId", null); + NodeRef person = personService.createPerson(properties); + + // Check the person exists + assertEquals(oldUsername, nodeService.getProperty(person, ContentModel.PROP_USERNAME)); + assertEquals(person, personService.getPerson(oldUsername)); + assertFalse("new user should not exist yet", personService.personExists(newUsername)); + return person; + } + }, false, true); + + // Run the RenameUser cmd line tool + // - override exit so we don't and assert normal exit + // - Don't ask for a password as we may not know it in a test + // - call start rather than main to get correct instance + RenameUser renameUser = new RenameUser() + { + @Override + protected void exit(int status) + { + assertEquals("Tool exit status should be normal", 0, status); + } + }; + renameUser.setLogin(false); + renameUser.start(new String[] {"-user", username, oldUsername, newUsername}); + + // Check person has been renamed and the delete it. + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + String newUserName = (String) nodeService.getProperty(person, ContentModel.PROP_USERNAME); + assertEquals(newUsername, newUserName); + + // Check the person exists + assertEquals(newUsername, nodeService.getProperty(person, ContentModel.PROP_USERNAME)); + assertEquals(person, personService.getPerson(newUsername)); + assertFalse("old user should no longer exist", personService.personExists(oldUsername)); + + // Get rid of the test person + personService.deletePerson(newUsername); + return null; + } + }, false, true); + } } diff --git a/source/java/org/alfresco/tools/RenameUser.java b/source/java/org/alfresco/tools/RenameUser.java new file mode 100644 index 0000000000..66bd017c95 --- /dev/null +++ b/source/java/org/alfresco/tools/RenameUser.java @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.tools; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.batch.BatchProcessWorkProvider; +import org.alfresco.repo.batch.BatchProcessor; +import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker; +import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorkerAdaptor; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.person.PersonServiceImpl; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.NoSuchPersonException; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.VmShutdownListener; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Rename user tool. This tool provides minimal support for renaming users. + * See {@link displayHelp} message for restrictions. + *
+ * Usage: renameUser -user username [options] oldUsername newUsername");
+ *        renameUser -user username [options] -file filename");
+ * 
+ * The csv file has a simple comma separated list, with + * a pair of usernames on each line. Comments and blank + * lines may also be included. For example: + *
+ * # List of usernames to change
+ * 
+ * # oldUsername,newUsername
+ * johnp,ceo # President and CEO
+ * johnn,cto # CTO and Chairman
+ * 
+ * + * @author Alan Davis + */ +public class RenameUser extends Tool +{ + private static Log logger = LogFactory.getLog(RenameUser.class); + + /** User Rename Tool Context */ + protected RenameUserToolContext context; + private boolean login = true; + + PersonService personService; + NodeService nodeService; + + private PersonService getPersonService() + { + if (personService == null) + { + personService = getServiceRegistry().getPersonService(); + } + return personService; + } + + private NodeService getNodeService() + { + if (nodeService == null) + { + nodeService = getServiceRegistry().getNodeService(); + } + return nodeService; + } + + public void setLogin(boolean login) + { + this.login = login; + } + + /** + * Entry Point + * + * @param args + */ + public static void main(String[] args) + { + Tool tool = new RenameUser(); + tool.start(args); + } + + /* + * (non-Javadoc) + * @see org.alfresco.tools.Tool#processArgs(java.lang.String[]) + */ + @Override + protected ToolContext processArgs(String[] args) + throws ToolArgumentException + { + context = new RenameUserToolContext(); + context.setLogin(login); + + int i = 0; + while (i < args.length) + { + if (args[i].equals("-h") || args[i].equals("-help")) + { + context.setHelp(true); + break; + } + else if (args[i].equals("-user")) + { + i++; + if (i == args.length || args[i].length() == 0) + { + throw new ToolArgumentException("The value for the option -user must be specified"); + } + context.setUsername(args[i]); + } + else if (args[i].equals("-pwd")) + { + i++; + if (i == args.length || args[i].length() == 0) + { + throw new ToolArgumentException("The value for the option -pwd must be specified"); + } + context.setPassword(args[i]); + } + else if (args[i].equals("-encoding")) + { + i++; + if (i == args.length || args[i].length() == 0) + { + throw new ToolArgumentException("The value for the option -encoding must be specified"); + } + try + { + context.encoding = Charset.forName(args[i]); + } + catch (IllegalCharsetNameException e) + { + throw new ToolArgumentException("The value is not recognised"); + } + catch (UnsupportedCharsetException e) + { + throw new ToolArgumentException("The value is unsupported"); + } + } + else if (args[i].equals("-quiet")) + { + context.setQuiet(true); + } + else if (args[i].equals("-verbose")) + { + context.setVerbose(true); + } + else if (args[i].equals("-f") || args[i].equals("-file")) + { + i++; + if (i == args.length || args[i].length() == 0) + { + throw new ToolArgumentException("The value for the option -file must be specified"); + } + context.setFilename(args[i]); + } + else if (!args[i].startsWith("-")) + { + i++; + if (i == args.length || args[i-1].trim().length() == 0 || args[i].trim().length() == 0) + { + throw new ToolArgumentException("Both must be specified"); + } + if (context.userCount() > 0) + { + throw new ToolArgumentException("Only one pair may be " + + "specified on the command line. See the -file option"); + } + String oldUsername = args[i-1].trim(); + String newUsername = args[i].trim(); + String error = context.add(-1, null, oldUsername, newUsername); + if (error != null) + { + throw new ToolArgumentException(error); + } + } + else + { + throw new ToolArgumentException("Unknown option " + args[i]); + } + + // next argument + i++; + } + + return context; + } + + /* (non-Javadoc) + * @see org.alfresco.tools.Tool#displayHelp() + */ + protected @Override + /*package*/ void displayHelp() + { + logError("This tool provides minimal support for renaming users. It fixes"); + logError("authorities, group memberships and current zone (older versions"); + logError("still require a property change)."); + logError(""); + logError("WARNING: It does NOT change properties that store the username such"); + logError(" as (creator, modifier, lock owner or owner). Of these owner"); + logError(" and lock affect user rights. The username is also used"); + logError(" directly in workflow, for RM caveats, for Share invites and"); + logError(" auditing"); + logError(""); + logError("Usage: renameUser -user username [options] oldUsername newUsername"); + logError(" renameUser -user username [options] -file filename"); + logError(""); + logError(" username: username for login"); + logError("oldUsername: current username "); + logError("newUsername: replacement username "); + logError(""); + logError("Options:"); + logError(" -h[elp] display this help"); + logError(" -pwd password for login"); + logError(" -f[ile] csv file of old and new usernames"); + logError(" -encoding for source file (default: " + Charset.defaultCharset() + ")"); + logError(" -quiet do not display any messages during rename"); + logError(" -verbose report rename progress"); + } + + /* + * (non-Javadoc) + * @see org.alfresco.tools.Tool#getToolName() + */ + @Override + protected String getToolName() + { + return "Alfresco Rename User"; + } + + /* + * (non-Javadoc) + * @see org.alfresco.tools.Tool#execute() + */ + @Override + protected int execute() throws ToolException + { + // Used for ability to be final and have a set + final AtomicInteger status = new AtomicInteger(0); + + BatchProcessWorker worker = new BatchProcessWorkerAdaptor() + { + public void process(final User user) throws Throwable + { + RunAsWork runAsWork = new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + try + { + renameUser(user.getOldUsername(), user.getNewUsername()); + } + catch (Throwable t) + { + status.set(handleError(t)); + } + return null; + } + }; + AuthenticationUtil.runAs(runAsWork, context.getUsername()); + } + }; + + // Use 2 threads, 20 User objects per transaction. Log every 100 entries. + BatchProcessor processor = new BatchProcessor( + "HomeFolderProviderSynchronizer", + getServiceRegistry().getTransactionService().getRetryingTransactionHelper(), + new WorkProvider(context), + 2, 20, + null, + logger, 100); + processor.process(worker, true); + + return status.get(); + } + + private void renameUser(String oldUsername, String newUsername) + { + logInfo("\""+oldUsername+"\" --> \""+newUsername+"\""); + try + { + NodeRef person = getPersonService().getPerson(oldUsername, false); + + // Allow us to update the username just like the LDAP process + AlfrescoTransactionSupport.bindResource(PersonServiceImpl.KEY_ALLOW_UID_UPDATE, Boolean.TRUE); + + // Update the username property which will result in a PersonServiceImpl.onUpdateProperties call + // on commit. + getNodeService().setProperty(person, ContentModel.PROP_USERNAME, newUsername); + } + catch (NoSuchPersonException e) + { + logError("User does not exist: "+oldUsername); + } + } + + public class User + { + private final String oldUsername; + private final String newUsername; + + public User(String oldUsername, String newUsername) + { + this.oldUsername = oldUsername; + this.newUsername = newUsername; + } + + public String getOldUsername() + { + return oldUsername; + } + + public String getNewUsername() + { + return newUsername; + } + } + + public class RenameUserToolContext extends ToolContext + { + /** + * Old and new usernames to change. + */ + private List usernames = new ArrayList(); + + // Internal - used check the name has not been used before. + private Set uniqueNames = new HashSet(); + + /** + * Source filename of usernames. + */ + private String filename; + + /** + * Encoding of filename of usernames. + */ + private Charset encoding = Charset.defaultCharset(); + + public void setFilename(String filename) + { + this.filename = filename; + } + + public String add(int lineNumber, String line, String oldUsername, String newUsername) + { + String error = null; + if (oldUsername.equals(newUsername)) + { + error = "Old and new usernames are the same"; + if (line != null) + error = "Error on line " + lineNumber + " ("+error+"): " + line; + } + else if (uniqueNames.contains(oldUsername)) + { + error = "Old username already specified"; + if (line != null) + error = "Error on line " + lineNumber + " ("+error+"): " + line; + } + else if (uniqueNames.contains(newUsername)) + { + error = "New username already specified"; + if (line != null) + error = "Error on line " + lineNumber + " ("+error+"): " + line; + } + else + { + add(new User(oldUsername, newUsername)); + } + return error; + } + + private void add(User user) + { + usernames.add(user); + uniqueNames.add(user.getOldUsername()); + uniqueNames.add(user.getNewUsername()); + } + + public int userCount() + { + return usernames.size(); + } + + public Iterator iterator() + { + return usernames.iterator(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.tools.ToolContext#validate() + */ + @Override + /*package*/ void validate() + { + super.validate(); + + if (filename != null) + { + if (userCount() > 0) + { + throw new ToolArgumentException(" should not have been specified if " + + " has been specified on the command line."); + } + File file = new File(filename); + if (!file.exists()) + { + throw new ToolArgumentException("File " + filename + " does not exist."); + } + if (!readFile(file)) + { + throw new ToolArgumentException("File " + filename + " contained errors."); + } + } + + if (userCount() == 0) + { + throw new ToolArgumentException("No old and new usernames have been specified."); + } + } + + /** + * Read the user names out of the file. + * @param file to be read + * @return {@code true} if there were no problems found with the file contents. + */ + private boolean readFile(File file) + { + BufferedReader in = null; + boolean noErrors = true; + try + { + in = new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding.name())); + int lineNumber = 1; + for (String line = in.readLine(); line != null; line = in.readLine(), lineNumber++) + { + int i = line.indexOf('#'); + if (i != -1) + { + line = line.substring(0, i); + } + if (line.trim().length() != 0) + { + String[] names = line.split(","); + String oldUsername = names[0].trim(); + String newUsername = names[1].trim(); + if (names.length != 2 || oldUsername.length() == 0 || newUsername.length() == 0) + { + RenameUser.this.logError("Error on line " + lineNumber + ": " + line); + noErrors = false; + } + else + { + String error = context.add(lineNumber, line, oldUsername, newUsername); + if (error != null) + { + RenameUser.this.logError(error); + noErrors = false; + } + } + } + } + } + catch (IOException e) + { + throw new ToolArgumentException("Failed to read .", e); + } + finally + { + if (in != null) + { + try + { + in.close(); + } catch (IOException e) + { + // ignore + } + } + } + return noErrors; + } + } + + // BatchProcessWorkProvider returns batches of 100 User objects. + private class WorkProvider implements BatchProcessWorkProvider + { + private static final int BATCH_SIZE = 100; + + private final VmShutdownListener vmShutdownLister = new VmShutdownListener("getRenameUserWorkProvider"); + private final Iterator iterator; + private final int size; + + public WorkProvider(RenameUserToolContext context) + { + iterator = context.iterator(); + size = context.userCount(); + } + + @Override + public synchronized int getTotalEstimatedWorkSize() + { + return size; + } + + @Override + public synchronized Collection getNextWork() + { + if (vmShutdownLister.isVmShuttingDown()) + { + return Collections.emptyList(); + } + + Collection results = new ArrayList(BATCH_SIZE); + while (results.size() < BATCH_SIZE && iterator.hasNext()) + { + results.add(iterator.next()); + } + return results; + } + } +} diff --git a/source/java/org/alfresco/tools/RenameUserTest.java b/source/java/org/alfresco/tools/RenameUserTest.java new file mode 100644 index 0000000000..2b333e45ee --- /dev/null +++ b/source/java/org/alfresco/tools/RenameUserTest.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.tools; + +import static org.junit.Assert.assertEquals; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.util.Iterator; + +import org.alfresco.tools.RenameUser.User; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit test for RenameUser. {@link PersonTest} contains integration tests. + * + * @author Alan Davis + */ +public class RenameUserTest +{ + private String[] args; + private File file; + private RenameUser renameUser = new RenameUser(); + + @Before + public void setUp() throws Exception + { + args = new String[6]; + args[0] = "-user"; + args[1] = "admin"; + args[2] = "-pwd"; + args[3] = "admin"; + args[4] = "oldUsername"; + args[5] = "newUsername"; + } + + @After + public void tearDown()// throws Exception + { + if (file != null && file.exists()) + { + file.delete(); + } + } + + private void createFile(String content) throws Exception + { + file = File.createTempFile("RenameUserTest", ".txt"); + args[4] = "-file"; + args[5] = file.getPath(); + + BufferedWriter out = new BufferedWriter(new FileWriter(file)); + out.write(content); + out.close(); + } + + private void processArgsAndValidate() + { + renameUser.processArgs(args); + renameUser.context.validate(); + } + + // Check that the expected (supplied) usernames are in the context + private void assertUsers(String... usernames) + { + int length = usernames.length/2; + assertEquals("Must have an even number of usernames passed to assertUsers", usernames.length, length*2); + + assertEquals(length, renameUser.context.userCount()); + Iterator iterator = renameUser.context.iterator(); + for (int i=0; i