[jbosscache-commits] JBoss Cache SVN: r6046 - in core/trunk/src: main/java/org/jboss/cache/config and 7 other directories.

jbosscache-commits at lists.jboss.org jbosscache-commits at lists.jboss.org
Wed Jun 25 14:31:09 EDT 2008


Author: manik.surtani at jboss.com
Date: 2008-06-25 14:31:09 -0400 (Wed, 25 Jun 2008)
New Revision: 6046

Added:
   core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterceptor.java
   core/trunk/src/main/java/org/jboss/cache/mvcc/NodeReference.java
   core/trunk/src/main/java/org/jboss/cache/mvcc/ReadCommittedNode.java
   core/trunk/src/main/java/org/jboss/cache/mvcc/RepeatableReadNode.java
   core/trunk/src/test/java/org/jboss/cache/api/mvcc/
   core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java
   core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockParentForChildInsertRemoveTest.java
   core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockTest.java
   core/trunk/src/test/java/org/jboss/cache/api/mvcc/NodeAPIMVCCTest.java
   core/trunk/src/test/java/org/jboss/cache/mvcc/MVCCFullStackTest.java
Modified:
   core/trunk/src/main/java/org/jboss/cache/AbstractNode.java
   core/trunk/src/main/java/org/jboss/cache/DataContainer.java
   core/trunk/src/main/java/org/jboss/cache/DataContainerImpl.java
   core/trunk/src/main/java/org/jboss/cache/NodeFactory.java
   core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java
   core/trunk/src/main/java/org/jboss/cache/VersionedNode.java
   core/trunk/src/main/java/org/jboss/cache/config/Configuration.java
   core/trunk/src/main/java/org/jboss/cache/factories/InterceptorChainFactory.java
   core/trunk/src/main/java/org/jboss/cache/factories/LockManagerFactory.java
   core/trunk/src/main/java/org/jboss/cache/invocation/NodeInvocationDelegate.java
   core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java
   core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java
   core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java
   core/trunk/src/test/java/org/jboss/cache/api/NodeAPITest.java
Log:
Partially complete MVCC implementation.  Still WIP.

Modified: core/trunk/src/main/java/org/jboss/cache/AbstractNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/AbstractNode.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/AbstractNode.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -14,7 +14,7 @@
  *
  * @author manik
  */
-public abstract class AbstractNode<K, V>// implements Node<K, V>
+public abstract class AbstractNode<K, V>
 {
    protected Map<Object, Node<K, V>> children;
    protected Fqn<?> fqn;

Modified: core/trunk/src/main/java/org/jboss/cache/DataContainer.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/DataContainer.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/DataContainer.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -1,6 +1,7 @@
 package org.jboss.cache;
 
 import org.jboss.cache.marshall.NodeData;
+import org.jboss.cache.mvcc.InternalNode;
 import org.jboss.cache.optimistic.DataVersion;
 import org.jboss.cache.transaction.GlobalTransaction;
 
@@ -208,4 +209,19 @@
     * @return see above.
     */
    Object[] createNodes(Fqn fqn);
+
+   /**
+    * Similar to {@link #peek(Fqn)} except that the underlying node is NOT wrapped as a {@link org.jboss.cache.NodeSPI}.
+    *
+    * @param f fqn to peek
+    * @return internal node
+    */
+   InternalNode peekInternalNode(Fqn f);
+
+   /**
+    * Sets a new root node
+    *
+    * @param nodeInvocationDelegate
+    */
+   void setRoot(NodeSPI nodeInvocationDelegate);
 }

Modified: core/trunk/src/main/java/org/jboss/cache/DataContainerImpl.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/DataContainerImpl.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/DataContainerImpl.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -11,6 +11,7 @@
 import org.jboss.cache.invocation.NodeInvocationDelegate;
 import org.jboss.cache.lock.LockManager;
 import org.jboss.cache.marshall.NodeData;
+import org.jboss.cache.mvcc.InternalNode;
 import org.jboss.cache.optimistic.DataVersion;
 import org.jboss.cache.transaction.GlobalTransaction;
 
@@ -527,6 +528,14 @@
       return new Object[]{result, n};
    }
 
