You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by md...@apache.org on 2011/12/27 19:17:40 UTC

svn commit: r1224964 [1/2] - in /jackrabbit/sandbox/jackrabbit-microkernel/src: main/java/org/apache/jackrabbit/ main/java/org/apache/jackrabbit/state/ main/java/org/apache/jackrabbit/utils/ test/java/org/apache/jackrabbit/ test/java/org/apache/jackrab...

Author: mduerig
Date: Tue Dec 27 18:17:39 2011
New Revision: 1224964

URL: http://svn.apache.org/viewvc?rev=1224964&view=rev
Log:
Microkernel based prototype of JCR implementation (WIP)
- re-implement transient space

Added:
    jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java
    jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function1.java
    jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/
    jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceTest.java
Modified:
    jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/ItemImpl.java
    jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/NodeImpl.java
    jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/PropertyImpl.java
    jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionContext.java
    jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionImpl.java
    jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java
    jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/RepositoryTest.java

Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/ItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/ItemImpl.java?rev=1224964&r1=1224963&r2=1224964&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/ItemImpl.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/ItemImpl.java Tue Dec 27 18:17:39 2011
@@ -20,7 +20,7 @@
 package org.apache.jackrabbit;
 
 import org.apache.jackrabbit.SessionImpl.Context;
-import org.apache.jackrabbit.state.NodeDelta;
+import org.apache.jackrabbit.state.ChangeTree;
 
 import javax.jcr.Item;
 import javax.jcr.ItemNotFoundException;
@@ -85,12 +85,24 @@ abstract class ItemImpl implements Item 
 
     @Override
     public boolean isNew() {
-        return getNodeDelta().isNew();
+        try {
+            return getNodeDelta().isTransient();
+        }
+        catch (ItemNotFoundException e) {
+            // should never happen
+            throw new IllegalStateException(e);
+        }
     }
 
     @Override
     public boolean isModified() {
-        return getNodeDelta().isModified();
+        try {
+            return getNodeDelta().hasChanges();
+        }
+        catch (ItemNotFoundException e) {
+            // should never happen
+            throw new IllegalStateException(e);
+        }
     }
 
     @Override
@@ -136,9 +148,9 @@ abstract class ItemImpl implements Item 
     protected final boolean isStale() {
         return !sessionContext.getRevision().equals(revision);
     }
-    
-    protected final NodeDelta getNodeDelta() {
-        return sessionContext.getTransientSpace().getNodeDelta(path);
+
+    protected final ChangeTree.NodeDelta getNodeDelta() throws ItemNotFoundException {
+        return sessionContext.getTransientSpace().getNode(path);
     }
 
 }

Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/NodeImpl.java?rev=1224964&r1=1224963&r2=1224964&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/NodeImpl.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/NodeImpl.java Tue Dec 27 18:17:39 2011
@@ -25,21 +25,22 @@ import org.apache.jackrabbit.json.JsonVa
 import org.apache.jackrabbit.json.JsonValue.JsonObject;
 import org.apache.jackrabbit.json.JsonValue.Type;
 import org.apache.jackrabbit.json.UnescapingJsonTokenizer;
-import org.apache.jackrabbit.state.NodeDelta;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.spi.commons.iterator.Iterators;
+import org.apache.jackrabbit.spi.commons.iterator.Transformer;
+import org.apache.jackrabbit.state.ChangeTree;
 import org.apache.jackrabbit.state.TransientSpace;
 import org.apache.jackrabbit.utils.Arrays;
 import org.apache.jackrabbit.utils.ChildItemCollector;
 import org.apache.jackrabbit.utils.NodeIteratorAdapter;
 import org.apache.jackrabbit.utils.PropertyIteratorAdapter;
 import org.apache.jackrabbit.utils.ValueConverter;
-import org.apache.jackrabbit.mk.api.MicroKernel;
-import org.apache.jackrabbit.spi.commons.iterator.Iterators;
-import org.apache.jackrabbit.spi.commons.iterator.Transformer;
 
 import javax.jcr.Binary;
 import javax.jcr.InvalidItemStateException;
 import javax.jcr.Item;
 import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
 import javax.jcr.ItemVisitor;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
@@ -97,7 +98,7 @@ public class NodeImpl extends ItemImpl i
 
         TransientSpace transientSpace = sessionContext.getTransientSpace();
         Path newPath = path.concat(relPath);
-        transientSpace.addNode(newPath.getParent(), newPath.getName());
+        transientSpace.getNode(newPath.getParent()).addNode(newPath.getName());
         return getNode(relPath);
     }
 
@@ -109,7 +110,7 @@ public class NodeImpl extends ItemImpl i
 
         TransientSpace transientSpace = sessionContext.getTransientSpace();
         Path newPath = path.concat(relPath);
-        transientSpace.addNode(newPath.getParent(), newPath.getName());
+        transientSpace.getNode(newPath.getParent()).addNode(newPath.getName());
         setPrimaryType(primaryNodeTypeName);
         return getNode(relPath);
     }
@@ -117,20 +118,20 @@ public class NodeImpl extends ItemImpl i
     @Override
     public void remove() throws RepositoryException {
         TransientSpace transientSpace = sessionContext.getTransientSpace();
-        transientSpace.removeNode(path);
+        transientSpace.getNode(path.getParent()).removeNode(path.getName());
     }
 
     @Override
     public Property setProperty(String name, Value value, int type) throws RepositoryException {
         TransientSpace transientSpace = sessionContext.getTransientSpace();
-        transientSpace.addProperty(path, name, ValueConverter.toJsonValue(value));
+        transientSpace.getNode(path).setValue(name, ValueConverter.toJsonValue(value));
         return getProperty(name);
     }
 
     @Override
     public Property setProperty(String name, Value[] values, int type) throws RepositoryException {
         TransientSpace transientSpace = sessionContext.getTransientSpace();
-        transientSpace.addProperty(path, name, ValueConverter.toJsonValue(values));
+        transientSpace.getNode(path).setValue(name, ValueConverter.toJsonValue(values));
         return getProperty(name);
     }
 
@@ -140,7 +141,7 @@ public class NodeImpl extends ItemImpl i
         ValueFactory valueFactory = sessionContext.getValueFactory();
         Value value = valueFactory.createValue(mixinName);
         Value[] values = getProperty("jcr:mixinTypes").getValues();
