Merged 1.4 to HEAD

svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@4380 svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@4386 .


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4659 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2006-12-19 15:04:47 +00:00
parent 31d1fa1fb1
commit 4443f42279
14 changed files with 601 additions and 46 deletions

View File

@@ -255,6 +255,20 @@
<property name="userNamesAreCaseSensitive"> <property name="userNamesAreCaseSensitive">
<value>${user.name.caseSensitive}</value> <value>${user.name.caseSensitive}</value>
</property> </property>
<!-- New properties after 1.4.0 to deal with duplicate user ids when found -->
<property name="processDuplicates">
<value>true</value>
</property>
<!-- one of: LEAVE, SPLIT, DELETE -->
<property name="duplicateMode">
<value>SPLIT</value>
</property>
<property name="lastIsBest">
<value>true</value>
</property>
<property name="includeAutoCreated">
<value>false</value>
</property>
</bean> </bean>
<bean name="homeFolderManager" class="org.alfresco.repo.security.person.HomeFolderManager"> <bean name="homeFolderManager" class="org.alfresco.repo.security.person.HomeFolderManager">
@@ -342,6 +356,9 @@
<!-- The ticket component. --> <!-- The ticket component. -->
<!-- Used for reauthentication --> <!-- Used for reauthentication -->
<bean id="ticketComponent" class="org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl"> <bean id="ticketComponent" class="org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl">
<property name="ticketsCache">
<ref bean="ticketsCache"/>
</property>
<!-- The period for which tickets are valid in XML duration format. --> <!-- The period for which tickets are valid in XML duration format. -->
<!-- The default is P1H for one hour. --> <!-- The default is P1H for one hour. -->
<property name="validDuration"> <property name="validDuration">

View File

@@ -153,4 +153,46 @@
</property> </property>
</bean> </bean>
<!-- ===================================== -->
<!-- Authentication Ticket Cache -->
<!-- ===================================== -->
<!-- The cross-transaction shared cache for In-Memory Tickets -->
<bean name="ticketsSharedCache" class="org.alfresco.repo.cache.EhCacheAdapter">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
<property name="cacheManager">
<ref bean="internalEHCacheManager" />
</property>
<property name="cacheName">
<value>ticketsCache</value>
</property>
<property name="maxElementsInMemory">
<value>1000</value>
</property>
<property name="overflowToDisk">
<value>false</value>
</property>
</bean>
</property>
</bean>
<!-- The transactional cache for In-Memory Tickets -->
<bean name="ticketsCache" class="org.alfresco.repo.cache.TransactionalCache">
<property name="sharedCache">
<ref bean="ticketsSharedCache" />
</property>
<property name="cacheManager" >
<ref bean="transactionalEHCacheManager" />
</property>
<property name="name">
<value>ticketsTransactionalCache</value>
</property>
<property name="maxCacheSize">
<value>10</value>
</property>
</bean>
</beans> </beans>

View File

@@ -475,11 +475,8 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
} }
catch (Throwable exception) catch (Throwable exception)
{ {
// Log the exception // DH: No logging of the exception. Leave the logging decision to the client code,
logger.error( // which can handle the rethrown exception.
"An error was encountered whilst executing the action '" + action.getActionDefinitionName() + "'.",
exception);
if (executedAsynchronously == true) if (executedAsynchronously == true)
{ {
// If one is specified, queue the compensating action ready for execution // If one is specified, queue the compensating action ready for execution

View File

@@ -3,9 +3,14 @@
*/ */
package org.alfresco.repo.admin.patch.util; package org.alfresco.repo.admin.patch.util;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; 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.HashMap;
import java.util.Map; import java.util.Map;
@@ -35,6 +40,12 @@ public class ImportFileUpdater
/** The destination export version number **/ /** The destination export version number **/
private static String EXPORT_VERSION = "1.4.0"; 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 **/ /** Element names **/
private static String NAME_EXPORTER_VERSION = "exporterVersion"; private static String NAME_EXPORTER_VERSION = "exporterVersion";
private static String NAME_RULE = "rule"; private static String NAME_RULE = "rule";
@@ -43,6 +54,16 @@ public class ImportFileUpdater
private String version; private String version;
private boolean shownWarning = false; 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. * 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); XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
factory.setNamespaceAware(true); factory.setNamespaceAware(true);
InputStream inputStream = new FileInputStream(source);
Reader inputReader = new InputStreamReader(inputStream, this.fileEncoding);
XmlPullParser xpp = factory.newPullParser(); XmlPullParser xpp = factory.newPullParser();
xpp.setInput(new FileReader(source)); xpp.setInput(new BufferedReader(inputReader));
return xpp; return xpp;
} }
@@ -108,6 +132,10 @@ public class ImportFileUpdater
{ {
throw new AlfrescoRuntimeException("The source file could not be loaded.", fileNotFound); 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(); OutputFormat format = OutputFormat.createPrettyPrint();
format.setNewLineAfterDeclaration(false); format.setNewLineAfterDeclaration(false);
format.setIndentSize(INDENT_SIZE); format.setIndentSize(INDENT_SIZE);
format.setEncoding("UTF-8"); format.setEncoding(this.fileEncoding);
return new XMLWriter(new FileOutputStream(destination), format); return new XMLWriter(new FileOutputStream(destination), format);
} }
@@ -573,11 +601,18 @@ public class ImportFileUpdater
ImportFileUpdater util = new ImportFileUpdater(); ImportFileUpdater util = new ImportFileUpdater();
util.updateImportFile(args[0], args[1]); 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 else
{ {
System.out.println(" ImportFileUpdater <source> <destination>"); System.out.println(" ImportFileUpdater <source> <destination>");
System.out.println(" source - 1.3 import file name to be updated"); 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(" destination - name of the generated 1.4 import file");
System.out.println(" file encoding (optional) - the file encoding, default is UTF-8");
} }
} }