+   public InternalNode peekInternalNode(Fqn f)
+   {
+      // Yuck!
+      NodeSPI nodeSPI = peek(f);
+      if (nodeSPI == null) return null;
+      return (InternalNode) ((NodeInvocationDelegate) nodeSPI).getDelegationTarget();
+   }
+
    public void setBuddyFqnTransformer(BuddyFqnTransformer buddyFqnTransformer)
    {
       this.buddyFqnTransformer = buddyFqnTransformer;

Modified: core/trunk/src/main/java/org/jboss/cache/NodeFactory.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/NodeFactory.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/NodeFactory.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -7,6 +7,7 @@
 package org.jboss.cache;
 
 import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
 import org.jboss.cache.factories.CommandsFactory;
 import org.jboss.cache.factories.ComponentFactory;
 import org.jboss.cache.factories.annotations.Inject;
@@ -15,6 +16,7 @@
 import org.jboss.cache.invocation.InvocationContextContainer;
 import org.jboss.cache.invocation.NodeInvocationDelegate;
 import org.jboss.cache.lock.LockStrategyFactory;
+import org.jboss.cache.mvcc.InternalNode;
 import org.jboss.cache.optimistic.TransactionWorkspace;
 import org.jboss.cache.optimistic.WorkspaceNode;
 import org.jboss.cache.optimistic.WorkspaceNodeImpl;
@@ -29,7 +31,7 @@
 public class NodeFactory<K, V> extends ComponentFactory
 {
    private CacheSPI<K, V> cache;
-   private boolean optimistic;
+   private boolean useVersionedNode;
    private Configuration configuration;
    private InvocationContextContainer invocationContextContainer;
    private InterceptorChain interceptorChain;
@@ -62,8 +64,8 @@
 
    @Inject
    public void injectDependencies(CacheSPI<K, V> cache, Configuration configuration,
-                                   InvocationContextContainer invocationContextContainer,
-                                   InterceptorChain interceptorChain, CommandsFactory commandsFactory, LockStrategyFactory lockStrategyFactory)
+                                  InvocationContextContainer invocationContextContainer,
+                                  InterceptorChain interceptorChain, CommandsFactory commandsFactory, LockStrategyFactory lockStrategyFactory)
    {
       this.cache = cache;
       this.configuration = configuration;
@@ -79,7 +81,7 @@
    @Start
    public void init()
    {
-      optimistic = configuration.isNodeLockingOptimistic();
+      useVersionedNode = configuration.getNodeLockingScheme() != NodeLockingScheme.PESSIMISTIC;
    }
 
 
@@ -98,7 +100,7 @@
     */
    public NodeSPI<K, V> createDataNode(Object childName, Fqn fqn, NodeSPI<K, V> parent, Map<K, V> data, boolean mapSafe)
    {
-      UnversionedNode un = optimistic ? new VersionedNode<K, V>(fqn, parent, data, cache) : new UnversionedNode<K, V>(childName, fqn, data, cache);
+      UnversionedNode un = useVersionedNode ? new VersionedNode<K, V>(fqn, parent, data, cache) : new UnversionedNode<K, V>(childName, fqn, data, cache);
       // always assume that new nodes do not have data loaded
       un.setDataLoaded(false);
       NodeInvocationDelegate<K, V> nid = new NodeInvocationDelegate(un);
@@ -136,4 +138,11 @@
       return createDataNode(null, Fqn.ROOT, null, null, false);
    }
 
+   public NodeSPI createNodeInvocationDelegate(InternalNode internalNode)
+   {
+      NodeInvocationDelegate nid = new NodeInvocationDelegate(internalNode);
+      nid.initialize(configuration, invocationContextContainer, componentRegistry, interceptorChain);
+      nid.injectDependencies(cache);
+      return nid;
+   }
 }

Modified: core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -50,15 +50,15 @@
    /**
     * A reference of the CacheImpl instance.
     */
-   private transient CacheSPI cache;
+   transient CacheSPI cache;
 
    /**
     * Map of general data keys to values.
     */
-   private final Map data = new HashMap();
+   final Map data = new HashMap();
 
    protected NodeSPI delegate;
-   private CommandsFactory commandsFactory;
+   CommandsFactory commandsFactory;
    protected LockStrategyFactory lockStrategyFactory;
 
    /**

Modified: core/trunk/src/main/java/org/jboss/cache/VersionedNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/VersionedNode.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/VersionedNode.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -28,6 +28,7 @@
 {
    private static final String DATA_VERSION_INTERNAL_KEY = "_JBOSS_INTERNAL_OPTIMISTIC_DATA_VERSION";
    private DataVersion version; // make sure this is NOT initialized to anything, even a null!  Since the UnversionedNode constructor may set this value based on a data version passed along in the data map.
+
    static
    {
       log = LogFactory.getLog(VersionedNode.class);
@@ -106,4 +107,18 @@
       }
       super.setInternalState(state);
    }
+
+   @Override
+   public VersionedNode copy()
+   {
+      VersionedNode n = new VersionedNode(fqn, getParent(), data, cache);
+      n.children = children;
+      n.commandsFactory = commandsFactory;
+      n.delegate = delegate;
+      n.flags.clear();
+      n.flags.addAll(flags);
+      n.lockStrategyFactory = lockStrategyFactory;
+      n.version = version;
+      return n;
+   }
 }

Modified: core/trunk/src/main/java/org/jboss/cache/config/Configuration.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/config/Configuration.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/config/Configuration.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -7,7 +7,6 @@
 package org.jboss.cache.config;
 
 import org.jboss.cache.Version;
-import org.jboss.cache.config.parsing.XmlConfigurationParser2x;
 import org.jboss.cache.config.parsing.JGroupsStackParser;
 import org.jboss.cache.factories.annotations.NonVolatile;
 import org.jboss.cache.lock.IsolationLevel;
@@ -15,9 +14,9 @@
 import org.w3c.dom.Element;
 
 import java.net.URL;
+import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
-import java.util.List;
-import java.util.Collections;
 
 /**
  * Encapsulates the configuration of a Cache.
@@ -133,12 +132,17 @@
    public enum NodeLockingScheme
    {
       /**
+       * Data is locked using the MVCC locking scheme.  This is the default locking scheme in JBoss Cache 3.0.0.
+       *
+       * @see <a href="http://wiki.jboss.org/wiki/JBossCacheMVCC">http://wiki.jboss.org/wiki/JBossCacheMVCC</a>
+       */
+      MVCC,
+      /**
        * Data is exclusively locked during modification.
        *
        * @see <a href="http://en.wikipedia.org/wiki/Concurrency_control">http://en.wikipedia.org/wiki/Concurrency_control (pessimistic)</a>
        */
       PESSIMISTIC,
-
       /**
        * Data is unlocked during modification, modifications merged at commit.
        *
@@ -189,7 +193,7 @@
    private boolean syncRollbackPhase = false;
    private BuddyReplicationConfig buddyReplicationConfig;
    private boolean nodeLockingOptimistic = false;
-   private NodeLockingScheme nodeLockingScheme = NodeLockingScheme.PESSIMISTIC;
+   private NodeLockingScheme nodeLockingScheme = NodeLockingScheme.PESSIMISTIC; // TODO: Make this default MVCC once MVCC is completely implemented.
    private String muxStackName = null;
    private boolean usingMultiplexer = false;
    private transient RuntimeConfig runtimeConfig;
@@ -198,7 +202,7 @@
    private boolean useLazyDeserialization = false;
    private int objectInputStreamPoolSize = 50;
    private int objectOutputStreamPoolSize = 50;
-   private List<CustomInterceptorConfig> customInterceptors = Collections.EMPTY_LIST;
+   private List<CustomInterceptorConfig> customInterceptors = Collections.emptyList();
 
    // ------------------------------------------------------------------------------------------------------------
    //   SETTERS - MAKE SURE ALL SETTERS PERFORM testImmutability()!!!
@@ -537,6 +541,7 @@
       return this.shutdownHookBehavior;
    }
 
+   @Deprecated
    public boolean isNodeLockingOptimistic()
    {
       return nodeLockingOptimistic;

Modified: core/trunk/src/main/java/org/jboss/cache/factories/InterceptorChainFactory.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/factories/InterceptorChainFactory.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/factories/InterceptorChainFactory.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -7,6 +7,7 @@
 package org.jboss.cache.factories;
 
 import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
 import org.jboss.cache.config.ConfigurationException;
 import org.jboss.cache.factories.annotations.DefaultFactoryFor;
 import org.jboss.cache.interceptors.*;
@@ -84,8 +85,12 @@
             //Nothing...
       }
 
-      if (!optimistic)
+      if (configuration.getNodeLockingScheme() == NodeLockingScheme.MVCC)
       {
+         interceptorChain.appendIntereceptor(createInterceptor(MVCCLockingInterceptor.class));
+      }
+      else if (configuration.getNodeLockingScheme() == NodeLockingScheme.PESSIMISTIC)
+      {
          interceptorChain.appendIntereceptor(createInterceptor(PessimisticLockInterceptor.class));
       }
 

Modified: core/trunk/src/main/java/org/jboss/cache/factories/LockManagerFactory.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/factories/LockManagerFactory.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/factories/LockManagerFactory.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -2,6 +2,7 @@
 
 import org.jboss.cache.factories.annotations.DefaultFactoryFor;
 import org.jboss.cache.lock.LockManager;
+import org.jboss.cache.lock.MVCCLockManager;
 import org.jboss.cache.lock.NodeBasedLockManager;
 import org.jboss.cache.lock.PessimisticNodeBasedLockManager;
 
@@ -18,13 +19,15 @@
    @SuppressWarnings({"unchecked", "deprecation"})
    protected <T> T construct(Class<T> componentType)
    {
-      if (configuration.isNodeLockingOptimistic())
+      switch (configuration.getNodeLockingScheme())
       {
-         return (T) super.construct(NodeBasedLockManager.class);
+         case MVCC:
+            return (T) super.construct(MVCCLockManager.class);
+         case OPTIMISTIC:
+            return (T) super.construct(NodeBasedLockManager.class);
+         case PESSIMISTIC:
+         default:
+            return (T) super.construct(PessimisticNodeBasedLockManager.class);
       }
-      else
-      {
-         return (T) super.construct(PessimisticNodeBasedLockManager.class);
-      }
    }
 }

Added: core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterceptor.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterceptor.java	                        (rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterceptor.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -0,0 +1,392 @@
+package org.jboss.cache.interceptors;
+
+import org.jboss.cache.DataContainer;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.InvocationContext;
+import org.jboss.cache.NodeFactory;
+import org.jboss.cache.NodeSPI;
+import org.jboss.cache.commands.VisitableCommand;
+import org.jboss.cache.commands.read.ExistsCommand;
+import org.jboss.cache.commands.read.GetChildrenNamesCommand;
+import org.jboss.cache.commands.read.GetDataMapCommand;
+import org.jboss.cache.commands.read.GetKeyValueCommand;
+import org.jboss.cache.commands.read.GetKeysCommand;
+import org.jboss.cache.commands.read.GetNodeCommand;
+import org.jboss.cache.commands.read.GravitateDataCommand;
+import org.jboss.cache.commands.tx.CommitCommand;
+import org.jboss.cache.commands.tx.RollbackCommand;
+import org.jboss.cache.commands.write.*;
+import org.jboss.cache.factories.annotations.Inject;
+import org.jboss.cache.factories.annotations.Start;
+import org.jboss.cache.interceptors.base.PostProcessingCommandInterceptor;
+import org.jboss.cache.invocation.NodeInvocationDelegate;
+import org.jboss.cache.lock.IsolationLevel;
+import org.jboss.cache.lock.LockManager;
+import static org.jboss.cache.lock.LockType.WRITE;
+import org.jboss.cache.mvcc.InternalNode;
+import org.jboss.cache.mvcc.ReadCommittedNode;
+import org.jboss.cache.mvcc.RepeatableReadNode;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Interceptor to implement <a href="http://wiki.jboss.org/wiki/JBossCacheMVCC">MVCC</a> functionality.
+ *
+ * @author Manik Surtani (<a href="mailto:manik at jboss.org">manik at jboss.org</a>)
+ * @see <a href="http://wiki.jboss.org/wiki/JBossCacheMVCC">MVCC designs</a>
+ * @since 3.0
+ */
+public class MVCCLockingInterceptor extends PostProcessingCommandInterceptor
+{
+   // TODO: Implement me.
+
+   // Need to think about how this will behave with 1.  LockParentForChildInsertRemove and 2.  CacheLoading.
+
+   // Need to think further about cached node lookups.  Walking the tree sucks ass.
+
+   // How will wrapper nodes unwrap?
+
+   boolean isUsingRepeatableRead, ignoreWriteSkew, lockParentForChildInsertRemove;
+   LockManager lockManager;
+   DataContainer dataContainer;
+   NodeFactory nodeFactory;
+
+   @Inject
+   public void setDependencies(LockManager lockManager, DataContainer dataContainer, NodeFactory nodeFactory)
+   {
+      this.lockManager = lockManager;
+      this.dataContainer = dataContainer;
+      this.nodeFactory = nodeFactory;
+   }
+
+   @Start
+   public void start()
+   {
+      isUsingRepeatableRead = configuration.getIsolationLevel() == IsolationLevel.REPEATABLE_READ; // otherwise default to READ_COMMITTED
+      ignoreWriteSkew = true; // todo this should come from a cfg variable
+      lockParentForChildInsertRemove = configuration.isLockParentForChildInsertRemove();
+   }
+
+   @Override
+   public Object handlePutDataMapCommand(InvocationContext ctx, PutDataMapCommand command) throws Throwable
+   {
+      getWrappedNode(ctx, command.getFqn(), true, true); // get the node and stick it in the context.
+      return invokeNextInterceptor(ctx, command);
+   }
+
+   @Override
+   public Object handlePutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable
+   {
+      getWrappedNode(ctx, command.getFqn(), true, true); // get the node and stick it in the context.
+      return invokeNextInterceptor(ctx, command);
+   }
+
+   @Override
+   public Object handlePutForExternalReadCommand(InvocationContext ctx, PutForExternalReadCommand command) throws Throwable
+   {
+      getWrappedNode(ctx, command.getFqn(), true, true); // get the node and stick it in the context.
+      return invokeNextInterceptor(ctx, command);
+   }
+
+   @Override
+   public Object handleRemoveNodeCommand(InvocationContext ctx, RemoveNodeCommand command) throws Throwable
+   {
+      return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1), Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleClearDataCommand(InvocationContext ctx, ClearDataCommand command) throws Throwable
+   {
+      return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1), Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleEvictFqnCommand(InvocationContext ctx, EvictCommand command) throws Throwable
+   {
+      return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1), Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleInvalidateCommand(InvocationContext ctx, InvalidateCommand command) throws Throwable
+   {
+      return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1), Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleRemoveKeyCommand(InvocationContext ctx, RemoveKeyCommand command) throws Throwable
+   {
+      return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1), Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleGetDataMapCommand(InvocationContext ctx, GetDataMapCommand command) throws Throwable
+   {
+      return handleReadCommand(ctx, command, Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleExistsNodeCommand(InvocationContext ctx, ExistsCommand command) throws Throwable
+   {
+      return handleReadCommand(ctx, command, Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable
+   {
+      return handleReadCommand(ctx, command, Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleGetNodeCommand(InvocationContext ctx, GetNodeCommand command) throws Throwable
+   {
+      return handleReadCommand(ctx, command, Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleGetKeysCommand(InvocationContext ctx, GetKeysCommand command) throws Throwable
+   {
+      return handleReadCommand(ctx, command, Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleGetChildrenNamesCommand(InvocationContext ctx, GetChildrenNamesCommand command) throws Throwable
+   {
+      return handleReadCommand(ctx, command, Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleMoveCommand(InvocationContext ctx, MoveCommand command) throws Throwable
+   {
+      List<Fqn> list = new ArrayList<Fqn>();
+      list.add(command.getFqn());
+      list.add(command.getTo());
+      return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1), list);
+   }
+
+   @Override
+   public Object handleGravitateDataCommand(InvocationContext ctx, GravitateDataCommand command) throws Throwable
+   {
+      return handleReadCommand(ctx, command, Collections.singletonList(command.getFqn()));
+   }
+
+   @Override
+   public Object handleCreateNodeCommand(InvocationContext ctx, CreateNodeCommand command) throws Throwable
+   {
+      getWrappedNode(ctx, command.getFqn(), true, true); // get the node and stick it in the context.
+      return invokeNextInterceptor(ctx, command);
+   }
+
+   @Override
+   public Object handleRollbackCommand(InvocationContext ctx, RollbackCommand command) throws Throwable
+   {
+      Object retval = null;
+      try
+      {
+         retval = invokeNextInterceptor(ctx, command);
+      }
+      finally
+      {
+         transactionalCleanup(false, ctx);
+      }
+      return retval;
+   }
+
+   @Override
+   public Object handleCommitCommand(InvocationContext ctx, CommitCommand command) throws Throwable
+   {
+      Object retval = null;
+      try
+      {
+         retval = invokeNextInterceptor(ctx, command);
+      }
+      finally
+      {
+         transactionalCleanup(true, ctx);
+      }
+      return retval;
+   }
+
+   protected void doAfterCall(InvocationContext ctx, VisitableCommand command)
+   {
+      // for non-transactional stuff.
+      if (ctx.getTransaction() == null)
+      {
+         List<Fqn> locks;
+         if (!(locks = ctx.getLocks()).isEmpty())
+         {
+            // clean up.
+            Fqn[] fqnsToUnlock = new Fqn[locks.size()];
+            fqnsToUnlock = locks.toArray(fqnsToUnlock);
+            Object owner = Thread.currentThread();
+
+            for (int i = fqnsToUnlock.length - 1; i > -1; i--)
+            {
+               // for each of these, swap refs
+               ReadCommittedNode rcn = (ReadCommittedNode) ctx.lookUpNode(fqnsToUnlock[i]);
+               rcn.copyNodeForUpdate(dataContainer, ignoreWriteSkew);
+               rcn.commitUpdate(dataContainer, nodeFactory);
+               // and then unlock
+               lockManager.unlock(fqnsToUnlock[i], owner);
+            }
+            ctx.clearLocks();
+         }
+      }
+   }
+
+   protected void transactionalCleanup(boolean commit, InvocationContext ctx)
+   {
+      if (ctx.getTransaction() != null)
+      {
+         List<Fqn> locks;
+         if (!(locks = ctx.getTransactionContext().getLocks()).isEmpty())
+         {
+            // clean up.
+            Fqn[] fqnsToUnlock = new Fqn[locks.size()];
+            fqnsToUnlock = locks.toArray(fqnsToUnlock);
+            Object owner = ctx.getGlobalTransaction();
+
+            for (int i = fqnsToUnlock.length - 1; i > -1; i--)
+            {
+               ReadCommittedNode rcn = (ReadCommittedNode) ctx.lookUpNode(fqnsToUnlock[i]);
+               if (commit)
+               {
+                  // for each of these, swap refs
+                  rcn.copyNodeForUpdate(dataContainer, ignoreWriteSkew);
+                  rcn.commitUpdate(dataContainer, nodeFactory);
+               }
+               else
+               {
+                  rcn.rollbackUpdate();
+               }
+               // and then unlock
+               lockManager.unlock(fqnsToUnlock[i], owner);
+            }
+            ctx.clearLocks();
+         }
+      }
+   }
+
+   // ----------------- actual implementation details ----------------------------
+
+   protected Object handleReadCommand(InvocationContext ctx, VisitableCommand command, List<Fqn> fqns) throws Throwable
+   {
+      // does the node exist in the context?
+      for (Fqn f : fqns)
+      {
+         if (ctx.lookUpNode(f) == null)
+         {
+            // simple implementation.  Peek the node, wrap it, put wrapped node in the context.
+            InternalNode node = dataContainer.peekInternalNode(f);
+            if (node != null)
+            {
+               NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(node) : new ReadCommittedNode(node);
+               ctx.putLookedUpNode(f, wrapped);
+            }
+         }
+      }
+      return invokeNextInterceptor(ctx, command);
+   }
+
+   protected Object handleWriteCommand(InvocationContext ctx, VisitableCommand command, List<Fqn> fqnsToRead, List<Fqn> fqnsToWrite) throws Throwable
+   {
+      for (Fqn f : fqnsToRead)
+      {
+         if (ctx.lookUpNode(f) == null)
+         {
+            // simple implementation.  Peek the node, wrap it, put wrapped node in the context.
+            InternalNode node = dataContainer.peekInternalNode(f);
+            if (node != null)
+            {
+               NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(node) : new ReadCommittedNode(node);
+               ctx.putLookedUpNode(f, wrapped);
+            }
+         }
+      }
+
+      for (Fqn f : fqnsToWrite)
+      {
+         if (ctx.lookUpNode(f) == null)
+         {
+            // simple implementation.  Peek the node, wrap it, put wrapped node in the context.
+            InternalNode node = dataContainer.peekInternalNode(f);
+            if (node != null)
+            {
+               lockManager.lock(f, WRITE, ctx);
+               NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(node) : new ReadCommittedNode(node);
+               ctx.putLookedUpNode(f, wrapped);
+            }
+         }
+      }
+
+      return invokeNextInterceptor(ctx, command);
+   }
+
+   /**
+    * 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 node, 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 as well, if necessary.  If the parent is locked,
+    * it too will be added to the context if it wasn't there already.
+    *
+    * @param fqn            to retrieve
+    * @param lock           if true, a lock will be acquired.
+    * @param createIfAbsent if true, will be created if absent.
+    * @return a NodeSPI or null.
+    */
+   protected NodeSPI getWrappedNode(InvocationContext context, Fqn fqn, boolean lock, boolean createIfAbsent) throws InterruptedException
+   {
+      NodeSPI n = context.lookUpNode(fqn);
+      if (n != null)
+      {
+         // acquire lock if needed
+         if (lock && !isLocked(context, fqn)) lockManager.lockAndRecord(fqn, WRITE, context);
+         if (trace) log.trace("Retrieving wrapped node " + fqn);
+         return n;
+      }
+
+      // else, fetch from dataContainer.
+      InternalNode in = dataContainer.peekInternalNode(fqn);
+      if (in != null)
+      {
+         // do we need a lock?
+         if (lock && !isLocked(context, fqn)) lockManager.lockAndRecord(fqn, WRITE, context);
+         NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(in) : new ReadCommittedNode(in);
+         context.putLookedUpNode(fqn, wrapped);
+         return wrapped;
+      }
+
+      // else, do we need to create one?
+      if (createIfAbsent)
+      {
+         Fqn parentFqn = fqn.getParent();
+         NodeSPI parent = getWrappedNode(context, parentFqn, false, createIfAbsent);
+         // do we need to lock the parent to create children?
+         if (lockParentForChildInsertRemove || parent.isLockForChildInsertRemove())
+         {
+            // get a lock on the parent.
+            if (!isLocked(context, parentFqn)) lockManager.lockAndRecord(parentFqn, WRITE, context);
+         }
+
+         // now to lock and create the node.
+         if (!isLocked(context, fqn)) lockManager.lockAndRecord(fqn, WRITE, context);
+         NodeSPI temp = parent.getOrCreateChild(fqn.getLastElement(), context.getGlobalTransaction());
+         in = (InternalNode) ((NodeInvocationDelegate) temp).getDelegationTarget();
+         NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(in) : new ReadCommittedNode(in);
+         context.putLookedUpNode(fqn, wrapped);
+         return wrapped;
+      }
+
+      return null;
+   }
+
+   protected boolean isLocked(InvocationContext ctx, Fqn fqn)
+   {
+      // 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 that is a problem.
+      // Better to check our records and lock again if necessary.
+      return ctx.getLocks().contains(fqn);
+   }
+}

Modified: core/trunk/src/main/java/org/jboss/cache/invocation/NodeInvocationDelegate.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/invocation/NodeInvocationDelegate.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/invocation/NodeInvocationDelegate.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -283,16 +283,7 @@
 
       if (child == null)
       {
-         Option o2;
-         try
-         {
-            o2 = o1.clone();
-         }
-         catch (CloneNotSupportedException e)
-         {
-            // should never happen
-            throw new RuntimeException(e);
-         }
+         Option o2 = o1.copy();
          spi.getInvocationContext().setOptionOverrides(o1);
          spi.put(nf, null);
 

Added: core/trunk/src/main/java/org/jboss/cache/mvcc/NodeReference.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/mvcc/NodeReference.java	                        (rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/mvcc/NodeReference.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -0,0 +1,284 @@
+package org.jboss.cache.mvcc;
+
+import org.jboss.cache.CacheSPI;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.Node;
+import org.jboss.cache.NodeSPI;
+import org.jboss.cache.lock.NodeLock;
+import org.jboss.cache.optimistic.DataVersion;
+import org.jboss.cache.transaction.GlobalTransaction;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * // TODO Document this
+ *
+ * @author Manik Surtani (<a href="mailto:manik at jboss.org">manik at jboss.org</a>)
+ * @since 3.0
+ */
+public class NodeReference implements InternalNode
+{
+   transient volatile InternalNode delegate;
+
+   public NodeReference(InternalNode delegate)
+   {
+      this.delegate = delegate;
+   }
+
+   public InternalNode getDelegate()
+   {
+      return delegate;
+   }
+
+   public void setDelegate(InternalNode delegate)
+   {
+      this.delegate = delegate;
+   }
+
+   public NodeSPI getParent()
+   {
+      return delegate.getParent();
+   }
+
+   public CacheSPI getCache()
+   {
+      return delegate.getCache();
+   }
+
+   public boolean isChildrenLoaded()
+   {
+      return delegate.isChildrenLoaded();
+   }
+
+   public void setChildrenLoaded(boolean flag)
+   {
+      delegate.setChildrenLoaded(flag);
+   }
+
+   public Object getDirect(Object key)
+   {
+      return delegate.getDirect(key);
+   }
+
+   public Map getDataDirect()
+   {
+      return delegate.getDataDirect();
+   }
+
+   public Object putDirect(Object key, Object value)
+   {
+      return delegate.putDirect(key, value);
+   }
+
+   public NodeSPI getOrCreateChild(Object child_name, GlobalTransaction gtx, boolean notify)
+   {
+      return delegate.getOrCreateChild(child_name, gtx, notify);
+   }
+
+   public Object removeDirect(Object key)
+   {
+      return delegate.removeDirect(key);
+   }
+
+   public void addChildDirect(NodeSPI child)
+   {
+      delegate.addChildDirect(child);
+   }
+
+   public NodeSPI addChildDirect(Fqn f)
+   {
+      return delegate.addChildDirect(f);
+   }
+
+   public NodeSPI addChildDirect(Fqn f, boolean notify)
+   {
+      return delegate.addChildDirect(f, notify);
+   }
+
+   public NodeSPI addChildDirect(Object o, boolean notify)
+   {
+      return delegate.addChildDirect(o, notify);
+   }
+
+   public void clearDataDirect()
+   {
+      delegate.clearDataDirect();
+   }
+
+   public NodeSPI getChildDirect(Fqn fqn)
+   {
+      return delegate.getChildDirect(fqn);
+   }
+
+   public Set getChildrenNamesDirect()
+   {
+      return delegate.getChildrenNamesDirect();
+   }
+
+   public Set getKeysDirect()
+   {
+      return delegate.getKeysDirect();
+   }
+
+   public boolean removeChildDirect(Object childName)
+   {
+      return delegate.removeChildDirect(childName);
+   }
+
+   public boolean removeChildDirect(Fqn f)
+   {
+      return delegate.removeChildDirect(f);
+   }
+
+   public Map getChildrenMapDirect()
+   {
+      return delegate.getChildrenMapDirect();
+   }
+
+   public void setChildrenMapDirect(Map children)
+   {
+      delegate.setChildrenMapDirect(children);
+   }
+
+   public void putAllDirect(Map data)
+   {
+      delegate.putAllDirect(data);
+   }
+
+   public void removeChildrenDirect()
+   {
+      delegate.removeChildrenDirect();
+   }
+
+   public void setVersion(DataVersion version)
+   {
+      delegate.setVersion(version);
+   }
+
+   public DataVersion getVersion()
+   {
+      return delegate.getVersion();
+   }
+
+   public Fqn getFqn()
+   {
+      return delegate.getFqn();
+   }
+
+   public void setFqn(Fqn fqn)
+   {
+      delegate.setFqn(fqn);
+   }
+
+   public NodeSPI getChildDirect(Object childName)
+   {
+      return delegate.getChildDirect(childName);
+   }
+
+   public Set getChildrenDirect()
+   {
+      return delegate.getChildrenDirect();
+   }
+
+   public boolean hasChildrenDirect()
+   {
+      return delegate.hasChildrenDirect();
+   }
+
+   public Set getChildrenDirect(boolean includeMarkedForRemoval)
+   {
+      return delegate.getChildrenDirect(includeMarkedForRemoval);
+   }
+
+   public boolean isDataLoaded()
+   {
+      return delegate.isDataLoaded();
+   }
+
+   public void setDataLoaded(boolean dataLoaded)
+   {
+      delegate.setDataLoaded(dataLoaded);
+   }
+
+   public boolean isValid()
+   {
+      return delegate.isValid();
+   }
+
+   public void setValid(boolean valid, boolean recursive)
+   {
+      delegate.setValid(valid, recursive);
+   }
+
+   public boolean isLockForChildInsertRemove()
+   {
+      return delegate.isLockForChildInsertRemove();
+   }
+
+   public void setLockForChildInsertRemove(boolean lockForChildInsertRemove)
+   {
+      delegate.setLockForChildInsertRemove(lockForChildInsertRemove);
+   }
+
+   public void setInternalState(Map state)
+   {
+      delegate.setInternalState(state);
+   }
+
+   public Map getInternalState(boolean onlyInternalState)
+   {
+      return delegate.getInternalState(onlyInternalState);
+   }
+
+   public void releaseObjectReferences(boolean recursive)
+   {
+      delegate.releaseObjectReferences(recursive);
+   }
+
+   public boolean isDeleted()
+   {
+      return delegate.isDeleted();
+   }
+
+   public void markAsDeleted(boolean marker)
+   {
+      delegate.markAsDeleted(marker);
+   }
+
+   public void markAsDeleted(boolean marker, boolean recursive)
+   {
+      delegate.markAsDeleted(marker, recursive);
+   }
+
+   public void setResident(boolean resident)
+   {
+      delegate.setResident(resident);
+   }
+
+   public boolean isResident()
+   {
+      return delegate.isResident();
+   }
+
+   public InternalNode copy()
+   {
+      InternalNode cloneDelegate = delegate.copy();
+      return new NodeReference(cloneDelegate);
+   }
+
+   public NodeLock getLock()
+   {
+      return delegate.getLock();
+   }
+
+   public void addChild(Object nodeName, Node nodeToAdd)
+   {
+      delegate.addChild(nodeName, nodeToAdd);
+   }
+
+   public void printDetails(StringBuffer sb, int indent)
+   {
+      delegate.printDetails(sb, indent);
+   }
+}

Added: core/trunk/src/main/java/org/jboss/cache/mvcc/ReadCommittedNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/mvcc/ReadCommittedNode.java	                        (rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/mvcc/ReadCommittedNode.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -0,0 +1,63 @@
+package org.jboss.cache.mvcc;
+
+import org.jboss.cache.DataContainer;
+import org.jboss.cache.NodeFactory;
+import org.jboss.cache.invocation.NodeInvocationDelegate;
+import org.jboss.cache.optimistic.DataVersion;
+import org.jboss.cache.optimistic.DefaultDataVersion;
+
+/**
+ * Repeatable read would do a simple delegation to the underlying node.
+ *
+ * @author Manik Surtani (<a href="mailto:manik at jboss.org">manik at jboss.org</a>)
+ * @since 3.0
+ */
+public class ReadCommittedNode extends NodeInvocationDelegate
+{
+   protected volatile InternalNode backup;
+   protected boolean changed;
+
+   public ReadCommittedNode(InternalNode node)
+   {
+      super(node);
+   }
+
+   public void copyNodeForUpdate(DataContainer container, boolean ignoreWriteSkew)
+   {
+      changed = true;
+      backup = node;
+      node = backup.copy();
+      // TODO: Make sure this works with custom versions as well!
+      DataVersion newVersion = ((DefaultDataVersion) node.getVersion()).increment();
+      node.setVersion(newVersion);
+   }
+
+   public void commitUpdate(DataContainer container, NodeFactory nf)
+   {
+      if (changed)
+      {
+         updateNode(container, nf);
+         changed = false;
+         backup = null;
+      }
+   }
+
+   protected void updateNode(DataContainer container, NodeFactory nf)
+   {
+      // TODO: Deal with removes and moves
+      // TODO: Deal with creating - what if children is null?
+
+      ((NodeReference) backup).setDelegate(((NodeReference) node).getDelegate());
+      node = backup;
+   }
+
+   public void rollbackUpdate()
+   {
+      if (changed)
+      {
+         node = backup;
+         backup = null;
+         changed = false;
+      }
+   }
+}

Added: core/trunk/src/main/java/org/jboss/cache/mvcc/RepeatableReadNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/mvcc/RepeatableReadNode.java	                        (rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/mvcc/RepeatableReadNode.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -0,0 +1,57 @@
+package org.jboss.cache.mvcc;
+
+import org.jboss.cache.CacheException;
+import org.jboss.cache.DataContainer;
+import org.jboss.cache.NodeFactory;
+import org.jboss.cache.NodeSPI;
+import org.jboss.cache.optimistic.DataVersion;
+import org.jboss.cache.optimistic.DefaultDataVersion;
+
+/**
+ * // TODO Document this
+ *
+ * @author Manik Surtani (<a href="mailto:manik at jboss.org">manik at jboss.org</a>)
+ * @since 3.0
+ */
+public class RepeatableReadNode extends ReadCommittedNode
+{
+   public RepeatableReadNode(InternalNode node)
+   {
+      super(node);
+   }
+
+   @Override
+   public void copyNodeForUpdate(DataContainer container, boolean ignoreWriteSkew)
+   {
+      changed = true;
+      backup = node;
+      // write skew check
+      DataVersion underlyingNodeVersion = container.peek(getFqn()).getVersion();
+      if (ignoreWriteSkew || backup.getVersion().equals(underlyingNodeVersion))
+      {
+         node = backup.copy();
+         // TODO: Make sure this works with custom versions as well!
+         DataVersion newVersion = ((DefaultDataVersion) node.getVersion()).increment();
+         node.setVersion(newVersion);
+      }
+      else
+      {
+         throw new CacheException("Detected write skew.  Attempting to overwrite version " + backup.getVersion() + " but current version has progressed to " + underlyingNodeVersion);
+      }
+   }
+
+   @Override
+   protected void updateNode(DataContainer dataContainer, NodeFactory nf)
+   {
+      // TODO: Deal with removes and moves
+      if (getFqn().isRoot())
+      {
+         dataContainer.setRoot(nf.createNodeInvocationDelegate(node));
+      }
+      else
+      {
+         NodeSPI parent = dataContainer.peek(getFqn().getParent());
+         parent.addChildDirect(nf.createNodeInvocationDelegate(node));
+      }
+   }
+}

Modified: core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -1,5 +1,6 @@
 package org.jboss.cache.api;
 
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
 import org.testng.annotations.Test;
 
 
@@ -9,8 +10,10 @@
 @Test(groups = "functional")
 public class CacheAPIOptimisticTest extends CacheAPITest
 {
-   public CacheAPIOptimisticTest()
+   @Override
+   protected NodeLockingScheme getNodeLockingScheme()
    {
-      optimistic = true;
+      return NodeLockingScheme.OPTIMISTIC;
    }
+
 }

Modified: core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -8,6 +8,7 @@
 import org.jboss.cache.Node;
 import org.jboss.cache.Region;
 import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
 import org.jboss.cache.config.ConfigurationException;
 import org.jboss.cache.notifications.annotation.CacheListener;
 import org.jboss.cache.notifications.annotation.NodeCreated;
@@ -35,16 +36,15 @@
 public class CacheAPITest
 {
    private Cache<String, String> cache;
-   protected boolean optimistic;
    final List<String> events = new ArrayList<String>();
 
    @BeforeMethod(alwaysRun = true)
    public void setUp() throws Exception
    {
       // start a single cache instance
-      CacheFactory<String, String> cf = new DefaultCacheFactory();
+      CacheFactory<String, String> cf = new DefaultCacheFactory<String, String>();
       cache = cf.createCache("META-INF/conf-test/local-tx-service.xml", false);
-      cache.getConfiguration().setNodeLockingScheme(optimistic ? Configuration.NodeLockingScheme.OPTIMISTIC : Configuration.NodeLockingScheme.PESSIMISTIC);
+      cache.getConfiguration().setNodeLockingScheme(getNodeLockingScheme());
       cache.start();
       events.clear();
    }
@@ -55,6 +55,10 @@
       if (cache != null) cache.stop();
    }
 
+   protected NodeLockingScheme getNodeLockingScheme()
+   {
+      return NodeLockingScheme.PESSIMISTIC;
+   }
 
    /**
     * Tests that the configuration contains the values expected, as well as immutability of certain elements

Modified: core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -1,5 +1,9 @@
 package org.jboss.cache.api;
 
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.jboss.cache.interceptors.OptimisticNodeInterceptor;
+import org.jboss.cache.interceptors.PessimisticLockInterceptor;
+import org.jboss.cache.interceptors.base.CommandInterceptor;
 import org.testng.annotations.Test;
 
 /**
@@ -8,8 +12,22 @@
 @Test(groups = "functional")
 public class NodeAPIOptimisticTest extends NodeAPITest
 {
-   public NodeAPIOptimisticTest()
+   protected NodeLockingScheme getNodeLockingScheme()
    {
-      optimistic = true;
+      return NodeLockingScheme.OPTIMISTIC;
    }
+
+   protected void assertNodeLockingScheme()
+   {
+      assert cache.getConfiguration().getNodeLockingScheme() == NodeLockingScheme.OPTIMISTIC;
+      boolean interceptorChainOK = false;
+
+      for (CommandInterceptor i : cache.getInterceptorChain())
+      {
+         if (i instanceof PessimisticLockInterceptor) assert false : "Not an optimistic locking chain!!";
+         if (i instanceof OptimisticNodeInterceptor) interceptorChainOK = true;
+      }
+
+      assert interceptorChainOK : "Not an optimistic locking chain!!";
+   }
 }

Modified: core/trunk/src/test/java/org/jboss/cache/api/NodeAPITest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/NodeAPITest.java	2008-06-25 18:29:49 UTC (rev 6045)
+++ core/trunk/src/test/java/org/jboss/cache/api/NodeAPITest.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -4,7 +4,9 @@
 import org.jboss.cache.DefaultCacheFactory;
 import org.jboss.cache.Fqn;
 import org.jboss.cache.Node;
-import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import static org.jboss.cache.config.Configuration.NodeLockingScheme.OPTIMISTIC;
+import org.jboss.cache.interceptors.MVCCLockingInterceptor;
 import org.jboss.cache.interceptors.OptimisticNodeInterceptor;
 import org.jboss.cache.interceptors.PessimisticLockInterceptor;
 import org.jboss.cache.interceptors.base.CommandInterceptor;
@@ -33,21 +35,19 @@
 {
    private Node<Object, Object> rootNode;
 
-   private CacheSPI<Object, Object> cache;
+   protected CacheSPI<Object, Object> cache;
 
    private TransactionManager tm;
 
    private static final Fqn<String> A = Fqn.fromString("/a"), B = Fqn.fromString("/b"), C = Fqn.fromString("/c"), D = Fqn
          .fromString("/d");
 
-   protected boolean optimistic = false;
-
    @BeforeMethod(alwaysRun = true)
    public void setUp() throws Exception
    {
       // start a single cache instance
       cache = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache("META-INF/conf-test/local-tx-service.xml", false);
-      cache.getConfiguration().setNodeLockingScheme(optimistic ? Configuration.NodeLockingScheme.OPTIMISTIC : Configuration.NodeLockingScheme.PESSIMISTIC);
+      cache.getConfiguration().setNodeLockingScheme(getNodeLockingScheme());
       cache.start();
       rootNode = cache.getRoot();
       tm = cache.getTransactionManager();
@@ -77,23 +77,29 @@
       }
    }
 
-   private void assertOptimistic()
+   protected NodeLockingScheme getNodeLockingScheme()
    {
-      assert cache.getConfiguration().isNodeLockingOptimistic();
+      return NodeLockingScheme.PESSIMISTIC;
+   }
+
+   protected void assertNodeLockingScheme()
+   {
+      assert cache.getConfiguration().getNodeLockingScheme() == NodeLockingScheme.PESSIMISTIC;
       boolean interceptorChainOK = false;
 
       for (CommandInterceptor i : cache.getInterceptorChain())
       {
-         if (i instanceof PessimisticLockInterceptor) assert false : "Not an optimistic locking chain!!";
-         if (i instanceof OptimisticNodeInterceptor) interceptorChainOK = true;
+         if (i instanceof PessimisticLockInterceptor) interceptorChainOK = true;
+         if (i instanceof OptimisticNodeInterceptor) assert false : "Not a pessimistic locking chain!!";
+         if (i instanceof MVCCLockingInterceptor) assert false : "Not a pessimistic locking chain!!";
       }
 
-      assert interceptorChainOK : "Not an optimistic locking chain!!";
+      assert interceptorChainOK : "Not a pessimistic locking chain!!";
    }
 
    public void testAddingData()
    {
-      if (optimistic) assertOptimistic();
+      assertNodeLockingScheme();
 
       Node<Object, Object> nodeA = rootNode.addChild(A);
       nodeA.put("key", "value");
@@ -177,7 +183,7 @@
       Node<Object, Object> nodeB = nodeA.addChild(B);
       Node<Object, Object> nodeC = nodeB.addChild(C);
 
-      if (!optimistic)
+      if (getNodeLockingScheme() != OPTIMISTIC)
       {
          assertEquals(3, cache.getNumberOfNodes());
          assertEquals(4, cache.getNumberOfLocksHeld());
@@ -187,7 +193,7 @@
       tm.begin();
       assertEquals(0, cache.getNumberOfLocksHeld());
       nodeC.put("key", "value");
-      if (!optimistic) assertEquals(4, cache.getNumberOfLocksHeld());
+      if (getNodeLockingScheme() != OPTIMISTIC) assertEquals(4, cache.getNumberOfLocksHeld());
       tm.commit();
    }
 
@@ -289,7 +295,7 @@
       cache.put(A_B, "1", "1");
       cache.put(A_C, "2", "2");
 
-      if (!optimistic)
+      if (getNodeLockingScheme() != OPTIMISTIC)
       {
          assertEquals(3, cache.getNumberOfNodes());
          assertEquals(4, cache.getNumberOfLocksHeld());
@@ -329,7 +335,8 @@
    public void testGetChildAPI()
    {
       // creates a Node<Object, Object> with fqn /a/b/c
-      rootNode.addChild(A).addChild(B).addChild(C);
+      Node childA = rootNode.addChild(A);
+      childA.addChild(B).addChild(C);
 
       rootNode.getChild(A).put("key", "value");
       rootNode.getChild(A).getChild(B).put("key", "value");

Copied: core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java (from rev 6034, core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java)
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java	                        (rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -0,0 +1,20 @@
+package org.jboss.cache.api.mvcc;
+
+import org.jboss.cache.api.CacheAPITest;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.testng.annotations.Test;
+
+
+/**
+ * MVCC version of {@link org.jboss.cache.api.CacheAPITest}
+ */
+ at Test(groups = "functional")
+public class CacheAPIMVCCTest extends CacheAPITest
+{
+   @Override
+   protected NodeLockingScheme getNodeLockingScheme()
+   {
+      return NodeLockingScheme.MVCC;
+   }
+
+}
\ No newline at end of file


Property changes on: core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision
Name: svn:eol-style
   + native

Added: core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockParentForChildInsertRemoveTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockParentForChildInsertRemoveTest.java	                        (rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockParentForChildInsertRemoveTest.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -0,0 +1,12 @@
+package org.jboss.cache.api.mvcc;
+
+import org.testng.annotations.Test;
+
+ at Test(groups = "functional")
+public class LockParentForChildInsertRemoveTest extends LockTest
+{
+   public LockParentForChildInsertRemoveTest()
+   {
+      lockParentForInsertRemove = true;
+   }
+}

Added: core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockTest.java	                        (rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockTest.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -0,0 +1,153 @@
+package org.jboss.cache.api.mvcc;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.DefaultCacheFactory;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.config.Configuration.CacheMode;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
+import org.jboss.cache.invocation.InvocationContextContainer;
+import org.jboss.cache.lock.LockManager;
+import org.jboss.cache.lock.MVCCLockManager.LockContainer;
+import org.jboss.cache.transaction.DummyTransactionManagerLookup;
+import org.jboss.cache.util.TestingUtil;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import javax.transaction.TransactionManager;
+import java.util.Collections;
+
+/**
+ * @author Manik Surtani (<a href="mailto:manik at jboss.org">manik at jboss.org</a>)
+ * @since 3.0
+ */
+ at Test(groups = "functional")
+public class LockTest
+{
+   Cache<String, String> cache;
+   TransactionManager tm;
+   Fqn A = Fqn.fromString("/a");
+   Fqn AB = Fqn.fromString("/a/b");
+   Fqn ABC = Fqn.fromString("/a/b/c");
+   LockManager lockManager;
+   InvocationContextContainer icc;
+   boolean lockParentForInsertRemove = false;
+
+   @BeforeMethod
+   public void setUp()
+   {
+      cache = new DefaultCacheFactory<String, String>().createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.LOCAL), false);
+      cache.getConfiguration().setNodeLockingScheme(NodeLockingScheme.MVCC);
+      cache.getConfiguration().setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
+      cache.getConfiguration().setLockParentForChildInsertRemove(lockParentForInsertRemove);
+      cache.start();
+      lockManager = TestingUtil.extractComponentRegistry(cache).getComponent(LockManager.class);
+      icc = TestingUtil.extractComponentRegistry(cache).getComponent(InvocationContextContainer.class);
+      tm = TestingUtil.extractComponentRegistry(cache).getComponent(TransactionManager.class);
+   }
+
+   @AfterMethod
+   public void tearDown()
+   {
+      TestingUtil.killCaches(cache);
+   }
+
+   private void assertLocked(Fqn fqn)
+   {
+      assert lockManager.isLocked(fqn) : fqn + " not locked!";
+      assert icc.get().getLocks().contains(fqn) : "Lock not recorded for " + fqn;
+   }
+
+   private void assertNotLocked(Fqn fqn)
+   {
+      // can't rely on the negative test since other nodes may share the same lock with lock striping.
+//      assert !lockManager.isLocked(fqn) : fqn + " is locked!";
+      assert !icc.get().getLocks().contains(fqn) : fqn + " lock recorded!";
+   }
+
+   private void assertNoLocks()
+   {
+      LockContainer lc = (LockContainer) TestingUtil.extractField(lockManager, "lockContainer");
+      assert lc.getNumLocksHeld() == 0 : "Stale locks exist!" + lockManager.printLockInfo();
+      assert icc.get().getLocks().isEmpty() : "Stale (?) locks recorded! " + icc.get().getLocks();
+   }
+
+   public void testLocksOnPutKeyVal() throws Exception
+   {
+      tm.begin();
+      cache.put(AB, "k", "v");
+      if (lockParentForInsertRemove)
+         assertLocked(Fqn.ROOT);
+      else
+         assertNotLocked(Fqn.ROOT);
+      assertLocked(A);
+      assertLocked(AB);
+      assertNotLocked(ABC);
+      tm.commit();
+
+      assertNoLocks();
+
+      tm.begin();
+      assert cache.get(AB, "k").equals("v");
+      assertNotLocked(Fqn.ROOT);
+      assertNotLocked(A);
+      assertNotLocked(AB);
+      assertNotLocked(ABC);
+      tm.commit();
+
+      assertNoLocks();
+
+      tm.begin();
+      cache.put(ABC, "k", "v");
+      assertNotLocked(Fqn.ROOT);
+      assertNotLocked(A);
+      if (lockParentForInsertRemove)
+         assertLocked(AB);
+      else
+         assertNotLocked(AB);
+      assertLocked(ABC);
+      tm.commit();
+
+      assertNoLocks();
+   }
+
+   public void testLocksOnPutData() throws Exception
+   {
+      tm.begin();
+      cache.put(AB, Collections.singletonMap("k", "v"));
+      if (lockParentForInsertRemove)
+         assertLocked(Fqn.ROOT);
+      else
+         assertNotLocked(Fqn.ROOT);
+      assertLocked(A);
+      assertLocked(AB);
+      assertNotLocked(ABC);
+      tm.commit();
+
+      assertNoLocks();
+
+      tm.begin();
+      assert cache.get(AB, "k").equals("v");
+      assertNotLocked(Fqn.ROOT);
+      assertNotLocked(A);
+      assertNotLocked(AB);
+      assertNotLocked(ABC);
+      tm.commit();
+
+      assertNoLocks();
+
+      tm.begin();
+      cache.put(ABC, Collections.singletonMap("k", "v"));
+      assertNotLocked(Fqn.ROOT);
+      assertNotLocked(A);
+      if (lockParentForInsertRemove)
+         assertLocked(AB);
+      else
+         assertNotLocked(AB);
+      assertLocked(ABC);
+      tm.commit();
+
+      assertNoLocks();
+   }
+}

Copied: core/trunk/src/test/java/org/jboss/cache/api/mvcc/NodeAPIMVCCTest.java (from rev 6034, core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java)
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/mvcc/NodeAPIMVCCTest.java	                        (rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/api/mvcc/NodeAPIMVCCTest.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -0,0 +1,37 @@
+package org.jboss.cache.api.mvcc;
+
+import org.jboss.cache.api.NodeAPITest;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.jboss.cache.interceptors.MVCCLockingInterceptor;
+import org.jboss.cache.interceptors.OptimisticNodeInterceptor;
+import org.jboss.cache.interceptors.PessimisticLockInterceptor;
+import org.jboss.cache.interceptors.base.CommandInterceptor;
+import org.testng.annotations.Test;
+
+/**
+ * An MVCC version of {@link org.jboss.cache.api.NodeAPITest}
+ */
+ at Test(groups = "functional")
+public class NodeAPIMVCCTest extends NodeAPITest
+{
+   protected NodeLockingScheme getNodeLockingScheme()
+   {
+      return NodeLockingScheme.MVCC;
+   }
+
+   protected void assertNodeLockingScheme()
+   {
+      assert cache.getConfiguration().getNodeLockingScheme() == NodeLockingScheme.MVCC;
+      boolean interceptorChainOK = false;
+
+      for (CommandInterceptor i : cache.getInterceptorChain())
+      {
+         if (i instanceof PessimisticLockInterceptor) assert false : "Not an MVCC locking chain!!";
+         if (i instanceof OptimisticNodeInterceptor) assert false : "Not an MVCC locking chain!!";
+         if (i instanceof MVCCLockingInterceptor) interceptorChainOK = true;
+
+      }
+
+      assert interceptorChainOK : "Not an MVCC locking chain!!";
+   }
+}
\ No newline at end of file

Added: core/trunk/src/test/java/org/jboss/cache/mvcc/MVCCFullStackTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/mvcc/MVCCFullStackTest.java	                        (rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/mvcc/MVCCFullStackTest.java	2008-06-25 18:31:09 UTC (rev 6046)
@@ -0,0 +1,33 @@
+package org.jboss.cache.mvcc;
+
+import org.jboss.cache.CacheSPI;
+import org.jboss.cache.DefaultCacheFactory;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.jboss.cache.interceptors.MVCCLockingInterceptor;
+import org.jboss.cache.util.TestingUtil;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+/**
+ * @author Manik Surtani (<a href="mailto:manik at jboss.org">manik at jboss.org</a>)
+ * @since 3.0
+ */
+ at Test(groups = "functional")
+public class MVCCFullStackTest
+{
+   CacheSPI<Object, Object> cache;
+
+   @AfterMethod
+   public void tearDown()
+   {
+      TestingUtil.killCaches(cache);
+   }
+
+   public void testDefaultConfiguration()
+   {
+      cache = (CacheSPI<Object, Object>) new DefaultCacheFactory<Object, Object>().createCache();
+
+      assert TestingUtil.findInterceptor(cache, MVCCLockingInterceptor.class) != null : "MVCC interceptor should be in stack";
+      assert cache.getConfiguration().getNodeLockingScheme() == NodeLockingScheme.MVCC;
+   }
+}




More information about the jbosscache-commits mailing list