-        transientSpace.setMixin(path, ValueConverter.toJsonValue(Arrays.add(values, value)));
+        transientSpace.getNode(path).setValue("jcr:mixinTypes", ValueConverter.toJsonValue(Arrays.add(values, value)));
     }
 
     @Override
@@ -149,7 +150,7 @@ public class NodeImpl extends ItemImpl i
         ValueFactory valueFactory = sessionContext.getValueFactory();
         Value value = valueFactory.createValue(mixinName);
         Value[] values = getProperty("jcr:mixinTypes").getValues();
-        transientSpace.setMixin(path, ValueConverter.toJsonValue(Arrays.remove(values, value)));
+        transientSpace.getNode(path).setValue("jcr:mixinTypes", ValueConverter.toJsonValue(Arrays.remove(values, value)));
     }
 
     @Override
@@ -157,7 +158,7 @@ public class NodeImpl extends ItemImpl i
         TransientSpace transientSpace = sessionContext.getTransientSpace();
         ValueFactory valueFactory = sessionContext.getValueFactory();
         Value value = valueFactory.createValue(nodeTypeName);
-        transientSpace.setPrimaryType(path, ValueConverter.toJsonValue(value));
+        transientSpace.getNode(path).setValue("jcr:primaryType", ValueConverter.toJsonValue(value));
     }
 
     
