jackrabbit-oak-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mdue...@apache.org
Subject svn commit: r1300973 [3/3] - in /jackrabbit/oak/trunk/oak-jcr: ./ src/main/java/org/apache/jackrabbit/oak/jcr/ src/main/java/org/apache/jackrabbit/oak/jcr/configuration/ src/main/java/org/apache/jackrabbit/oak/jcr/json/ src/main/java/org/apache/jackrab...
Date Thu, 15 Mar 2012 13:33:33 GMT
Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/ChangeTree.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/ChangeTree.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/ChangeTree.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/ChangeTree.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,626 @@
+/*
+ * 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.oak.jcr.state;
+
+import org.apache.commons.collections.map.AbstractReferenceMap;
+import org.apache.commons.collections.map.ReferenceMap;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.oak.jcr.util.Function1;
+import org.apache.jackrabbit.oak.jcr.util.Iterators;
+import org.apache.jackrabbit.oak.jcr.util.Path;
+import org.apache.jackrabbit.oak.jcr.util.Predicate;
+import org.apache.jackrabbit.oak.model.PropertyState;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.PathNotFoundException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static org.apache.jackrabbit.oak.jcr.util.Unchecked.cast;
+
+/**
+ * A change tree records changes to a tree of nodes and properties. <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. <p/>
+ */
+public class ChangeTree {
+    private final NodeDelta root;
+    private final Predicate<Path> nodeExists;
+    private final Listener listener;
+
+    /** Keep Existing instances at least as long as referenced by a client */
+    private final Map<Path, Existing> existing = cast(new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK));
+
+    /**
+     * Listener for modifications in the hierarchy
+     */
+    public interface Listener {
+        void added(NodeDelta nodeDelta);
+        void removed(NodeDelta nodeDelta);
+        void moved(Path source, NodeDelta nodeDelta);
+        void setValue(NodeDelta parent, String name, JsonValue value);
+    }
+
+    /**
+     * Create a new change tree rooted at {@code rootPath}.
+     * @param rootPath  root path for this change tree
+     * @param listener  listener for changes in the hierarchy
+     * @param nodeExists  predicate which determines whether a path exists on the
+     *                    persistent layer.
+     */
+    public ChangeTree(final Path rootPath, Listener listener, Predicate<Path> nodeExists) {
+        this.nodeExists = nodeExists;
+        this.listener = listener;
+        
+        root = new Existing(null, "", rootPath) {
+            @Override
+            public Path getPath() {
+                return rootPath;
+            }
+        };
+    }
+
+    /**
+     * @return {@code true} iff {@code path} exists either transiently or on
+     * the persistence layer.
+     */
+    public boolean nodeExists(Path path) {
+        return getNode(path) != null;
+    }
+
+    /**
+     * @param path
+     * @return a {@code NodeDelta} instance for the given {@code path} or {@code null}
+     * if {@code path} does not exist transiently nor on the persistence layer.
+     */
+    public NodeDelta getNode(Path path) {
+        NodeDelta delta = root;
+        for (String name : path.getNames()) {
+            delta = delta.getNode(name);
+            if (delta == null) {
+                return null;
+            }
+        }
+        return delta;
+    }
+
+    /**
+     * @return  {@code true} iff this change tree has transient changes.
+     */
+    public boolean hasChanges() {  
+        return root.hasChanges();
+    }
+
+    /**
+     * {@code NodeDelta} instances record changes to a node. {@code NodeDelta}'s
+     * subclasses correspond to these changes:
+     *
+     * <ul>
+     * <li>{@link org.apache.jackrabbit.state.ChangeTree.Added} represents a transiently
+     *      added node.</li>
+     * <li>{@link org.apache.jackrabbit.state.ChangeTree.Removed} represents a transiently
+     *      removed node.</li>
+     * <li>{@link org.apache.jackrabbit.state.ChangeTree.Existing} represents a node which
+     *      is otherwise touched. That is, which either has property modifications or a has a
+     *      child node which is touched. </li>
+     * </ul>
+     */
+    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;
+        }
+
+        /**
+         * @return the parent of this node
+         */
+        public NodeDelta getParent() {
+            return parent;
+        }
+
+        /**
+         * @return transient path to this node
+         */
+        public Path getPath() {
+            return parent.getPath().concat(name);
+        }
+
+        /**
+         * @return transient name of this node
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * @return persistent path to this node or {@code null} if this node is not
+         * an {@link org.apache.jackrabbit.state.ChangeTree.Existing existing} node.
+         */
+        public Path getPersistentPath() {
+            return null;
+        }
+
+        /**
+         * @return {@code true} iff this node has been transiently removed.
+         */
+        public abstract boolean isRemoved();
+
+        /**
+         * @return {@code true} iff this node has been transiently added.
+         */
+        public abstract boolean isAdded();
+
+        /**
+         * @return {@code true} iff this node has been transiently moved.
+         */
+        public abstract boolean isMoved();
+
+        /**
+         * @return {@code true} iff this node is transient.
+         */
+        public abstract boolean isTransient();
+
+        /**
+         * @return {@code true} iff this node has changes. A node has changes
+         * iff it either has changed properties or one of its child nodes has changes.
+         */
+        public boolean hasChanges() {
+            return !properties.isEmpty() || !childNodes.isEmpty();
+        }
+
+        /**
+         * @param name
+         * @return  {@code true} iff this node has a child node with the given {@code name}.
+         */
+        public final boolean hasNode(String name) {
+            return getNode(name) != null;
+        }
+
+        /**
+         * @param name
+         * @return  the child node with the given {@code name} or {@code null} if none.
+         */
+        public abstract NodeDelta getNode(String name);
+
+        /**
+         * @return  Iterator of all added nodes
+         */
+        public Iterator<NodeDelta> getNodes() {
+            return Iterators.filter(childNodes().iterator(), new Predicate<NodeDelta>() {
+                @Override
+                public boolean evaluate(NodeDelta delta) {
+                    return delta.isTransient() && !delta.isRemoved();
+                }
+            });
+        }
+
+        /**
+         * @param name
+         * @return  {@code true} iff this node has a modified child node of the given {@code name}.
+         */
+        public boolean isNodeModified(String name) {
+            NodeDelta node = childNodes.get(name);
+            return node != null && node.isTransient();
+        }
+
+        /**
+         * @param name
+         * @return {@code true} iff a property with the given name has been added,
+         * removed or modified.
+         */
+        public boolean hasProperty(String name) {
+            return properties.containsKey(name);
+        }
+
+        /**
+         * @param name
+         * @return  the value of the property with the given {@code name}, JSON {@code null} if the
+         * property has been removed or {@code null} if if does not exist.
+         */
+        public JsonValue getPropertyValue(String name) {
+            return properties.get(name);
+        }
+
+        /**
+         * @return  an iterator for all added and modified properties.
+         */
+        public Iterator<PropertyState> getPropertyStates() {
+            Iterator<Entry<String, JsonValue>> entries =
+                Iterators.filter(properties.entrySet().iterator(),
+                        new Predicate<Entry<String, JsonValue>>() {
+                            @Override
+                            public boolean evaluate(Entry<String, JsonValue> entry) {
+                                return entry.getValue() != JsonAtom.NULL;
+                            }
+                        });
+
+            return Iterators.map(entries,
+                    new Function1<Entry<String, JsonValue>, PropertyState>() {
+                        @Override
+                        public PropertyState apply(final Entry<String, JsonValue> entry) {
+                            return new PropertyStateImpl(entry.getKey(), entry.getValue());
+                        }
+                    });
+        }
+
+        /**
+         * Add a node with the given {@code name}.
+         * @param name
+         * @return  the added node
+         * @throws javax.jcr.ItemExistsException
+         */
+        public NodeDelta addNode(String name) throws ItemExistsException {
+            if (hasNode(name)) {
+                throw new ItemExistsException(name);
+            }
+
+            NodeDelta added = addChild(new Added(this, name));
+            notifyAdded(added);
+            return added;
+        }
+
+        /**
+         * Remove the node with the given {@code name}.
+         * @param name
+         * @return  the removed node
+         * @throws javax.jcr.ItemNotFoundException
+         */
+        public NodeDelta removeNode(String name) throws ItemNotFoundException {
+            NodeDelta delta = getNode(name);
+            if (delta == null) {
+                throw new ItemNotFoundException(name);
+            }
+
+            NodeDelta removed = delta.remove();
+            notifyRemoved(removed);
+            return removed;
+        }
+
+        /**
+         * Move the node with the given {@code name} to {@code destination}.
+         * @param name
+         * @param destination
+         * @throws javax.jcr.ItemNotFoundException
+         * @throws javax.jcr.ItemExistsException
+         * @throws javax.jcr.PathNotFoundException
+         */
+        public void moveNode(String name, Path destination) throws ItemNotFoundException, ItemExistsException,
+                PathNotFoundException {
+
+            NodeDelta source = getNode(name);
+            if (source == null) {
+                throw new ItemNotFoundException(name);
+            }
+
+            if (nodeExists(destination)) {
+                throw new ItemExistsException(destination.toJcrPath());
+            }
+
+            Path destParentPath = destination.getParent();
+            if (!nodeExists(destParentPath)) {
+                throw new PathNotFoundException(destParentPath.toJcrPath());
+            }
+
+            Path sourcePath = source.getPath();
+            NodeDelta moved = source.moveTo(destParentPath, destination.getName());
+            notifyMoved(sourcePath, moved);
+        }
+
+        /**
+         * Set the property with the given {@code name} to {@code value} or remove the
+         * property if {@code value} is {@code null} or JSON {@code null}.
+         * @param name
+         * @param value
+         */
+        public void setValue(String name, JsonValue value) {
+            if (value == null) {
+                value = JsonAtom.NULL;
+            }
+            
+            if (value.isNull() && properties.containsKey(name) && properties.get(name) != JsonAtom.NULL) {
+                properties.remove(name);
+            }
+            else {
+                properties.put(name, value);
+                touch();
+            }
+            notifySetValue(this, name, value);
+        }
+
+        //------------------------------------------< internal >---
+
+        void touch() { }
+
+        NodeDelta remove() {
+            return parent.addChild(new Removed(parent, name));
+        }
+
+        NodeDelta moveTo(Path parentPath, String name) {
+            remove();
+            this.name = name;
+            NodeDelta parent = ChangeTree.this.getNode(parentPath);
+            return parent.addChild(this);
+        }
+
+        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);
+            delta.parent = this;
+            touch();
+            return delta;
+        }
+
+        private void notifyAdded(NodeDelta added) {
+            if (listener != null) {
+                listener.added(added);
+            }
+        }
+        
+        private void notifyRemoved(NodeDelta removed) {
+            if (listener != null) {
+                listener.removed(removed);
+            }
+        }
+        
+        private void notifyMoved(Path sourcePath, NodeDelta moved) {
+            if (listener != null) {
+                listener.moved(sourcePath, moved);
+            }
+        }
+        
+        private void notifySetValue(NodeDelta parent, String name, JsonValue value) {
+            if (listener != null) {
+                listener.setValue(parent, name, value);
+            }
+        }
+    }
+
+    //------------------------------------------< private/internal >---
+
+    /**
+     * @return A {@code Existing} instance for the given {@code parent} and {@code name}.
+     * Returns a previously allocated instance if not yet garbage collected.
+     * <em>Note:</em> returning fresh instances while previously allocated ones are still
+     * referenced in client code results in schizophrenia: same node multiple states.
+     */
+    private Existing existing(NodeDelta parent, String name, Path persistentPath) {
+        Existing e = existing.get(persistentPath);
+        if (e == null) {
+            e = new Existing(parent, name, persistentPath);
+            existing.put(persistentPath, e);
+        }
+        return e;
+    }
+
+    /**
+     * Represents an existing node. That is, a node which exists on the persistence layer.
+     */
+    private class Existing extends NodeDelta {
+        private final Path persistentPath;
+        private boolean isMoved;
+
+        Existing(NodeDelta parent, String name, Path persistentPath) {
+            super(parent, name);
+            this.persistentPath = persistentPath;
+        }
+
+        @Override
+        public Path getPersistentPath() {
+            return persistentPath;
+        }
+
+        @Override
+        public boolean isRemoved() {
+            return false;
+        }
+
+        @Override
+        public boolean isAdded() {
+            return false;
+        }
+
+        @Override
+        public boolean isMoved() {
+            return isMoved;
+        }
+
+        @Override
+        public boolean isTransient() {
+            return !isMoved;
+        }
+
+        @Override
+        public NodeDelta getNode(String name) {
+            NodeDelta delta = getChild(name);
+            if (delta == null) {
+                Path path = persistentPath.concat(name);
+                return nodeExists.evaluate(path)
+                        ? existing(this, name, path)
+                        : null;
+            }
+            else {
+                return delta.isRemoved() ? null : delta;
+            }
+        }
+
+        @Override
+        void touch() {
+            if (parent != null && ! parent.hasChild(name)) {
+                parent.addChild(this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Existing[" + getPath() + ']';
+        }
+
+        //------------------------------------------< internal >---
+
+        @Override
+        NodeDelta moveTo(Path parentPath, String name) {
+            isMoved = true;
+            return super.moveTo(parentPath, name);
+        }
+    }
+
+    /**
+     * Represents a transiently added node.
+     */
+    private class Added extends NodeDelta {
+        Added(NodeDelta parent, String name) {
+            super(parent, name);
+        }
+
+        @Override
+        public boolean isRemoved() {
+            return false;
+        }
+
+        @Override
+        public boolean isAdded() {
+            return true;
+        }
+
+        @Override
+        public boolean isMoved() {
+            return false;
+        }
+
+        @Override
+        public boolean isTransient() {
+            return true;
+        }
+
+        @Override
+        public NodeDelta getNode(String name) {
+            NodeDelta delta = getChild(name);
+            return delta == null || delta.isRemoved() ? null : delta;
+        }
+
+        @Override
+        public String toString() {
+            return "Added[" + getPath() + ']';
+        }
+    }
+
+    /**
+     * Represents a transiently removed node.
+     */
+    private class Removed extends NodeDelta {
+        Removed(NodeDelta parent, String name) {
+            super(parent, name);
+        }
+
+        @Override
+        public boolean isRemoved() {
+            return true;
+        }
+
+        @Override
+        public boolean isAdded() {
+            return false;
+        }
+
+        @Override
+        public boolean isMoved() {
+            return false;
+        }
+
+        @Override
+        public boolean isTransient() {
+            return true;
+        }
+
+        @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() {
+            throw new IllegalStateException("Removed");
+        }
+
+        @Override
+        NodeDelta moveTo(Path parentPath, String name) {
+            throw new IllegalStateException("Removed");
+        }
+
+        @Override
+        public String toString() {
+            return "Removed[" + getPath() + ']';
+        }
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/EmptyNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/EmptyNodeState.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/EmptyNodeState.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/EmptyNodeState.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,57 @@
+/*
+ * 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.oak.jcr.state;
+
+import org.apache.jackrabbit.oak.jcr.util.Iterators;
+import org.apache.jackrabbit.oak.model.AbstractNodeState;
+import org.apache.jackrabbit.oak.model.ChildNodeEntry;
+import org.apache.jackrabbit.oak.model.PropertyState;
+
+import java.util.Iterator;
+
+/**
+ * A {@code NodeState} implementation which is empty. That is, does not
+ * have properties nor child nodes.
+ */
+public final class EmptyNodeState extends AbstractNodeState {
+    public static final EmptyNodeState INSTANCE = new EmptyNodeState();
+    
+    private EmptyNodeState() { }
+
+    @Override
+    public Iterable<PropertyState> getProperties() {
+        return new Iterable<PropertyState>() {
+            @Override
+            public Iterator<PropertyState> iterator() {
+                return Iterators.empty();
+            }
+        };
+    }
+
+    @Override
+    public Iterable<ChildNodeEntry> getChildNodeEntries(long offset, int count) {
+        return new Iterable<ChildNodeEntry>() {
+            @Override
+            public Iterator<ChildNodeEntry> iterator() {
+                return Iterators.empty();
+            }
+        };
+    }
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/NodeStateProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/NodeStateProvider.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/NodeStateProvider.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/NodeStateProvider.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,94 @@
+/*
+ * 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.oak.jcr.state;
+
+import org.apache.commons.collections.map.LRUMap;
+import org.apache.jackrabbit.oak.jcr.SessionImpl.Context;
+import org.apache.jackrabbit.oak.jcr.configuration.RepositoryConfiguration;
+import org.apache.jackrabbit.oak.jcr.state.ChangeTree.NodeDelta;
+import org.apache.jackrabbit.oak.jcr.util.Path;
+import org.apache.jackrabbit.oak.jcr.util.Unchecked;
+
+import java.util.Map;
+
+public class NodeStateProvider {
+    private final Context sessionContext;
+    private final TransientSpace transientSpace;
+    private final Map<Path, TransientNodeState> cache;
+
+    public NodeStateProvider(Context sessionContext, TransientSpace transientSpace) {
+        this.sessionContext = sessionContext;
+        this.transientSpace = transientSpace;
+
+        RepositoryConfiguration config = sessionContext.getGlobalContext().getInstance(RepositoryConfiguration.class);
+        if (config.getNodeStateCacheSize() <= 0) {
+            cache = null;
+        }
+        else {
+            cache = Unchecked.cast(new LRUMap(config.getNodeStateCacheSize()));
+        }
+    }
+
+    public TransientNodeState getNodeState(Path path) {
+        TransientNodeState state = cache == null ? null : cache.get(path);
+        if (state == null) {
+            NodeDelta delta = transientSpace.getNodeDelta(path);
+            if (delta == null) {
+                return null;
+            }
+            state = new TransientNodeState(sessionContext, delta);
+            if (cache != null) {
+                cache.put(path, state);
+            }
+        }
+        return state;
+    }
+
+    public void release(Path path) {
+        if (cache != null) {
+            cache.remove(path);
+        }
+    }
+
+    public void clear() {
+        if (cache != null) {
+            cache.clear();
+        }
+    }
+
+    //------------------------------------------< internal/private >---
+
+    TransientNodeState getNodeState(NodeDelta nodeDelta) {
+        Path path = nodeDelta.getPath();
+        TransientNodeState state = cache == null ? null : cache.get(path);
+        if (state == null) {
+            state = new TransientNodeState(sessionContext, nodeDelta);
+            if (cache != null) {
+                cache.put(path, state);
+            }
+        }
+        return state;
+    }
+
+    NodeDelta getNodeDelta(Path path) {
+        return transientSpace.getNodeDelta(path);
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/PersistentNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/PersistentNodeState.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/PersistentNodeState.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/PersistentNodeState.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,189 @@
+/*
+ * 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.oak.jcr.state;
+
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.oak.jcr.json.JsonHandler;
+import org.apache.jackrabbit.oak.jcr.json.JsonParser;
+import org.apache.jackrabbit.oak.jcr.json.JsonTokenizer;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonArray;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.oak.jcr.json.Token;
+import org.apache.jackrabbit.oak.jcr.json.UnescapingJsonTokenizer;
+import org.apache.jackrabbit.oak.jcr.util.Function0;
+import org.apache.jackrabbit.oak.jcr.util.Path;
+import org.apache.jackrabbit.oak.model.AbstractChildNodeEntry;
+import org.apache.jackrabbit.oak.model.AbstractNodeState;
+import org.apache.jackrabbit.oak.model.ChildNodeEntry;
+import org.apache.jackrabbit.oak.model.NodeState;
+import org.apache.jackrabbit.oak.model.PropertyState;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@code NodeState} implementation on top of a {@code MicroKernel}.
+ */
+public class PersistentNodeState extends AbstractNodeState {
+    private final MicroKernel microkernel;
+    private final String revision;
+    private final Path path;
+
+    /**
+     * Create a new {@code NodeState} instance for the given {@code path} and {@code revision}.
+     *
+     * @param microkernel
+     * @param path
+     * @param revision
+     */
+    public PersistentNodeState(MicroKernel microkernel, Path path, String revision) {
+        this.microkernel = microkernel;
+        this.path = path;
+        this.revision = revision;
+    }
+
+    private final Function0<Map<String, PropertyStateImpl>> properties =
+        new Function0<Map<String, PropertyStateImpl>>() {
+            private Map<String, PropertyStateImpl> properties;
+
+            @Override
+            public Map<String, PropertyStateImpl> apply() {
+                if (properties == null) {
+                    properties = readProperties();
+                }
+                return properties;
+            }
+    };
+
+    @Override
+    public PropertyState getProperty(String name) {
+        return properties.apply().get(name);
+    }
+
+    @Override
+    public Iterable<? extends PropertyState> getProperties() {
+        return properties.apply().values();
+    }
+
+    @Override
+    public NodeState getChildNode(String name) {
+        if (microkernel.nodeExists(name, revision)) {
+            return new PersistentNodeState(microkernel, path.concat(name), revision);
+        }
+        else {
+            return null;
+        }
+    }
+
+    @Override
+    public long getChildNodeCount() {
+        JsonValue count = properties.apply().get(":childNodeCount").getValue();
+        long c = toLong(count);
+        if (c < 0) {
+            return super.getChildNodeCount();
+        }
+        else {
+            return c;
+        }
+    }
+
+    @Override
+    public Iterable<? extends ChildNodeEntry> getChildNodeEntries(final long offset, final int count) {
+        String json = microkernel.getNodes(path.toMkPath(), revision, 1, offset, count, null);
+        final List<ChildNodeEntry> childNodeEntries = new ArrayList<ChildNodeEntry>();
+
+        new JsonParser(new JsonHandler(){
+            @Override
+            public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+                super.object(parser, key, tokenizer);
+                childNodeEntries.add(createChildNodeEntry(key.text()));
+            }
+        }).parseObject(new UnescapingJsonTokenizer(json));
+
+        return childNodeEntries;
+    }
+
+    //------------------------------------------< private >---
+
+    private ChildNodeEntry createChildNodeEntry(final String name) {
+        return new AbstractChildNodeEntry() {
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            @Override
+            public NodeState getNode() {
+                return new PersistentNodeState(microkernel, path.concat(name), revision);
+            }
+
+            @Override
+            public String toString() {
+                return "ChildNodeEntry(" + name + ')';
+            }
+        };
+    }
+
+    private static long toLong(JsonValue count) {
+        if (count == null || !count.isAtom()) {
+            return -1;
+        }
+        else {
+            try {
+                return Long.parseLong(count.asAtom().value());
+            }
+            catch (NumberFormatException e) {
+                return -1;
+            }
+        }
+    }
+
+    private Map<String, PropertyStateImpl> readProperties() {
+        String json = microkernel.getNodes(path.toMkPath(), revision, 0, 0, -1, null);
+        final Map<String, PropertyStateImpl> properties = new HashMap<String, PropertyStateImpl>();
+
+        new JsonParser(new JsonHandler(){
+            JsonArray multiValue;
+
+            @Override
+            public void atom(Token key, Token value) {
+                if (multiValue == null) {
+                    properties.put(key.text(), new PropertyStateImpl(key.text(), new JsonAtom(value)));
+                }
+                else {
+                    multiValue.add(new JsonAtom(value));
+                }
+            }
+
+            @Override
+            public void array(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+                multiValue = new JsonArray();
+                super.array(parser, key, tokenizer);
+                properties.put(key.text(), new PropertyStateImpl(key.text(), multiValue));
+                multiValue = null;
+            }
+        }).parseObject(new UnescapingJsonTokenizer(json));
+
+        return properties;
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/PropertyStateImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/PropertyStateImpl.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/PropertyStateImpl.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/PropertyStateImpl.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,33 @@
+package org.apache.jackrabbit.oak.jcr.state;
+
+import org.apache.jackrabbit.oak.jcr.json.JsonValue;
+import org.apache.jackrabbit.oak.model.AbstractPropertyState;
+
+public class PropertyStateImpl extends AbstractPropertyState {
+private final String name;
+private final JsonValue value;
+
+public PropertyStateImpl(String name, JsonValue value) {
+    this.name = name;
+    this.value = value;
+}
+
+public JsonValue getValue() {
+    return value;
+}
+
+@Override
+public String getName() {
+    return name;
+}
+
+@Override
+public String getEncodedValue() {
+    return value.toJson();
+}
+
+@Override
+public String toString() {
+    return name + ':' + getEncodedValue();
+}
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/TransientNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/TransientNodeState.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/TransientNodeState.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/TransientNodeState.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,319 @@
+/*
+ * 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.oak.jcr.state;
+
+import org.apache.jackrabbit.oak.jcr.SessionImpl.Context;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.oak.jcr.state.ChangeTree.NodeDelta;
+import org.apache.jackrabbit.oak.jcr.util.Function1;
+import org.apache.jackrabbit.oak.jcr.util.Iterators;
+import org.apache.jackrabbit.oak.jcr.util.PagedIterator;
+import org.apache.jackrabbit.oak.jcr.util.Path;
+import org.apache.jackrabbit.oak.jcr.util.Predicate;
+import org.apache.jackrabbit.oak.model.ChildNodeEntry;
+import org.apache.jackrabbit.oak.model.NodeState;
+import org.apache.jackrabbit.oak.model.PropertyState;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.PathNotFoundException;
+import java.util.Iterator;
+
+import static org.apache.jackrabbit.oak.jcr.util.Iterators.toIterable;
+
+
+/**
+ * A {@code TransientNodeState} instance uses a {@code TransientSpace} to record changes
+ * to a {@code PersistedNodeState}.
+ */
+public class TransientNodeState {
+    private static final int BATCH_SIZE = 256;
+
+    private final Context sessionContext;
+
+    private String revision;
+    private NodeState persistentNodeState;
+    private NodeDelta nodeDelta;
+
+    TransientNodeState(Context sessionContext, NodeDelta nodeDelta) {
+        this.sessionContext = sessionContext;
+        this.nodeDelta = nodeDelta;
+    }
+
+    /**
+     * @return {@code true} iff this is the root node
+     */
+    public boolean isRoot() {
+        return getPath().isRoot();
+    }
+
+    /**
+     * @return the path of this node
+     */
+    public Path getPath() {
+        return getNodeDelta().getPath();
+    }
+
+    /**
+     * @return the name of this node
+     */
+    public String getName() {
+        return getPath().getName();
+    }
+
+    /**
+     * @return {@code true} iff this node has been transiently added.
+     */
+    public boolean isNew() {
+        NodeDelta delta = getNodeDelta();
+        return delta.isTransient() && !delta.isRemoved();
+    }
+
+    /**
+     * @return {@code true} iff this node has been transiently modified.
+     */
+    public boolean isModified() {
+        return getNodeDelta().isTransient();
+    }
+
+    /**
+     * Transiently add a node with the given {@code name}.
+     * @param name
+     * @return the added node
+     * @throws javax.jcr.ItemExistsException if a node with that name exists already.
+     */
+    public TransientNodeState addNode(String name) throws ItemExistsException {
+        NodeDelta child = getNodeDelta().addNode(name);
+        return getNodeState(child);
+    }
+
+    /**
+     * Transiently remove this node.
+     * @throws javax.jcr.ItemNotFoundException if this node has been removed already
+     */
+    public void remove() throws ItemNotFoundException {
+        getNodeStateProvider().release(getPath());
+        getNodeDelta().getParent().removeNode(getName());
+    }
+
+    /**
+     * Transiently move this node.
+     * @param name  name of this node at its {@code destination}
+     * @param destination
+     * @throws javax.jcr.ItemExistsException  {@code name} exists at {@code destination}
+     * @throws javax.jcr.PathNotFoundException  {@code destination} does not exist
+     * @throws javax.jcr.ItemNotFoundException  {@code name} does not exist
+     */
+    public void move(String name, Path destination) throws ItemExistsException, PathNotFoundException,
+            ItemNotFoundException {
+
+        getNodeDelta().moveNode(name, destination);
+        getNodeStateProvider().release(getPath().concat(name));
+    }
+
+    /**
+     * Transiently set a property.
+     * @param name  Name of the property.
+     * @param value  Value of the property. Use {@code null} or {@code JsonAtom.NULL}
+     *               to remove the property.
+     */
+    public void setProperty(String name, JsonValue value) {
+        getNodeDelta().setValue(name, value);
+    }
+
+    /**
+     * @return {@code true} iff this instance has child nodes.
+     */
+    public boolean hasNodes() {
+        return getNodes().hasNext();
+    }
+
+    /**
+     * @return Iterator of all child node states of this instance.
+     */
+    public Iterator<TransientNodeState> getNodes() {
+        Iterator<? extends ChildNodeEntry> persistedEntries = Iterators.flatten(
+            new PagedIterator<ChildNodeEntry>(BATCH_SIZE) {
+                @Override
+                protected Iterator<? extends ChildNodeEntry> getPage(long pos, int size) {
+                    return getPersistentNodeState().getChildNodeEntries(pos, size).iterator();
+                }
+            });
+
+        final NodeDelta delta = getNodeDelta();
+
+        Iterator<ChildNodeEntry> unmodifiedEntries = Iterators.filter(persistedEntries,
+            new Predicate<ChildNodeEntry>() {
+                @Override
+                public boolean evaluate(ChildNodeEntry entry) {
+                    return !delta.isNodeModified(entry.getName());
+                }
+            });
+
+        Iterator<TransientNodeState> unmodifiedStates = Iterators.map(unmodifiedEntries,
+            new Function1<ChildNodeEntry, TransientNodeState>() {
+                @Override
+                public TransientNodeState apply(ChildNodeEntry entry) {
+                    return getNodeState(delta.getNode(entry.getName()));
+                }
+            });
+
+        Iterator<TransientNodeState> modifiedStates = Iterators.map(toIterable(delta.getNodes()).iterator(),
+            new Function1<NodeDelta, TransientNodeState>() {
+                @Override
+                public TransientNodeState apply(NodeDelta delta) {
+                    return getNodeState(delta);
+                }
+            });
+
+        return Iterators.chain(unmodifiedStates, modifiedStates);
+    }
+
+    /**
+     * @return {@code true} iff this instance has properties
+     */
+    public boolean hasProperties() {
+        return getProperties().hasNext();
+    }
+
+    /**
+     * @return Iterator of all property states of this instance.
+     */
+    public Iterator<PropertyState> getProperties() {
+        Iterable<? extends PropertyState> propertyStates = getPersistentNodeState().getProperties();
+        final NodeDelta delta = getNodeDelta();
+
+        Iterator<PropertyState> propertyEntries =
+            Iterators.filter(propertyStates.iterator(),
+                new Predicate<PropertyState>() {
+                    @Override
+                    public boolean evaluate(PropertyState state) {
+                        return !state.getName().startsWith(":") && !delta.hasProperty(state.getName());
+                    }
+                });
+
+        Iterator<PropertyState> modifiedProperties = delta.getPropertyStates();
+        return Iterators.chain(propertyEntries, Iterators.toIterable(modifiedProperties).iterator());
+    }
+
+    /**
+     * @param name  name of the property
+     * @return  value of the property named {@code name}.
+     * @throws javax.jcr.ItemNotFoundException  if no such property exists.
+     */
+    public JsonValue getPropertyValue(String name) throws ItemNotFoundException {
+        JsonValue value = getPropertyValueOrNull(name);
+        if (value == null) {
+            throw new ItemNotFoundException(name);
+        }
+
+        return value;
+    }
+
+    /**
+     * @param name name of the property
+     * @return {@code true} iff this instance has a property name {@code name}.
+     */
+    public boolean hasProperty(String name) {
+        return getPropertyValueOrNull(name) != null;
+    }
+
+    /**
+     * @param name name of the property
+     * @return {@code true} iff the property named {@code name} has been transiently added.
+     */
+    public boolean isPropertyNew(String name) {
+        JsonValue value = getNodeDelta().getPropertyValue(name);
+        return value != null && !value.isNull() && getPersistedPropertyValue(name) == null;
+    }
+
+    /**
+     * @param name name of the property
+     * @return {@code true} iff the property named {@code name} has been transiently modified.
+     */
+    public boolean isPropertyModified(String name) {
+        return getNodeDelta().hasProperty(name);
+    }
+
+    /**
+     * Transiently remove a property.
+     * @param name  name of the property to remove.
+     */
+    public void removeProperty(String name) {
+        getNodeDelta().setValue(name, null);
+    }
+
+    @Override
+    public String toString() {
+        return "TransientNodeState(" + getPath().toString() + ')';
+    }
+
+    //------------------------------------------< private >---
+
+    private NodeStateProvider getNodeStateProvider() {
+        return sessionContext.getNodeStateProvider();
+    }
+    
+    private TransientNodeState getNodeState(NodeDelta nodeDelta) {
+        return getNodeStateProvider().getNodeState(nodeDelta);
+    }
+
+    private JsonValue getPropertyValueOrNull(String name) {
+        JsonValue value = getNodeDelta().getPropertyValue(name);
+        if (value == null) {
+            return getPersistedPropertyValue(name);
+        }
+        else {
+            return value == JsonAtom.NULL ? null : value;
+        }
+    }
+
+    private JsonValue getPersistedPropertyValue(String name) {
+        PropertyState state = getPersistentNodeState().getProperty(name);
+        if (state == null) {
+            return null;
+        }
+        else {
+            return ((PropertyStateImpl) state).getValue();  // fixme: don't cast
+        }
+    }
+
+    private synchronized NodeState getPersistentNodeState() {
+        Path path = getNodeDelta().getPersistentPath();
+        String baseRevision = sessionContext.getRevision();
+        if (persistentNodeState == null || !revision.equals(baseRevision)) {
+            revision = baseRevision;
+            if (path == null) {
+                persistentNodeState = EmptyNodeState.INSTANCE;
+            }
+            else {
+                persistentNodeState = new PersistentNodeState(sessionContext.getMicrokernel(), path, revision);
+            }
+        }
+
+        return persistentNodeState;
+    }
+
+    private NodeDelta getNodeDelta() {
+        return nodeDelta = getNodeStateProvider().getNodeDelta(nodeDelta.getPath());
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/TransientSpace.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/TransientSpace.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/TransientSpace.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/TransientSpace.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,144 @@
+/*
+ * 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.oak.jcr.state;
+
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.api.MicroKernelException;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue;
+import org.apache.jackrabbit.oak.jcr.state.ChangeTree.Listener;
+import org.apache.jackrabbit.oak.jcr.state.ChangeTree.NodeDelta;
+import org.apache.jackrabbit.oak.jcr.util.Path;
+import org.apache.jackrabbit.oak.jcr.util.Predicate;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * {@code TransientSpace} instances use a {@link org.apache.jackrabbit.state.ChangeTree} to
+ * record transient changes in a JCR hierarchy. Changes can be persisted by calling
+ * {@link #save()}. A transient space is bound to a specific revision. Calling
+ * {@link #refresh(boolean)} updates the revision to the latest.
+ */
+public class TransientSpace {
+    private final MicroKernel microkernel;
+    private final String workspace;
+    private final ChangeLog changeLog = new ChangeLog();
+    private final Listener changeTreeListener = new Listener() {
+        @Override
+        public void added(NodeDelta nodeDelta) {
+            changeLog.addNode(nodeDelta.getPath());
+        }
+
+        @Override
+        public void removed(NodeDelta nodeDelta) {
+            changeLog.removeNode(nodeDelta.getPath());
+        }
+
+        @Override
+        public void moved(Path source, NodeDelta nodeDelta) {
+            changeLog.moveNode(source, nodeDelta.getPath());
+        }
+
+        @Override
+        public void setValue(NodeDelta parent, String name, JsonValue value) {
+            changeLog.setProperty(parent.getPath(), name, value);
+        }
+    };
+
+    private ChangeTree changeTree;
+    private String revision;
+
+    /**
+     * Create a new transient space for the given {@code workspace}, {@code microkernel}
+     * and {@code revision}.
+     * @param workspace
+     * @param microkernel
+     * @param revision
+     */
+    public TransientSpace(final String workspace, final MicroKernel microkernel, final String revision) {
+        this.microkernel = microkernel;
+        this.workspace = workspace;
+        this.revision = revision;
+
+        changeTree = createChangeTree(workspace, changeTreeListener);
+    }
+
+    /**
+     * @param path
+     * @return the node delta for the given {@code path}. This is either a persisted node in the
+     * revision currently bound to this transient space or a transient node or {@code null}
+     * if no such node delta exists.
+     */
+    public NodeDelta getNodeDelta(Path path) {
+        return changeTree.getNode(path);
+    }
+
+    /**
+     * Atomically persist all transient changes
+     * @return  the new revision resulting from saving all transient changes.
+     * @throws javax.jcr.RepositoryException
+     */
+    public String save() throws RepositoryException {
+        try {
+            revision = microkernel.commit("", changeLog.toJsop(), revision, "");
+            changeLog.clear();
+            changeTree = createChangeTree(workspace, changeTreeListener);
+            return revision;
+        }
+        catch (MicroKernelException e) {
+            throw new RepositoryException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Refresh to the latest revision of the persistence store. If {@code keepChanges}
+     * is {@code true} transient changes are kept, other wise transient changes are discarded.
+     * <em>Note</em>: Keeping transient changes might cause conflicts on subsequent save operations.
+     * @param keepChanges
+     * @return the new revision
+     */
+    public String refresh(boolean keepChanges) {
+        revision = microkernel.getHeadRevision();
+        if (!keepChanges) {
+            changeLog.clear();
+            changeTree = createChangeTree(workspace, changeTreeListener);
+        }
+
+        return revision;
+    }
+
+    /**
+     * @return {@code true} iff the transient space contains transient changes.
+     */
+    public boolean isDirty() {
+        return changeTree.hasChanges();
+    }
+
+    //------------------------------------------< private >---
+
+    private ChangeTree createChangeTree(final String workspace, Listener listener) {
+        return new ChangeTree(Path.create(workspace), listener, new Predicate<Path>() {
+            @Override
+            public boolean evaluate(Path path) {
+                return microkernel.nodeExists(path.toMkPath(), revision);
+            }
+        });
+    }
+
+}

Copied: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Function0.java (from r1299739, jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function0.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Function0.java?p2=jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Function0.java&p1=jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function0.java&r1=1299739&r2=1300973&rev=1300973&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function0.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Function0.java Thu Mar 15 13:33:32 2012
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.jackrabbit.utils;
+package org.apache.jackrabbit.oak.jcr.util;
 
 /**
  * 0-ary function. That is, a constant.

Copied: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Function1.java (from r1299713, jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function1.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Function1.java?p2=jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Function1.java&p1=jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function1.java&r1=1299713&r2=1300973&rev=1300973&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Function1.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Function1.java Thu Mar 15 13:33:32 2012
@@ -14,7 +14,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.jackrabbit.utils;
+package org.apache.jackrabbit.oak.jcr.util;
 
 /**
  * Type safe counter part of {@link org.apache.commons.collections.Transformer}.

Copied: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ItemNameMatcher.java (from r1299713, jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/ItemNameMatcher.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ItemNameMatcher.java?p2=jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ItemNameMatcher.java&p1=jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/ItemNameMatcher.java&r1=1299713&r2=1300973&rev=1300973&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/ItemNameMatcher.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ItemNameMatcher.java Thu Mar 15 13:33:32 2012
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.jackrabbit.utils;
+package org.apache.jackrabbit.oak.jcr.util;
 
 import java.util.StringTokenizer;
 

Copied: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Iterators.java (from r1299781, jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Iterators.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Iterators.java?p2=jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Iterators.java&p1=jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Iterators.java&r1=1299781&r2=1300973&rev=1300973&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Iterators.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Iterators.java Thu Mar 15 13:33:32 2012
@@ -14,7 +14,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.jackrabbit.utils;
+package org.apache.jackrabbit.oak.jcr.util;
 
 import org.apache.commons.collections.iterators.ArrayIterator;
 import org.apache.commons.collections.iterators.EmptyIterator;

Copied: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/PagedIterator.java (from r1299746, jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/PagedIterator.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/PagedIterator.java?p2=jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/PagedIterator.java&p1=jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/PagedIterator.java&r1=1299746&r2=1300973&rev=1300973&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/PagedIterator.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/PagedIterator.java Thu Mar 15 13:33:32 2012
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.jackrabbit.utils;
+package org.apache.jackrabbit.oak.jcr.util;
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;

Copied: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Path.java (from r1299713, jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/Path.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Path.java?p2=jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Path.java&p1=jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/Path.java&r1=1299713&r2=1300973&rev=1300973&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/Path.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Path.java Thu Mar 15 13:33:32 2012
@@ -17,7 +17,8 @@
  * under the License.
  */
 
-package org.apache.jackrabbit;
+package org.apache.jackrabbit.oak.jcr.util;
+
 
 import org.apache.jackrabbit.mk.util.PathUtils;
 

Copied: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Predicate.java (from r1299713, jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Predicate.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Predicate.java?p2=jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Predicate.java&p1=jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Predicate.java&r1=1299713&r2=1300973&rev=1300973&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Predicate.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Predicate.java Thu Mar 15 13:33:32 2012
@@ -14,7 +14,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.jackrabbit.utils;
+package org.apache.jackrabbit.oak.jcr.util;
 
 /**
  * Type safe counter part of {@link org.apache.commons.collections.Predicate}.

Copied: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Unchecked.java (from r1299739, jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Unchecked.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Unchecked.java?p2=jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Unchecked.java&p1=jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Unchecked.java&r1=1299739&r2=1300973&rev=1300973&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/Unchecked.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/Unchecked.java Thu Mar 15 13:33:32 2012
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.jackrabbit.utils;
+package org.apache.jackrabbit.oak.jcr.util;
 
 /**
  * Utility class for hiding casts.

Copied: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ValueConverter.java (from r1299713, jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/ValueConverter.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ValueConverter.java?p2=jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ValueConverter.java&p1=jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/ValueConverter.java&r1=1299713&r2=1300973&rev=1300973&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/utils/ValueConverter.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ValueConverter.java Thu Mar 15 13:33:32 2012
@@ -17,11 +17,11 @@
  * under the License.
  */
 
-package org.apache.jackrabbit.utils;
+package org.apache.jackrabbit.oak.jcr.util;
 
-import org.apache.jackrabbit.json.JsonValue;
-import org.apache.jackrabbit.json.JsonValue.JsonArray;
-import org.apache.jackrabbit.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonArray;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonAtom;
 
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;



Mime
View raw message