View File

@@ -17,6 +17,7 @@
package org.alfresco.repo.cache; package org.alfresco.repo.cache;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import javax.transaction.Status; import javax.transaction.Status;
import javax.transaction.UserTransaction; import javax.transaction.UserTransaction;
@@ -86,6 +87,9 @@ public class CacheTest extends TestCase
assertEquals("AAA", backingCache.get("A")); assertEquals("AAA", backingCache.get("A"));
Collection<String> keys = backingCache.getKeys();
assertEquals("Backing cache didn't return correct number of keys", 1, keys.size());
backingCache.remove("A"); backingCache.remove("A");
assertNull(backingCache.get("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)); assertEquals("Item not updated in txn cache", "XXX", transactionalCache.get(updatedTxnThree));
assertFalse("Item was put into backing cache", backingCache.contains(updatedTxnThree)); assertFalse("Item was put into backing cache", backingCache.contains(updatedTxnThree));
// check that the keys collection is correct
Collection<String> 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 // commit the transaction
txn.commit(); txn.commit();

View File

@@ -17,6 +17,7 @@
package org.alfresco.repo.cache; package org.alfresco.repo.cache;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import net.sf.ehcache.Cache; import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException; import net.sf.ehcache.CacheException;
@@ -64,6 +65,12 @@ public class EhCacheAdapter<K extends Serializable, V extends Serializable>
} }
} }
@SuppressWarnings("unchecked")
public Collection<K> getKeys()
{
return cache.getKeys();
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public V get(K key) public V get(K key)
{ {

View File

@@ -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.
* <P/>
* 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<K extends Serializable, V extends Serializable> implements SimpleCache<K, V>
{
public NullCache()
{
}
/** NO-OP */
public boolean contains(K key)
{
return false;
}
public Collection<K> getKeys()
{
return Collections.<K>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;
}
}

View File

@@ -17,6 +17,7 @@
package org.alfresco.repo.cache; package org.alfresco.repo.cache;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
/** /**
* Basic caching interface. * Basic caching interface.
@@ -32,6 +33,8 @@ public interface SimpleCache<K extends Serializable, V extends Serializable>
{ {
public boolean contains(K key); public boolean contains(K key);
public Collection<K> getKeys();
public V get(K key); public V get(K key);
public void put(K key, V value); public void put(K key, V value);

View File

@@ -17,6 +17,8 @@
package org.alfresco.repo.cache; package org.alfresco.repo.cache;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import net.sf.ehcache.Cache; import net.sf.ehcache.Cache;
@@ -227,6 +229,39 @@ public class TransactionalCache<K extends Serializable, V extends Serializable>
} }
} }
/**
* 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<K> getKeys()
{
Collection<K> keys = null;
// in-txn layering
if (AlfrescoTransactionSupport.getTransactionId() != null)
{
keys = new HashSet<K>(23);
TransactionData txnData = getTransactionData();
if (!txnData.isClearOn)
{
// the backing cache is not due for a clear
Collection<K> backingKeys = (Collection<K>) sharedCache.getKeys();
keys.addAll(backingKeys);
}
// add keys
keys.addAll((Collection<K>) txnData.updatedItemsCache.getKeys());
// remove keys
keys.removeAll((Collection<K>) txnData.removedItemsCache.getKeys());
}
else
{
// no transaction, so just use the backing cache
keys = (Collection<K>) sharedCache.getKeys();
}
// done
return keys;
}
/** /**
* Checks the per-transaction caches for the object before going to the shared cache. * Checks the per-transaction caches for the object before going to the shared cache.
* If the thread is not in a transaction, then the shared cache is accessed directly. * If the thread is not in a transaction, then the shared cache is accessed directly.

View File

@@ -39,6 +39,8 @@ import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.dao.SaltSource; import net.sf.acegisecurity.providers.dao.SaltSource;
import org.alfresco.model.ContentModel; 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.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
@@ -54,6 +56,7 @@ import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@SuppressWarnings("unchecked")
public class AuthenticationTest extends TestCase public class AuthenticationTest extends TestCase
{ {
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
@@ -82,6 +85,8 @@ public class AuthenticationTest extends TestCase
private TicketComponent ticketComponent; private TicketComponent ticketComponent;
private SimpleCache<String, Ticket> ticketsCache;
private AuthenticationService authenticationService; private AuthenticationService authenticationService;
private AuthenticationService pubAuthenticationService; private AuthenticationService pubAuthenticationService;
@@ -116,6 +121,7 @@ public class AuthenticationTest extends TestCase
authenticationComponentImpl = (AuthenticationComponent) ctx.getBean("authenticationComponentImpl"); authenticationComponentImpl = (AuthenticationComponent) ctx.getBean("authenticationComponentImpl");
// permissionServiceSPI = (PermissionServiceSPI) // permissionServiceSPI = (PermissionServiceSPI)
// ctx.getBean("permissionService"); // ctx.getBean("permissionService");
ticketsCache = (SimpleCache<String, Ticket>) ctx.getBean("ticketsCache");
dao = (MutableAuthenticationDao) ctx.getBean("alfDaoImpl"); dao = (MutableAuthenticationDao) ctx.getBean("alfDaoImpl");
authenticationManager = (AuthenticationManager) ctx.getBean("authenticationManager"); authenticationManager = (AuthenticationManager) ctx.getBean("authenticationManager");
@@ -143,7 +149,6 @@ public class AuthenticationTest extends TestCase
deleteAndy(); deleteAndy();
authenticationComponent.clearCurrentSecurityContext(); authenticationComponent.clearCurrentSecurityContext();
} }
private void deleteAndy() private void deleteAndy()
@@ -476,6 +481,7 @@ public class AuthenticationTest extends TestCase
tc.setOneOff(false); tc.setOneOff(false);
tc.setTicketsExpire(false); tc.setTicketsExpire(false);
tc.setValidDuration("P0D"); tc.setValidDuration("P0D");
tc.setTicketsCache(ticketsCache);
dao.createUser("Andy", "ticket".toCharArray()); dao.createUser("Andy", "ticket".toCharArray());
@@ -499,6 +505,7 @@ public class AuthenticationTest extends TestCase
tc.setOneOff(true); tc.setOneOff(true);
tc.setTicketsExpire(false); tc.setTicketsExpire(false);
tc.setValidDuration("P0D"); tc.setValidDuration("P0D");
tc.setTicketsCache(ticketsCache);
dao.createUser("Andy", "ticket".toCharArray()); dao.createUser("Andy", "ticket".toCharArray());
@@ -530,6 +537,7 @@ public class AuthenticationTest extends TestCase
tc.setOneOff(false); tc.setOneOff(false);
tc.setTicketsExpire(true); tc.setTicketsExpire(true);
tc.setValidDuration("P5S"); tc.setValidDuration("P5S");
tc.setTicketsCache(ticketsCache);
dao.createUser("Andy", "ticket".toCharArray()); dao.createUser("Andy", "ticket".toCharArray());
@@ -619,6 +627,7 @@ public class AuthenticationTest extends TestCase
tc.setOneOff(false); tc.setOneOff(false);
tc.setTicketsExpire(true); tc.setTicketsExpire(true);
tc.setValidDuration("P1D"); tc.setValidDuration("P1D");
tc.setTicketsCache(ticketsCache);
dao.createUser("Andy", "ticket".toCharArray()); dao.createUser("Andy", "ticket".toCharArray());

View File

@@ -16,11 +16,12 @@
*/ */
package org.alfresco.repo.security.authentication; package org.alfresco.repo.security.authentication;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.cmr.repository.datatype.Duration; import org.alfresco.service.cmr.repository.datatype.Duration;
import org.alfresco.util.GUID; import org.alfresco.util.GUID;
public class InMemoryTicketComponentImpl implements TicketComponent public class InMemoryTicketComponentImpl implements TicketComponent
@@ -33,13 +34,18 @@ public class InMemoryTicketComponentImpl implements TicketComponent
private boolean oneOff; private boolean oneOff;
private HashMap<String, Ticket> tickets = new HashMap<String, Ticket>(); private SimpleCache<String, Ticket> ticketsCache; // Can't use Ticket as it's private
public InMemoryTicketComponentImpl() public InMemoryTicketComponentImpl()
{ {
super(); super();
} }
public void setTicketsCache(SimpleCache<String, Ticket> ticketsCache)
{
this.ticketsCache = ticketsCache;
}
public String getTicket(String userName) throws AuthenticationException public String getTicket(String userName) throws AuthenticationException
{ {
Date expiryDate = null; Date expiryDate = null;
@@ -48,7 +54,7 @@ public class InMemoryTicketComponentImpl implements TicketComponent
expiryDate = Duration.add(new Date(), validDuration); expiryDate = Duration.add(new Date(), validDuration);
} }
Ticket ticket = new Ticket(ticketsExpire, expiryDate, userName); Ticket ticket = new Ticket(ticketsExpire, expiryDate, userName);
tickets.put(ticket.getTicketId(), ticket); ticketsCache.put(ticket.getTicketId(), ticket);
return GRANTED_AUTHORITY_TICKET_PREFIX + ticket.getTicketId(); 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()); String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length());
Ticket ticket = tickets.get(key); Ticket ticket = ticketsCache.get(key);
if (ticket == null) if (ticket == null)
{ {
throw new AuthenticationException("Missing ticket for " + ticketString); throw new AuthenticationException("Missing ticket for " + ticketString);
@@ -74,7 +80,7 @@ public class InMemoryTicketComponentImpl implements TicketComponent
// TODO: Strengthen ticket as GUID is predicatble // TODO: Strengthen ticket as GUID is predicatble
if(oneOff) if(oneOff)
{ {
tickets.remove(key); ticketsCache.remove(key);
} }
return ticket.getUserName(); return ticket.getUserName();
} }
@@ -82,16 +88,16 @@ public class InMemoryTicketComponentImpl implements TicketComponent
public void invalidateTicketById(String ticketString) public void invalidateTicketById(String ticketString)
{ {
String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length()); String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length());
tickets.remove(key); ticketsCache.remove(key);
} }
public void invalidateTicketByUser(String userName) public void invalidateTicketByUser(String userName)
{ {
Set<String> toRemove = new HashSet<String>(); Set<String> toRemove = new HashSet<String>();
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)) if(ticket.getUserName().equals(userName))
{ {
toRemove.add(ticket.getTicketId()); toRemove.add(ticket.getTicketId());
@@ -100,14 +106,16 @@ public class InMemoryTicketComponentImpl implements TicketComponent
for(String id: toRemove) 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 boolean expires;
private Date expiryDate; private Date expiryDate;

View File

@@ -21,6 +21,7 @@ import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.Writer; import java.io.Writer;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import javax.naming.NamingEnumeration; import javax.naming.NamingEnumeration;
@@ -147,6 +148,8 @@ public class LDAPPersonExportSource implements ExportSource
writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view",
NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl()); NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl());
HashSet<String> uids = new HashSet<String>();
InitialDirContext ctx = null; InitialDirContext ctx = null;
try try
{ {
@@ -161,7 +164,7 @@ public class LDAPPersonExportSource implements ExportSource
userSearchCtls.setCountLimit(Integer.MAX_VALUE); userSearchCtls.setCountLimit(Integer.MAX_VALUE);
NamingEnumeration searchResults = ctx.search(searchBase, personQuery, userSearchCtls); NamingEnumeration searchResults = ctx.search(searchBase, personQuery, userSearchCtls);
while (searchResults.hasMoreElements()) RESULT_LOOP: while (searchResults.hasMoreElements())
{ {
SearchResult result = (SearchResult) searchResults.next(); SearchResult result = (SearchResult) searchResults.next();
Attributes attributes = result.getAttributes(); Attributes attributes = result.getAttributes();
@@ -171,22 +174,31 @@ public class LDAPPersonExportSource implements ExportSource
if (errorOnMissingUID) if (errorOnMissingUID)
{ {
throw new ExportSourceImporterException( throw new ExportSourceImporterException(
"User returned by user search does not have mandatory user id attribute " + attributes); "User returned by user search does not have mandatory user id attribute "
+ attributes);
} }
else else
{ {
s_logger.warn("User returned by user search does not have mandatory user id attribute " + attributes); s_logger.warn("User returned by user search does not have mandatory user id attribute "
continue; + attributes);
continue RESULT_LOOP;
} }
} }
String uid = (String) uidAttribute.get(0); 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()) if (s_logger.isDebugEnabled())
{ {
s_logger.debug("Adding user for " + uid); s_logger.debug("Adding user for " + uid);
} }
writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON
.getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs); .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs);

