From jbosscache-commits at lists.jboss.org Thu Feb 19 08:07:44 2009 Content-Type: multipart/mixed; boundary="===============5626625073050849981==" MIME-Version: 1.0 From: jbosscache-commits at lists.jboss.org To: jbosscache-commits at lists.jboss.org Subject: [jbosscache-commits] JBoss Cache SVN: r7732 - in core/branches/3.0.X/src: test/java/org/jboss/cache/api and 1 other directory. Date: Thu, 19 Feb 2009 08:07:43 -0500 Message-ID: --===============5626625073050849981== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Author: manik.surtani(a)jboss.com Date: 2009-02-19 08:07:43 -0500 (Thu, 19 Feb 2009) New Revision: 7732 Added: core/branches/3.0.X/src/test/java/org/jboss/cache/api/ReAddDeletedNodeTe= st.java Modified: core/branches/3.0.X/src/main/java/org/jboss/cache/mvcc/MVCCNodeHelper.ja= va Log: JBCACHE-1481 - Re-adding a node deleted via parent inside transaction brea= ks the parent Modified: core/branches/3.0.X/src/main/java/org/jboss/cache/mvcc/MVCCNodeHe= lper.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- core/branches/3.0.X/src/main/java/org/jboss/cache/mvcc/MVCCNodeHelper.j= ava 2009-02-19 13:03:56 UTC (rev 7731) +++ core/branches/3.0.X/src/main/java/org/jboss/cache/mvcc/MVCCNodeHelper.j= ava 2009-02-19 13:07:43 UTC (rev 7732) @@ -46,15 +46,15 @@ import java.util.Map; = /** - * Utility functions to manipulate wrapping {@link org.jboss.cache.Interna= lNode}s as {@link org.jboss.cache.mvcc.ReadCommittedNode} - * or {@link org.jboss.cache.mvcc.RepeatableReadNode}s. Would also entail= locking, if necessary. + * Utility functions to manipulate wrapping {@link org.jboss.cache.Interna= lNode}s as {@link + * org.jboss.cache.mvcc.ReadCommittedNode} or {@link org.jboss.cache.mvcc.= RepeatableReadNode}s. Would also entail + * locking, if necessary. * * @author Manik Surtani (manik = AT jboss DOT org) * @since 3.0 */ @NonVolatile -public class MVCCNodeHelper -{ +public class MVCCNodeHelper { DataContainer dataContainer; NodeFactory nodeFactory; private static final Log log =3D LogFactory.getLog(MVCCNodeHelper.class= ); @@ -66,8 +66,7 @@ private boolean lockParentForChildInsertRemove; = @Inject - public void injectDependencies(DataContainer dataContainer, NodeFactory= nodeFactory, LockManager lockManager, Configuration configuration) - { + public void injectDependencies(DataContainer dataContainer, NodeFactory= nodeFactory, LockManager lockManager, Configuration configuration) { this.nodeFactory =3D nodeFactory; this.dataContainer =3D dataContainer; this.configuration =3D configuration; @@ -75,8 +74,7 @@ } = @Start - public void start() - { + public void start() { defaultLockAcquisitionTimeout =3D configuration.getLockAcquisitionTi= meout(); writeSkewCheck =3D configuration.isWriteSkewCheck(); lockParentForChildInsertRemove =3D configuration.isLockParentForChil= dInsertRemove(); @@ -84,23 +82,23 @@ = = /** - * Attempts to provide the context with a set of wrapped nodes based on= the Collection of fqns passed in. If the nodes - * already exist in the context then the node is not wrapped again. + * Attempts to provide the context with a set of wrapped nodes based on= the Collection of fqns passed in. If the + * nodes already exist in the context then the node is not wrapped agai= n. *

- * {@link InternalNode}s are wrapped using {@link org.jboss.cache.NodeF= actory#createWrappedNode(org.jboss.cache.InternalNode, org.jboss.cache.Inte= rnalNode)} - * and as such, null internal nodes are treated according to isolation = level used. See {@link org.jboss.cache.NodeFactory#createWrappedNode(org.j= boss.cache.InternalNode, org.jboss.cache.InternalNode)} - * for details on this behaviour. + * {@link InternalNode}s are wrapped using {@link org.jboss.cache.NodeF= actory#createWrappedNode(org.jboss.cache.InternalNode, + * org.jboss.cache.InternalNode)} and as such, null internal nodes are = treated according to isolation level used. + * See {@link org.jboss.cache.NodeFactory#createWrappedNode(org.jboss.c= ache.InternalNode, + * org.jboss.cache.InternalNode)} for details on this behaviour. *

- * Note that if the context has the {@link org.jboss.cache.config.Optio= n#isForceWriteLock()} option set, then write locks are - * acquired and the node is copied. + * Note that if the context has the {@link org.jboss.cache.config.Optio= n#isForceWriteLock()} option set, then write + * locks are acquired and the node is copied. *

* * @param ctx current invocation context * @param fqns collection of Fqns. Should not be null. * @throws InterruptedException if write locks are forced and the lock = manager is interrupted. */ - public void wrapNodesForReading(InvocationContext ctx, Collection = fqns) throws InterruptedException - { + public void wrapNodesForReading(InvocationContext ctx, Collection = fqns) throws InterruptedException { boolean forceWriteLock =3D ctx.getOptionOverrides().isForceWriteLock= (); = // does the node exist in the context? @@ -108,8 +106,8 @@ } = /** - * Similar to {@link #wrapNodesForReading(org.jboss.cache.InvocationCon= text, java.util.Collection)} except - * that this version takes a single Fqn parameter to wrap a single node. + * Similar to {@link #wrapNodesForReading(org.jboss.cache.InvocationCon= text, java.util.Collection)} except that this + * version takes a single Fqn parameter to wrap a single node. * * @param ctx current invocation context * @param fqn fqn to fetch and wrap @@ -117,31 +115,24 @@ * @return read committed node, or null if one is not found. * @throws InterruptedException if write locks are forced and the lock = manager is interrupted. */ - public NodeSPI wrapNodeForReading(InvocationContext ctx, Fqn fqn, boole= an putInContext) throws InterruptedException - { + public NodeSPI wrapNodeForReading(InvocationContext ctx, Fqn fqn, boole= an putInContext) throws InterruptedException { return wrapNodeForReading(ctx, fqn, ctx.getOptionOverrides().isForce= WriteLock(), putInContext); } = @SuppressWarnings("unchecked") - private NodeSPI wrapNodeForReading(InvocationContext ctx, Fqn f, boolea= n writeLockForced, boolean putInContext) throws InterruptedException - { + private NodeSPI wrapNodeForReading(InvocationContext ctx, Fqn f, boolea= n writeLockForced, boolean putInContext) throws InterruptedException { NodeSPI n; - if (writeLockForced) - { + if (writeLockForced) { if (trace) log.trace("Forcing lock on reading node " + f); return wrapNodeForWriting(ctx, f, true, false, false, false, fals= e); - } - else if ((n =3D ctx.lookUpNode(f)) =3D=3D null) - { + } else if ((n =3D ctx.lookUpNode(f)) =3D=3D null) { if (trace) log.trace("Node " + f + " is not in context, fetching = from container."); // simple implementation. Peek the node, wrap it, put wrapped no= de in the context. InternalNode[] nodes =3D dataContainer.peekInternalNodeAndDirectP= arent(f, false); ReadCommittedNode wrapped =3D nodeFactory.createWrappedNode(nodes= [0], nodes[1]); // even though parents aren't needed for reading, we hold = on to this ref in case the node is later written to. if (putInContext && wrapped !=3D null) ctx.putLookedUpNode(f, wra= pped); return wrapped; - } - else - { + } else { if (trace) log.trace("Node " + f + " is already in context."); return n; } @@ -152,20 +143,18 @@ * * @param ctx context * @param fqn Fqn to lock - * @return true if a lock was needed and acquired, false if it didn't n= eed to acquire the lock (i.e., lock was already held) + * @return true if a lock was needed and acquired, false if it didn't n= eed to acquire the lock (i.e., lock was + * already held) * @throws InterruptedException if interrupted * @throws TimeoutException if we are unable to acquire the lock af= ter a specified timeout. */ - private boolean acquireLock(InvocationContext ctx, Fqn fqn) throws Inte= rruptedException, TimeoutException - { + private boolean acquireLock(InvocationContext ctx, Fqn fqn) throws Inte= rruptedException, TimeoutException { // don't EVER use lockManager.isLocked() since with lock striping it= may be the case that we hold the relevant // lock which may be shared with another Fqn that we have a lock for= already. // nothing wrong, just means that we fail to record the lock. And t= hat is a problem. // Better to check our records and lock again if necessary. - if (!ctx.hasLock(fqn)) - { - if (!lockManager.lockAndRecord(fqn, WRITE, ctx)) - { + if (!ctx.hasLock(fqn)) { + if (!lockManager.lockAndRecord(fqn, WRITE, ctx)) { Object owner =3D lockManager.getWriteOwner(fqn); throw new TimeoutException("Unable to acquire lock on Fqn [" += fqn + "] after [" + ctx.getLockAcquisitionTimeout(defaultLockAcquisitionTi= meout) + "] milliseconds for requestor [" + lockManager.getLockOwner(ctx) += "]! Lock held by [" + owner + "]"); } @@ -176,10 +165,10 @@ = /** * First checks in contexts for the existence of the node. If it does = exist, it will return it, acquiring a lock if - * necessary. Otherwise, it will peek in the dataContainer, wrap the n= ode, lock if necessary, and add it to the context. - * If it doesn't even exist in the dataContainer and createIfAbsent is = true, it will create a new node and add it to the - * data structure. It will lock the node, and potentially the parent a= s well, if necessary. If the parent is locked, - * it too will be added to the context if it wasn't there already. + * necessary. Otherwise, it will peek in the dataContainer, wrap the n= ode, lock if necessary, and add it to the + * context. If it doesn't even exist in the dataContainer and createIfA= bsent is true, it will create a new node and + * add it to the data structure. It will lock the node, and potentiall= y the parent as well, if necessary. If the + * parent is locked, it too will be added to the context if it wasn't t= here already. * * @param context invocation context * @param fqn to retrieve @@ -192,54 +181,47 @@ * @throws InterruptedException if interrupted */ @SuppressWarnings("unchecked") - public ReadCommittedNode wrapNodeForWriting(InvocationContext context, = Fqn fqn, boolean lockForWriting, boolean createIfAbsent, boolean includeInv= alidNodes, boolean forRemoval, boolean force) throws InterruptedException - { + public ReadCommittedNode wrapNodeForWriting(InvocationContext context, = Fqn fqn, boolean lockForWriting, boolean createIfAbsent, boolean includeInv= alidNodes, boolean forRemoval, boolean force) throws InterruptedException { Fqn parentFqn =3D null; ReadCommittedNode n =3D (ReadCommittedNode) context.lookUpNode(fqn); if (createIfAbsent && n !=3D null && n.isNullNode()) n =3D null; if (n !=3D null) // exists in context! Just acquire lock if needed,= and wrap. { // acquire lock if needed - if (lockForWriting && acquireLock(context, fqn)) - { + if (lockForWriting && acquireLock(context, fqn)) { // create a copy of the underlying node n.markForUpdate(dataContainer, writeSkewCheck); } if (trace) log.trace("Retrieving wrapped node " + fqn); - if (n.isDeleted() && createIfAbsent) - { + if (n.isDeleted() && createIfAbsent) { if (trace) log.trace("Node is deleted in current scope. Need = to un-delete."); n.markAsDeleted(false); n.setValid(true, false); + // has the parent been deleted too? :-( + wrapNodeForWriting(context, fqn.getParent(), true, true, inclu= deInvalidNodes, false, force); } - } - else - { + } else { // else, fetch from dataContainer. InternalNode[] nodes =3D dataContainer.peekInternalNodeAndDirectP= arent(fqn, includeInvalidNodes); InternalNode in =3D nodes[0]; - if (in !=3D null) - { + if (in !=3D null) { // exists in cache! Just acquire lock if needed, and wrap. // do we need a lock? boolean needToCopy =3D false; - if (lockForWriting && acquireLock(context, fqn)) - { + if (lockForWriting && acquireLock(context, fqn)) { needToCopy =3D true; } n =3D nodeFactory.createWrappedNode(in, nodes[1]); context.putLookedUpNode(fqn, n); if (needToCopy) n.markForUpdate(dataContainer, writeSkewCheck); - } - else if (createIfAbsent) // else, do we need to create one? + } else if (createIfAbsent) // else, do we need to create one? { parentFqn =3D fqn.getParent(); NodeSPI parent =3D wrapNodeForWriting(context, parentFqn, fals= e, createIfAbsent, false, false, false); // do we need to lock the parent to create children? boolean parentLockNeeded =3D isParentLockNeeded(parent.getDele= gationTarget()); // get a lock on the parent. - if (parentLockNeeded && acquireLock(context, parentFqn)) - { + if (parentLockNeeded && acquireLock(context, parentFqn)) { ReadCommittedNode parentRCN =3D (ReadCommittedNode) context= .lookUpNode(parentFqn); parentRCN.markForUpdate(dataContainer, writeSkewCheck); } @@ -267,8 +249,9 @@ } = /** - * The same as {@link #wrapNodeForWriting(org.jboss.cache.InvocationCon= text, org.jboss.cache.Fqn, boolean, boolean, boolean, boolean, boolean)} - * except that it takes in an {@link org.jboss.cache.InternalNode} inst= ead of a {@link Fqn}. Saves on a lookup. + * The same as {@link #wrapNodeForWriting(org.jboss.cache.InvocationCon= text, org.jboss.cache.Fqn, boolean, boolean, + * boolean, boolean, boolean)} except that it takes in an {@link org.jb= oss.cache.InternalNode} instead of a {@link + * Fqn}. Saves on a lookup. *

* Also assumes that the node exists, and hence will not be created. *

@@ -279,27 +262,22 @@ * @throws InterruptedException if interrupted */ @SuppressWarnings("unchecked") - public NodeSPI wrapNodeForWriting(InvocationContext context, InternalNo= de node, InternalNode parent) throws InterruptedException - { + public NodeSPI wrapNodeForWriting(InvocationContext context, InternalNo= de node, InternalNode parent) throws InterruptedException { Fqn fqn =3D node.getFqn(); NodeSPI n =3D context.lookUpNode(fqn); if (n !=3D null) // exists in context! Just acquire lock if needed,= and wrap. { // acquire lock if needed - if (acquireLock(context, fqn)) - { + if (acquireLock(context, fqn)) { // create a copy of the underlying node n.markForUpdate(dataContainer, writeSkewCheck); } if (trace) log.trace("Retrieving wrapped node " + fqn); - } - else - { + } else { // exists in cache! Just acquire lock if needed, and wrap. // do we need a lock? boolean needToCopy =3D false; - if (acquireLock(context, fqn)) - { + if (acquireLock(context, fqn)) { needToCopy =3D true; } n =3D nodeFactory.createWrappedNode(node, parent); @@ -319,8 +297,7 @@ * @throws InterruptedException if the lock manager is interrupted. */ @SuppressWarnings("unchecked") - public List wrapNodesRecursivelyForRemoval(InvocationContext ctx, = Fqn fqn) throws InterruptedException - { + public List wrapNodesRecursivelyForRemoval(InvocationContext ctx, = Fqn fqn) throws InterruptedException { // when removing a node we want to get a lock on the Fqn anyway and = return the wrapped node. if (fqn.isRoot()) throw new CacheException("Attempting to remove Fqn= .ROOT!"); = @@ -329,8 +306,7 @@ boolean needToCopyParent =3D false; boolean parentLockNeeded =3D isParentLockNeeded(parentFqn, ctx); ReadCommittedNode parent =3D null; - if (parentLockNeeded) - { + if (parentLockNeeded) { needToCopyParent =3D acquireLock(ctx, parentFqn); // Ensure the node is in the context. parent =3D wrapAndPutInContext(ctx, parentFqn, needToCopyParent); @@ -341,16 +317,12 @@ // Ensure the node is in the context. ReadCommittedNode node =3D wrapAndPutInContext(ctx, fqn, needToCopyN= ode); = - if (node =3D=3D null || node.isNullNode()) - { + if (node =3D=3D null || node.isNullNode()) { // node does not exist; return an empty list since there is nothi= ng to remove! return Collections.emptyList(); - } - else - { + } else { // update child ref on parent to point to child as this is now a = copy. - if (parentLockNeeded && (needToCopyNode || needToCopyParent)) - { + if (parentLockNeeded && (needToCopyNode || needToCopyParent)) { if (parent =3D=3D null) throw new NodeNotExistsException("Pare= nt node " + parentFqn + " does not exist!"); parent.getDelegationTarget().addChild(node.getDelegationTarget= ()); } @@ -360,8 +332,7 @@ List fqnsToBeRemoved =3D new LinkedList(); fqnsToBeRemoved.add(fqn); = - if (!childMap.isEmpty()) - { + if (!childMap.isEmpty()) { for (InternalNode n : childMap.values()) lockForWritingRecursi= ve(n.getFqn(), ctx, fqnsToBeRemoved); } = @@ -374,20 +345,18 @@ * * @param fqn Fqn to lock * @param ctx invocation context to add wrapped node to - * @param fqnList fqnList to update - this list should not be null but = should be initially empty and will be populated - * with a list of all Fqns locked in this call. + * @param fqnList fqnList to update - this list should not be null but = should be initially empty and will be + * populated with a list of all Fqns locked in this call. * @throws InterruptedException if interrupted */ @SuppressWarnings("unchecked") - private void lockForWritingRecursive(Fqn fqn, InvocationContext ctx, Li= st fqnList) throws InterruptedException - { + private void lockForWritingRecursive(Fqn fqn, InvocationContext ctx, Li= st fqnList) throws InterruptedException { acquireLock(ctx, fqn); // lock node if (fqnList !=3D null) fqnList.add(fqn); = // now wrap and add to the context ReadCommittedNode rcn =3D wrapNodeForWriting(ctx, fqn, true, false, = true, false, false); - if (rcn !=3D null) - { + if (rcn !=3D null) { rcn.markForUpdate(dataContainer, writeSkewCheck); Map> children =3D rcn.getDelegationTar= get().getChildrenMap(); for (InternalNode child : children.values()) @@ -396,8 +365,9 @@ } = /** - * Identical to {@link #lockForWritingRecursive(org.jboss.cache.Fqn, or= g.jboss.cache.InvocationContext, java.util.List)} - * except that it uses an {@link org.jboss.cache.InternalNode} instead = of an {@link Fqn} - saves a lookup. + * Identical to {@link #lockForWritingRecursive(org.jboss.cache.Fqn, or= g.jboss.cache.InvocationContext, + * java.util.List)} except that it uses an {@link org.jboss.cache.Inter= nalNode} instead of an {@link Fqn} - saves a + * lookup. * * @param node node to lock recursively * @param ctx invocation context @@ -405,16 +375,14 @@ * @throws InterruptedException if interrupted */ @SuppressWarnings("unchecked") - private void lockForWritingRecursive(InternalNode node, InternalNode pa= rent, InvocationContext ctx, List fqnList) throws InterruptedException - { + private void lockForWritingRecursive(InternalNode node, InternalNode pa= rent, InvocationContext ctx, List fqnList) throws InterruptedException= { Fqn fqn =3D node.getFqn(); acquireLock(ctx, fqn); // lock node if (fqnList !=3D null) fqnList.add(fqn); = // now wrap and add to the context NodeSPI rcn =3D wrapNodeForWriting(ctx, node, parent); - if (rcn !=3D null) - { + if (rcn !=3D null) { rcn.markForUpdate(dataContainer, writeSkewCheck); Map> children =3D node.getChildrenMap(= ); for (InternalNode child : children.values()) lockForWritingRecurs= ive(child, node, ctx, fqnList); @@ -423,9 +391,9 @@ = = /** - * Wraps a node and puts it in the context, optionally copying the node= for updating if forUpdate is true. - * If the node is already in the context, a new wrapped node is not cre= ated, but the existing one is still checked - * for changes and potentially marked for update if forUpdate = is true. + * Wraps a node and puts it in the context, optionally copying the node= for updating if forUpdate is + * true. If the node is already in the context, a new wrapped = node is not created, but the existing one is + * still checked for changes and potentially marked for update if f= orUpdate is true. * * @param ctx invocation context to add node to * @param fqn fqn of node to add @@ -433,11 +401,9 @@ * @return the ReadCommittedNode wrapper, or null if the node does not = exist. */ @SuppressWarnings("unchecked") - private ReadCommittedNode wrapAndPutInContext(InvocationContext ctx, Fq= n fqn, boolean forUpdate) - { + private ReadCommittedNode wrapAndPutInContext(InvocationContext ctx, Fq= n fqn, boolean forUpdate) { ReadCommittedNode node =3D (ReadCommittedNode) ctx.lookUpNode(fqn); - if (node =3D=3D null) - { + if (node =3D=3D null) { InternalNode[] nodes =3D dataContainer.peekInternalNodeAndDirectP= arent(fqn, false); node =3D nodeFactory.createWrappedNode(nodes[0], nodes[1]); ctx.putLookedUpNode(fqn, node); @@ -450,14 +416,13 @@ } = /** - * An overloaded version of {@link #isParentLockNeeded(org.jboss.cache.= Fqn, org.jboss.cache.InvocationContext)} - * which takes in an {@link org.jboss.cache.InternalNode} instead of a = {@link Fqn}. + * An overloaded version of {@link #isParentLockNeeded(org.jboss.cache.= Fqn, org.jboss.cache.InvocationContext)} which + * takes in an {@link org.jboss.cache.InternalNode} instead of a {@link= Fqn}. * * @param parent parent node to test * @return true if parent lock is needed, false otherwise. */ - private boolean isParentLockNeeded(InternalNode parent) - { + private boolean isParentLockNeeded(InternalNode parent) { return lockParentForChildInsertRemove || (parent !=3D null && parent= .isLockForChildInsertRemove()); } = @@ -468,8 +433,7 @@ * @param ctx invocation context * @return true if parent lock is needed, false otherwise. */ - private boolean isParentLockNeeded(Fqn parent, InvocationContext ctx) - { + private boolean isParentLockNeeded(Fqn parent, InvocationContext ctx) { ReadCommittedNode parentNodeTmp =3D (ReadCommittedNode) ctx.lookUpNo= de(parent); InternalNode in =3D parentNodeTmp =3D=3D null ? dataContainer.peekIn= ternalNode(parent, true) : parentNodeTmp.getDelegationTarget(); return isParentLockNeeded(in); Added: core/branches/3.0.X/src/test/java/org/jboss/cache/api/ReAddDeletedNo= deTest.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- core/branches/3.0.X/src/test/java/org/jboss/cache/api/ReAddDeletedNodeT= est.java (rev 0) +++ core/branches/3.0.X/src/test/java/org/jboss/cache/api/ReAddDeletedNodeT= est.java 2009-02-19 13:07:43 UTC (rev 7732) @@ -0,0 +1,40 @@ +package org.jboss.cache.api; + +import org.jboss.cache.AbstractSingleCacheTest; +import org.jboss.cache.CacheSPI; +import org.jboss.cache.Fqn; +import org.jboss.cache.UnitTestCacheFactory; +import org.testng.annotations.Test; + +import javax.transaction.TransactionManager; + +(a)Test(groups =3D {"functional"}, sequential =3D true, testName =3D "api.= ReAddDeletedNodeTest") +public class ReAddDeletedNodeTest extends AbstractSingleCacheTest { + private CacheSPI cache; + + public CacheSPI createCache() { + // start a single cache instance + UnitTestCacheFactory cf =3D new UnitTestCacheFactory= (); + cache =3D (CacheSPI) cf.createCache("configs/local-t= x.xml", false, getClass()); + cache.getConfiguration().setEvictionConfig(null); + cache.start(); + return cache; + } + + public void testReAdd() throws Exception { + TransactionManager tm =3D cache.getTransactionManager(); + Fqn testFqn =3D Fqn.fromElements("a", "a", "a"); + + tm.begin(); + cache.put(testFqn, "x", "x"); + cache.removeNode(testFqn.getParent()); + cache.put(testFqn, "x", "x"); + assert cache.getNode(testFqn) !=3D null : testFqn + " should not be = null (before commit)"; + assert cache.getNode(testFqn.getParent()) !=3D null : testFqn.getPar= ent() + " should not be null (before commit)"; + assert cache.getNode(testFqn.getParent().getParent()) !=3D null : te= stFqn.getParent().getParent() + " should not be null (before commit)"; + tm.commit(); + assert cache.getNode(testFqn) !=3D null : testFqn + " should not be = null (after commit)"; + assert cache.getNode(testFqn.getParent()) !=3D null : testFqn.getPar= ent() + " should not be null (after commit)"; + assert cache.getNode(testFqn.getParent().getParent()) !=3D null : te= stFqn.getParent().getParent() + " should not be null (after commit)"; + } +} --===============5626625073050849981==--