diff --git a/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml b/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml index 3c481a30bd..9272d63016 100644 --- a/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml +++ b/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml @@ -56,8 +56,8 @@ - - + + diff --git a/config/alfresco/subsystems/Synchronization/default/default-synchronization.properties b/config/alfresco/subsystems/Synchronization/default/default-synchronization.properties index d3e535bfab..36d8417e29 100644 --- a/config/alfresco/subsystems/Synchronization/default/default-synchronization.properties +++ b/config/alfresco/subsystems/Synchronization/default/default-synchronization.properties @@ -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 false then all 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 * * ? diff --git a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java index a32a84db7d..454d398d14 100644 --- a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java +++ b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java @@ -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 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 @@ -128,7 +132,12 @@ 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 getPaths(String pattern, String classPath) throws IOException { - ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources(pattern); List documentPaths = new ArrayList(resources.length); for (Resource resource : resources) diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java index a3ab3e8134..309ade8f4f 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java @@ -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 - * true 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 true then all users and groups are queried from + * the user registry and updated locally. When false 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 force 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 * true, 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 visitedZoneIds, final Set allZoneIds) + private void syncWithPlugin(final String zone, UserRegistry userRegistry, boolean forceUpdate, + boolean allowDeletions, boolean splitTxns, final Set visitedZoneIds, final Set 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 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 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 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(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 authorityDeletionProcessor = new BatchProcessor( 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) { diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java index 0927c25d8d..73b97e286d 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java @@ -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() { @@ -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() { @@ -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() { @@ -369,7 +369,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase List persons = new ArrayList(new RandomPersonCollection(100)); List groups = new ArrayList(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. emptyList(), groups)); - ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true); + ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true, true); tearDownTestUsersAndGroups(); } diff --git a/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java index 90b9a11c2a..6066a92876 100644 --- a/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java +++ b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java @@ -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 - * true then all 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 false 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 true then all users and groups are queried from + * the user registry and updated locally. When false 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 force 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 * true, users and groups are created/updated in batches of 10 for increased performance. If * false, 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); } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizerJob.java b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizerJob.java index 0a48fb9233..c3067f8fa8 100644 --- a/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizerJob.java +++ b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizerJob.java @@ -32,9 +32,9 @@ import org.quartz.JobExecutionException; /** * A scheduled job that regularly invokes a {@link UserRegistrySynchronizer}. Supports a - * synchronizeChangesOnly string parameter. When "true" means that the - * {@link UserRegistrySynchronizer#synchronize(boolean)} method will be called with a false argument rather - * than the default true. + * synchronizeChangesOnly string parameter. When "false" means that the + * {@link UserRegistrySynchronizer#synchronize(boolean)} method will be called with a true forceUpdate + * argument rather than the default false. * * @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()); diff --git a/source/test-resources/sync-test-context.xml b/source/test-resources/sync-test-context.xml index 3c3e6eb342..260e659320 100644 --- a/source/test-resources/sync-test-context.xml +++ b/source/test-resources/sync-test-context.xml @@ -15,8 +15,8 @@ - - + +