diff --git a/repository/src/test/java/org/alfresco/repo/lock/mem/AbstractLockStoreTxTest.java b/repository/src/test/java/org/alfresco/repo/lock/mem/AbstractLockStoreTxTest.java index 484b2d50e4..8e386388c5 100644 --- a/repository/src/test/java/org/alfresco/repo/lock/mem/AbstractLockStoreTxTest.java +++ b/repository/src/test/java/org/alfresco/repo/lock/mem/AbstractLockStoreTxTest.java @@ -1,494 +1,489 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2023 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ -package org.alfresco.repo.lock.mem; - -import java.util.Date; - -import jakarta.transaction.NotSupportedException; -import jakarta.transaction.SystemException; -import jakarta.transaction.UserTransaction; - -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.lock.LockType; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.dao.ConcurrencyFailureException; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - -/** - * Integration tests that check transaction related functionality of {@link LockStore} implementations. - * @author Matt Ward - */ -public abstract class AbstractLockStoreTxTest -{ - /** - * Instance of the Class Under Test. - */ - protected T lockStore; - - protected static ApplicationContext ctx; - protected static TransactionService transactionService; - - /** - * Concrete subclasses must implement this method to provide the tests with a LockStore instance. - * - * @return LockStore to test - */ - protected abstract T createLockStore(); - - @BeforeClass - public static void setUpSpringContext() - { - ctx = ApplicationContextHelper.getApplicationContext(); - transactionService = (TransactionService) ctx.getBean("TransactionService"); - } - - @Before - public void setUpLockStore() - { - lockStore = createLockStore(); - } - - /** - * - * Inner transaction should fail while outer succeeds - */ - @Test - public void testRepeatableRead_01() throws Exception - { - - } - - @Test - public void testRepeatableReadsInTransaction() throws NotSupportedException, SystemException - { - final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); - UserTransaction txA = txService.getUserTransaction(); - - final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); - final NodeRef nodeRef2 = new NodeRef("workspace://SpacesStore/UUID-2"); - Date now = new Date(); - Date expires = new Date(now.getTime() + 180000); - final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, - "jbloggs", expires, Lifetime.EPHEMERAL, null); - - - Thread txB = new Thread("TxB") - { - @Override - public void run() - { - Object main = AbstractLockStoreTxTest.this; - UserTransaction tx = txService.getUserTransaction(); - try - { - tx.begin(); - try - { - // txB read lock state - LockState lockState = lockStore.get(nodeRef); - assertEquals("jbloggs", lockState.getOwner()); - assertEquals(Lifetime.EPHEMERAL, lockState.getLifetime()); - - // Wait, while txA changes the lock state - passControl(this, main); - - // assert txB still sees state A - lockState = lockStore.get(nodeRef); - assertEquals("jbloggs", lockState.getOwner()); - - // Wait, while txA checks whether it can see lock for nodeRef2 (though it doesn't exist yet) - passControl(this, main); - - // txB sets a value, already seen as non-existent lock by txA - lockStore.set(nodeRef2, LockState.createLock(nodeRef2, LockType.WRITE_LOCK, - "csmith", null, Lifetime.EPHEMERAL, null)); - } - finally - { - tx.rollback(); - } - } - catch (Throwable e) - { - throw new RuntimeException("Error in transaction B", e); - } - finally - { - // Stop 'main' from waiting - synchronized(main) - { - main.notifyAll(); - } - } - } - }; - - txA.begin(); - try - { - // txA set lock state 1 - lockStore.set(nodeRef, lockState1); - - // Wait while txB reads and checks the LockState - txB.setDaemon(true); - txB.start(); - passControl(this, txB); - - // txA set different lock state - AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); // Current lock owner needed to change lock. - final LockState lockState2 = LockState.createWithOwner(lockState1, "another"); - lockStore.set(nodeRef, lockState2); - - // Wait while txB reads/checks the LockState again for nodeRef - passControl(this, txB); - - // Another update - AuthenticationUtil.setFullyAuthenticatedUser("another"); // Current lock owner needed to change lock. - final LockState lockState3 = LockState.createWithOwner(lockState2, "bsmith"); - lockStore.set(nodeRef, lockState3); - // Check we can see the update. - assertEquals("bsmith", lockStore.get(nodeRef).getOwner()); - - // Perform a read, that we know will retrieve a null value - assertNull("nodeRef2 LockState", lockStore.get(nodeRef2)); - - // Wait while txB populates the store with a value for nodeRef2 - passControl(this, txB); - - // Perform the read again - update should not be visible in this transaction - assertNull("nodeRef2 LockState", lockStore.get(nodeRef2)); - } - finally - { - txA.rollback(); - } - } - - protected void passControl(Object from, Object to) - { - synchronized(to) - { - to.notifyAll(); - } - synchronized(from) - { - try - { - // TODO: wait should be called in a loop with repeated wait condition check, - // but what's the condition we're waiting on? - from.wait(10000); - } - catch (InterruptedException error) - { - throw new RuntimeException(error); - } - } - } - - @Test - public void testCannotSetLockWhenChangedByAnotherTx() throws NotSupportedException, SystemException - { - final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); - UserTransaction txA = txService.getUserTransaction(); - final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); - Date now = new Date(); - Date expires = new Date(now.getTime() + 180000); - final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, - "jbloggs", expires, Lifetime.EPHEMERAL, null); - - - Thread txB = new Thread("TxB") - { - @Override - public void run() - { - Object main = AbstractLockStoreTxTest.this; - UserTransaction tx = txService.getUserTransaction(); - try - { - tx.begin(); - try - { - // txB read lock state - LockState lockState = lockStore.get(nodeRef); - assertEquals("jbloggs", lockState.getOwner()); - assertEquals(Lifetime.EPHEMERAL, lockState.getLifetime()); - - // Wait, while txA changes the lock state - passControl(this, main); - - try - { - // Attempt to change the lock state for a NodeRef should fail - // when it has been modified by another tx since this tx last inspected it. - AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); // Current lock owner - lockStore.set(nodeRef, LockState.createLock(nodeRef, LockType.WRITE_LOCK, - "csmith", null, Lifetime.EPHEMERAL, null)); - fail("Exception should have been thrown but was not."); - } - catch (ConcurrencyFailureException e) - { - // Good! - } - } - finally - { - tx.rollback(); - } - } - catch (Throwable e) - { - throw new RuntimeException("Error in transaction B", e); - } - finally - { - // Stop 'main' from waiting - synchronized(main) - { - main.notifyAll(); - } - } - } - }; - - txA.begin(); - try - { - // txA set lock state 1 - lockStore.set(nodeRef, lockState1); - - // Wait while txB reads and checks the LockState - txB.setDaemon(true); - txB.start(); - passControl(this, txB); - - // txA set different lock state - AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); // Current lock owner needed to change lock. - final LockState lockState2 = LockState.createWithOwner(lockState1, "another"); - lockStore.set(nodeRef, lockState2); - - // Wait while txB attempts to modify the lock info - passControl(this, txB); - - // Lock shouldn't have changed since this tx updated it. - assertEquals(lockState2, lockStore.get(nodeRef)); - } - finally - { - txA.rollback(); - } - } - - @Test - public void testCanChangeLockIfLatestValueIsHeldEvenIfAlreadyChangedByAnotherTx() throws NotSupportedException, SystemException - { - final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); - UserTransaction txA = txService.getUserTransaction(); - final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); - final Date now = new Date(); - Date expired = new Date(now.getTime() - 180000); - final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, - "jbloggs", expired, Lifetime.EPHEMERAL, null); - - final LockState lockState2 = LockState.createWithOwner(lockState1, "another"); - - Thread txB = new Thread("TxB") - { - @Override - public void run() - { - Object main = AbstractLockStoreTxTest.this; - UserTransaction tx = txService.getUserTransaction(); - try - { - tx.begin(); - try - { - AuthenticationUtil.setFullyAuthenticatedUser("new-user"); - - // txB read lock state - LockState readLockState = lockStore.get(nodeRef); - assertEquals(lockState2, readLockState); - - // Set new value, even though txA has already set new values - // (but not since this tx's initial read) - Date expiresFuture = new Date(now.getTime() + 180000); - final LockState newUserLockState = LockState.createLock(nodeRef, LockType.WRITE_LOCK, - "new-user", expiresFuture, Lifetime.EPHEMERAL, null); - lockStore.set(nodeRef, newUserLockState); - - // Read - assertEquals(newUserLockState, lockStore.get(nodeRef)); - } - finally - { - tx.rollback(); - } - } - catch (Throwable e) - { - throw new RuntimeException("Error in transaction B", e); - } - finally - { - // Stop 'main' from waiting - synchronized(main) - { - main.notifyAll(); - } - } - } - }; - - txA.begin(); - try - { - AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); // Current lock owner needed to change lock. - - // txA set lock state 1 - lockStore.set(nodeRef, lockState1); - assertEquals(lockState1, lockStore.get(nodeRef)); - - // txA set different lock state - lockStore.set(nodeRef, lockState2); - assertEquals(lockState2, lockStore.get(nodeRef)); - - // Wait while txB modifies the lock info - txB.setDaemon(true); - txB.start(); - passControl(this, txB); - - // This tx should still see the same state, though it has been changed by txB. - assertEquals(lockState2, lockStore.get(nodeRef)); - } - finally - { - txA.rollback(); - } - } - - - @Test - public void testNotOnlyCurrentLockOwnerCanChangeInfo() throws NotSupportedException, SystemException - { - final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); - UserTransaction txA = txService.getUserTransaction(); - final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); - Date now = new Date(); - Date expires = new Date(now.getTime() + 180000); - final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, - "jbloggs", expires, Lifetime.EPHEMERAL, null); - - txA.begin(); - try - { - AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); - - // Set initial lock state - lockStore.set(nodeRef, lockState1); - - // Set different lock state - // Current lock owner is still authenticated (jbloggs) - final LockState lockState2 = LockState.createWithOwner(lockState1, "csmith"); - lockStore.set(nodeRef, lockState2); - - // Check update - assertEquals(lockState2, lockStore.get(nodeRef)); - - // Incorrect lock owner - this shouldn't fail. See ACE-2181 - final LockState lockState3 = LockState.createWithOwner(lockState1, "dsmithers"); - - lockStore.set(nodeRef, lockState3); - - // Check update. - assertEquals(lockState3, lockStore.get(nodeRef)); - } - finally - { - txA.rollback(); - } - } - - @Test - public void testOtherUserCanChangeLockInfoOnceExpired() throws NotSupportedException, SystemException - { - final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); - UserTransaction txA = txService.getUserTransaction(); - final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); - Date now = new Date(); - Date expired = new Date(now.getTime() - 900); - final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, - "jbloggs", expired, Lifetime.EPHEMERAL, null); - - txA.begin(); - try - { - AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); - - // Set initial lock state - lockStore.set(nodeRef, lockState1); - - // Set different lock state - AuthenticationUtil.setFullyAuthenticatedUser("csmith"); - Date expiresFuture = new Date(now.getTime() + 180000); - final LockState lockState2 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, - "csmith", expiresFuture, Lifetime.EPHEMERAL, null); - lockStore.set(nodeRef, lockState2); - - // Updated, since lock had expired. - assertEquals(lockState2, lockStore.get(nodeRef)); - - // Incorrect lock owner - this shouldn't fail - // LockStore does not check for lock owning - // and is owned by csmith. - AuthenticationUtil.setFullyAuthenticatedUser("dsmithers"); - final LockState lockState3 = LockState.createWithOwner(lockState2, "dsmithers"); - - lockStore.set(nodeRef, lockState3); - - // Check update. - assertEquals(lockState3, lockStore.get(nodeRef)); - } - finally - { - txA.rollback(); - } - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2023 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.lock.mem; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import java.util.Date; +import jakarta.transaction.NotSupportedException; +import jakarta.transaction.SystemException; +import jakarta.transaction.UserTransaction; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.dao.ConcurrencyFailureException; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; + +/** + * Integration tests that check transaction related functionality of {@link LockStore} implementations. + * + * @author Matt Ward + */ +public abstract class AbstractLockStoreTxTest +{ + /** + * Instance of the Class Under Test. + */ + protected T lockStore; + + protected static ApplicationContext ctx; + protected static TransactionService transactionService; + + /** + * Concrete subclasses must implement this method to provide the tests with a LockStore instance. + * + * @return LockStore to test + */ + protected abstract T createLockStore(); + + @BeforeClass + public static void setUpSpringContext() + { + ctx = ApplicationContextHelper.getApplicationContext(); + transactionService = (TransactionService) ctx.getBean("TransactionService"); + } + + @Before + public void setUpLockStore() + { + lockStore = createLockStore(); + } + + /** + *
    + *
  • Start outer txn
  • + *
  • Modify lock in outer txn
  • + *
  • Start inner txn
  • + *
  • Modify lock in inner txn
  • + *
+ * Inner transaction should fail while outer succeeds + */ + @Test + public void testRepeatableRead_01() throws Exception + { + + } + + @Test + public void testRepeatableReadsInTransaction() throws NotSupportedException, SystemException + { + final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); + UserTransaction txA = txService.getUserTransaction(); + + final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); + final NodeRef nodeRef2 = new NodeRef("workspace://SpacesStore/UUID-2"); + Date now = new Date(); + Date expires = new Date(now.getTime() + 180000); + final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, + "jbloggs", expires, Lifetime.EPHEMERAL, null); + + Thread txB = new Thread("TxB") { + @Override + public void run() + { + Object main = AbstractLockStoreTxTest.this; + UserTransaction tx = txService.getUserTransaction(); + try + { + tx.begin(); + try + { + // txB read lock state + LockState lockState = lockStore.get(nodeRef); + assertEquals("jbloggs", lockState.getOwner()); + assertEquals(Lifetime.EPHEMERAL, lockState.getLifetime()); + + // Wait, while txA changes the lock state + passControl(this, main); + + // assert txB still sees state A + lockState = lockStore.get(nodeRef); + assertEquals("jbloggs", lockState.getOwner()); + + // Wait, while txA checks whether it can see lock for nodeRef2 (though it doesn't exist yet) + passControl(this, main); + + // txB sets a value, already seen as non-existent lock by txA + lockStore.set(nodeRef2, LockState.createLock(nodeRef2, LockType.WRITE_LOCK, + "csmith", null, Lifetime.EPHEMERAL, null)); + } + finally + { + tx.rollback(); + } + } + catch (Throwable e) + { + throw new RuntimeException("Error in transaction B", e); + } + finally + { + // Stop 'main' from waiting + synchronized (main) + { + main.notifyAll(); + } + } + } + }; + + txA.begin(); + try + { + // txA set lock state 1 + lockStore.set(nodeRef, lockState1); + + // Perform a read, that we know will retrieve a null value (and null will be cached for this transaction) + assertNull("nodeRef2 LockState", lockStore.get(nodeRef2)); + + // Wait while txB reads and checks the LockState + txB.setDaemon(true); + txB.start(); + passControl(this, txB); + + // txA set different lock state + AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); // Current lock owner needed to change lock. + final LockState lockState2 = LockState.createWithOwner(lockState1, "another"); + lockStore.set(nodeRef, lockState2); + + // Wait while txB reads/checks the LockState again for nodeRef + passControl(this, txB); + + // Another update + AuthenticationUtil.setFullyAuthenticatedUser("another"); // Current lock owner needed to change lock. + final LockState lockState3 = LockState.createWithOwner(lockState2, "bsmith"); + lockStore.set(nodeRef, lockState3); + // Check we can see the update. + assertEquals("bsmith", lockStore.get(nodeRef).getOwner()); + + // Wait while txB populates the store with a value for nodeRef2 + passControl(this, txB); + + // Perform the read again - update should not be visible in this transaction (was already cached) + assertNull("nodeRef2 LockState", lockStore.get(nodeRef2)); + } + finally + { + txA.rollback(); + } + } + + protected void passControl(Object from, Object to) + { + synchronized (to) + { + to.notifyAll(); + } + synchronized (from) + { + try + { + // TODO: wait should be called in a loop with repeated wait condition check, + // but what's the condition we're waiting on? + from.wait(10000); + } + catch (InterruptedException error) + { + throw new RuntimeException(error); + } + } + } + + @Test + public void testCannotSetLockWhenChangedByAnotherTx() throws NotSupportedException, SystemException + { + final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); + UserTransaction txA = txService.getUserTransaction(); + final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); + Date now = new Date(); + Date expires = new Date(now.getTime() + 180000); + final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, + "jbloggs", expires, Lifetime.EPHEMERAL, null); + + Thread txB = new Thread("TxB") { + @Override + public void run() + { + Object main = AbstractLockStoreTxTest.this; + UserTransaction tx = txService.getUserTransaction(); + try + { + tx.begin(); + try + { + // txB read lock state + LockState lockState = lockStore.get(nodeRef); + assertEquals("jbloggs", lockState.getOwner()); + assertEquals(Lifetime.EPHEMERAL, lockState.getLifetime()); + + // Wait, while txA changes the lock state + passControl(this, main); + + try + { + // Attempt to change the lock state for a NodeRef should fail + // when it has been modified by another tx since this tx last inspected it. + AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); // Current lock owner + lockStore.set(nodeRef, LockState.createLock(nodeRef, LockType.WRITE_LOCK, + "csmith", null, Lifetime.EPHEMERAL, null)); + fail("Exception should have been thrown but was not."); + } + catch (ConcurrencyFailureException e) + { + // Good! + } + } + finally + { + tx.rollback(); + } + } + catch (Throwable e) + { + throw new RuntimeException("Error in transaction B", e); + } + finally + { + // Stop 'main' from waiting + synchronized (main) + { + main.notifyAll(); + } + } + } + }; + + txA.begin(); + try + { + // txA set lock state 1 + lockStore.set(nodeRef, lockState1); + + // Wait while txB reads and checks the LockState + txB.setDaemon(true); + txB.start(); + passControl(this, txB); + + // txA set different lock state + AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); // Current lock owner needed to change lock. + final LockState lockState2 = LockState.createWithOwner(lockState1, "another"); + lockStore.set(nodeRef, lockState2); + + // Wait while txB attempts to modify the lock info + passControl(this, txB); + + // Lock shouldn't have changed since this tx updated it. + assertEquals(lockState2, lockStore.get(nodeRef)); + } + finally + { + txA.rollback(); + } + } + + @Test + public void testCanChangeLockIfLatestValueIsHeldEvenIfAlreadyChangedByAnotherTx() throws NotSupportedException, SystemException + { + final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); + UserTransaction txA = txService.getUserTransaction(); + final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); + final Date now = new Date(); + Date expired = new Date(now.getTime() - 180000); + final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, + "jbloggs", expired, Lifetime.EPHEMERAL, null); + + final LockState lockState2 = LockState.createWithOwner(lockState1, "another"); + + Thread txB = new Thread("TxB") { + @Override + public void run() + { + Object main = AbstractLockStoreTxTest.this; + UserTransaction tx = txService.getUserTransaction(); + try + { + tx.begin(); + try + { + AuthenticationUtil.setFullyAuthenticatedUser("new-user"); + + // txB read lock state + LockState readLockState = lockStore.get(nodeRef); + assertEquals(lockState2, readLockState); + + // Set new value, even though txA has already set new values + // (but not since this tx's initial read) + Date expiresFuture = new Date(now.getTime() + 180000); + final LockState newUserLockState = LockState.createLock(nodeRef, LockType.WRITE_LOCK, + "new-user", expiresFuture, Lifetime.EPHEMERAL, null); + lockStore.set(nodeRef, newUserLockState); + + // Read + assertEquals(newUserLockState, lockStore.get(nodeRef)); + } + finally + { + tx.rollback(); + } + } + catch (Throwable e) + { + throw new RuntimeException("Error in transaction B", e); + } + finally + { + // Stop 'main' from waiting + synchronized (main) + { + main.notifyAll(); + } + } + } + }; + + txA.begin(); + try + { + AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); // Current lock owner needed to change lock. + + // txA set lock state 1 + lockStore.set(nodeRef, lockState1); + assertEquals(lockState1, lockStore.get(nodeRef)); + + // txA set different lock state + lockStore.set(nodeRef, lockState2); + assertEquals(lockState2, lockStore.get(nodeRef)); + + // Wait while txB modifies the lock info + txB.setDaemon(true); + txB.start(); + passControl(this, txB); + + // This tx should still see the same state, though it has been changed by txB. + assertEquals(lockState2, lockStore.get(nodeRef)); + } + finally + { + txA.rollback(); + } + } + + @Test + public void testNotOnlyCurrentLockOwnerCanChangeInfo() throws NotSupportedException, SystemException + { + final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); + UserTransaction txA = txService.getUserTransaction(); + final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); + Date now = new Date(); + Date expires = new Date(now.getTime() + 180000); + final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, + "jbloggs", expires, Lifetime.EPHEMERAL, null); + + txA.begin(); + try + { + AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); + + // Set initial lock state + lockStore.set(nodeRef, lockState1); + + // Set different lock state + // Current lock owner is still authenticated (jbloggs) + final LockState lockState2 = LockState.createWithOwner(lockState1, "csmith"); + lockStore.set(nodeRef, lockState2); + + // Check update + assertEquals(lockState2, lockStore.get(nodeRef)); + + // Incorrect lock owner - this shouldn't fail. See ACE-2181 + final LockState lockState3 = LockState.createWithOwner(lockState1, "dsmithers"); + + lockStore.set(nodeRef, lockState3); + + // Check update. + assertEquals(lockState3, lockStore.get(nodeRef)); + } + finally + { + txA.rollback(); + } + } + + @Test + public void testOtherUserCanChangeLockInfoOnceExpired() throws NotSupportedException, SystemException + { + final TransactionService txService = (TransactionService) ctx.getBean("TransactionService"); + UserTransaction txA = txService.getUserTransaction(); + final NodeRef nodeRef = new NodeRef("workspace://SpacesStore/UUID-1"); + Date now = new Date(); + Date expired = new Date(now.getTime() - 900); + final LockState lockState1 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, + "jbloggs", expired, Lifetime.EPHEMERAL, null); + + txA.begin(); + try + { + AuthenticationUtil.setFullyAuthenticatedUser("jbloggs"); + + // Set initial lock state + lockStore.set(nodeRef, lockState1); + + // Set different lock state + AuthenticationUtil.setFullyAuthenticatedUser("csmith"); + Date expiresFuture = new Date(now.getTime() + 180000); + final LockState lockState2 = LockState.createLock(nodeRef, LockType.WRITE_LOCK, + "csmith", expiresFuture, Lifetime.EPHEMERAL, null); + lockStore.set(nodeRef, lockState2); + + // Updated, since lock had expired. + assertEquals(lockState2, lockStore.get(nodeRef)); + + // Incorrect lock owner - this shouldn't fail + // LockStore does not check for lock owning + // and is owned by csmith. + AuthenticationUtil.setFullyAuthenticatedUser("dsmithers"); + final LockState lockState3 = LockState.createWithOwner(lockState2, "dsmithers"); + + lockStore.set(nodeRef, lockState3); + + // Check update. + assertEquals(lockState3, lockStore.get(nodeRef)); + } + finally + { + txA.rollback(); + } + } +}