Merged V3.2 to HEAD

17462: ETHREEOH-3346: New meaning to synchronization.synchronizeChangesOnly property
      - In the LDAP sync performance optimizations we always used the differential queries to determine the users and groups to be updated. Deletions were determined by a separate query.
      - This meant that if you ever did want to force the update of all users it wasn't possible.
      - So now when the flag is false it means don't use differential queries in the scheduled sync job.
      - The scheduled job now processes deletions regardless.
      - The default value for the property is now true.
   17431: ETHREEOH-3274: Refix NTLM support for share
      - Fixed NPE introduced by ETHREEOH-2767
      - Made web.xml validate against schema for JBoss
      - Reintroduced missing open comment in webscript-framework-config-custom.xml.sample
   17426: ETHREEOH-2997: Fix ticket parameter passing into NTLM/Kerberos WebDAV authentication filters
      - A NPE was stopping it from working
   17425: ETHREEOH-3282: Fixed NPE preventing upload from working with NTLM SSO enabled
   17368: ETHREEOH-3197: Use utf8_bin collation in MySQL out of the box to avoid problems with comparison of accented characters
   17361: ETHREEOH-3276: Don't attempt to start an LDAP sync when the repository is read only
   17347: ETHREEOH-3206: Fix LocalFeedTaskProcessor to work with JBoss 5


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@17464 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Dave Ward
2009-11-13 12:40:33 +00:00
parent 1fa09a014d
commit abe965cf15
8 changed files with 87 additions and 54 deletions

View File

@@ -56,8 +56,8 @@
<property name="applicationContextManager">
<ref bean="Authentication" />
</property>
<property name="retryingTransactionHelper">
<ref bean="retryingTransactionHelper" />
<property name="transactionService">
<ref bean="transactionService" />
</property>
<property name="ruleService">
<ref bean="ruleService" />

View File

@@ -2,12 +2,14 @@
# This properties file is used to configure user registry syncronisation (e.g. LDAP)
#
# Should the scheduled sync job only query users and groups changed since the
# last sync? Note that when true, the sync job will not be able to detect which
# users or groups have been removed from the directory (but obviously group
# membership changes would still be reflected). When false, a more regular
# differential sync on login can still be enabled.
synchronization.synchronizeChangesOnly=false
# Should the scheduled sync job use differential or full queries on the user
# registries to determine the set of local users to be updated? When true,
# each user registry is only queried for those users and groups modified since
# the most recent modification date of all the objects last queried from that
# same source. When <code>false</code> then <i>all</i> users and groups are
# queried from the user registry and updated locally. Nevertheless, a separate
# query will be run by the scheduled sync job to determine deletions.
synchronization.synchronizeChangesOnly=true
# The cron expression defining when imports should take place
synchronization.import.cron=0 0 0 * * ?

View File