@@ -277,15 +278,14 @@ public class NodeImpl extends ItemImpl i
 
     @Override
     public NodeIterator getNodes() throws RepositoryException {
-        final NodeDelta delta = getNodeDelta();
-
-        Iterator<Entry<String, JsonValue>> childItems =
-                Iterators.iteratorChain(getPersistedItems(this), getAddedNodes(delta));
+        Iterator<Entry<String, JsonValue>> persistedItems = getPersistedItems(this);
+        Iterator<Entry<String, JsonValue>> addedNodes = getAddedNodes(getNodeDelta());
+        Iterator<Entry<String, JsonValue>> childItems = Iterators.iteratorChain(persistedItems, addedNodes);
 
         return new NodeIteratorAdapter(new ChildItemCollector<Node>(childItems) {
             @Override
             protected boolean include(String name, JsonValue value) {
-                return value.type() == Type.OBJECT && !delta.hasRemovedNode(name);
+                return value.type() == Type.OBJECT;
             }
 
             @Override
@@ -297,17 +297,14 @@ public class NodeImpl extends ItemImpl i
 
     @Override
     public NodeIterator getNodes(final String namePattern) throws RepositoryException {
-        final NodeDelta delta = getNodeDelta();
-
-        Iterator<Entry<String, JsonValue>> childItems =
-                Iterators.iteratorChain(getPersistedItems(this), getAddedNodes(delta));
+        Iterator<Entry<String, JsonValue>> persistedItems = getPersistedItems(this);
+        Iterator<Entry<String, JsonValue>> addedNodes = getAddedNodes(getNodeDelta());
+        Iterator<Entry<String, JsonValue>> childItems = Iterators.iteratorChain(persistedItems, addedNodes);
 
         return new NodeIteratorAdapter(new ChildItemCollector<Node>(childItems) {
             @Override
             protected boolean include(String name, JsonValue value) {
-                return value.type() == Type.OBJECT
-                        && ChildItemCollector.matches(name, namePattern)
-                        && !delta.hasRemovedNode(name);
+                return value.type() == Type.OBJECT && ChildItemCollector.matches(name, namePattern);
             }
 
             @Override
@@ -319,17 +316,14 @@ public class NodeImpl extends ItemImpl i
 
     @Override
     public NodeIterator getNodes(final String[] nameGlobs) throws RepositoryException {
-        final NodeDelta delta = getNodeDelta();
-
-        Iterator<Entry<String, JsonValue>> childItems =
-                Iterators.iteratorChain(getPersistedItems(this), getAddedNodes(delta));
+        Iterator<Entry<String, JsonValue>> persistedItems = getPersistedItems(this);
+        Iterator<Entry<String, JsonValue>> addedNodes = getAddedNodes(getNodeDelta());
+        Iterator<Entry<String, JsonValue>> childItems = Iterators.iteratorChain(persistedItems, addedNodes);
 
         return new NodeIteratorAdapter(new ChildItemCollector<Node>(childItems) {
             @Override
             protected boolean include(String name, JsonValue value) {
-                return value.type() == Type.OBJECT
-                        && ChildItemCollector.matches(name, nameGlobs)
-                        && !delta.hasRemovedNode(name);
+                return value.type() == Type.OBJECT && ChildItemCollector.matches(name, nameGlobs);
             }
 
             @Override
@@ -356,17 +350,14 @@ public class NodeImpl extends ItemImpl i
 
     @Override
     public PropertyIterator getProperties() throws RepositoryException {
-        final NodeDelta delta = getNodeDelta();
-
-        Iterator<Entry<String, JsonValue>> childItems =
-                Iterators.iteratorChain(getPersistedItems(this), getProperties(delta));
+        Iterator<Entry<String, JsonValue>> persistedItems = getPersistedItems(this);
+        Iterator<Entry<String, JsonValue>> addedProperties = getProperties(getNodeDelta());
+        Iterator<Entry<String, JsonValue>> childItems = Iterators.iteratorChain(persistedItems, addedProperties);
 
         return new PropertyIteratorAdapter(new ChildItemCollector<Property>(childItems){
             @Override
             protected boolean include(String name, JsonValue value) {
-                return !name.startsWith(":")
-                        && value.type() != Type.OBJECT
-                        && !delta.hasRemovedProperty(name);
+                return !name.startsWith(":") && value.type() != Type.OBJECT;
             }
 
             @Override
@@ -378,18 +369,15 @@ public class NodeImpl extends ItemImpl i
 
     @Override
     public PropertyIterator getProperties(final String namePattern) throws RepositoryException {
-        final NodeDelta delta = getNodeDelta();
-
-        Iterator<Entry<String, JsonValue>> childItems =
-                Iterators.iteratorChain(getPersistedItems(this), getProperties(delta));
+        Iterator<Entry<String, JsonValue>> persistedItems = getPersistedItems(this);
+        Iterator<Entry<String, JsonValue>> addedProperties = getProperties(getNodeDelta());
+        Iterator<Entry<String, JsonValue>> childItems = Iterators.iteratorChain(persistedItems, addedProperties);
 
         return new PropertyIteratorAdapter(new ChildItemCollector<Property>(childItems){
             @Override
             protected boolean include(String name, JsonValue value) {
-                return !name.startsWith(":")
-                        && value.type() != Type.OBJECT
-                        && ChildItemCollector.matches(name, namePattern)
-                        && ! delta.hasRemovedProperty(name);
+                return !name.startsWith(":") && value.type() != Type.OBJECT
+                    && ChildItemCollector.matches(name, namePattern);
             }
 
             @Override
@@ -401,18 +389,15 @@ public class NodeImpl extends ItemImpl i
 
     @Override
     public PropertyIterator getProperties(final String[] nameGlobs) throws RepositoryException {
-        final NodeDelta delta = getNodeDelta();
-
-        Iterator<Entry<String, JsonValue>> childItems =
-                Iterators.iteratorChain(getPersistedItems(this), getProperties(delta));
+        Iterator<Entry<String, JsonValue>> persistedItems = getPersistedItems(this);
+        Iterator<Entry<String, JsonValue>> addedProperties = getProperties(getNodeDelta());
+        Iterator<Entry<String, JsonValue>> childItems = Iterators.iteratorChain(persistedItems, addedProperties);
 
         return new PropertyIteratorAdapter(new ChildItemCollector<Property>(childItems){
             @Override
             protected boolean include(String name, JsonValue value) {
-                return !name.startsWith(":")
-                        && value.type() != Type.OBJECT
-                        && ChildItemCollector.matches(name, nameGlobs)
-                        && ! delta.hasRemovedProperty(name);
+                return !name.startsWith(":") && value.type() != Type.OBJECT
+                    && ChildItemCollector.matches(name, nameGlobs);
             }
 
             @Override
@@ -646,11 +631,19 @@ public class NodeImpl extends ItemImpl i
     }
 
     private static JsonObject getNode(Context sessionContext, Path path) {
-        NodeDelta delta = sessionContext.getTransientSpace().getNodeDelta(path);
-        if (delta == null) {
+        if (!sessionContext.getTransientSpace().nodeExists(path)) {
             return null;
         }
 
+        ChangeTree.NodeDelta delta;
+        try {
+            delta = sessionContext.getTransientSpace().getNode(path);
+        }
+        catch (ItemNotFoundException e) {
+            // should not happen
+            throw new IllegalStateException(e);
+        }
+
         Path persistedPath = delta.getPersistentPath();
         if (persistedPath == null) {
             return JsonObject.EMPTY;
@@ -672,16 +665,16 @@ public class NodeImpl extends ItemImpl i
         return node.getNode().value().entrySet().iterator();
     }
 
-    private static Iterator<Entry<String, JsonValue>> getAddedNodes(NodeDelta delta) {
-        return Iterators.transformIterator(delta.getAddedNodes().keySet().iterator(),
-                new Transformer<String, Entry<String, JsonValue>>() {
+    private static Iterator<Entry<String, JsonValue>> getAddedNodes(ChangeTree.NodeDelta delta) {
+        return Iterators.transformIterator(delta.getNodes(),
+                new Transformer<Entry<String, ChangeTree.NodeDelta>, Entry<String, JsonValue>>() {
 
                     @Override
-                    public Entry<String, JsonValue> transform(final String name) {
+                    public Entry<String, JsonValue> transform(final Entry<String, ChangeTree.NodeDelta> entry) {
                         return new Entry<String, JsonValue>() {
                             @Override
                             public String getKey() {
-                                return name;
+                                return entry.getKey();
                             }
 
                             @Override
@@ -691,16 +684,16 @@ public class NodeImpl extends ItemImpl i
 
                             @Override
                             public JsonValue setValue(JsonValue value) {
-                        assert false;
-                        return null;
+                                assert false;
+                                return null;
+                            }
+                        };
                     }
-                };
-            }
-        });
+                });
     }
 
-    private Iterator<? extends Entry<String, JsonValue>> getProperties(NodeDelta delta) {
-        return delta.getProperties().entrySet().iterator();
+    private static Iterator<Entry<String, JsonValue>> getProperties(ChangeTree.NodeDelta delta) {
+        return delta.getProperties();
     }
 
 }

Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/PropertyImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/PropertyImpl.java?rev=1224964&r1=1224963&r2=1224964&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/PropertyImpl.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/PropertyImpl.java Tue Dec 27 18:17:39 2011
@@ -25,13 +25,14 @@ import org.apache.jackrabbit.json.JsonVa
 import org.apache.jackrabbit.json.JsonValue.JsonObject;
 import org.apache.jackrabbit.json.JsonValue.Type;
 import org.apache.jackrabbit.json.UnescapingJsonTokenizer;
-import org.apache.jackrabbit.state.NodeDelta;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.state.ChangeTree;
 import org.apache.jackrabbit.state.TransientSpace;
 import org.apache.jackrabbit.utils.ValueConverter;
-import org.apache.jackrabbit.mk.api.MicroKernel;
 
 import javax.jcr.Binary;
 import javax.jcr.InvalidItemStateException;
+import javax.jcr.ItemNotFoundException;
 import javax.jcr.ItemVisitor;
 import javax.jcr.Node;
 import javax.jcr.PathNotFoundException;
@@ -74,25 +75,20 @@ public class PropertyImpl extends ItemIm
 
     @Override
     public void remove() throws RepositoryException {
-        TransientSpace transientSpace = sessionContext.getTransientSpace();
-        transientSpace.removeProperty(path);
+        setValue((Value) null);
     }
 
     @Override
     public void setValue(Value value) throws RepositoryException {
         TransientSpace transientSpace = sessionContext.getTransientSpace();
-        if (value == null) {
-            remove();
-        }
-        else {
-            transientSpace.setValue(path, ValueConverter.toJsonValue(value));
-        }
+        transientSpace.getNode(path.getParent()).setValue(path.getName(),
+                value == null ? null : ValueConverter.toJsonValue(value));
     }
 
     @Override
     public void setValue(Value[] values) throws RepositoryException {
         TransientSpace transientSpace = sessionContext.getTransientSpace();
-        transientSpace.setValue(path, ValueConverter.toJsonValue(values));
+        transientSpace.getNode(path.getParent()).setValue(path.getName(), ValueConverter.toJsonValue(values));
     }
 
     @Override
@@ -320,16 +316,21 @@ public class PropertyImpl extends ItemIm
 
     private static JsonValue getProperty(Context sessionContext, Path path) {
         Path parentPath = path.getParent();
-        NodeDelta delta = sessionContext.getTransientSpace().getNodeDelta(parentPath);
-        if (delta == null) {
+        if (!sessionContext.getTransientSpace().nodeExists(parentPath)) {
             return null;
         }
 
-        String name = path.getName();
-        if (delta.hasRemovedProperty(name)) {
-            return null;
+        ChangeTree.NodeDelta delta;
+        try {
+            delta = sessionContext.getTransientSpace().getNode(parentPath);
+        }
+        catch (ItemNotFoundException e) {
+            // should never happen
+            throw new IllegalStateException(e);
         }
-        if (delta.hasAddedProperty(name)) {
+
+        String name = path.getName();
+        if (delta.hasProperty(name)) {
             return delta.getProperty(name);
         }
 

Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionContext.java?rev=1224964&r1=1224963&r2=1224964&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionContext.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionContext.java Tue Dec 27 18:17:39 2011
@@ -19,9 +19,9 @@
 
 package org.apache.jackrabbit;
 
+import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.security.CredentialsInfo;
 import org.apache.jackrabbit.state.TransientSpace;
-import org.apache.jackrabbit.mk.api.MicroKernel;
 
 import javax.jcr.Session;
 import javax.jcr.ValueFactory;

Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionImpl.java?rev=1224964&r1=1224963&r2=1224964&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionImpl.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/SessionImpl.java Tue Dec 27 18:17:39 2011
@@ -19,10 +19,10 @@
 
 package org.apache.jackrabbit;
 
+import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.security.Authenticator;
 import org.apache.jackrabbit.security.CredentialsInfo;
 import org.apache.jackrabbit.state.TransientSpace;
-import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
@@ -136,7 +136,7 @@ public class SessionImpl implements Sess
         this.workspaceName = workspaceName;
         this.revision = revision;
         microKernel = globalContext.getInstance(MicroKernel.class);
-        transientSpace = new TransientSpace(microKernel,  revision, Path.create(workspaceName));
+        transientSpace = new TransientSpace(workspaceName, microKernel, revision);
     }
 
     //------------------------------------------< Session >--- 

Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java?rev=1224964&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java (added)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java Tue Dec 27 18:17:39 2011
@@ -0,0 +1,580 @@
+package org.apache.jackrabbit.state;
+
+import org.apache.jackrabbit.Path;
+import org.apache.jackrabbit.json.JsonValue;
+import org.apache.jackrabbit.spi.commons.iterator.Iterators;
+import org.apache.jackrabbit.spi.commons.iterator.Predicate;
+import org.apache.jackrabbit.utils.Function1;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A change tree records changes to a tree of nodes and properties and it provides
+ * a consolidated view of these changes. Changes are described by operations for
+ * adding nodes, removing nodes, moving nodes and setting properties. <p/>
+ *
+ * A consolidate change list can be obtained from change trees. A change list is
+ * a list of operations. A consolidated changes list is a change list which is
+ * equivalent to the change list consisting of all operations applied to the change
+ * tree and which is minimal amongst all change lists which are equivalent to the
+ * former one. <p/>
+ *
+ * Two change lists are equivalent iff their effect on a tree of nodes and properties
+ * is the same. That is, when applied to a tree both result in the same final state
+ * of that tree. <p/>
+ *
+ * A change list is minimal amongst a set of change lists, if no change list in that
+ * set contains more operations. <p/>
+ *
+ * Internally a change tree is a tree of node deltas. A node delta describes whether
+ * a node has been added, removed, moved or whether its properties have been changed.
+ * A change tree contains a node delta for each touched node. A node is touched if it
+ * is modified or one of its child nodes is touched. A node is modified if it is
+ * transient or has modified properties. A node is transient if it is either added,
+ * removed or moved. <p/>
+ *
+ * A move operation is conceptually handled as a remove operation followed by an add
+ * operation of the respective sub tree. In order to keep track of the move operation
+ * the individual remove and add operations are marked as belonging to that move
+ * operation. <p/>
+ *
+ * Since a remove node operation cancels a preceding add node operation for the same
+ * node and because a move operation resembles a remove followed by an add operation
+ * following equivalencies apply for move operations:
+ *
+ * <ul>
+ * <li>Removing a node after it has been moved is equivalent to removing the original
+ *   node.</li>
+ * <li>Moving a node after it has been added is equivalent to adding the node at the
+ *   target of the move.</li>
+ * <li>Moving a moved node is equivalent to directly moving from the original source
+ *   to the eventual target.</li>
+ * </ul>
+ */
+public class ChangeTree {
+    private final NodeDelta root;
+    private final Function1<String, Boolean> nodeExists;
+
+    public ChangeTree(final Path rootPath, Function1<String, Boolean> nodeExists) {
+        this.nodeExists = nodeExists;
+
+        root = new Existing(null, "") {
+            @Override
+            public Path getPath() {
+                return rootPath;
+            }
+        };
+    }
+
+    public boolean nodeExists(Path path) {
+        return getNodeOrNull(path) != null;
+    }
+
+    public NodeDelta getNode(Path path) throws ItemNotFoundException {
+        NodeDelta delta = getNodeOrNull(path);
+        if (delta == null) {
+            throw new ItemNotFoundException(path.getPath());
+        }
+
+        return delta;
+    }
+
+    public void collectChanges(Listener listener) {
+        root.accept(new ChangeVisitor(listener));
+    }
+
+    public void clear() {
+        root.clear();
+    }
+
+    public boolean hasChanges() {
+        return root.hasChanges();   
+    }
+    
+    private NodeDelta getNodeOrNull(Path path) {
+        NodeDelta delta = root;
+        for (String name : path.getNames()) {
+            delta = delta.getNode(name);
+            if (delta == null) {
+                return null;
+            }
+        }
+        return delta;
+    }
+
+    public interface Visitor {
+        void visit(Existing delta);
+        void visit(Added delta);
+        void visit(Removed delta);
+        void visit(Moved delta);
+    }
+
+    public interface Listener {
+        void addNode(Path path);
+        void removeNode(Path path);
+        void moveNode(Path sourcePath, Path destinationPath);
+        void setProperty(Path path, JsonValue value);
+    }
+
+    private class ChangeVisitor implements Visitor {
+        private final Listener listener;
+
+        public ChangeVisitor(Listener listener) {
+            this.listener = listener;
+        }
+
+        @Override
+        public void visit(Existing delta) {
+            handleProperties(delta);
+            visitNodes(delta);
+        }
+
+        @Override
+        public void visit(Added delta) {
+            listener.addNode(delta.getPath());
+            handleProperties(delta);
+            visitNodes(delta);
+        }
+
+        @Override
+        public void visit(Removed delta) {
+            if (!delta.isMoved()) {
+                listener.removeNode(delta.getPath());
+            }
+        }
+
+        @Override
+        public void visit(Moved delta) {
+            delta.source.accept(this);
+            listener.moveNode(delta.getSourcePath(), delta.getPath());
+        }
+
+        private void handleProperties(NodeDelta delta) {
+            Path path = delta.getPath();
+            for (Entry<String, JsonValue> property : delta.properties.entrySet()) {
+                String name = property.getKey();
+                JsonValue value = property.getValue();
+                listener.setProperty(path.concat(name), value);
+            }
+        }
+
+        private void visitNodes(NodeDelta delta) {
+            for (NodeDelta node : delta.childNodes()) {
+                node.accept(this);
+            }
+        }
+    }
+
+    public abstract class NodeDelta {
+        private final Map<String, NodeDelta> childNodes = new HashMap<String, NodeDelta>();
+        private final Map<String, JsonValue> properties = new HashMap<String, JsonValue>();
+        protected NodeDelta parent;
+        protected String name;
+
+        NodeDelta(NodeDelta parent, String name) {
+            this.parent = parent;
+            this.name = name;
+        }
+
+        public Path getPath() {
+            return parent.getPath().concat(name);
+        }
+
+        public Path getPersistentPath() {
+            return null;
+        }
+
+        public abstract boolean isRemoved();
+        public abstract boolean isMoved();
+        public abstract boolean isTransient();
+
+        public abstract void accept(Visitor visitor);
+
+        public boolean hasChanges() {
+            return !childNodes.isEmpty() || !properties.isEmpty();
+        }
+        
+        public final boolean hasNode(String name) {
+            return getNode(name) != null;
+        }
+
+        public abstract NodeDelta getNode(String name);
+
+        public Iterator<Entry<String, NodeDelta>> getNodes() {
+            return Iterators.filterIterator(childNodes.entrySet().iterator(), new Predicate<Entry<String, NodeDelta>>() {
+                @Override
+                public boolean evaluate(Entry<String, NodeDelta> entry) {
+                    NodeDelta delta = entry.getValue();
+                    return delta.isTransient() && !delta.isRemoved();
+                }
+            });
+        }
+
+        public boolean hasProperty(String name) {
+            return properties.containsKey(name);
+        }
+        
+        public JsonValue getProperty(String name) {
+            return properties.get(name);
+        }
+
+        public Iterator<Entry<String, JsonValue>> getProperties() {
+            return Iterators.filterIterator(properties.entrySet().iterator(), new Predicate<Entry<String, JsonValue>>() {
+                @Override
+                public boolean evaluate(Entry<String, JsonValue> entry) {
+                    return entry.getValue() != null;
+                }
+            });
+        }
+
+        public NodeDelta addNode(String name) throws ItemExistsException {
+            if (hasNode(name)) {
+                throw new ItemExistsException(name);
+            }
+
+            return addChild(new Added(this, name));
+        }
+
+        public NodeDelta removeNode(String name) throws ItemNotFoundException {
+            NodeDelta delta = getNode(name);
+            if (delta == null) {
+                throw new ItemNotFoundException(name);
+            }
+
+            return delta.remove();
+        }
+
+        public void moveNode(String name, Path destination) throws ItemNotFoundException, ItemExistsException {
+            NodeDelta source = getNode(name);
+            if (source == null) {
+                throw new ItemNotFoundException(name);
+            }
+
+            if (nodeExists(destination)) {
+                throw new ItemExistsException(destination.getPath());
+            }
+
+            Path destParentPath = destination.getParent();
+            if (!nodeExists(destParentPath)) {
+                throw new ItemNotFoundException(destParentPath.getPath());
+            }
+
+            if (source.isTransient()) {
+                removeChild(name);
+            }
+            else {
+                addChild(new Removed(this, name, true));
+            }
+
+            // Resolve destination only *after* source has been removed in order
+            // to make sure nodes on any common path prefix are already touched.
+            NodeDelta destParent = ChangeTree.this.getNode(destParentPath);
+            source.moveTo(destParent, destination.getName());
+        }
+
+        public void setValue(String name, JsonValue value) {
+            if (value == null && properties.containsKey(name) && properties.get(name) != null) {
+                properties.remove(name);
+            }
+            else {
+                properties.put(name, value);
+                touch();
+            }
+        }
+
+        @Override
+        public String toString() {
+            return getPath().toString();
+        }
+
+        //------------------------------------------< internal >---
+
+        void touch() { }
+
+        abstract NodeDelta remove() throws ItemNotFoundException;
+        abstract void moveTo(NodeDelta parent, String name);
+
+        final void clear() {
+            childNodes.clear();
+            properties.clear();
+        }
+
+        final Iterable<NodeDelta> childNodes() {
+            return childNodes.values();
+        }
+
+        final NodeDelta getChild(String name) {
+            return childNodes.get(name);
+        }
+
+        final boolean hasChild(String name) {
+            return childNodes.containsKey(name);
+        }
+
+        final NodeDelta addChild(NodeDelta delta) {
+            childNodes.put(delta.name, delta);
+            touch();
+            return delta;
+        }
+
+        final NodeDelta removeChild(String name) {
+            NodeDelta delta = childNodes.remove(name);
+            if (delta != null) {
+                touch();
+            }
+            return delta;
+        }
+    }
+    
+    public class Existing extends NodeDelta {
+        Existing(NodeDelta parent, String name) {
+            super(parent, name);
+        }
+
+        @Override
+        public Path getPersistentPath() {
+            return getPath();
+        }
+
+        @Override
+        public boolean isRemoved() {
+            return false;
+        }
+
+        @Override
+        public boolean isMoved() {
+            return false;
+        }
+
+        @Override
+        public boolean isTransient() {
+            return false;
+        }
+
+        @Override
+        public void accept(Visitor visitor) {
+            visitor.visit(this);
+        }
+
+        @Override
+        public NodeDelta getNode(String name) {
+            NodeDelta delta = getChild(name);
+            if (delta == null) {
+                return nodeExists.apply(getPath().concat(name).getMkPath())
+                    ? new Existing(this, name)
+                    : null;
+            }
+            else {
+                return delta.isRemoved() ? null : delta;
+            }
+        }
+
+        @Override
+        void touch() {
+            if (parent != null && ! parent.hasChild(name)) {
+                parent.addChild(this);
+            }
+        }
+
+        @Override
+        NodeDelta remove() {
+            return parent.addChild(new Removed(parent, name, false));
+        }
+
+        @Override
+        void moveTo(NodeDelta parent, String name) {
+            parent.addChild(new Moved(parent, name, this));
+        }
+    }
+
+    public class Added extends NodeDelta {
+        Added(NodeDelta parent, String name) {
+            super(parent, name);
+        }
+
+        @Override
+        public boolean isRemoved() {
+            return false;
+        }
+
+        @Override
+        public boolean isMoved() {
+            return false;
+        }
+
+        @Override
+        public boolean isTransient() {
+            return true;
+        }
+
+        @Override
+        public void accept(Visitor visitor) {
+            visitor.visit(this);
+        }
+
+        @Override
+        public NodeDelta getNode(String name) {
+            return getChild(name);
+        }
+
+        @Override
+        NodeDelta remove() {
+            return parent.removeChild(name);
+        }
+
+        @Override
+        void moveTo(NodeDelta parent, String name) {
+            this.parent = parent;
+            this.name = name;
+            parent.addChild(this);
+        }
+    }
+
+    public class Moved extends NodeDelta {
+        private final NodeDelta source;
+
+        Moved(NodeDelta parent, String name, NodeDelta source) {
+            super(parent, name);
+            this.source = source;
+        }
+
+        Path getSourcePath() {
+            return source.getPath();
+        }
+
+        @Override
+        public Path getPersistentPath() {
+            return source.getPersistentPath();
+        }
+
+        @Override
+        public boolean isRemoved() {
+            return false;
+        }
+
+        @Override
+        public boolean isMoved() {
+            return true;
+        }
+
+        @Override
+        public boolean isTransient() {
+            return true;
+        }
+
+        @Override
+        public void accept(Visitor visitor) {
+            visitor.visit(this);
+        }
+
+        @Override
+        public NodeDelta getNode(String name) {
+            return source.getNode(name);
+        }
+
+        @Override
+        public NodeDelta addNode(String name) throws ItemExistsException {
+            return source.addNode(name);
+        }
+
+        @Override
+        public NodeDelta removeNode(String name) throws ItemNotFoundException {
+            return source.removeNode(name);
+        }
+
+        @Override
+        public void moveNode(String name, Path destination) throws ItemNotFoundException, ItemExistsException {
+            source.moveNode(name, destination);
+        }
+
+        @Override
+        public void setValue(String name, JsonValue value) {
+            source.setValue(name, value);
+        }
+
+        @Override
+        NodeDelta remove() throws ItemNotFoundException {
+            // convert source from moved away to removed
+            if (source.parent.childNodes.remove(source.name) != null) {
+                source.parent.removeNode(source.name);
+            }
+
+            return parent.removeChild(name);
+        }
+
+        @Override
+        void moveTo(NodeDelta parent, String name) {
+            parent.addChild(new Moved(parent, name, source));
+        }
+    }
+
+    public class Removed extends NodeDelta {
+        private final boolean moved;
+
+        Removed(NodeDelta parent, String name, boolean moved) {
+            super(parent, name);
+            this.moved = moved;
+        }
+
+        @Override
+        public boolean isRemoved() {
+            return true;
+        }
+
+        @Override
+        public boolean isMoved() {
+            return moved;
+        }
+
+        @Override
+        public boolean isTransient() {
+            return true;
+        }
+
+        @Override
+        public void accept(Visitor visitor) {
+            visitor.visit(this);
+        }
+
+        @Override
+        public NodeDelta getNode(String name) {
+            throw new IllegalStateException("Removed");
+        }
+
+        @Override
+        public NodeDelta addNode(String name) {
+            throw new IllegalStateException("Removed");
+        }
+
+        @Override
+        public NodeDelta removeNode(String name) {
+            throw new IllegalStateException("Removed");
+        }
+
+        @Override
+        public void moveNode(String name, Path destination) {
+            throw new IllegalStateException("Removed");
+        }
+
+        @Override
+        public void setValue(String name, JsonValue value) {
+            throw new IllegalStateException("Removed");
+        }
+
+        @Override
+        NodeDelta remove() throws ItemNotFoundException {
+            throw new IllegalStateException("Removed");
+        }
+
+        @Override
+        void moveTo(NodeDelta parent, String name) {
+            throw new IllegalStateException("Removed");
+        }
+    }
+    
+}

Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java?rev=1224964&r1=1224963&r2=1224964&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java Tue Dec 27 18:17:39 2011
@@ -1,298 +1,78 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
 package org.apache.jackrabbit.state;
 
 import org.apache.jackrabbit.Path;
 import org.apache.jackrabbit.json.JsonValue;
-import org.apache.jackrabbit.json.JsonValue.JsonArray;
-import org.apache.jackrabbit.json.JsonValue.JsonAtom;
-import org.apache.jackrabbit.state.Operation.AddNode;
-import org.apache.jackrabbit.state.Operation.AddProperty;
-import org.apache.jackrabbit.state.Operation.Move;
-import org.apache.jackrabbit.state.Operation.RemoveNode;
-import org.apache.jackrabbit.state.Operation.RemoveProperty;
-import org.apache.jackrabbit.state.Operation.Reorder;
-import org.apache.jackrabbit.state.Operation.SetMixin;
-import org.apache.jackrabbit.state.Operation.SetPrimaryType;
-import org.apache.jackrabbit.state.Operation.SetValue;
-import org.apache.jackrabbit.state.Operation.Visitor;
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.mk.api.MicroKernelException;
+import org.apache.jackrabbit.state.ChangeTree.Listener;
+import org.apache.jackrabbit.utils.Function1;
 
-import javax.jcr.PathNotFoundException;
+import javax.jcr.ItemNotFoundException;
 import javax.jcr.RepositoryException;
-import javax.jcr.UnsupportedRepositoryOperationException;
 
 public class TransientSpace {
-    private final ChangeLog changeLog = new ChangeLog();
-    private final MicroKernel microKernel;
-    private final Path rootPath;
-
+    private final MicroKernel microkernel;
+    private final ChangeTree changeTree;
     private String revision;
-    private NodeDelta root;
 
-    public TransientSpace(MicroKernel microKernel, String revision, Path rootPath) {
-        this.microKernel = microKernel;
-        this.rootPath = rootPath;
+    public TransientSpace(String workspace, final MicroKernel microkernel, final String revision) {
+        this.microkernel = microkernel;
         this.revision = revision;
-        root = new NodeDelta(rootPath, rootPath);
-    }
-
-    public void apply(Operation operation) throws RepositoryException {
-        operation.accept(new Visitor() {
-            @Override
-            public void visit(Operation.AddNode addNode) throws RepositoryException {
-                Path parentPath = addNode.getParent();
-                String name = addNode.getName();
-                NodeDelta parent = getOrCreateNodeDelta(parentPath);
-                parent.addNode(name, new NodeDelta(parentPath.concat(name), null));
-            }
-
-            @Override
-            public void visit(Operation.AddProperty addProperty) throws RepositoryException {
-                NodeDelta parent = getOrCreateNodeDelta(addProperty.getParent());
-                parent.addProperty(addProperty.getName(), addProperty.getValue());
-            }
-
-            @Override
-            public void visit(Operation.SetValue setValue) throws RepositoryException {
-                Path path = setValue.getPath();
-                NodeDelta parent = getOrCreateNodeDelta(path.getParent());
-                parent.setValue(path.getName(), setValue.getValue());
-            }
-
-            @Override
-            public void visit(Operation.RemoveNode removeNode) throws RepositoryException {
-                Path path = removeNode.getPath();
-                NodeDelta parent = getOrCreateNodeDelta(path.getParent());
-                parent.removeNode(path.getName());
-            }
 
+        changeTree = new ChangeTree(Path.create(workspace), new Function1<String, Boolean>() {
             @Override
-            public void visit(RemoveProperty removeProperty) throws RepositoryException {
-                Path path = removeProperty.getPath();
-                NodeDelta parent = getOrCreateNodeDelta(path.getParent());
-                parent.setValue(path.getName(), JsonAtom.NULL);
-            }
-
-            @Override
-            public void visit(Operation.Reorder reorder) {
-                // todo implement visit(Reorder)
-            }
-
-            @Override
-            public void visit(Operation.SetMixin setMixin) throws RepositoryException {
-                NodeDelta parent = getOrCreateNodeDelta(setMixin.getPath());
-                parent.setValue("jcr:mixinTypes", setMixin.getMixins());
-            }
-
-            @Override
-            public void visit(Operation.SetPrimaryType setPrimaryType) throws RepositoryException {
-                NodeDelta parent = getOrCreateNodeDelta(setPrimaryType.getPath());
-                parent.setValue("jcr:primaryType", setPrimaryType.getPrimaryType());
-            }
-
-            @Override
-            public void visit(Operation.Move move) throws RepositoryException {
-                Path source = move.getSource();
-                Path destination = move.getDestination();
-
-                NodeDelta sourceNode = getOrCreateNodeDelta(source);
-                NodeDelta sourceParent = getOrCreateNodeDelta(source.getParent());
-                sourceParent.removeNode(source.getName());
-                sourceNode.setSessionPath(destination);
-
-                NodeDelta destinationParent = getOrCreateNodeDelta(destination.getParent());
-                destinationParent.addNode(destination.getName(), sourceNode);
-            }
-
-            private NodeDelta getOrCreateNodeDelta(Path path) throws PathNotFoundException {
-                NodeDelta delta = root;
-                for (String name : path.getNames()) {
-                    if (delta.hasChildNode(name)) {
-                        delta = delta.getChildNode(name);
-                    }
-                    else if (delta.hasAddedNode(name)) {
-                        delta = delta.getAddedNode(name);
-                    }
-                    else if (delta.hasRemovedNode(name)) {
-                        throw new PathNotFoundException(path.toString());
-                    }
-                    else {
-                        Path persistedPath = delta.getPersistentPath();
-                        if (persistedPath == null) {
-                            throw new PathNotFoundException(path.toString());
-                        }
-
-                        persistedPath = persistedPath.concat(name);
-                        if (!microKernel.nodeExists(persistedPath.getMkPath(), revision)) {
-                            throw new PathNotFoundException(path.toString());
-                        }
-
-                        Path sessionPath = delta.getSessionPath().concat(name);
-                        delta = delta.addChild(name, new NodeDelta(sessionPath, persistedPath));
-                    }
-                }
-                return delta;
+            public Boolean apply(String path) {
+                return microkernel.nodeExists(path, getRevision());
             }
         });
-
-        changeLog.addOperation(operation);
     }
 
-    public NodeDelta getNodeDelta(final Path path) {
-        if (!rootPath.getWorkspace().equals(path.getWorkspace())) {
-            return new NodeDelta(path, path);
-        }
-
-        NodeDelta delta = root;
-        for (String name : path.getNames()) {
-            if (delta.hasRemovedNode(name)) {
-                return null;
-            }
-            else if (delta.hasAddedNode(name)) {
-                delta = delta.getAddedNode(name);
-            }
-            else if (delta.hasChildNode(name)) {
-                delta = delta.getChildNode(name);
-            }
-            else {
-                return new NodeDelta(path, path);
-            }
-        }
-        return delta;
-    }
-
-    public ChangeLog getChangeLog() {
-        return changeLog;
+    public ChangeTree.NodeDelta getNode(Path path) throws ItemNotFoundException {
+        return changeTree.getNode(path);
     }
 
-    public String refresh(boolean keepChanges) throws RepositoryException {
-        NodeDelta oldRoot = root;
-        String oldRevision = revision;
-        try {
-            root = new NodeDelta(rootPath, rootPath);
-            revision = microKernel.getHeadRevision();
-
-            if (keepChanges) {
-                apply(changeLog);
-            }
-            else {
-                changeLog.clear();
-            }
-
-            return revision;
-        }
-        catch (RepositoryException e) {
-            root = oldRoot;
-            revision = oldRevision;
-            throw e;
-        }
+    public boolean nodeExists(Path path) {
+        return changeTree.nodeExists(path);
     }
 
     public String save() throws RepositoryException {
         try {
             final StringBuilder jsop = new StringBuilder();
 
-            changeLog.accept(new Visitor() {
+            changeTree.collectChanges(new Listener() {
                 @Override
-                public void visit(AddNode addNode) {
-                    Path target = addNode.getParent().concat(addNode.getName());
+                public void addNode(Path path) {
                     jsop.append("+\"")
-                        .append(target.getMkPath())
-                        .append("\":")
-                        .append("{}")
-                        .append('\n');
-                }
-
-                @Override
-                public void visit(AddProperty addProperty) {
-                    Path target = addProperty.getParent().concat(addProperty.getName());
-                    jsop.append("^\"")
-                        .append(target.getMkPath())
-                        .append("\":")
-                            .append(addProperty.getValue().toJson())
-                        .append('\n');
-                }
-
-                @Override
-                public void visit(SetValue setValue) {
-                    jsop.append("^\"")
-                        .append(setValue.getPath().getMkPath())
-                        .append("\":")
-                        .append(setValue.getValue().toJson())
-                        .append('\n');
+                            .append(path.getMkPath())
+                            .append("\":{}");
                 }
 
                 @Override
-                public void visit(RemoveNode removeNode) {
+                public void removeNode(Path path) {
                     jsop.append("-\"")
-                            .append(removeNode.getPath().getMkPath())
-                            .append("\"\n");
+                            .append(path.getMkPath())
+                            .append('"');
                 }
 
                 @Override
-                public void visit(RemoveProperty removeProperty) {
-                    jsop.append("^\"")
-                            .append(removeProperty.getPath().getMkPath())
-                            .append("\":null\n");
-                }
-
-                @Override
-                public void visit(Reorder reorder) throws UnsupportedRepositoryOperationException {
-                    throw new UnsupportedRepositoryOperationException("Reorder");
-                }
-
-                @Override
-                public void visit(SetMixin setMixin) {
-                    Path target = setMixin.getPath().concat("jcr:mixinTypes");
-                    jsop.append("^\"")
-                            .append(target.getMkPath())
-                            .append("\":")
-                            .append(setMixin.getMixins().toJson())
-                            .append('\n');
+                public void moveNode(Path sourcePath, Path destinationPath) {
+                    jsop.append(">\"")
+                            .append(sourcePath.getMkPath())
+                            .append("\":\"")
+                            .append(destinationPath.getMkPath())
+                            .append('"');
                 }
 
                 @Override
-                public void visit(SetPrimaryType setPrimaryType) {
-                    Path target = setPrimaryType.getPath().concat("jcr:primaryType");
+                public void setProperty(Path path, JsonValue value) {
                     jsop.append("^\"")
-                        .append(target.getMkPath())
-                        .append("\":")
-                            .append(setPrimaryType.getPrimaryType().toJson())
-                        .append('\n');
-                }
-
-                @Override
-                public void visit(Move move) {
-                    jsop.append(">\"")
-                            .append(move.getSource().getMkPath())
-                        .append("\":\"")
-                            .append(move.getDestination().getMkPath())
-                        .append("\"\n");
+                            .append(path.getMkPath())
+                            .append("\":").append(value == null ? "null" : value.toJson());
                 }
             });
 
-            revision = microKernel.commit("", jsop.toString(), revision, "");
-            root = new NodeDelta(rootPath, rootPath);
-            changeLog.clear();
+            revision = microkernel.commit("", jsop.toString(), revision, "");
+            changeTree.clear();
             return revision;
         }
         catch (MicroKernelException e) {
@@ -300,45 +80,21 @@ public class TransientSpace {
         }
     }
 
-    public boolean isDirty() {
-        return !changeLog.isEmpty();
-    }
-
-    //------------------------------------------< convenience >---
-
-    public void addNode(Path parent, String name) throws RepositoryException {
-        apply(new Operation.AddNode(parent, name));
-    }
-
-    public void addProperty(Path parent, String name, JsonValue value) throws RepositoryException {
-        apply(new Operation.AddProperty(parent, name, value));
-    }
-
-    public void setValue(Path path, JsonValue value) throws RepositoryException {
-        apply(new Operation.SetValue(path, value));
-    }
-
-    public void removeNode(Path path) throws RepositoryException {
-        apply(new Operation.RemoveNode(path));
-    }
-
-    public void removeProperty(Path path) throws RepositoryException {
-        apply(new Operation.RemoveProperty(path));
-    }
+    public String refresh(boolean keepChanges) {
+        if (!keepChanges) {
+            changeTree.clear();
+        }
 
-    public void reorder() throws RepositoryException {
-        apply(new Operation.Reorder());
+        return revision = microkernel.getHeadRevision();
     }
 
-    public void setMixin(Path path, JsonArray mixins) throws RepositoryException {
-        apply(new Operation.SetMixin(path, mixins));
+    public boolean isDirty() {
+        return changeTree.hasChanges();
     }
 
-    public void setPrimaryType(Path path, JsonValue type) throws RepositoryException {
-        apply(new Operation.SetPrimaryType(path, type));
-    }
+    //------------------------------------------< private >---
 
-    public void move(Path source, Path target) throws RepositoryException {
-        apply(new Operation.Move(source, target));
+    private String getRevision() {
+        return revision;
     }
 }

Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function1.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function1.java?rev=1224964&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function1.java (added)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function1.java Tue Dec 27 18:17:39 2011
@@ -0,0 +1,5 @@
+package org.apache.jackrabbit.utils;
+
+public interface Function1<S, T> {
+    T apply(S s);
+}

Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/RepositoryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/RepositoryTest.java?rev=1224964&r1=1224963&r2=1224964&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/RepositoryTest.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/RepositoryTest.java Tue Dec 27 18:17:39 2011
@@ -80,7 +80,7 @@ import static org.junit.Assert.assertTru
 import static org.junit.Assert.fail;
 
 public class RepositoryTest {
-    private static final String URL = "fs:target/repository-test/repository;clean";
+    private static final String URL = "fs:target/repository-test/repository";
     // private static final String URL = "mem:";
 
     private static final String TEST_NODE = "test_node";
@@ -1131,7 +1131,7 @@ public class RepositoryTest {
 
             session1.save();
             try {
-                session2.refresh(true);
+                session2.save();
                 fail();
             }
             catch (RepositoryException e) {