[
https://issues.apache.org/jira/browse/JCR-3887?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Thomas Thonhofer updated JCR-3887:
----------------------------------
Description:
Without transactions, when you create a Node, lock it and then move it, the
(now nonexistent) source Node will no longer be locked, but the target Node
will be. If the target node should not be locked, we can either unlock the
source Node before move, or unlock the target Node after move. However, this
stops working as soon as we start using transactions.
In our example code (see below), the following problems occur:
a) If we call unlock on the source Node before the move, committing the
transaction fails and complains that the target (!) node is not locked
b) If we call unlock on the target node after the move instead (see commented
out line), committing the transaction fails and complains that the target node
is not locked
c) If we don't call unlock at all, the assertion that tests the lock after the
move fails because the target node is locked
As far as I have investigated, this is the problem:
Because we are in a transaction, the real changes of the move are not done
immediately but upon commit (as it should be). This also includes updating the
node path in the global LockManager. The unlocking however doesn't happen
during the actual commit, but during the prepare, so it will always happen
before the move. Because the move didn't happen yet, the global LockManager
doesn't have an entry for the new path of the node yet, so if
org.apache.jackrabbit.core.lock.LockManagerImpl tries to do the unlock in the
method internalUnlock(NodeImpl node), it will throw "LockException: Node not
locked", because it can't find the correct path for the lock.
----------------------------------------------
Test code:
----------------------------------------------
{code}
package com.infinica.tests.jackrabbit;
import java.io.File;
import java.io.IOException;
import javax.jcr.LoginException;
import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.lock.LockException;
import javax.jcr.lock.LockManager;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
import org.apache.jackrabbit.core.TransientRepository;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class JackrabbitTransactionTest
{
public static final String NODE_CONTENT = "content";
public static final String PROPERTY_DATA = "data";
private File tempDir;
@Before
public void before() throws IOException
{
tempDir = File.createTempFile("jackrabbit_test",
"");
tempDir.delete();
}
@After
public void after()
{
if(tempDir != null)
{
delete(tempDir);
}
}
private static void delete(File file)
{
File[] children = file.listFiles();
if(children != null)
{
for(File child: children)
{
delete(child);
}
}
if(!file.delete())
{
System.err.println("Could not
delete: " + file);
}
}
@Test
public void testMoveAndLock() throws LoginException,
RepositoryException,
XAException, NotSupportedException, SystemException, IllegalStateException,
RollbackException,
SecurityException, HeuristicMixedException, HeuristicRollbackException
{
GeronimoTransactionManager tm = new
GeronimoTransactionManager();
tm.begin();
Repository repository = new
TransientRepository(tempDir);
String token;
Session session = repository.login(new
SimpleCredentials("admin",
"admin".toCharArray()));
tm.getTransaction().enlistResource((XAResource)
session);
LockManager lockMan =
session.getWorkspace().getLockManager();
try
{
String user =
session.getUserID();
String name =
repository.getDescriptor(Repository.REP_NAME_DESC);
System.out.println("Logged in as
" + user +
" to a " + name + " repository.");
Node root =
session.getRootNode();
Node folderSrc =
createNode(root, "src");
Node fileSrc =
createFile(folderSrc, "test.txt",
"Test");
System.out.println("Created
source file: "
+ fileSrc.getPath());
session.save();
token =
lockMan.lock("/src/test.txt", false,
false, Long.MAX_VALUE, "admin").getLockToken();
}
finally
{
removeLockTokens(lockMan);
tm.commit();
session.logout();
}
tm.begin();
session = repository.login(new
SimpleCredentials("admin", "admin".toCharArray()));
tm.getTransaction().enlistResource((XAResource)
session);
lockMan =
session.getWorkspace().getLockManager();
try
{
Node root =
session.getRootNode();
lockMan.addLockToken(token);
lockMan.unlock("/src/test.txt");
Node folderDest =
createNode(root, "dest");
session.move("/src/test.txt",
"/dest/test.txt");
session.save();
// lockMan.unlock("/dest/test.txt");
Assert.assertTrue(root.hasNode("src"));
Assert.assertFalse(root.hasNode("src/test.txt"));
folderDest =
root.getNode(folderDest.getName());
Node fileDest =
folderDest.getNode("test.txt");
Assert.assertEquals("Test",
getFileStringProperty(fileDest,
PROPERTY_DATA));
Assert.assertFalse(lockMan.isLocked(fileDest.getPath()));
}
finally
{
removeLockTokens(lockMan);
tm.commit();
session.logout();
}
}
/**
* Necessary because of JCR-3438.
*/
private static void removeLockTokens(LockManager man) throws
LockException,
RepositoryException
{
if(man == null)
{
return;
}
for(String token: man.getLockTokens())
{
man.removeLockToken(token);
}
}
private String getFileStringProperty(Node file, String
property) throws RepositoryException
{
Node contentNode = file.getNode(NODE_CONTENT);
return
contentNode.getProperty(property).getString();
}
private Node createFile(Node parent, String path, String
content) throws RepositoryException
{
Node node = parent.addNode(path);
node.addMixin("mix:lockable");
Node contentNode = node.addNode(NODE_CONTENT);
contentNode.setProperty(PROPERTY_DATA, content);
return node;
}
private static Node createNode(Node parent, String path) throws
RepositoryException
{
return parent.addNode(path);
}
}
{code}
was:
Without transactions, when you create a Node, lock it and then move it, the
(now nonexistent) source Node will no longer be locked, but the target Node
will be. If the target node should not be locked, we can either unlock the
source Node before move, or unlock the target Node after move. However, this
stops working as soon as we start using transactions.
In our example code (see below), the following problems occur:
a) If we call unlock on the source Node before the move, committing the
transaction fails and complains that the target (!) node is not locked
b) If we call unlock on the target node after the move instead (see commented
out line), committing the transaction fails and complains that the target node
is not locked
c) If we don't call unlock at all, the assertion that tests the lock after the
move fails because the target node is locked
As far as I have investigated, this is the problem:
Because we are in a transaction, the real changes of the move are not done
immediately but
upon commit (as it should be). This also includes updating the node path in the
global LockManager. The unlocking however doesn't happen during the actual
commit, but during the prepare, so it will always happen before the move.
Because the move didn't happen yet, the global LockManager
doesn't have an entry for the new path of the node yet, so if
org.apache.jackrabbit.core.lock.LockManagerImpl tries to do the unlock in the
method internalUnlock(NodeImpl node), it will throw "LockException: Node not
locked", because it can't find the correct path for the lock.
----------------------------------------------
Test code:
----------------------------------------------
{code}
package com.infinica.tests.jackrabbit;
import java.io.File;
import java.io.IOException;
import javax.jcr.LoginException;
import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.lock.LockException;
import javax.jcr.lock.LockManager;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
import org.apache.jackrabbit.core.TransientRepository;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class JackrabbitTransactionTest
{
public static final String NODE_CONTENT = "content";
public static final String PROPERTY_DATA = "data";
private File tempDir;
@Before
public void before() throws IOException
{
tempDir = File.createTempFile("jackrabbit_test",
"");
tempDir.delete();
}
@After
public void after()
{
if(tempDir != null)
{
delete(tempDir);
}
}
private static void delete(File file)
{
File[] children = file.listFiles();
if(children != null)
{
for(File child: children)
{
delete(child);
}
}
if(!file.delete())
{
System.err.println("Could not
delete: " + file);
}
}
@Test
public void testMoveAndLock() throws LoginException,
RepositoryException,
XAException, NotSupportedException, SystemException, IllegalStateException,
RollbackException,
SecurityException, HeuristicMixedException, HeuristicRollbackException
{
GeronimoTransactionManager tm = new
GeronimoTransactionManager();
tm.begin();
Repository repository = new
TransientRepository(tempDir);
String token;
Session session = repository.login(new
SimpleCredentials("admin",
"admin".toCharArray()));
tm.getTransaction().enlistResource((XAResource)
session);
LockManager lockMan =
session.getWorkspace().getLockManager();
try
{
String user =
session.getUserID();
String name =
repository.getDescriptor(Repository.REP_NAME_DESC);
System.out.println("Logged in as
" + user +
" to a " + name + " repository.");
Node root =
session.getRootNode();
Node folderSrc =
createNode(root, "src");
Node fileSrc =
createFile(folderSrc, "test.txt",
"Test");
System.out.println("Created
source file: "
+ fileSrc.getPath());
session.save();
token =
lockMan.lock("/src/test.txt", false,
false, Long.MAX_VALUE, "admin").getLockToken();
}
finally
{
removeLockTokens(lockMan);
tm.commit();
session.logout();
}
tm.begin();
session = repository.login(new
SimpleCredentials("admin", "admin".toCharArray()));
tm.getTransaction().enlistResource((XAResource)
session);
lockMan =
session.getWorkspace().getLockManager();
try
{
Node root =
session.getRootNode();
lockMan.addLockToken(token);
lockMan.unlock("/src/test.txt");
Node folderDest =
createNode(root, "dest");
session.move("/src/test.txt",
"/dest/test.txt");
session.save();
// lockMan.unlock("/dest/test.txt");
Assert.assertTrue(root.hasNode("src"));
Assert.assertFalse(root.hasNode("src/test.txt"));
folderDest =
root.getNode(folderDest.getName());
Node fileDest =
folderDest.getNode("test.txt");
Assert.assertEquals("Test",
getFileStringProperty(fileDest,
PROPERTY_DATA));
Assert.assertFalse(lockMan.isLocked(fileDest.getPath()));
}
finally
{
removeLockTokens(lockMan);
tm.commit();
session.logout();
}
}
/**
* Necessary because of JCR-3438.
*/
private static void removeLockTokens(LockManager man) throws
LockException,
RepositoryException
{
if(man == null)
{
return;
}
for(String token: man.getLockTokens())
{
man.removeLockToken(token);
}
}
private String getFileStringProperty(Node file, String
property) throws RepositoryException
{
Node contentNode = file.getNode(NODE_CONTENT);
return
contentNode.getProperty(property).getString();
}
private Node createFile(Node parent, String path, String
content) throws RepositoryException
{
Node node = parent.addNode(path);
node.addMixin("mix:lockable");
Node contentNode = node.addNode(NODE_CONTENT);
contentNode.setProperty(PROPERTY_DATA, content);
return node;
}
private static Node createNode(Node parent, String path) throws
RepositoryException
{
return parent.addNode(path);
}
}
{code}
> Cannot combine move and unlock in a transaction
> -----------------------------------------------
>
> Key: JCR-3887
> URL: https://issues.apache.org/jira/browse/JCR-3887
> Project: Jackrabbit Content Repository
> Issue Type: Bug
> Components: locks, transactions
> Affects Versions: 2.10
> Reporter: Thomas Thonhofer
>
> Without transactions, when you create a Node, lock it and then move it, the
> (now nonexistent) source Node will no longer be locked, but the target Node
> will be. If the target node should not be locked, we can either unlock the
> source Node before move, or unlock the target Node after move. However, this
> stops working as soon as we start using transactions.
> In our example code (see below), the following problems occur:
> a) If we call unlock on the source Node before the move, committing the
> transaction fails and complains that the target (!) node is not locked
> b) If we call unlock on the target node after the move instead (see commented
> out line), committing the transaction fails and complains that the target
> node is not locked
> c) If we don't call unlock at all, the assertion that tests the lock after
> the move fails because the target node is locked
> As far as I have investigated, this is the problem:
> Because we are in a transaction, the real changes of the move are not done
> immediately but upon commit (as it should be). This also includes updating
> the node path in the global LockManager. The unlocking however doesn't happen
> during the actual commit, but during the prepare, so it will always happen
> before the move. Because the move didn't happen yet, the global LockManager
> doesn't have an entry for the new path of the node yet, so if
> org.apache.jackrabbit.core.lock.LockManagerImpl tries to do the unlock in the
> method internalUnlock(NodeImpl node), it will throw "LockException: Node not
> locked", because it can't find the correct path for the lock.
> ----------------------------------------------
> Test code:
> ----------------------------------------------
> {code}
> package com.infinica.tests.jackrabbit;
> import java.io.File;
> import java.io.IOException;
> import javax.jcr.LoginException;
> import javax.jcr.Node;
> import javax.jcr.Repository;
> import javax.jcr.RepositoryException;
> import javax.jcr.Session;
> import javax.jcr.SimpleCredentials;
> import javax.jcr.lock.LockException;
> import javax.jcr.lock.LockManager;
> import javax.transaction.HeuristicMixedException;
> import javax.transaction.HeuristicRollbackException;
> import javax.transaction.NotSupportedException;
> import javax.transaction.RollbackException;
> import javax.transaction.SystemException;
> import javax.transaction.xa.XAException;
> import javax.transaction.xa.XAResource;
> import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
> import org.apache.jackrabbit.core.TransientRepository;
> import org.junit.After;
> import org.junit.Assert;
> import org.junit.Before;
> import org.junit.Test;
> public class JackrabbitTransactionTest
> {
> public static final String NODE_CONTENT = "content";
> public static final String PROPERTY_DATA = "data";
> private File tempDir;
> @Before
> public void before() throws IOException
> {
> tempDir =
> File.createTempFile("jackrabbit_test", "");
> tempDir.delete();
> }
> @After
> public void after()
> {
> if(tempDir != null)
> {
> delete(tempDir);
> }
> }
> private static void delete(File file)
> {
> File[] children = file.listFiles();
> if(children != null)
> {
> for(File child: children)
> {
> delete(child);
> }
> }
> if(!file.delete())
> {
> System.err.println("Could not
> delete: " + file);
> }
> }
> @Test
> public void testMoveAndLock() throws LoginException,
> RepositoryException,
> XAException, NotSupportedException, SystemException, IllegalStateException,
> RollbackException,
> SecurityException, HeuristicMixedException, HeuristicRollbackException
> {
> GeronimoTransactionManager tm = new
> GeronimoTransactionManager();
> tm.begin();
> Repository repository = new
> TransientRepository(tempDir);
> String token;
> Session session = repository.login(new
> SimpleCredentials("admin",
> "admin".toCharArray()));
>
> tm.getTransaction().enlistResource((XAResource) session);
> LockManager lockMan =
> session.getWorkspace().getLockManager();
> try
> {
> String user =
> session.getUserID();
> String name =
> repository.getDescriptor(Repository.REP_NAME_DESC);
> System.out.println("Logged in
> as " + user +
> " to a " + name + " repository.");
> Node root =
> session.getRootNode();
> Node folderSrc =
> createNode(root, "src");
> Node fileSrc =
> createFile(folderSrc, "test.txt",
> "Test");
> System.out.println("Created
> source file: "
> + fileSrc.getPath());
> session.save();
> token =
> lockMan.lock("/src/test.txt", false,
> false, Long.MAX_VALUE, "admin").getLockToken();
> }
> finally
> {
> removeLockTokens(lockMan);
> tm.commit();
> session.logout();
> }
> tm.begin();
> session = repository.login(new
> SimpleCredentials("admin", "admin".toCharArray()));
>
> tm.getTransaction().enlistResource((XAResource) session);
> lockMan =
> session.getWorkspace().getLockManager();
> try
> {
> Node root =
> session.getRootNode();
> lockMan.addLockToken(token);
>
> lockMan.unlock("/src/test.txt");
> Node folderDest =
> createNode(root, "dest");
> session.move("/src/test.txt",
> "/dest/test.txt");
> session.save();
> // lockMan.unlock("/dest/test.txt");
>
> Assert.assertTrue(root.hasNode("src"));
>
> Assert.assertFalse(root.hasNode("src/test.txt"));
> folderDest =
> root.getNode(folderDest.getName());
> Node fileDest =
> folderDest.getNode("test.txt");
> Assert.assertEquals("Test",
> getFileStringProperty(fileDest,
> PROPERTY_DATA));
>
> Assert.assertFalse(lockMan.isLocked(fileDest.getPath()));
> }
> finally
> {
> removeLockTokens(lockMan);
> tm.commit();
> session.logout();
> }
> }
> /**
> * Necessary because of JCR-3438.
> */
> private static void removeLockTokens(LockManager man) throws
> LockException,
> RepositoryException
> {
> if(man == null)
> {
> return;
> }
> for(String token: man.getLockTokens())
> {
> man.removeLockToken(token);
> }
> }
> private String getFileStringProperty(Node file, String
> property) throws RepositoryException
> {
> Node contentNode = file.getNode(NODE_CONTENT);
> return
> contentNode.getProperty(property).getString();
> }
> private Node createFile(Node parent, String path, String
> content) throws RepositoryException
> {
> Node node = parent.addNode(path);
> node.addMixin("mix:lockable");
> Node contentNode = node.addNode(NODE_CONTENT);
> contentNode.setProperty(PROPERTY_DATA,
> content);
> return node;
> }
> private static Node createNode(Node parent, String path)
> throws RepositoryException
> {
> return parent.addNode(path);
> }
> }
> {code}
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)