diff --git a/config/alfresco/messages/system-messages.properties b/config/alfresco/messages/system-messages.properties index e3da0b1309..cef83f502a 100644 --- a/config/alfresco/messages/system-messages.properties +++ b/config/alfresco/messages/system-messages.properties @@ -18,4 +18,10 @@ system.config_check.warn.starting_with_errors=Alfresco is starting with errors. system.openoffice.info.connection_verified=The connection to OpenOffice has been established. system.openoffice.err.connection_failed=An initial OpenOffice connection could not be established. system.openoffice.err.connection_lost=The OpenOffice connection has been lost. -system.openoffice.err.connection_remade=The OpenOffice connection was re-established. \ No newline at end of file +system.openoffice.err.connection_remade=The OpenOffice connection was re-established. + +# Locks +system.locks.err.failed_to_acquire_lock=Failed to get lock ''{0}'' using token ''{1}''. +system.locks.err.lock_resource_missing=Failed to manipulate lock ''{0}'' using token ''{1}''. The lock resource no longer exists. +system.locks.err.lock_update_count=Failed to update lock ''{0}'' using token ''{1}''. {2} locks were updated when {3} should have been. +system.locks.err.excl_lock_exists=Failed to get lock ''{0}'' using token ''{1}''. An exclusive lock in the hierarchy exists. \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/locks/AbstractLockDAOImpl.java b/source/java/org/alfresco/repo/domain/locks/AbstractLockDAOImpl.java index 19ad130ea3..5b323010a0 100644 --- a/source/java/org/alfresco/repo/domain/locks/AbstractLockDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/locks/AbstractLockDAOImpl.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.StringTokenizer; import org.alfresco.repo.domain.QNameDAO; +import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; import org.springframework.dao.ConcurrencyFailureException; @@ -63,7 +64,7 @@ public abstract class AbstractLockDAOImpl implements LockDAO this.qnameDAO = qnameDAO; } - public boolean getLock(QName lockQName, String lockToken, long timeToLive) + public void getLock(QName lockQName, String lockToken, long timeToLive) { String qnameNamespaceUri = lockQName.getNamespaceURI(); String qnameLocalName = lockQName.getLocalName(); @@ -114,7 +115,9 @@ public abstract class AbstractLockDAOImpl implements LockDAO boolean canTakeLock = canTakeLock(existingLock, lockToken, requiredExclusiveLockResourceId); if (!canTakeLock) { - return false; + throw new LockAcquisitionException( + LockAcquisitionException.ERR_EXCLUSIVE_LOCK_EXISTS, + lockQName, lockToken); } existingLocksMap.put(existingLock, existingLock); } @@ -142,24 +145,25 @@ public abstract class AbstractLockDAOImpl implements LockDAO timeToLive); } } - return true; + // Done } - public boolean refreshLock(QName lockQName, String lockToken, long timeToLive) + public void refreshLock(QName lockQName, String lockToken, long timeToLive) { - return updateLocks(lockQName, lockToken, lockToken, timeToLive); + updateLocks(lockQName, lockToken, lockToken, timeToLive); } - public boolean releaseLock(QName lockQName, String lockToken) + public void releaseLock(QName lockQName, String lockToken) { - return updateLocks(lockQName, lockToken, LOCK_TOKEN_RELEASED, 0L); + updateLocks(lockQName, lockToken, LOCK_TOKEN_RELEASED, 0L); } /** * Put new values against the given exclusive lock. This works against the related locks as * well. + * @throws LockAcquisitionException on failure */ - private boolean updateLocks(QName lockQName, String lockToken, String newLockToken, long timeToLive) + private void updateLocks(QName lockQName, String lockToken, String newLockToken, long timeToLive) { String qnameNamespaceUri = lockQName.getNamespaceURI(); String qnameLocalName = lockQName.getLocalName(); @@ -181,7 +185,9 @@ public abstract class AbstractLockDAOImpl implements LockDAO if (exclusiveLockResource == null) { // If the exclusive lock doesn't exist, the locks don't exist - return false; + throw new LockAcquisitionException( + LockAcquisitionException.ERR_LOCK_RESOURCE_MISSING, + lockQName, lockToken); } Long exclusiveLockResourceId = exclusiveLockResource.getId(); // Split the lock name @@ -194,12 +200,11 @@ public abstract class AbstractLockDAOImpl implements LockDAO // Check if (updateCount != requiredUpdateCount) { - return false; - } - else - { - return true; + throw new LockAcquisitionException( + LockAcquisitionException.ERR_LOCK_UPDATE_COUNT, + lockQName, lockToken, new Integer(updateCount), new Integer(requiredUpdateCount)); } + // Done } /** diff --git a/source/java/org/alfresco/repo/domain/locks/LockDAO.java b/source/java/org/alfresco/repo/domain/locks/LockDAO.java index 990e6490fb..21fd6f92a0 100644 --- a/source/java/org/alfresco/repo/domain/locks/LockDAO.java +++ b/source/java/org/alfresco/repo/domain/locks/LockDAO.java @@ -24,6 +24,7 @@ */ package org.alfresco.repo.domain.locks; +import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.service.namespace.QName; /** @@ -45,8 +46,9 @@ public interface LockDAO * @param timeToLive the time (in milliseconds) that the lock must remain * @return Returns true if the lock was taken, * otherwise false + * @throws LockAcquisitionException on failure */ - boolean getLock(QName lockQName, String lockToken, long timeToLive); + void getLock(QName lockQName, String lockToken, long timeToLive); /** * Refresh a held lock. This is successful if the lock in question still exists @@ -58,8 +60,9 @@ public interface LockDAO * @param timeToLive the new time to live (in milliseconds) * @return Returns true if the lock was updated, * otherwise false + * @throws LockAcquisitionException on failure */ - boolean refreshLock(QName lockQName, String lockToken, long timeToLive); + void refreshLock(QName lockQName, String lockToken, long timeToLive); /** * Release a lock. The lock token must still apply and all the shared and exclusive @@ -75,5 +78,5 @@ public interface LockDAO * (still) held under the lock token and were * valid at the time of release, otherwise false */ - boolean releaseLock(QName lockQName, String lockToken); + void releaseLock(QName lockQName, String lockToken); } diff --git a/source/java/org/alfresco/repo/domain/locks/LockDAOTest.java b/source/java/org/alfresco/repo/domain/locks/LockDAOTest.java index 8efcca37f0..a1003b2b09 100644 --- a/source/java/org/alfresco/repo/domain/locks/LockDAOTest.java +++ b/source/java/org/alfresco/repo/domain/locks/LockDAOTest.java @@ -29,6 +29,7 @@ import java.util.concurrent.locks.ReentrantLock; import junit.framework.TestCase; +import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -91,24 +92,32 @@ public class LockDAOTest extends TestCase private String lock(final QName lockName, final long timeToLive, boolean expectSuccess) { - String token = lock(lockName, timeToLive); - if (expectSuccess) + try { - assertNotNull( - "Expected to get lock " + lockName + " with TTL of " + timeToLive, - token); + String token = lock(lockName, timeToLive); + if (!expectSuccess) + { + fail("Expected lock " + lockName + " to have been denied"); + } + return token; } - else + catch (LockAcquisitionException e) { - assertNull( - "Expected lock " + lockName + " to have been denied", - token); + if (expectSuccess) + { + // oops + throw new RuntimeException("Expected to get lock " + lockName + " with TTL of " + timeToLive, e); + } + else + { + return null; + } } - return token; } /** * Do the lock in a new transaction * @return Returns the lock token or null if it didn't work + * @throws LockAcquisitionException on failure */ private String lock(final QName lockName, final long timeToLive) { @@ -117,8 +126,8 @@ public class LockDAOTest extends TestCase public String execute() throws Throwable { String txnId = AlfrescoTransactionSupport.getTransactionId(); - boolean locked = lockDAO.getLock(lockName, txnId, timeToLive); - return locked ? txnId : null; + lockDAO.getLock(lockName, txnId, timeToLive); + return txnId; } }; return txnHelper.doInTransaction(callback); @@ -130,21 +139,24 @@ public class LockDAOTest extends TestCase { public Boolean execute() throws Throwable { - return lockDAO.refreshLock(lockName, lockToken, timeToLive); + lockDAO.refreshLock(lockName, lockToken, timeToLive); + return Boolean.TRUE; } }; - Boolean released = txnHelper.doInTransaction(callback); - if (expectSuccess) + try { - assertTrue( - "Expected to have refreshed lock " + lockName, - released.booleanValue()); + txnHelper.doInTransaction(callback); + if (!expectSuccess) + { + fail("Expected to have failed to refresh lock " + lockName); + } } - else + catch (LockAcquisitionException e) { - assertFalse( - "Expected to have failed to refresh lock " + lockName, - released.booleanValue()); + if (expectSuccess) + { + throw new RuntimeException("Expected to have refreshed lock " + lockName, e); + } } } @@ -154,21 +166,24 @@ public class LockDAOTest extends TestCase { public Boolean execute() throws Throwable { - return lockDAO.releaseLock(lockName, lockToken); + lockDAO.releaseLock(lockName, lockToken); + return Boolean.TRUE; } }; - Boolean released = txnHelper.doInTransaction(callback); - if (expectSuccess) + try { - assertTrue( - "Expected to have released lock " + lockName, - released.booleanValue()); + txnHelper.doInTransaction(callback); + if (!expectSuccess) + { + fail("Expected to have failed to release lock " + lockName); + } } - else + catch (LockAcquisitionException e) { - assertFalse( - "Expected to have failed to release lock " + lockName, - released.booleanValue()); + if (expectSuccess) + { + throw new RuntimeException("Expected to have released lock " + lockName, e); + } } } @@ -387,10 +402,15 @@ public class LockDAOTest extends TestCase String tokenAAA = null; while (true) { - tokenAAA = lock(lockAAA, 100000L); // Lock for a long time - if (tokenAAA != null) + try { - break; // Got the lock + tokenAAA = lock(lockAAA, 100000L); // Lock for a long time + // Success + break; + } + catch (LockAcquisitionException e) + { + // OK. Keep trying. } try { wait(20L); } catch (InterruptedException e) {} } diff --git a/source/java/org/alfresco/repo/domain/locks/ibatis/LockDAOImpl.java b/source/java/org/alfresco/repo/domain/locks/ibatis/LockDAOImpl.java index 88303e7fcc..ef07e9131e 100644 --- a/source/java/org/alfresco/repo/domain/locks/ibatis/LockDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/locks/ibatis/LockDAOImpl.java @@ -144,8 +144,8 @@ public class LockDAOImpl extends AbstractLockDAOImpl updateLockEntity.setSharedResourceId(lockEntity.getSharedResourceId()); updateLockEntity.setExclusiveResourceId(lockEntity.getExclusiveResourceId()); updateLockEntity.setLockToken(lockToken); - long now = System.currentTimeMillis(); - long exp = now + timeToLive; + long now = (timeToLive > 0) ? System.currentTimeMillis() : 0L; + long exp = (timeToLive > 0) ? (now + timeToLive) : 0L; updateLockEntity.setStartTime(new Long(now)); updateLockEntity.setExpiryTime(new Long(exp)); template.update(UPDATE_LOCK, updateLockEntity, 1); @@ -164,8 +164,8 @@ public class LockDAOImpl extends AbstractLockDAOImpl params.put("exclusiveLockResourceId", exclusiveLockResourceId); params.put("oldLockToken", oldLockToken); params.put("newLockToken", newLockToken); - long now = System.currentTimeMillis(); - long exp = now + timeToLive; + long now = (timeToLive > 0) ? System.currentTimeMillis() : 0L; + long exp = (timeToLive > 0) ? (now + timeToLive) : 0L; params.put("newStartTime", new Long(now)); params.put("newExpiryTime", new Long(exp)); int updateCount = template.update(UPDATE_EXCLUSIVE_LOCK, params); diff --git a/source/java/org/alfresco/repo/lock/LockAcquisitionException.java b/source/java/org/alfresco/repo/lock/LockAcquisitionException.java new file mode 100644 index 0000000000..f739654bb4 --- /dev/null +++ b/source/java/org/alfresco/repo/lock/LockAcquisitionException.java @@ -0,0 +1,89 @@ +/* + * 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 + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * 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 + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.lock; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.namespace.QName; + +/** + * Exception generated when a lock cannot be acquired. + * + * @author Derek Hulley + * @since 3.2 + */ +public class LockAcquisitionException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = 8215858379509645862L; + + /** + *