getTicketComponents();
-
- final public void afterPropertiesSet() throws Exception
- {
- initialised = true;
- if (sysAdminCache != null)
- {
- sysAdminCache.put(KEY_SYSADMIN_MAX_USERS, initialMaxUsers);
- sysAdminCache.put(KEY_SYSADMIN_ALLOWED_USERS, initialAllowedUsers);
- }
- }
-
}
diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
index c0745a8bdc..3c0e8771e0 100644
--- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
+++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
@@ -38,6 +38,8 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.attributes.Attribute;
import org.alfresco.repo.attributes.LongAttributeValue;
import org.alfresco.repo.attributes.MapAttributeValue;
+import org.alfresco.repo.lock.JobLockService;
+import org.alfresco.repo.lock.LockAcquisitionException;
import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.management.subsystems.ChildApplicationContextManager;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
@@ -50,6 +52,8 @@ import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.security.AuthorityService;
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.util.AbstractLifecycleBean;
import org.alfresco.util.PropertyMap;
import org.apache.commons.logging.Log;
@@ -65,7 +69,8 @@ import org.springframework.context.ApplicationEvent;
* the 'chain' of application contexts, managed by a {@link ChildApplicationContextManager}, and compares its
* timestamped user and group information with the local users and groups last retrieved from the same source. Any
* updates and additions made to those users and groups are applied to the local copies. The ordering of each
- * {@link UserRegistry} in the chain determines its precedence when it comes to user and group name collisions.
+ * {@link UserRegistry} in the chain determines its precedence when it comes to user and group name collisions. The
+ * {@link JobLockService} is used to ensure that in a cluster, no two nodes actually run a synchronize at the same time.
*
* The force
argument determines whether a complete or partial set of information is queried from the
* {@link UserRegistry}. When true
then all users and groups are queried. With this complete set of
@@ -84,12 +89,20 @@ import org.springframework.context.ApplicationEvent;
*/
public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean implements UserRegistrySynchronizer
{
- /** The number of users / groups we add at a time in a transaction **/
+
+ /** The number of users / groups we add at a time in a transaction *. */
private static final int BATCH_SIZE = 10;
/** The logger. */
private static final Log logger = LogFactory.getLog(ChainingUserRegistrySynchronizer.class);
+ /** The name of the lock used to ensure that a synchronize does not run on more than one node at the same time. */
+ private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI,
+ "ChainingUserRegistrySynchronizer");
+
+ /** The maximum time this lock will be held for (1 day). */
+ private static final long LOCK_TTL = 1000 * 60 * 60 * 24;
+
/** The path in the attribute service below which we persist attributes. */
private static final String ROOT_ATTRIBUTE_PATH = ".ChainingUserRegistrySynchronizer";
@@ -117,13 +130,16 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
/** The retrying transaction helper. */
private RetryingTransactionHelper retryingTransactionHelper;
- /** Should we trigger a differential sync when missing people log in? */
+ /** The job lock service. */
+ private JobLockService jobLockService;
+
+ /** Should we trigger a differential sync when missing people log in?. */
private boolean syncWhenMissingPeopleLogIn = true;
- /** Should we trigger a differential sync on startup? */
+ /** Should we trigger a differential sync on startup?. */
private boolean syncOnStartup = true;
- /** Should we auto create a missing person on log in? */
+ /** Should we auto create a missing person on log in?. */
private boolean autoCreatePeopleOnLogin = true;
/**
@@ -193,7 +209,18 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
}
/**
- * Controls whether we auto create a missing person on log in
+ * Sets the job lock service.
+ *
+ * @param jobLockService
+ * the job lock service
+ */
+ public void setJobLockService(JobLockService jobLockService)
+ {
+ this.jobLockService = jobLockService;
+ }
+
+ /**
+ * Controls whether we auto create a missing person on log in.
*
* @param autoCreatePeopleOnLogin
* true
if we should auto create a missing person on log in
@@ -204,7 +231,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
}
/**
- * Controls whether we trigger a differential sync when missing people log in
+ * Controls whether we trigger a differential sync when missing people log in.
*
* @param syncWhenMissingPeopleLogIn
* if we should trigger a sync when missing people log in
@@ -215,7 +242,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
}
/**
- * Controls whether we trigger a differential sync when the subsystem starts up
+ * Controls whether we trigger a differential sync when the subsystem starts up.
*
* @param syncOnStartup
* if we should trigger a sync on startup
@@ -231,10 +258,36 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
*/
public void synchronize(boolean force, boolean splitTxns)
{
+ // First, try to obtain a lock to ensure we are the only node trying to run this job
+ try
+ {
+ if (splitTxns)
+ {
+ // If this is an automated sync on startup or scheduled sync, don't even wait around for the lock.
+ // Assume the sync will be completed on another node.
+ this.jobLockService.getTransactionalLock(ChainingUserRegistrySynchronizer.LOCK_QNAME,
+ ChainingUserRegistrySynchronizer.LOCK_TTL, 0, 1);
+ }
+ else
+ {
+ // If this is a login-triggered sync, give it a few retries before giving up
+ this.jobLockService.getTransactionalLock(ChainingUserRegistrySynchronizer.LOCK_QNAME,
+ ChainingUserRegistrySynchronizer.LOCK_TTL, 3000, 10);
+ }
+ }
+ catch (LockAcquisitionException e)
+ {
+ // Don't proceed with the sync if it is running on another node
+ ChainingUserRegistrySynchronizer.logger
+ .warn("User registry synchronization already running in another thread. Synchronize aborted");
+ return;
+ }
+
Set visitedZoneIds = new TreeSet();
Collection instanceIds = this.applicationContextManager.getInstanceIds();
- // Work out the set of all zone IDs in the authentication chain so that we can decide which users / groups need
+ // Work out the set of all zone IDs in the authentication chain so that we can decide which users / groups
+ // need
// 're-zoning'
Set allZoneIds = new TreeSet();
for (String id : instanceIds)
@@ -894,6 +947,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
return zones;
}
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent)
+ */
@Override
protected void onBootstrap(ApplicationEvent event)
{
@@ -928,6 +985,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
}
}
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent)
+ */
@Override
protected void onShutdown(ApplicationEvent event)
{
diff --git a/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java
index 12c7a54c1c..9220d6ff94 100644
--- a/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java
+++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -18,7 +18,7 @@
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
- * FLOSS exception. You should have recieved a copy of the text describing
+ * FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
@@ -26,7 +26,8 @@ package org.alfresco.repo.transaction;
import javax.transaction.UserTransaction;
-import org.alfresco.repo.cache.SimpleCache;
+import org.alfresco.repo.admin.SysAdminParams;
+import org.alfresco.repo.security.authentication.AuthenticationContext;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.VmShutdownListener;
import org.alfresco.util.transaction.SpringAwareUserTransaction;
@@ -45,59 +46,65 @@ public class TransactionServiceImpl implements TransactionService
private static VmShutdownListener shutdownListener = new VmShutdownListener("TransactionService");
private PlatformTransactionManager transactionManager;
+ private AuthenticationContext authenticationContext;
private int maxRetries = -1;
private int minRetryWaitMs = -1;
private int maxRetryWaitMs = -1;
private int retryWaitIncrementMs = -1;
-
- // SysAdmin cache - used to cluster certain JMX operations
- private SimpleCache sysAdminCache;
- private final static String KEY_SYSADMIN_ALLOW_WRITE = "sysAdminCache.txAllowWrite";
-
-
+
+ // SysAdmin cache - used to cluster certain configuration parameters
+ private SysAdminParams sysAdminParams;
+ private boolean allowWrite;
+
/**
* Set the transaction manager to use
*
- * @param transactionManager platform transaction manager
+ * @param transactionManager
+ * platform transaction manager
*/
public void setTransactionManager(PlatformTransactionManager transactionManager)
{
this.transactionManager = transactionManager;
}
-
- public void setSysAdminCache(SimpleCache sysAdminCache)
+
+ /**
+ * Sets the authentication context.
+ *
+ * @param authenticationContext
+ * the authentication context
+ */
+ public void setAuthenticationContext(AuthenticationContext authenticationContext)
{
- this.sysAdminCache = sysAdminCache;
+ this.authenticationContext = authenticationContext;
+ }
+
+ public void setSysAdminParams(SysAdminParams sysAdminParams)
+ {
+ this.sysAdminParams = sysAdminParams;
}
/**
* Set the read-only mode for all generated transactions.
*
- * @param allowWrite false if all transactions must be read-only
+ * @param allowWrite
+ * false if all transactions must be read-only
*/
public void setAllowWrite(boolean allowWrite)
{
- sysAdminCache.put(KEY_SYSADMIN_ALLOW_WRITE, allowWrite);
+ this.allowWrite = allowWrite;
}
-
+
public boolean isReadOnly()
{
if (shutdownListener.isVmShuttingDown())
{
return true;
}
- try
- {
- Boolean allowWrite = (Boolean)sysAdminCache.get(KEY_SYSADMIN_ALLOW_WRITE);
- return (allowWrite == null ? false : ! allowWrite);
- }
- catch (IllegalStateException e)
- {
- // The cache is not working
- return true;
- }
+ // Make the repo writable to the system user, so that e.g. the allow write flag can still be edited by JMX
+ return !this.allowWrite || !this.authenticationContext.isCurrentUserTheSystemUser()
+ && !this.sysAdminParams.getAllowWrite();
}
-
+
/**
* @see RetryingTransactionHelper#setMaxRetries(int)
*/
@@ -135,25 +142,19 @@ public class TransactionServiceImpl implements TransactionService
*/
public UserTransaction getUserTransaction()
{
- SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
- transactionManager,
- isReadOnly(),
- TransactionDefinition.ISOLATION_DEFAULT,
- TransactionDefinition.PROPAGATION_REQUIRED,
+ SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, isReadOnly(),
+ TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRED,
TransactionDefinition.TIMEOUT_DEFAULT);
return txn;
}
-
+
/**
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED
*/
public UserTransaction getUserTransaction(boolean readOnly)
{
- SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
- transactionManager,
- (readOnly | isReadOnly()),
- TransactionDefinition.ISOLATION_DEFAULT,
- TransactionDefinition.PROPAGATION_REQUIRED,
+ SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, (readOnly | isReadOnly()),
+ TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRED,
TransactionDefinition.TIMEOUT_DEFAULT);
return txn;
}
@@ -163,11 +164,8 @@ public class TransactionServiceImpl implements TransactionService
*/
public UserTransaction getNonPropagatingUserTransaction()
{
- SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
- transactionManager,
- isReadOnly(),
- TransactionDefinition.ISOLATION_DEFAULT,
- TransactionDefinition.PROPAGATION_REQUIRES_NEW,
+ SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, isReadOnly(),
+ TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRES_NEW,
TransactionDefinition.TIMEOUT_DEFAULT);
return txn;
}
@@ -177,18 +175,15 @@ public class TransactionServiceImpl implements TransactionService
*/
public UserTransaction getNonPropagatingUserTransaction(boolean readOnly)
{
- SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
- transactionManager,
- (readOnly | isReadOnly()),
- TransactionDefinition.ISOLATION_DEFAULT,
- TransactionDefinition.PROPAGATION_REQUIRES_NEW,
+ SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, (readOnly | isReadOnly()),
+ TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRES_NEW,
TransactionDefinition.TIMEOUT_DEFAULT);
return txn;
}
/**
- * Creates a new helper instance. It can be reused or customized by the client code:
- * each instance is new and initialized afresh.
+ * Creates a new helper instance. It can be reused or customized by the client code: each instance is new and
+ * initialized afresh.
*/
public RetryingTransactionHelper getRetryingTransactionHelper()
{
diff --git a/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java
index e9592e9c93..ff95c2f256 100644
--- a/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java
+++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java
@@ -29,10 +29,9 @@ import javax.transaction.Status;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
-import net.sf.ehcache.Cache;
-import net.sf.ehcache.CacheManager;
-import org.alfresco.repo.cache.EhCacheAdapter;
+import org.alfresco.repo.admin.SysAdminParams;
+import org.alfresco.repo.security.authentication.AuthenticationContext;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeService;
@@ -59,17 +58,10 @@ public class TransactionServiceImplTest extends TestCase
{
transactionManager = (PlatformTransactionManager) ctx.getBean("transactionManager");
transactionService = new TransactionServiceImpl();
- transactionService.setTransactionManager(transactionManager);
-
- CacheManager cacheManager = new CacheManager();
- Cache sysAdminEhCache = new Cache("sysAdminCache", 10, false, true, 0L, 0L);
- cacheManager.addCache(sysAdminEhCache);
- EhCacheAdapter sysAdminCache = new EhCacheAdapter();
- sysAdminCache.setCache(sysAdminEhCache);
-
- transactionService.setSysAdminCache(sysAdminCache);
-
+ transactionService.setTransactionManager(transactionManager);
transactionService.setAllowWrite(true);
+ transactionService.setAuthenticationContext((AuthenticationContext) ctx.getBean("authenticationContext"));
+ transactionService.setSysAdminParams((SysAdminParams) ctx.getBean("sysAdminParams"));
nodeService = (NodeService) ctx.getBean("dbNodeService");
}
diff --git a/source/test-resources/sync-test-context.xml b/source/test-resources/sync-test-context.xml
index 8eef1087c8..6658891f94 100644
--- a/source/test-resources/sync-test-context.xml
+++ b/source/test-resources/sync-test-context.xml
@@ -16,7 +16,10 @@