@@ -47,6 +47,9 @@ import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.site.SiteService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
@@ -59,7 +62,7 @@ import freemarker.template.DefaultObjectWrapper;
/**
* The local (ie. not grid) feed task processor is responsible for processing the individual feed job
*/
public class LocalFeedTaskProcessor extends FeedTaskProcessor
public class LocalFeedTaskProcessor extends FeedTaskProcessor implements ApplicationContextAware
{
private static final Log logger = LogFactory.getLog(LocalFeedTaskProcessor.class);
@@ -74,6 +77,7 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor
private String defaultEncoding;
private List<String> templateSearchPaths;
private boolean useRemoteCallbacks;
private ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// used to start/end/commit transaction
// note: currently assumes that all dao services are configured with this mapper / data source
@@ -129,6 +133,11 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor
this.sqlMapper = sqlMapper;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.resolver = applicationContext;
}
public void startTransaction() throws SQLException
{
sqlMapper.startTransaction();
@@ -307,7 +316,6 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor
// Helper to return a list of resource document paths based on a search pattern.
private List<String> getPaths(String pattern, String classPath) throws IOException
{
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(pattern);
List<String> documentPaths = new ArrayList<String>(resources.length);
for (Resource resource : resources)

View File

@@ -59,6 +59,7 @@ import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.AbstractLifecycleBean;
import org.alfresco.util.PropertyMap;
import org.apache.commons.logging.Log;
@@ -132,6 +133,9 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
/** The attribute service. */
private AttributeService attributeService;
/** The transaction service. */
private TransactionService transactionService;
/** The retrying transaction helper. */
private RetryingTransactionHelper retryingTransactionHelper;
@@ -215,14 +219,15 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
}
/**
* Sets the retrying transaction helper.
* Sets the transaction service.
*
* @param retryingTransactionHelper
* the new retrying transaction helper
* @param transactionService
* the transaction service
*/
public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
public void setTransactionService(TransactionService transactionService)
{
this.retryingTransactionHelper = retryingTransactionHelper;
this.transactionService = transactionService;
this.retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
}
/**
@@ -315,10 +320,18 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
/*
* (non-Javadoc)
* @see org.alfresco.repo.security.sync.UserRegistrySynchronizer#synchronize(boolean, boolean)
* @see org.alfresco.repo.security.sync.UserRegistrySynchronizer#synchronize(boolean, boolean, boolean)
*/
public void synchronize(boolean force, boolean splitTxns)
public void synchronize(boolean forceUpdate, boolean allowDeletions, boolean splitTxns)
{
// Don't proceed with the sync if the repository is read only
if (this.transactionService.isReadOnly())
{
ChainingUserRegistrySynchronizer.logger
.warn("Unable to proceed with user registry synchronization. Repository is read only.");
return;
}
String lockToken = null;
// Let's ensure all exceptions get logged
@@ -380,10 +393,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
ChainingUserRegistrySynchronizer.logger
.info("Synchronizing users and groups with user registry '" + id + "'");
}
if (force && ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
if (allowDeletions && ChainingUserRegistrySynchronizer.logger.isWarnEnabled())
{
ChainingUserRegistrySynchronizer.logger
.warn("Forced synchronization with user registry '"
.warn("Full synchronization with user registry '"
+ id
+ "'; some users and groups previously created by synchronization with this user registry may be removed.");
}
@@ -393,7 +406,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
boolean requiresNew = splitTxns
|| AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY;
syncWithPlugin(id, plugin, force, requiresNew, visitedZoneIds, allZoneIds);
syncWithPlugin(id, plugin, forceUpdate, allowDeletions, requiresNew, visitedZoneIds, allZoneIds);
}
}
catch (NoSuchBeanDefinitionException e)
@@ -439,7 +452,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
{
try
{
synchronize(false, false);
synchronize(false, false, false);
}
catch (Exception e)
{
@@ -474,8 +487,16 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
* tell those that have been deleted from the registry.
* @param userRegistry
* the user registry for the zone.
* @param force
* <code>true</code> if user and group deletions are to be processed.
* @param forceUpdate
* Should the complete set of users and groups be updated / created locally or just those known to have
* changed since the last sync? When <code>true</code> then <i>all</i> users and groups are queried from
* the user registry and updated locally. When <code>false</code> then each source is only queried for
* those users and groups modified since the most recent modification date of all the objects last
* queried from that same source.
* @param allowDeletions
* Should a complete set of user and group IDs be queried from the user registries in order to determine
* deletions? This parameter is independent of <code>force</code> as a separate query is run to process
* updates.
* @param splitTxns
* Can the modifications to Alfresco be split across multiple transactions for maximum performance? If
* <code>true</code>, users and groups are created/updated in batches for increased performance. If
@@ -490,8 +511,8 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
* recorded against a user or group is invalid for the current authentication chain and whether the user
* or group needs to be 're-zoned'.
*/
private void syncWithPlugin(final String zone, UserRegistry userRegistry, boolean force, boolean splitTxns,
final Set<String> visitedZoneIds, final Set<String> allZoneIds)
private void syncWithPlugin(final String zone, UserRegistry userRegistry, boolean forceUpdate,
boolean allowDeletions, boolean splitTxns, final Set<String> visitedZoneIds, final Set<String> allZoneIds)
{
// Create a prefixed zone ID for use with the authority service
final String zoneId = AuthorityService.ZONE_AUTH_EXT_PREFIX + zone;
@@ -499,7 +520,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
// The set of zones we associate with new objects (default plus registry specific)
final Set<String> zoneSet = getZones(zoneId);
long lastModifiedMillis = getMostRecentUpdateTime(
long lastModifiedMillis = forceUpdate ? -1 : getMostRecentUpdateTime(
ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns);
Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
@@ -708,7 +729,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
Set<String> deletionCandidates = null;
// If we got back some groups, we have to cross reference them with the set of known authorities
if (force || !groupAssocsToCreate.isEmpty())
if (allowDeletions || !groupAssocsToCreate.isEmpty())
{
// Get current set of known authorities
Set<String> allZoneAuthorities = this.retryingTransactionHelper.doInTransaction(
@@ -724,7 +745,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
allZoneAuthorities.addAll(groupAnalyzer.getAllZoneAuthorities());
// Prune our set of authorities according to deletions
if (force)
if (allowDeletions)
{
deletionCandidates = new TreeSet<String>(allZoneAuthorities);
userRegistry.processDeletions(deletionCandidates);
@@ -822,8 +843,8 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
// Process persons and their parent associations
lastModifiedMillis = getMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE,
zoneId, splitTxns);
lastModifiedMillis = forceUpdate ? -1 : getMostRecentUpdateTime(
ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns);
lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{
@@ -994,7 +1015,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
}
// Delete authorities if we have complete information for the zone
if (force)
if (allowDeletions)
{
BatchProcessor<String> authorityDeletionProcessor = new BatchProcessor<String>(
this.retryingTransactionHelper, this.ruleService, this.applicationEventPublisher,
@@ -1214,7 +1235,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
{
try
{
synchronize(false, true);
synchronize(false, false, true);
}
catch (Exception e)
{

View File

@@ -171,7 +171,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
{
newGroup("G2", "U1", "U3", "U4"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U5")
}));
this.synchronizer.synchronize(true, true);
this.synchronizer.synchronize(true, true, true);
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{
@@ -208,7 +208,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
new NodeDescription[] {}), new MockUserRegistry("Z1", new NodeDescription[] {},
new NodeDescription[] {}), new MockUserRegistry("Z2", new NodeDescription[] {},
new NodeDescription[] {}));
this.synchronizer.synchronize(true, true);
this.synchronizer.synchronize(true, true, true);
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{
@@ -271,7 +271,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
public Object execute() throws Throwable
{
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(false, false);
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(false, false, false);
// Stay in the same transaction
assertExists("Z1", "U1");
assertEmailEquals("U1", "changeofemail@alfresco.com");
@@ -332,7 +332,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
{
newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U4", "U5")
}));
this.synchronizer.synchronize(true, true);
this.synchronizer.synchronize(true, true, true);
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{
@@ -369,7 +369,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
List<NodeDescription> persons = new ArrayList<NodeDescription>(new RandomPersonCollection(100));
List<NodeDescription> groups = new ArrayList<NodeDescription>(new RandomGroupCollection(100, persons));
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", persons, groups));
this.synchronizer.synchronize(true, true);
this.synchronizer.synchronize(true, true, true);
tearDownTestUsersAndGroups();
}
@@ -394,7 +394,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
}, true, true);
ChainingUserRegistrySynchronizerTest.this.applicationContextManager.setUserRegistries(new MockUserRegistry(
"Z0", Collections.<NodeDescription> emptyList(), groups));
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true);
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true, true);
tearDownTestUsersAndGroups();
}

View File

@@ -48,19 +48,21 @@ public interface UserRegistrySynchronizer
* users and groups last retrieved from the same sources. Any updates and additions made to those users and groups
* are applied to the local Alfresco copies.
*
* @param force
* Should a complete or partial set of information be queried from the external sources? When
* <code>true</code> then <i>all</i> users and groups are queried. With this complete set of information,
* the implementation is able to identify which users and groups have been deleted, so it will delete
* users and groups as well as update and create them. When <code>false</code> then each source is only
* queried for those users and groups modified since the most recent modification date of all the objects
* last queried from that same source. In this mode, local users and groups are created and updated, but
* not deleted.
* @param forceUpdate
* Should the complete set of users and groups be updated / created locally or just those known to have
* changed since the last sync? When <code>true</code> then <i>all</i> users and groups are queried from
* the user registry and updated locally. When <code>false</code> then each source is only queried for
* those users and groups modified since the most recent modification date of all the objects last
* queried from that same source.
* @param allowDeletions
* Should a complete set of user and group IDs be queried from the user registries in order to determine
* deletions? This parameter is independent of <code>force</code> as a separate query is run to process
* updates.
* @param splitTxns
* Can the modifications to Alfresco be split across multiple transactions for maximum performance? If
* <code>true</code>, users and groups are created/updated in batches of 10 for increased performance. If
* <code>false</code>, all users and groups are processed in the current transaction. This is required if
* calling synchronously (e.g. in response to an authentication event in the same transaction).
*/
public void synchronize(boolean force, boolean splitTxns);
public void synchronize(boolean forceUpdate, boolean allowDeletions, boolean splitTxns);
}

View File

@@ -32,9 +32,9 @@ import org.quartz.JobExecutionException;
/**
* A scheduled job that regularly invokes a {@link UserRegistrySynchronizer}. Supports a
* <code>synchronizeChangesOnly</code> string parameter. When <code>"true"</code> means that the
* {@link UserRegistrySynchronizer#synchronize(boolean)} method will be called with a <code>false</code> argument rather
* than the default <code>true</code>.
* <code>synchronizeChangesOnly</code> string parameter. When <code>"false"</code> means that the
* {@link UserRegistrySynchronizer#synchronize(boolean)} method will be called with a <code>true</code> forceUpdate
* argument rather than the default <code>false</code>.
*
* @author dward
*/
@@ -55,7 +55,7 @@ public class UserRegistrySynchronizerJob implements Job
public Object doWork() throws Exception
{
userRegistrySynchronizer.synchronize(synchronizeChangesOnly == null
|| !Boolean.parseBoolean(synchronizeChangesOnly), true);
|| !Boolean.parseBoolean(synchronizeChangesOnly), true, true);
return null;
}
}, AuthenticationUtil.getSystemUserName());

View File

@@ -15,8 +15,8 @@
<property name="applicationContextManager">
<ref bean="testApplicationContextManager" />
</property>
<property name="retryingTransactionHelper">
<ref bean="retryingTransactionHelper" />
<property name="transactionService">
<ref bean="transactionService" />
</property>
<property name="ruleService">
<ref bean="ruleService" />