diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml
index 104899d7bf..bdbe70fc9d 100644
--- a/config/alfresco/authentication-services-context.xml
+++ b/config/alfresco/authentication-services-context.xml
@@ -255,6 +255,20 @@
${user.name.caseSensitive}
+
+
+ true
+
+
+
+ SPLIT
+
+
+ true
+
+
+ false
+
@@ -342,6 +356,9 @@
+
+
+
diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml
index a24976a99e..9943592d43 100644
--- a/config/alfresco/cache-context.xml
+++ b/config/alfresco/cache-context.xml
@@ -152,5 +152,47 @@
5000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ticketsCache
+
+
+ 1000
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ticketsTransactionalCache
+
+
+ 10
+
+
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java
index 0ec6be6296..e81db779ec 100644
--- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java
+++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java
@@ -475,11 +475,8 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
}
catch (Throwable exception)
{
- // Log the exception
- logger.error(
- "An error was encountered whilst executing the action '" + action.getActionDefinitionName() + "'.",
- exception);
-
+ // DH: No logging of the exception. Leave the logging decision to the client code,
+ // which can handle the rethrown exception.
if (executedAsynchronously == true)
{
// If one is specified, queue the compensating action ready for execution
diff --git a/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java b/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java
index 9d3c7bff99..5f30a2c328 100644
--- a/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java
+++ b/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java
@@ -3,9 +3,14 @@
*/
package org.alfresco.repo.admin.patch.util;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.FileReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
@@ -34,6 +39,12 @@ public class ImportFileUpdater
/** The destination export version number **/
private static String EXPORT_VERSION = "1.4.0";
+
+ /** Default encoding **/
+ private static String DEFAULT_ENCODING = "UTF-8";
+
+ /** File encoding */
+ private String fileEncoding = DEFAULT_ENCODING;
/** Element names **/
private static String NAME_EXPORTER_VERSION = "exporterVersion";
@@ -42,6 +53,16 @@ public class ImportFileUpdater
/** The current import version number **/
private String version;
private boolean shownWarning = false;
+
+ /**
+ * Set the file encoding.
+ *
+ * @param fileEncoding the file encoding
+ */
+ public void setFileEncoding(String fileEncoding)
+ {
+ this.fileEncoding = fileEncoding;
+ }
/**
* Updates the passed import file into the equivalent 1.4 format.
@@ -95,8 +116,11 @@ public class ImportFileUpdater
XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
factory.setNamespaceAware(true);
+ InputStream inputStream = new FileInputStream(source);
+ Reader inputReader = new InputStreamReader(inputStream, this.fileEncoding);
+
XmlPullParser xpp = factory.newPullParser();
- xpp.setInput(new FileReader(source));
+ xpp.setInput(new BufferedReader(inputReader));
return xpp;
}
@@ -108,6 +132,10 @@ public class ImportFileUpdater
{
throw new AlfrescoRuntimeException("The source file could not be loaded.", fileNotFound);
}
+ catch (UnsupportedEncodingException exception)
+ {
+ throw new AlfrescoRuntimeException("Unsupported encoding", exception);
+ }
}
/**
@@ -124,7 +152,7 @@ public class ImportFileUpdater
OutputFormat format = OutputFormat.createPrettyPrint();
format.setNewLineAfterDeclaration(false);
format.setIndentSize(INDENT_SIZE);
- format.setEncoding("UTF-8");
+ format.setEncoding(this.fileEncoding);
return new XMLWriter(new FileOutputStream(destination), format);
}
@@ -573,11 +601,18 @@ public class ImportFileUpdater
ImportFileUpdater util = new ImportFileUpdater();
util.updateImportFile(args[0], args[1]);
}
+ else if (args.length == 3)
+ {
+ ImportFileUpdater util = new ImportFileUpdater();
+ util.setFileEncoding(args[2]);
+ util.updateImportFile(args[0], args[1]);
+ }
else
{
System.out.println(" ImportFileUpdater ");
System.out.println(" source - 1.3 import file name to be updated");
System.out.println(" destination - name of the generated 1.4 import file");
+ System.out.println(" file encoding (optional) - the file encoding, default is UTF-8");
}
}
diff --git a/source/java/org/alfresco/repo/cache/CacheTest.java b/source/java/org/alfresco/repo/cache/CacheTest.java
index e85f3c43c0..4bdefef9af 100644
--- a/source/java/org/alfresco/repo/cache/CacheTest.java
+++ b/source/java/org/alfresco/repo/cache/CacheTest.java
@@ -17,6 +17,7 @@
package org.alfresco.repo.cache;
import java.io.Serializable;
+import java.util.Collection;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
@@ -86,6 +87,9 @@ public class CacheTest extends TestCase
assertEquals("AAA", backingCache.get("A"));
+ Collection keys = backingCache.getKeys();
+ assertEquals("Backing cache didn't return correct number of keys", 1, keys.size());
+
backingCache.remove("A");
assertNull(backingCache.get("A"));
}
@@ -141,6 +145,11 @@ public class CacheTest extends TestCase
assertEquals("Item not updated in txn cache", "XXX", transactionalCache.get(updatedTxnThree));
assertFalse("Item was put into backing cache", backingCache.contains(updatedTxnThree));
+ // check that the keys collection is correct
+ Collection transactionalKeys = transactionalCache.getKeys();
+ assertFalse("Transactionally removed item found in keys", transactionalKeys.contains(newGlobalOne));
+ assertTrue("Transactionally added item not found in keys", transactionalKeys.contains(updatedTxnThree));
+
// commit the transaction
txn.commit();
diff --git a/source/java/org/alfresco/repo/cache/EhCacheAdapter.java b/source/java/org/alfresco/repo/cache/EhCacheAdapter.java
index 5f2c37b9bf..85399c22a5 100644
--- a/source/java/org/alfresco/repo/cache/EhCacheAdapter.java
+++ b/source/java/org/alfresco/repo/cache/EhCacheAdapter.java
@@ -17,6 +17,7 @@
package org.alfresco.repo.cache;
import java.io.Serializable;
+import java.util.Collection;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
@@ -64,6 +65,12 @@ public class EhCacheAdapter
}
}
+ @SuppressWarnings("unchecked")
+ public Collection getKeys()
+ {
+ return cache.getKeys();
+ }
+
@SuppressWarnings("unchecked")
public V get(K key)
{
diff --git a/source/java/org/alfresco/repo/cache/NullCache.java b/source/java/org/alfresco/repo/cache/NullCache.java
new file mode 100644
index 0000000000..2024b8a5e4
--- /dev/null
+++ b/source/java/org/alfresco/repo/cache/NullCache.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+package org.alfresco.repo.cache;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * A cache that does nothing - always.
+ *
+ * There are conditions under which code that expects to be caching, should not be. Using this
+ * cache, it becomes possible to configure a valid cache in whilst still ensuring that the
+ * actual caching is not performed.
+ *
+ * @author Derek Hulley
+ */
+public class NullCache implements SimpleCache
+{
+ public NullCache()
+ {
+ }
+
+ /** NO-OP */
+ public boolean contains(K key)
+ {
+ return false;
+ }
+
+ public Collection getKeys()
+ {
+ return Collections.emptyList();
+ }
+
+ /** NO-OP */
+ public V get(K key)
+ {
+ return null;
+ }
+
+ /** NO-OP */
+ public void put(K key, V value)
+ {
+ return;
+ }
+
+ /** NO-OP */
+ public void remove(K key)
+ {
+ return;
+ }
+
+ /** NO-OP */
+ public void clear()
+ {
+ return;
+ }
+}
diff --git a/source/java/org/alfresco/repo/cache/SimpleCache.java b/source/java/org/alfresco/repo/cache/SimpleCache.java
index ef644120bd..30d91fe573 100644
--- a/source/java/org/alfresco/repo/cache/SimpleCache.java
+++ b/source/java/org/alfresco/repo/cache/SimpleCache.java
@@ -17,6 +17,7 @@
package org.alfresco.repo.cache;
import java.io.Serializable;
+import java.util.Collection;
/**
* Basic caching interface.
@@ -32,6 +33,8 @@ public interface SimpleCache
{
public boolean contains(K key);
+ public Collection getKeys();
+
public V get(K key);
public void put(K key, V value);
diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java
index f2080be2b4..208b57aab9 100644
--- a/source/java/org/alfresco/repo/cache/TransactionalCache.java
+++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java
@@ -17,6 +17,8 @@
package org.alfresco.repo.cache;
import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import net.sf.ehcache.Cache;
@@ -226,6 +228,39 @@ public class TransactionalCache
return true;
}
}
+
+ /**
+ * The keys returned are a union of the set of keys in the current transaction and
+ * those in the backing cache.
+ */
+ @SuppressWarnings("unchecked")
+ public Collection getKeys()
+ {
+ Collection keys = null;
+ // in-txn layering
+ if (AlfrescoTransactionSupport.getTransactionId() != null)
+ {
+ keys = new HashSet(23);
+ TransactionData txnData = getTransactionData();
+ if (!txnData.isClearOn)
+ {
+ // the backing cache is not due for a clear
+ Collection backingKeys = (Collection) sharedCache.getKeys();
+ keys.addAll(backingKeys);
+ }
+ // add keys
+ keys.addAll((Collection) txnData.updatedItemsCache.getKeys());
+ // remove keys
+ keys.removeAll((Collection) txnData.removedItemsCache.getKeys());
+ }
+ else
+ {
+ // no transaction, so just use the backing cache
+ keys = (Collection) sharedCache.getKeys();
+ }
+ // done
+ return keys;
+ }
/**
* Checks the per-transaction caches for the object before going to the shared cache.
diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java
index e307338a4a..6a9c1b485a 100644
--- a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java
+++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java
@@ -39,6 +39,8 @@ import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.dao.SaltSource;
import org.alfresco.model.ContentModel;
+import org.alfresco.repo.cache.SimpleCache;
+import org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl.Ticket;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -54,6 +56,7 @@ import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext;
+@SuppressWarnings("unchecked")
public class AuthenticationTest extends TestCase
{
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
@@ -81,6 +84,8 @@ public class AuthenticationTest extends TestCase
private SaltSource saltSource;
private TicketComponent ticketComponent;
+
+ private SimpleCache ticketsCache;
private AuthenticationService authenticationService;
@@ -116,6 +121,7 @@ public class AuthenticationTest extends TestCase
authenticationComponentImpl = (AuthenticationComponent) ctx.getBean("authenticationComponentImpl");
// permissionServiceSPI = (PermissionServiceSPI)
// ctx.getBean("permissionService");
+ ticketsCache = (SimpleCache) ctx.getBean("ticketsCache");
dao = (MutableAuthenticationDao) ctx.getBean("alfDaoImpl");
authenticationManager = (AuthenticationManager) ctx.getBean("authenticationManager");
@@ -143,7 +149,6 @@ public class AuthenticationTest extends TestCase
deleteAndy();
authenticationComponent.clearCurrentSecurityContext();
-
}
private void deleteAndy()
@@ -476,6 +481,7 @@ public class AuthenticationTest extends TestCase
tc.setOneOff(false);
tc.setTicketsExpire(false);
tc.setValidDuration("P0D");
+ tc.setTicketsCache(ticketsCache);
dao.createUser("Andy", "ticket".toCharArray());
@@ -499,6 +505,7 @@ public class AuthenticationTest extends TestCase
tc.setOneOff(true);
tc.setTicketsExpire(false);
tc.setValidDuration("P0D");
+ tc.setTicketsCache(ticketsCache);
dao.createUser("Andy", "ticket".toCharArray());
@@ -530,6 +537,7 @@ public class AuthenticationTest extends TestCase
tc.setOneOff(false);
tc.setTicketsExpire(true);
tc.setValidDuration("P5S");
+ tc.setTicketsCache(ticketsCache);
dao.createUser("Andy", "ticket".toCharArray());
@@ -619,6 +627,7 @@ public class AuthenticationTest extends TestCase
tc.setOneOff(false);
tc.setTicketsExpire(true);
tc.setValidDuration("P1D");
+ tc.setTicketsCache(ticketsCache);
dao.createUser("Andy", "ticket".toCharArray());
diff --git a/source/java/org/alfresco/repo/security/authentication/InMemoryTicketComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/InMemoryTicketComponentImpl.java
index db55b630e8..0451b6bf09 100644
--- a/source/java/org/alfresco/repo/security/authentication/InMemoryTicketComponentImpl.java
+++ b/source/java/org/alfresco/repo/security/authentication/InMemoryTicketComponentImpl.java
@@ -16,11 +16,12 @@
*/
package org.alfresco.repo.security.authentication;
+import java.io.Serializable;
import java.util.Date;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
+import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.cmr.repository.datatype.Duration;
import org.alfresco.util.GUID;
public class InMemoryTicketComponentImpl implements TicketComponent
@@ -33,13 +34,18 @@ public class InMemoryTicketComponentImpl implements TicketComponent
private boolean oneOff;
- private HashMap tickets = new HashMap();
+ private SimpleCache ticketsCache; // Can't use Ticket as it's private
public InMemoryTicketComponentImpl()
{
super();
}
+ public void setTicketsCache(SimpleCache ticketsCache)
+ {
+ this.ticketsCache = ticketsCache;
+ }
+
public String getTicket(String userName) throws AuthenticationException
{
Date expiryDate = null;
@@ -48,7 +54,7 @@ public class InMemoryTicketComponentImpl implements TicketComponent
expiryDate = Duration.add(new Date(), validDuration);
}
Ticket ticket = new Ticket(ticketsExpire, expiryDate, userName);
- tickets.put(ticket.getTicketId(), ticket);
+ ticketsCache.put(ticket.getTicketId(), ticket);
return GRANTED_AUTHORITY_TICKET_PREFIX + ticket.getTicketId();
}
@@ -61,7 +67,7 @@ public class InMemoryTicketComponentImpl implements TicketComponent
}
String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length());
- Ticket ticket = tickets.get(key);
+ Ticket ticket = ticketsCache.get(key);
if (ticket == null)
{
throw new AuthenticationException("Missing ticket for " + ticketString);
@@ -74,7 +80,7 @@ public class InMemoryTicketComponentImpl implements TicketComponent
// TODO: Strengthen ticket as GUID is predicatble
if(oneOff)
{
- tickets.remove(key);
+ ticketsCache.remove(key);
}
return ticket.getUserName();
}
@@ -82,16 +88,16 @@ public class InMemoryTicketComponentImpl implements TicketComponent
public void invalidateTicketById(String ticketString)
{
String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length());
- tickets.remove(key);
+ ticketsCache.remove(key);
}
public void invalidateTicketByUser(String userName)
{
Set toRemove = new HashSet();
- for(String key: tickets.keySet())
+ for(String key: ticketsCache.getKeys())
{
- Ticket ticket = tickets.get(key);
+ Ticket ticket = ticketsCache.get(key);
if(ticket.getUserName().equals(userName))
{
toRemove.add(ticket.getTicketId());
@@ -100,14 +106,16 @@ public class InMemoryTicketComponentImpl implements TicketComponent
for(String id: toRemove)
{
- tickets.remove(id);
+ ticketsCache.remove(id);
}
}
- private static class Ticket
+ public static class Ticket implements Serializable
{
+ private static final long serialVersionUID = -5904510560161261049L;
+
private boolean expires;
private Date expiryDate;
diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java
index 1b05e495b3..793a09fa62 100644
--- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java
+++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java
@@ -21,6 +21,7 @@ import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.Collection;
+import java.util.HashSet;
import java.util.Map;
import javax.naming.NamingEnumeration;
@@ -67,7 +68,7 @@ public class LDAPPersonExportSource implements ExportSource
private NamespaceService namespaceService;
private Map attributeDefaults;
-
+
private boolean errorOnMissingUID;
public LDAPPersonExportSource()
@@ -119,7 +120,7 @@ public class LDAPPersonExportSource implements ExportSource
{
this.errorOnMissingUID = errorOnMissingUID;
}
-
+
public void generateExport(XMLWriter writer)
{
QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService);
@@ -147,6 +148,8 @@ public class LDAPPersonExportSource implements ExportSource
writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view",
NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl());
+ HashSet uids = new HashSet();
+
InitialDirContext ctx = null;
try
{
@@ -157,35 +160,44 @@ public class LDAPPersonExportSource implements ExportSource
SearchControls userSearchCtls = new SearchControls();
userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
+
userSearchCtls.setCountLimit(Integer.MAX_VALUE);
NamingEnumeration searchResults = ctx.search(searchBase, personQuery, userSearchCtls);
- while (searchResults.hasMoreElements())
+ RESULT_LOOP: while (searchResults.hasMoreElements())
{
SearchResult result = (SearchResult) searchResults.next();
Attributes attributes = result.getAttributes();
Attribute uidAttribute = attributes.get(userIdAttributeName);
if (uidAttribute == null)
{
- if(errorOnMissingUID)
+ if (errorOnMissingUID)
{
- throw new ExportSourceImporterException(
- "User returned by user search does not have mandatory user id attribute " + attributes);
+ throw new ExportSourceImporterException(
+ "User returned by user search does not have mandatory user id attribute "
+ + attributes);
}
else
{
- s_logger.warn("User returned by user search does not have mandatory user id attribute " + attributes);
- continue;
+ s_logger.warn("User returned by user search does not have mandatory user id attribute "
+ + attributes);
+ continue RESULT_LOOP;
}
}
String uid = (String) uidAttribute.get(0);
+ if (uids.contains(uid))
+ {
+ s_logger.warn("Duplicate uid found - there will be more than one person object for this user - "
+ + uid);
+ }
+
+ uids.add(uid);
+
if (s_logger.isDebugEnabled())
{
s_logger.debug("Adding user for " + uid);
}
-
writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON
.getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs);
@@ -234,7 +246,7 @@ public class LDAPPersonExportSource implements ExportSource
else
{
String defaultValue = attributeDefaults.get(key);
- if(defaultValue != null)
+ if (defaultValue != null)
{
writer.characters(defaultValue.toCharArray(), 0, defaultValue.length());
}
@@ -243,7 +255,7 @@ public class LDAPPersonExportSource implements ExportSource
else
{
String defaultValue = attributeDefaults.get(key);
- if(defaultValue != null)
+ if (defaultValue != null)
{
writer.characters(defaultValue.toCharArray(), 0, defaultValue.length());
}
@@ -316,7 +328,7 @@ public class LDAPPersonExportSource implements ExportSource
TransactionService txs = (TransactionService) ctx.getBean("transactionComponent");
UserTransaction tx = txs.getUserTransaction();
tx.begin();
-
+
File file = new File(args[0]);
Writer writer = new BufferedWriter(new FileWriter(file));
XMLWriter xmlWriter = createXMLExporter(writer);
diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
index 74d656bf1a..310ea8681b 100644
--- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
@@ -27,6 +27,7 @@ import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
+import org.alfresco.repo.security.authentication.ldap.LDAPPersonExportSource;
import org.alfresco.repo.security.permissions.PermissionServiceSPI;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
@@ -41,9 +42,20 @@ import org.alfresco.service.cmr.security.NoSuchPersonException;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespacePrefixResolver;
import org.alfresco.service.namespace.QName;
+import org.alfresco.util.GUID;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
public class PersonServiceImpl implements PersonService
{
+ private static Log s_logger = LogFactory.getLog(PersonServiceImpl.class);
+
+ private static final String DELETE = "DELETE";
+
+ private static final String SPLIT = "SPLIT";
+
+ private static final String LEAVE = "LEAVE";
+
public static final String SYSTEM_FOLDER = "/sys:system";
public static final String PEOPLE_FOLDER = SYSTEM_FOLDER + "/sys:people";
@@ -67,9 +79,17 @@ public class PersonServiceImpl implements PersonService
private static Set mutableProperties;
private boolean userNamesAreCaseSensitive = false;
-
+
private String defaultHomeFolderProvider;
+ private boolean processDuplicates = true;
+
+ private String duplicateMode = LEAVE;
+
+ private boolean lastIsBest = true;
+
+ private boolean includeAutoCreated = false;
+
static
{
Set props = new HashSet();
@@ -96,12 +116,32 @@ public class PersonServiceImpl implements PersonService
{
this.userNamesAreCaseSensitive = userNamesAreCaseSensitive;
}
-
+
void setDefaultHomeFolderProvider(String defaultHomeFolderProvider)
{
this.defaultHomeFolderProvider = defaultHomeFolderProvider;
}
+ public void setDuplicateMode(String duplicateMode)
+ {
+ this.duplicateMode = duplicateMode;
+ }
+
+ public void setIncludeAutoCreated(boolean includeAutoCreated)
+ {
+ this.includeAutoCreated = includeAutoCreated;
+ }
+
+ public void setLastIsBest(boolean lastIsBest)
+ {
+ this.lastIsBest = lastIsBest;
+ }
+
+ public void setProcessDuplicates(boolean processDuplicates)
+ {
+ this.processDuplicates = processDuplicates;
+ }
+
public NodeRef getPerson(String userName)
{
NodeRef personNode = getPersonOrNull(userName);
@@ -130,21 +170,23 @@ public class PersonServiceImpl implements PersonService
public NodeRef getPersonOrNull(String searchUserName)
{
+ NodeRef returnRef = null;
+
SearchParameters sp = new SearchParameters();
sp.setLanguage(SearchService.LANGUAGE_LUCENE);
- sp.setQuery("TYPE:\\{http\\://www.alfresco.org/model/content/1.0\\}person +@cm\\:userName:\"" + searchUserName
- + "\"");
+ sp.setQuery("TYPE:\\{http\\://www.alfresco.org/model/content/1.0\\}person +@cm\\:userName:\""
+ + searchUserName + "\"");
sp.addStore(storeRef);
sp.excludeDataInTheCurrentTransaction(false);
ResultSet rs = null;
+ boolean singleton = true;
+
try
{
rs = searchService.query(sp);
- NodeRef returnRef = null;
-
for (ResultSetRow row : rs)
{
@@ -164,8 +206,8 @@ public class PersonServiceImpl implements PersonService
}
else
{
- throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName
- + " (case sensitive)");
+ singleton = false;
+ break;
}
}
}
@@ -179,15 +221,13 @@ public class PersonServiceImpl implements PersonService
}
else
{
- throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName
- + " (case insensitive)");
+ singleton = false;
+ break;
}
}
}
}
}
-
- return returnRef;
}
finally
{
@@ -196,6 +236,269 @@ public class PersonServiceImpl implements PersonService
rs.close();
}
}
+
+ if (singleton)
+ {
+ return returnRef;
+ }
+ else
+ {
+ return handleDuplicates(searchUserName);
+ }
+ }
+
+ private NodeRef handleDuplicates(String searchUserName)
+ {
+ if (processDuplicates)
+ {
+ NodeRef best = findBest(searchUserName);
+ if (duplicateMode.equalsIgnoreCase(SPLIT))
+ {
+ split(searchUserName, best);
+ s_logger.info("Split duplicate person objects for uid " + searchUserName);
+ }
+ else if (duplicateMode.equalsIgnoreCase(DELETE))
+ {
+ delete(searchUserName, best);
+ s_logger.info("Deleted duplicate person objects for uid " + searchUserName);
+ }
+ else
+ {
+ if (s_logger.isDebugEnabled())
+ {
+ s_logger.debug("Duplicate person objects exist for uid " + searchUserName);
+ }
+ }
+ return best;
+ }
+ else
+ {
+ if (userNamesAreCaseSensitive)
+ {
+ throw new AlfrescoRuntimeException("Found more than one user for "
+ + searchUserName + " (case sensitive)");
+ }
+ else
+ {
+ throw new AlfrescoRuntimeException("Found more than one user for "
+ + searchUserName + " (case insensitive)");
+ }
+ }
+ }
+
+ private void delete(String searchUserName, NodeRef best)
+ {
+ SearchParameters sp = new SearchParameters();
+ sp.setLanguage(SearchService.LANGUAGE_LUCENE);
+ sp.setQuery("TYPE:\\{http\\://www.alfresco.org/model/content/1.0\\}person +@cm\\:userName:\""
+ + searchUserName + "\"");
+ sp.addStore(storeRef);
+ sp.excludeDataInTheCurrentTransaction(false);
+
+ ResultSet rs = null;
+
+ try
+ {
+ rs = searchService.query(sp);
+
+ for (ResultSetRow row : rs)
+ {
+ NodeRef nodeRef = row.getNodeRef();
+ // Do not delete the best
+ if ((!best.equals(nodeRef)) && (nodeService.exists(nodeRef)))
+ {
+ String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(
+ nodeRef, ContentModel.PROP_USERNAME));
+
+ if (userNamesAreCaseSensitive)
+ {
+ if (realUserName.equals(searchUserName))
+ {
+ nodeService.deleteNode(nodeRef);
+ }
+ }
+ else
+ {
+ if (realUserName.equalsIgnoreCase(searchUserName))
+ {
+ nodeService.deleteNode(nodeRef);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (rs != null)
+ {
+ rs.close();
+ }
+ }
+
+ }
+
+ private void split(String searchUserName, NodeRef best)
+ {
+ SearchParameters sp = new SearchParameters();
+ sp.setLanguage(SearchService.LANGUAGE_LUCENE);
+ sp.setQuery("TYPE:\\{http\\://www.alfresco.org/model/content/1.0\\}person +@cm\\:userName:\""
+ + searchUserName + "\"");
+ sp.addStore(storeRef);
+ sp.excludeDataInTheCurrentTransaction(false);
+
+ ResultSet rs = null;
+
+ try
+ {
+ rs = searchService.query(sp);
+
+ for (ResultSetRow row : rs)
+ {
+ NodeRef nodeRef = row.getNodeRef();
+ // Do not delete the best
+ if ((!best.equals(nodeRef)) && (nodeService.exists(nodeRef)))
+ {
+ String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(
+ nodeRef, ContentModel.PROP_USERNAME));
+
+ if (userNamesAreCaseSensitive)
+ {
+ if (realUserName.equals(searchUserName))
+ {
+ nodeService.setProperty(nodeRef, ContentModel.PROP_USERNAME, searchUserName
+ + "(" + GUID.generate() + ")");
+ }
+ }
+ else
+ {
+ if (realUserName.equalsIgnoreCase(searchUserName))
+ {
+ nodeService.setProperty(nodeRef, ContentModel.PROP_USERNAME, searchUserName
+ + GUID.generate());
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (rs != null)
+ {
+ rs.close();
+ }
+ }
+
+ }
+
+ private NodeRef findBest(String searchUserName)
+ {
+ SearchParameters sp = new SearchParameters();
+ sp.setLanguage(SearchService.LANGUAGE_LUCENE);
+ sp.setQuery("TYPE:\\{http\\://www.alfresco.org/model/content/1.0\\}person +@cm\\:userName:\""
+ + searchUserName + "\"");
+ sp.addStore(storeRef);
+ sp.excludeDataInTheCurrentTransaction(false);
+ if (lastIsBest)
+ {
+ sp.addSort(SearchParameters.SORT_IN_DOCUMENT_ORDER_DESCENDING);
+ }
+ else
+ {
+ sp.addSort(SearchParameters.SORT_IN_DOCUMENT_ORDER_ASCENDING);
+ }
+
+ ResultSet rs = null;
+
+ NodeRef fallBack = null;
+
+ try
+ {
+ rs = searchService.query(sp);
+
+ for (ResultSetRow row : rs)
+ {
+ NodeRef nodeRef = row.getNodeRef();
+ if (fallBack == null)
+ {
+ fallBack = nodeRef;
+ }
+ // Do not delete the best
+ if (nodeService.exists(nodeRef))
+ {
+ String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(
+ nodeRef, ContentModel.PROP_USERNAME));
+
+ if (userNamesAreCaseSensitive)
+ {
+ if (realUserName.equals(searchUserName))
+ {
+ if (includeAutoCreated || !wasAutoCreated(nodeRef, searchUserName))
+ {
+ return nodeRef;
+ }
+ }
+ }
+ else
+ {
+ if (realUserName.equalsIgnoreCase(searchUserName))
+ {
+ if (includeAutoCreated || !wasAutoCreated(nodeRef, searchUserName))
+ {
+ return nodeRef;
+ }
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (rs != null)
+ {
+ rs.close();
+ }
+ }
+ return fallBack;
+ }
+
+ private boolean wasAutoCreated(NodeRef nodeRef, String userName)
+ {
+ String testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef,
+ ContentModel.PROP_FIRSTNAME));
+ if ((testString == null) || !testString.equals(userName))
+ {
+ return false;
+ }
+
+ testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef,
+ ContentModel.PROP_LASTNAME));
+ if ((testString == null) || !testString.equals(""))
+ {
+ return false;
+ }
+
+ testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef,
+ ContentModel.PROP_EMAIL));
+ if ((testString == null) || !testString.equals(""))
+ {
+ return false;
+ }
+
+ testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef,
+ ContentModel.PROP_ORGID));
+ if ((testString == null) || !testString.equals(""))
+ {
+ return false;
+ }
+
+ testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef,
+ ContentModel.PROP_HOME_FOLDER_PROVIDER));
+ if ((testString == null) || !testString.equals(defaultHomeFolderProvider))
+ {
+ return false;
+ }
+
+ return true;
}
public boolean createMissingPeople()
@@ -225,8 +528,8 @@ public class PersonServiceImpl implements PersonService
}
else
{
- String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personNode,
- ContentModel.PROP_USERNAME));
+ String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(
+ personNode, ContentModel.PROP_USERNAME));
properties.put(ContentModel.PROP_USERNAME, realUserName);
}
diff --git a/source/java/org/alfresco/repo/security/person/UIDBasedHomeFolderProvider.java b/source/java/org/alfresco/repo/security/person/UIDBasedHomeFolderProvider.java
index 1208023a48..87937f6a02 100644
--- a/source/java/org/alfresco/repo/security/person/UIDBasedHomeFolderProvider.java
+++ b/source/java/org/alfresco/repo/security/person/UIDBasedHomeFolderProvider.java
@@ -53,6 +53,12 @@ public class UIDBasedHomeFolderProvider extends ExistingPathBasedHomeFolderProvi
{
String uid = DefaultTypeConverter.INSTANCE.convert(String.class, getServiceRegistry().getNodeService()
.getProperty(person, ContentModel.PROP_USERNAME));
+
+ if((uid == null) || (uid.length() == 0))
+ {
+ throw new PersonException("Can not create a home space when the uid is null or empty");
+ }
+
FileInfo fileInfo;
// Test if it already exists