View File

@@ -27,6 +27,7 @@ import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.ldap.LDAPPersonExportSource;
import org.alfresco.repo.security.permissions.PermissionServiceSPI; import org.alfresco.repo.security.permissions.PermissionServiceSPI;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; 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.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.NamespacePrefixResolver;
import org.alfresco.service.namespace.QName; 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 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 SYSTEM_FOLDER = "/sys:system";
public static final String PEOPLE_FOLDER = SYSTEM_FOLDER + "/sys:people"; public static final String PEOPLE_FOLDER = SYSTEM_FOLDER + "/sys:people";
@@ -70,6 +82,14 @@ public class PersonServiceImpl implements PersonService
private String defaultHomeFolderProvider; private String defaultHomeFolderProvider;
private boolean processDuplicates = true;
private String duplicateMode = LEAVE;
private boolean lastIsBest = true;
private boolean includeAutoCreated = false;
static static
{ {
Set<QName> props = new HashSet<QName>(); Set<QName> props = new HashSet<QName>();
@@ -102,6 +122,26 @@ public class PersonServiceImpl implements PersonService
this.defaultHomeFolderProvider = 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) public NodeRef getPerson(String userName)
{ {
NodeRef personNode = getPersonOrNull(userName); NodeRef personNode = getPersonOrNull(userName);
@@ -130,21 +170,23 @@ public class PersonServiceImpl implements PersonService
public NodeRef getPersonOrNull(String searchUserName) public NodeRef getPersonOrNull(String searchUserName)
{ {
NodeRef returnRef = null;
SearchParameters sp = new SearchParameters(); SearchParameters sp = new SearchParameters();
sp.setLanguage(SearchService.LANGUAGE_LUCENE); 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.addStore(storeRef);
sp.excludeDataInTheCurrentTransaction(false); sp.excludeDataInTheCurrentTransaction(false);
ResultSet rs = null; ResultSet rs = null;
boolean singleton = true;
try try
{ {
rs = searchService.query(sp); rs = searchService.query(sp);
NodeRef returnRef = null;
for (ResultSetRow row : rs) for (ResultSetRow row : rs)
{ {
@@ -164,8 +206,8 @@ public class PersonServiceImpl implements PersonService
} }
else else
{ {
throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName singleton = false;
+ " (case sensitive)"); break;
} }
} }
} }
@@ -179,15 +221,13 @@ public class PersonServiceImpl implements PersonService
} }
else else
{ {
throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName singleton = false;
+ " (case insensitive)"); break;
} }
} }
} }
} }
} }
return returnRef;
} }
finally finally
{ {
@@ -196,6 +236,269 @@ public class PersonServiceImpl implements PersonService
rs.close(); 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() public boolean createMissingPeople()
@@ -225,8 +528,8 @@ public class PersonServiceImpl implements PersonService
} }
else else
{ {
String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personNode, String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(
ContentModel.PROP_USERNAME)); personNode, ContentModel.PROP_USERNAME));
properties.put(ContentModel.PROP_USERNAME, realUserName); properties.put(ContentModel.PROP_USERNAME, realUserName);
} }

View File

@@ -53,6 +53,12 @@ public class UIDBasedHomeFolderProvider extends ExistingPathBasedHomeFolderProvi
{ {
String uid = DefaultTypeConverter.INSTANCE.convert(String.class, getServiceRegistry().getNodeService() String uid = DefaultTypeConverter.INSTANCE.convert(String.class, getServiceRegistry().getNodeService()
.getProperty(person, ContentModel.PROP_USERNAME)); .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; FileInfo fileInfo;
// Test if it already exists // Test if it already exists