You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by mr...@apache.org on 2013/05/15 18:57:37 UTC

svn commit: r1482953 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ oak-jcr/src/main/java/...

Author: mreutegg
Date: Wed May 15 16:57:37 2013
New Revision: 1482953

URL: http://svn.apache.org/r1482953
Log:
OAK-168: Basic JCR VersionManager support
- Restore (work in progress)

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java   (contents, props changed)
      - copied, changed from r1482261, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionType.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionSelector.java   (with props)
    jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/RestoreTest.java   (with props)
Removed:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionType.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ReadWriteVersionManager.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/Utils.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionableState.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionContext.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionDelegate.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionManagerImpl.java

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java?rev=1482953&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java Wed May 15 16:57:37 2013
@@ -0,0 +1,86 @@
+/*
+ * 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.plugins.version;
+
+import java.util.Calendar;
+
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+
+/**
+ * <i>Inspired by Jackrabbit 2.x</i>
+ * <p/>
+ * This Class implements a version selector that selects a version by creation
+ * date. The selected version is the latest that is older or equal than the
+ * given date. If no version could be found <code>null</code> is returned
+ * unless the <code>returnLatest</code> flag is set to <code>true</code>, where
+ * the latest version is returned.
+ * <pre>
+ * V1.0 - 02-Sep-2006
+ * V1.1 - 03-Sep-2006
+ * V1.2 - 05-Sep-2006
+ *
+ * new DateVersionSelector("03-Sep-2006").select() -> V1.1
+ * new DateVersionSelector("04-Sep-2006").select() -> V1.1
+ * new DateVersionSelector("01-Sep-2006").select() -> null
+ * new DateVersionSelector("01-Sep-2006", true).select() -> V1.2
+ * new DateVersionSelector(null, true).select() -> V1.2
+ * </pre>
+ */
+public class DateVersionSelector implements VersionSelector {
+
+    /**
+     * a version date hint
+     */
+    private final long timestamp;
+
+    /**
+     * Creates a <code>DateVersionSelector</code> that will select the latest
+     * version of all those that are older than the given timestamp.
+     *
+     * @param timestamp reference timestamp
+     */
+    public DateVersionSelector(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    @Override
+    public NodeBuilder select(@Nonnull NodeBuilder history)
+            throws RepositoryException {
+        long latestDate = Long.MIN_VALUE;
+        NodeBuilder latestVersion = null;
+        for (String name: history.getChildNodeNames()) {
+            NodeBuilder v = history.getChildNode(name);
+            if (name.equals(JcrConstants.JCR_ROOTVERSION)) {
+                // ignore root version
+                continue;
+            }
+            long c = v.getProperty(JcrConstants.JCR_CREATED).getValue(Type.DATE);
+            if (c > latestDate && c <= timestamp) {
+                latestDate = c;
+                latestVersion = v;
+            }
+        }
+        return latestVersion;
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ReadWriteVersionManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ReadWriteVersionManager.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ReadWriteVersionManager.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ReadWriteVersionManager.java Wed May 15 16:57:37 2013
@@ -56,7 +56,6 @@ import org.apache.jackrabbit.oak.core.Im
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-import org.apache.jackrabbit.oak.util.TODO;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -161,8 +160,30 @@ class ReadWriteVersionManager extends Re
         createVersion(history, versionable);
     }
 
-    public void restore(NodeBuilder versionable) {
-        TODO.unimplemented();
+    public void restore(@Nonnull NodeBuilder versionable,
+                        @Nonnull String versionUUID)
+            throws CommitFailedException {
+        NodeBuilder history = getOrCreateVersionHistory(versionable);
+        // FIXME: inefficient because it iterates over all versions (worst case)
+        NodeBuilder version = null;
+        for (String name : history.getChildNodeNames()) {
+            if (name.equals(JCR_VERSIONLABELS)) {
+                continue;
+            }
+            NodeBuilder child = history.getChildNode(name);
+            if (versionUUID.equals(uuidFromNode(child))) {
+                version = child;
+            }
+        }
+        if (version == null) {
+            throw new CommitFailedException(CommitFailedException.VERSION,
+                    VersionExceptionCode.NO_SUCH_VERSION.ordinal(),
+                    "The VersionHistory with UUID: " + uuidFromNode(versionable) +
+                            " does not have a Version with UUID: " + versionUUID);
+        }
+        VersionableState versionableState = VersionableState.forRestore(
+                version, versionable, ntMgr);
+        versionableState.restore();
     }
 
     // TODO: more methods that modify versions

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/Utils.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/Utils.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/Utils.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/Utils.java Wed May 15 16:57:37 2013
@@ -25,6 +25,7 @@ import org.apache.jackrabbit.oak.api.Typ
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
 
 /**
@@ -48,4 +49,23 @@ public class Utils {
         }
         return p.getValue(Type.STRING);
     }
+
+    /**
+     * Returns the <code>jcr:primaryType</code> value of the given
+     * <code>node</code>.
+     *
+     * @param node a node.
+     * @return the <code>jcr:primaryType</code> value.
+     * @throws IllegalStateException if the node does not have a <code>jcr:primaryType</code>
+     *                               property.
+     */
+    @Nonnull
+    static String primaryTypeOf(@Nonnull NodeBuilder node)
+            throws IllegalStateException {
+        String primaryType = node.getName(JCR_PRIMARYTYPE);
+        if (primaryType == null) {
+            throw new IllegalStateException("Node does not have a jcr:primaryType");
+        }
+        return primaryType;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java Wed May 15 16:57:37 2013
@@ -86,6 +86,14 @@ class VersionEditor implements Editor {
     @Override
     public void propertyAdded(PropertyState after)
             throws CommitFailedException {
+        if (after.getName().equals(JcrConstants.JCR_BASEVERSION)
+                && this.after.hasProperty(JcrConstants.JCR_VERSIONHISTORY)
+                && !this.after.hasProperty(JcrConstants.JCR_ISCHECKEDOUT)
+                && !this.before.exists()) {
+            // sentinel node for restore
+            vMgr.restore(node, after.getValue(Type.REFERENCE));
+            return;
+        }
         if (!wasReadOnly) {
             return;
         }
@@ -116,7 +124,7 @@ class VersionEditor implements Editor {
                 vMgr.checkin(node);
             }
         } else if (propName.equals(VersionConstants.JCR_BASEVERSION)) {
-            vMgr.restore(node);
+            vMgr.restore(node, after.getValue(Type.REFERENCE));
         } else if (isVersionProperty(after)) {
             throwProtected(after.getName());
         } else if (wasReadOnly) {
@@ -191,7 +199,7 @@ class VersionEditor implements Editor {
     private static void throwCheckedIn(String msg)
             throws CommitFailedException {
         throw new CommitFailedException(CommitFailedException.VERSION,
-                VersionExceptionType.NODE_CHECKED_IN.ordinal(), msg);
+                VersionExceptionCode.NODE_CHECKED_IN.ordinal(), msg);
     }
 
     private static void throwProtected(String name)

Copied: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java (from r1482261, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionType.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java?p2=jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java&p1=jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionType.java&r1=1482261&r2=1482953&rev=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionType.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java Wed May 15 16:57:37 2013
@@ -19,16 +19,19 @@
 package org.apache.jackrabbit.oak.plugins.version;
 
 /**
- * <code>VersionExceptionType</code> contains the states for version related
+ * <code>VersionExceptionCode</code> contains the codes for version related
  * commit failures.
  */
-public enum VersionExceptionType {
+public enum VersionExceptionCode {
 
-    NODE_CHECKED_IN("Node is checked in");
+    UNEXPECTED_REPOSITORY_EXCEPTION("Unexpected RepositoryException"),
+    NODE_CHECKED_IN("Node is checked in"),
+    NO_SUCH_VERSION("No such Version"),
+    OPV_ABORT_ITEM_PRESENT("Item with OPV ABORT action present");
 
     private final String desc;
 
-    private VersionExceptionType(String desc) {
+    private VersionExceptionCode(String desc) {
         this.desc = desc;
     }
 

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionSelector.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionSelector.java?rev=1482953&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionSelector.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionSelector.java Wed May 15 16:57:37 2013
@@ -0,0 +1,66 @@
+/*
+ * 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.plugins.version;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+
+/**
+ * <i>Inspired by Jackrabbit 2.x</i>
+ * <p/>
+ * This Interface defines the version selector that needs to provide a version,
+ * given some hints and a version history. the selector is used in the various
+ * restore methods in order to select the correct version of previously versioned
+ * OPV=Version children upon restore. JSR170 states: <em>"This determination
+ * [of the version] depends on the configuration of the workspace and is outside
+ * the scope of this specification."</em>
+ * <p/>
+ * The version selection in jackrabbit works as follows:<br/>
+ * The <code>Node.restore()</code> methods uses the
+ * {@link DateVersionSelector} which is initialized with the creation date of
+ * the parent version. This selector selects the latest version that is equal
+ * or older than the given date. if no such version exists, the initial one
+ * is restored.<br/>
+ * The <code>Node.restoreByLabel()</code> uses the
+ * {@link LabelVersionSelector} which is initialized with the label of the
+ * version to be restored. This selector selects the version with the same
+ * label. if no such version exists, the initial one is restored.
+ * <p/>
+ *
+ * @see DateVersionSelector
+ * @see LabelVersionSelector
+ * @see javax.jcr.version.VersionManager#restore
+ */
+public interface VersionSelector {
+
+    /**
+     * Selects a version of the given version history. If this VersionSelector
+     * is unable to select one, it can return <code>null</code>. Please note,
+     * that a version selector is not allowed to return the root version.
+     *
+     * @param versionHistory version history to select a version from
+     * @return A version or <code>null</code>.
+     * @throws RepositoryException if an error occurs.
+     */
+    @CheckForNull
+    NodeBuilder select(@Nonnull NodeBuilder versionHistory) throws RepositoryException;
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionSelector.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionSelector.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionableState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionableState.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionableState.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionableState.java Wed May 15 16:57:37 2013
@@ -19,22 +19,38 @@
 package org.apache.jackrabbit.oak.plugins.version;
 
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.annotation.Nonnull;
 import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.nodetype.PropertyDefinition;
 import javax.jcr.version.OnParentVersionAction;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static javax.jcr.version.OnParentVersionAction.ABORT;
+import static javax.jcr.version.OnParentVersionAction.COMPUTE;
+import static javax.jcr.version.OnParentVersionAction.COPY;
+import static javax.jcr.version.OnParentVersionAction.IGNORE;
+import static javax.jcr.version.OnParentVersionAction.INITIALIZE;
+import static javax.jcr.version.OnParentVersionAction.VERSION;
+import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION;
+import static org.apache.jackrabbit.JcrConstants.JCR_CREATED;
 import static org.apache.jackrabbit.JcrConstants.JCR_FROZENMIXINTYPES;
 import static org.apache.jackrabbit.JcrConstants.JCR_FROZENNODE;
 import static org.apache.jackrabbit.JcrConstants.JCR_FROZENPRIMARYTYPE;
 import static org.apache.jackrabbit.JcrConstants.JCR_FROZENUUID;
+import static org.apache.jackrabbit.JcrConstants.JCR_ISCHECKEDOUT;
 import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_PREDECESSORS;
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
 import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONHISTORY;
 import static org.apache.jackrabbit.JcrConstants.MIX_VERSIONABLE;
 import static org.apache.jackrabbit.JcrConstants.NT_FROZENNODE;
+import static org.apache.jackrabbit.JcrConstants.NT_VERSIONEDCHILD;
+import static org.apache.jackrabbit.oak.plugins.version.Utils.primaryTypeOf;
 import static org.apache.jackrabbit.oak.plugins.version.Utils.uuidFromNode;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
@@ -42,17 +58,37 @@ import org.apache.jackrabbit.oak.api.Pro
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.core.IdentifierManager;
 import org.apache.jackrabbit.oak.core.ReadOnlyTree;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
 import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.util.TODO;
 
 /**
  * <code>VersionableState</code> provides methods to create a versionable state
  * for a version based on a versionable node.
+ * <p>
+ * The restore implementation of this class does not handle the removeExisting
+ * flag. It is expected that this is handled on a higher level. If this is not
+ * done the uniqueness constraint on the jcr:uuid will kick in and fail the
+ * commit.
+ * </p>
  */
 class VersionableState {
 
     private static final String JCR_CHILDVERSIONHISTORY = "jcr:childVersionHistory";
+    private static final Set<String> BASIC_PROPERTIES = new HashSet<String>();
+    private static final Set<String> BASIC_FROZEN_PROPERTIES = new HashSet<String>();
 
+    static {
+        BASIC_PROPERTIES.add(JCR_PRIMARYTYPE);
+        BASIC_PROPERTIES.add(JCR_UUID);
+        BASIC_PROPERTIES.add(JCR_MIXINTYPES);
+        BASIC_FROZEN_PROPERTIES.add(JCR_FROZENPRIMARYTYPE);
+        BASIC_FROZEN_PROPERTIES.add(JCR_FROZENUUID);
+        BASIC_FROZEN_PROPERTIES.add(JCR_FROZENMIXINTYPES);
+    }
+
+    private final NodeBuilder version;
     private final NodeBuilder frozenNode;
     private final NodeBuilder versionable;
     private final ReadOnlyNodeTypeManager ntMgr;
@@ -60,22 +96,10 @@ class VersionableState {
     private VersionableState(@Nonnull NodeBuilder version,
                              @Nonnull NodeBuilder versionable,
                              @Nonnull ReadOnlyNodeTypeManager ntMgr) {
-        this.frozenNode = checkNotNull(version).child(JCR_FROZENNODE);
+        this.version = checkNotNull(version);
+        this.frozenNode = version.child(JCR_FROZENNODE);
         this.versionable = checkNotNull(versionable);
         this.ntMgr = checkNotNull(ntMgr);
-        // initialize jcr:frozenNode
-        frozenNode.setProperty(JCR_UUID, IdentifierManager.generateUUID(), Type.STRING);
-        frozenNode.setProperty(JCR_PRIMARYTYPE, NT_FROZENNODE, Type.NAME);
-        Iterable<String> mixinTypes;
-        if (versionable.hasProperty(JCR_MIXINTYPES)) {
-            mixinTypes = versionable.getNames(JCR_MIXINTYPES);
-        } else {
-            mixinTypes = Collections.emptyList();
-        }
-        frozenNode.setProperty(JCR_FROZENMIXINTYPES, mixinTypes, Type.NAMES);
-        frozenNode.setProperty(JCR_FROZENPRIMARYTYPE,
-                versionable.getName(JCR_PRIMARYTYPE), Type.NAME);
-        frozenNode.setProperty(JCR_FROZENUUID, uuidFromNode(versionable), Type.STRING);
     }
 
     /**
@@ -92,12 +116,44 @@ class VersionableState {
     static VersionableState fromVersion(@Nonnull NodeBuilder version,
                                         @Nonnull NodeBuilder versionable,
                                         @Nonnull ReadOnlyNodeTypeManager ntMgr) {
+        return new VersionableState(version, versionable, ntMgr).init();
+    }
+
+    /**
+     * Creates a versionable state for a restore.
+     *
+     * @param version the version to restore.
+     * @param versionable the versionable node.
+     * @param ntMgr the node type manager.
+     * @return a versionable state.
+     */
+    static VersionableState forRestore(@Nonnull NodeBuilder version,
+                                       @Nonnull NodeBuilder versionable,
+                                       @Nonnull ReadOnlyNodeTypeManager ntMgr) {
         return new VersionableState(version, versionable, ntMgr);
     }
 
-    @Nonnull
-    NodeBuilder getFrozenNode() {
-        return frozenNode;
+    /**
+     * Creates a frozen node under the version and initializes it with the basic
+     * frozen properties (jcr:frozenPrimaryType, jcr:frozenMixinTypes and
+     * jcr:frozenUuid) from the given versionable node.
+     *
+     * @return this versionable state.
+     */
+    private VersionableState init() {
+        // initialize jcr:frozenNode
+        frozenNode.setProperty(JCR_UUID, IdentifierManager.generateUUID(), Type.STRING);
+        frozenNode.setProperty(JCR_PRIMARYTYPE, NT_FROZENNODE, Type.NAME);
+        Iterable<String> mixinTypes;
+        if (versionable.hasProperty(JCR_MIXINTYPES)) {
+            mixinTypes = versionable.getNames(JCR_MIXINTYPES);
+        } else {
+            mixinTypes = Collections.emptyList();
+        }
+        frozenNode.setProperty(JCR_FROZENMIXINTYPES, mixinTypes, Type.NAMES);
+        frozenNode.setProperty(JCR_FROZENPRIMARYTYPE, primaryTypeOf(versionable), Type.NAME);
+        frozenNode.setProperty(JCR_FROZENUUID, uuidFromNode(versionable), Type.STRING);
+        return this;
     }
 
     /**
@@ -112,15 +168,200 @@ class VersionableState {
             createState(versionable, frozenNode);
             return frozenNode;
         } catch (RepositoryException e) {
-            throw new CommitFailedException(CommitFailedException.VERSION, 0,
+            throw new CommitFailedException(CommitFailedException.VERSION,
+                    VersionExceptionCode.UNEXPECTED_REPOSITORY_EXCEPTION.ordinal(),
+                    "Unexpected RepositoryException", e);
+        }
+    }
+
+    /**
+     * Restore the versionable node to the given version.
+     *
+     * @return the versionable node.
+     * @throws CommitFailedException if the operation fails.
+     */
+    public NodeBuilder restore() throws CommitFailedException {
+        try {
+            long created = version.getProperty(JCR_CREATED).getValue(Type.DATE);
+            VersionSelector selector = new DateVersionSelector(created);
+            restoreFrozen(frozenNode, versionable, selector);
+            restoreVersionable(versionable, version);
+            return versionable;
+        } catch (RepositoryException e) {
+            throw new CommitFailedException(CommitFailedException.VERSION,
+                    VersionExceptionCode.UNEXPECTED_REPOSITORY_EXCEPTION.ordinal(),
                     "Unexpected RepositoryException", e);
         }
     }
 
+    //--------------------------< internal >------------------------------------
+
+    private void restoreState(@Nonnull NodeBuilder src,
+                              @Nonnull NodeBuilder dest,
+                              @Nonnull VersionSelector selector)
+            throws RepositoryException, CommitFailedException {
+        String primaryType = primaryTypeOf(src);
+        if (primaryType.equals(NT_FROZENNODE)) {
+            restoreFrozen(src, dest, selector);
+        } else if (primaryType.equals(NT_VERSIONEDCHILD)) {
+            restoreVersionedChild(src, dest, selector);
+        } else {
+            restoreNode(src, dest, selector);
+        }
+    }
+
+    /**
+     * Restore a nt:frozenNode.
+     */
+    private void restoreFrozen(NodeBuilder frozen,
+                               NodeBuilder dest,
+                               VersionSelector selector)
+            throws RepositoryException, CommitFailedException {
+        // 15.7.2 Restoring Type and Identifier
+        dest.setProperty(JCR_PRIMARYTYPE,
+                frozen.getName(JCR_FROZENPRIMARYTYPE), Type.NAME);
+        dest.setProperty(JCR_UUID,
+                frozen.getProperty(JCR_FROZENUUID).getValue(Type.STRING),
+                Type.STRING);
+        if (frozen.hasProperty(JCR_FROZENMIXINTYPES)) {
+            dest.setProperty(JCR_MIXINTYPES,
+                    frozen.getNames(JCR_FROZENMIXINTYPES), Type.NAMES);
+        }
+        // 15.7.3 Restoring Properties
+        for (PropertyState p : frozen.getProperties()) {
+            if (BASIC_FROZEN_PROPERTIES.contains(p.getName())) {
+                // ignore basic frozen properties we restored earlier
+                continue;
+            }
+            int action = getOPV(dest, p);
+            if (action == COPY || action == VERSION) {
+                dest.setProperty(p);
+            }
+        }
+        for (PropertyState p : dest.getProperties()) {
+            if (BASIC_PROPERTIES.contains(p.getName())) {
+                continue;
+            }
+            if (frozen.hasProperty(p.getName())) {
+                continue;
+            }
+            int action = getOPV(dest, p);
+            if (action == COPY || action == VERSION || action == ABORT) {
+                dest.removeProperty(p.getName());
+            } else if (action == IGNORE) {
+                // no action
+            } else if (action == INITIALIZE) {
+                resetToDefaultValue(dest, p);
+            } else if (action == COMPUTE) {
+                // only COMPUTE property definitions currently are
+                // jcr:primaryType and jcr:mixinTypes
+                // do nothing for now
+            }
+        }
+        restoreChildren(frozen, dest, selector);
+    }
+
+    /**
+     * Restore a copied node.
+     */
+    private void restoreNode(NodeBuilder src,
+                             NodeBuilder dest,
+                             VersionSelector selector)
+            throws RepositoryException, CommitFailedException {
+        copyProperties(src, dest, OPVForceCopy.INSTANCE, false);
+        restoreChildren(src, dest, selector);
+    }
+
+    /**
+     * Restore an nt:versionedChild node.
+     */
+    private void restoreVersionedChild(NodeBuilder versionedChild,
+                                       NodeBuilder dest,
+                                       VersionSelector selector)
+            throws RepositoryException {
+        // 15.7.5 Chained Versions on Restore
+        TODO.unimplemented().doNothing();
+        // ...
+        // restoreVersionable(dest, selector.select(history));
+    }
+
+    /**
+     * Restores children of a <code>src</code>.
+     */
+    private void restoreChildren(NodeBuilder src,
+                                 NodeBuilder dest,
+                                 VersionSelector selector)
+            throws RepositoryException, CommitFailedException {
+        // 15.7.6 Restoring Child Nodes
+        for (String name : src.getChildNodeNames()) {
+            NodeBuilder srcChild = src.getChildNode(name);
+            int action = getOPV(dest, srcChild, name);
+            if (action == COPY || action == VERSION) {
+                // replace on destination
+                dest.removeChildNode(name);
+                restoreState(srcChild, dest.child(name), selector);
+            }
+        }
+        for (String name : dest.getChildNodeNames()) {
+            if (src.hasChildNode(name)) {
+                continue;
+            }
+            NodeBuilder destChild = dest.getChildNode(name);
+            int action = getOPV(dest, destChild, name);
+            if (action == COPY || action == VERSION || action == ABORT) {
+                dest.removeChildNode(name);
+            } else if (action == IGNORE) {
+                // no action
+            } else if (action == INITIALIZE) {
+                TODO.unimplemented().doNothing();
+            } else if (action == COMPUTE) {
+                // there are currently no child node definitions
+                // with OPV compute
+            }
+        }
+    }
+
+    /**
+     * 15.7.7 Simple vs. Full Versioning after Restore
+     */
+    private void restoreVersionable(@Nonnull NodeBuilder versionable,
+                                    @Nonnull NodeBuilder version) {
+        checkNotNull(versionable).setProperty(JCR_ISCHECKEDOUT,
+                false, Type.BOOLEAN);
+        versionable.setProperty(JCR_BASEVERSION,
+                uuidFromNode(version), Type.REFERENCE);
+        versionable.setProperty(JCR_PREDECESSORS,
+                Collections.<String>emptyList(), Type.REFERENCES);
+    }
+
+    private void resetToDefaultValue(NodeBuilder dest, PropertyState p)
+            throws RepositoryException {
+        ReadOnlyTree tree = new ReadOnlyTree(dest.getNodeState());
+        PropertyDefinition def = ntMgr.getDefinition(tree, p, true);
+        Value[] values = def.getDefaultValues();
+        if (values != null) {
+            if (p.isArray()) {
+                p = PropertyStates.createProperty(p.getName(), values);
+                dest.setProperty(p);
+            } else if (values.length > 0) {
+                p = PropertyStates.createProperty(p.getName(), values[0]);
+                dest.setProperty(p);
+            }
+        }
+    }
+
     private void createState(NodeBuilder src,
                              NodeBuilder dest)
             throws CommitFailedException, RepositoryException {
-        copyProperties(src, dest, false, true);
+        copyProperties(src, dest, new OPVProvider() {
+            @Override
+            public int getAction(NodeBuilder src,
+                                 NodeBuilder dest,
+                                 PropertyState prop)
+                    throws RepositoryException {
+                return getOPV(src, prop);
+            }
+        }, true);
 
         // add the frozen children and histories
         for (String name : src.getChildNodeNames()) {
@@ -128,7 +369,8 @@ class VersionableState {
             int opv = getOPV(src, child, name);
 
             if (opv == OnParentVersionAction.ABORT) {
-                throw new CommitFailedException(CommitFailedException.VERSION, 1,
+                throw new CommitFailedException(CommitFailedException.VERSION,
+                        VersionExceptionCode.OPV_ABORT_ITEM_PRESENT.ordinal(),
                         "Checkin aborted due to OPV abort in " + name);
             }
             if (opv == OnParentVersionAction.VERSION) {
@@ -139,7 +381,7 @@ class VersionableState {
                     // else copy
                     copy(child, dest.child(name));
                 }
-            } else if (opv == OnParentVersionAction.COPY) {
+            } else if (opv == COPY) {
                 copy(child, dest.child(name));
             }
         }
@@ -147,13 +389,14 @@ class VersionableState {
 
     private void versionedChild(NodeBuilder src, NodeBuilder dest) {
         String ref = src.getProperty(JCR_VERSIONHISTORY).getValue(Type.REFERENCE);
+        dest.setProperty(JCR_PRIMARYTYPE, NT_VERSIONEDCHILD, Type.NAME);
         dest.setProperty(JCR_CHILDVERSIONHISTORY, ref, Type.REFERENCE);
     }
 
     private void copy(NodeBuilder src,
                       NodeBuilder dest)
             throws RepositoryException, CommitFailedException {
-        copyProperties(src, dest, true, false);
+        copyProperties(src, dest, OPVForceCopy.INSTANCE, false);
         for (String name : src.getChildNodeNames()) {
             NodeBuilder child = src.getChildNode(name);
             copy(child, dest.child(name));
@@ -162,31 +405,24 @@ class VersionableState {
 
     private void copyProperties(NodeBuilder src,
                                 NodeBuilder dest,
-                                boolean forceCopy,
+                                OPVProvider opvProvider,
                                 boolean ignoreTypeAndUUID)
             throws RepositoryException, CommitFailedException {
         // add the properties
         for (PropertyState prop : src.getProperties()) {
-            int opv;
-            if (forceCopy) {
-                opv = OnParentVersionAction.COPY;
-            } else {
-                opv = getOPV(src, prop);
-            }
+            int opv = opvProvider.getAction(src, dest, prop);
 
             String propName = prop.getName();
             if (opv == OnParentVersionAction.ABORT) {
-                throw new CommitFailedException(CommitFailedException.VERSION, 1,
+                throw new CommitFailedException(CommitFailedException.VERSION,
+                        VersionExceptionCode.OPV_ABORT_ITEM_PRESENT.ordinal(),
                         "Checkin aborted due to OPV abort in " + propName);
             }
-            if (ignoreTypeAndUUID
-                    && (propName.equals(JCR_PRIMARYTYPE)
-                        || propName.equals(JCR_MIXINTYPES)
-                        || propName.equals(JCR_UUID))) {
+            if (ignoreTypeAndUUID && BASIC_PROPERTIES.contains(propName)) {
                 continue;
             }
             if (opv == OnParentVersionAction.VERSION
-                    || opv == OnParentVersionAction.COPY) {
+                    || opv == COPY) {
                 dest.setProperty(prop);
             }
         }
@@ -202,7 +438,32 @@ class VersionableState {
 
     private int getOPV(NodeBuilder node, PropertyState property)
             throws RepositoryException {
-        return ntMgr.getDefinition(new ReadOnlyTree(node.getNodeState()),
-                property, false).getOnParentVersion();
+        if (property.getName().charAt(0) == ':') {
+            // FIXME: handle child order properly
+            return OnParentVersionAction.COPY;
+        } else {
+            return ntMgr.getDefinition(new ReadOnlyTree(node.getNodeState()),
+                    property, false).getOnParentVersion();
+        }
+    }
+
+    private interface OPVProvider {
+
+        public int getAction(NodeBuilder src,
+                             NodeBuilder dest,
+                             PropertyState prop)
+                throws RepositoryException;
+    }
+
+    private static final class OPVForceCopy implements OPVProvider {
+
+        private static final OPVProvider INSTANCE = new OPVForceCopy();
+
+        @Override
+        public int getAction(NodeBuilder src,
+                             NodeBuilder dest,
+                             PropertyState prop) throws RepositoryException {
+            return COPY;
+        }
     }
 }

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java Wed May 15 16:57:37 2013
@@ -163,7 +163,7 @@ abstract class ItemImpl<T extends ItemDe
                     if (nd == null) {
                         throw new AccessDeniedException("Root node is not accessible.");
                     }
-                    return new NodeImpl<NodeDelegate>(nd, sessionContext);
+                    return sessionContext.createNodeOrNull(nd);
                 }
 
                 String path = dlg.getPath();
@@ -183,7 +183,7 @@ abstract class ItemImpl<T extends ItemDe
                 if (nd == null) {
                     throw new AccessDeniedException(this + ": Ancestor access denied (" + depth + ')');
                 }
-                return new NodeImpl<NodeDelegate>(nd, sessionContext);
+                return sessionContext.createNodeOrNull(nd);
             }
         });
     }

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java Wed May 15 16:57:37 2013
@@ -123,9 +123,9 @@ public class NodeImpl<T extends NodeDele
     @Override
     @Nonnull
     public Node getParent() throws RepositoryException {
-        return perform(new ItemReadOperation<NodeImpl<NodeDelegate>>() {
+        return perform(new ItemReadOperation<Node>() {
             @Override
-            public NodeImpl<NodeDelegate> perform() throws RepositoryException {
+            public Node perform() throws RepositoryException {
                 if (dlg.isRoot()) {
                     throw new ItemNotFoundException("Root has no parent");
                 } else {
@@ -133,7 +133,7 @@ public class NodeImpl<T extends NodeDele
                     if (parent == null) {
                         throw new AccessDeniedException();
                     }
-                    return new NodeImpl<NodeDelegate>(parent, sessionContext);
+                    return sessionContext.createNodeOrNull(parent);
                 }
             }
         });
@@ -257,8 +257,7 @@ public class NodeImpl<T extends NodeDele
                 if (added == null) {
                     throw new ItemExistsException();
                 }
-
-                return new NodeImpl<NodeDelegate>(added, sessionContext);
+                return sessionContext.createNodeOrNull(added);
             }
         });
     }
@@ -481,16 +480,16 @@ public class NodeImpl<T extends NodeDele
     @Override
     @Nonnull
     public Node getNode(final String relPath) throws RepositoryException {
-        return perform(new ItemReadOperation<NodeImpl<?>>() {
+        return perform(new ItemReadOperation<Node>() {
             @Override
-            public NodeImpl<?> perform() throws RepositoryException {
+            public Node perform() throws RepositoryException {
                 String oakPath = getOakPathOrThrowNotFound(relPath);
 
                 NodeDelegate nd = dlg.getChild(oakPath);
                 if (nd == null) {
                     throw new PathNotFoundException(relPath);
                 } else {
-                    return new NodeImpl<NodeDelegate>(nd, sessionContext);
+                    return sessionContext.createNodeOrNull(nd);
                 }
             }
         });
@@ -987,6 +986,9 @@ public class NodeImpl<T extends NodeDele
      */
     @Override
     public void restore(String versionName, boolean removeExisting) throws RepositoryException {
+        if (!isNodeType(NodeType.MIX_VERSIONABLE)) {
+            throw new UnsupportedRepositoryOperationException("Node is not mix:versionable");
+        }
         getVersionManager().restore(getPath(), versionName, removeExisting);
     }
 
@@ -995,7 +997,16 @@ public class NodeImpl<T extends NodeDele
      */
     @Override
     public void restore(Version version, boolean removeExisting) throws RepositoryException {
-        getVersionManager().restore(version, removeExisting);
+        if (!isNodeType(NodeType.MIX_VERSIONABLE)) {
+            throw new UnsupportedRepositoryOperationException("Node is not mix:versionable");
+        }
+        String id = version.getContainingHistory().getVersionableIdentifier();
+        if (getIdentifier().equals(id)) {
+            getVersionManager().restore(version, removeExisting);
+        } else {
+            throw new VersionException("Version does not belong to the " +
+                    "VersionHistory of this node.");
+        }
     }
 
     /**
@@ -1008,8 +1019,8 @@ public class NodeImpl<T extends NodeDele
             // node at 'relPath' exists -> call restore on the target Node
             getNode(relPath).restore(version, removeExisting);
         } else {
-            // TODO
-            TODO.unimplemented();
+            String absPath = PathUtils.concat(getPath(), relPath);
+            getVersionManager().restore(absPath, version, removeExisting);
         }
     }
 

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java Wed May 15 16:57:37 2013
@@ -69,14 +69,14 @@ public class PropertyImpl extends ItemIm
     @Override
     @Nonnull
     public Node getParent() throws RepositoryException {
-        return perform(new ItemReadOperation<NodeImpl<?>>() {
+        return perform(new ItemReadOperation<Node>() {
             @Override
-            public NodeImpl<?> perform() throws RepositoryException {
+            public Node perform() throws RepositoryException {
                 NodeDelegate parent = dlg.getParent();
                 if (parent == null) {
                     throw new AccessDeniedException();
                 } else {
-                    return new NodeImpl<NodeDelegate>(dlg.getParent(), sessionContext);
+                    return sessionContext.createNodeOrNull(dlg.getParent());
                 }
             }
         });

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionContext.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionContext.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionContext.java Wed May 15 16:57:37 2013
@@ -33,11 +33,17 @@ import javax.jcr.observation.Observation
 import javax.jcr.security.AccessControlManager;
 import javax.jcr.version.VersionManager;
 
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate;
+import org.apache.jackrabbit.oak.jcr.delegate.PropertyDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
+import org.apache.jackrabbit.oak.jcr.delegate.VersionManagerDelegate;
 import org.apache.jackrabbit.oak.jcr.security.AccessManager;
+import org.apache.jackrabbit.oak.jcr.version.VersionHistoryImpl;
+import org.apache.jackrabbit.oak.jcr.version.VersionImpl;
 import org.apache.jackrabbit.oak.namepath.LocalNameMapper;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.namepath.NamePathMapperImpl;
@@ -167,6 +173,23 @@ public abstract class SessionContext imp
 
     public abstract DefinitionProvider getDefinitionProvider();
 
+    public NodeImpl createNodeOrNull(NodeDelegate nd) throws RepositoryException {
+        if (nd == null) {
+            return null;
+        }
+        PropertyDelegate pd = nd.getPropertyOrNull(JcrConstants.JCR_PRIMARYTYPE);
+        String type = pd != null ? pd.getString() : null;
+        if (JcrConstants.NT_VERSION.equals(type)) {
+            VersionManagerDelegate delegate = VersionManagerDelegate.create(getSessionDelegate());
+            return new VersionImpl(delegate.createVersion(nd), this);
+        } else if (JcrConstants.NT_VERSIONHISTORY.equals(type)) {
+            VersionManagerDelegate delegate = VersionManagerDelegate.create(getSessionDelegate());
+            return new VersionHistoryImpl(delegate.createVersionHistory(nd), this);
+        } else {
+            return new NodeImpl<NodeDelegate>(nd, this);
+        }
+    }
+
     public ValueFactory getValueFactory() {
         return valueFactory;
     }

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java Wed May 15 16:57:37 2013
@@ -40,7 +40,6 @@ import javax.jcr.retention.RetentionMana
 import javax.jcr.security.AccessControlException;
 import javax.jcr.security.AccessControlManager;
 
-import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.api.security.user.UserManager;
@@ -54,9 +53,6 @@ import org.apache.jackrabbit.oak.jcr.del
 import org.apache.jackrabbit.oak.jcr.delegate.PropertyDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.SessionOperation;
-import org.apache.jackrabbit.oak.jcr.delegate.VersionManagerDelegate;
-import org.apache.jackrabbit.oak.jcr.version.VersionHistoryImpl;
-import org.apache.jackrabbit.oak.jcr.version.VersionImpl;
 import org.apache.jackrabbit.oak.jcr.xml.ImportHandler;
 import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
 import org.apache.jackrabbit.oak.util.TODO;
@@ -118,23 +114,6 @@ public class SessionImpl implements Jack
         return sessionContext.getOakPathOrThrowNotFound(absPath);
     }
 
-    private NodeImpl<?> createNodeOrNull(NodeDelegate nd) throws RepositoryException {
-        if (nd == null) {
-            return null;
-        }
-        PropertyDelegate pd = nd.getPropertyOrNull(JcrConstants.JCR_PRIMARYTYPE);
-        String type = pd != null ? pd.getString() : null;
-        if (JcrConstants.NT_VERSION.equals(type)) {
-            VersionManagerDelegate delegate = VersionManagerDelegate.create(sd);
-            return new VersionImpl(delegate.createVersion(nd), sessionContext);
-        } else if (JcrConstants.NT_VERSIONHISTORY.equals(type)) {
-            VersionManagerDelegate delegate = VersionManagerDelegate.create(sd);
-            return new VersionHistoryImpl(delegate.createVersionHistory(nd), sessionContext);
-        } else {
-            return new NodeImpl<NodeDelegate>(nd, sessionContext);
-        }
-    }
-
     private PropertyImpl createPropertyOrNull(PropertyDelegate pd) {
         return pd == null ? null : new PropertyImpl(pd, sessionContext);
     }
@@ -144,7 +123,7 @@ public class SessionImpl implements Jack
             throws RepositoryException {
         NodeDelegate nd = sd.getNode(oakPath);
         if (nd != null) {
-            return createNodeOrNull(nd);
+            return sessionContext.createNodeOrNull(nd);
         }
         PropertyDelegate pd = sd.getProperty(oakPath);
         if (pd != null) {
@@ -166,7 +145,7 @@ public class SessionImpl implements Jack
         return perform(new CheckedSessionOperation<Node>() {
             @Override
             public Node perform() throws RepositoryException {
-                return createNodeOrNull(sd.getNode(getOakPathOrThrow(absPath)));
+                return sessionContext.createNodeOrNull(sd.getNode(getOakPathOrThrow(absPath)));
             }
         });
     }
@@ -267,7 +246,7 @@ public class SessionImpl implements Jack
                 if (nd == null) {
                     throw new AccessDeniedException("Root node is not accessible.");
                 }
-                return createNodeOrNull(nd);
+                return sessionContext.createNodeOrNull(nd);
             }
         });
     }
@@ -295,7 +274,7 @@ public class SessionImpl implements Jack
                 if (nd == null) {
                     throw new ItemNotFoundException("Node with id " + id + " does not exist.");
                 }
-                return createNodeOrNull(nd);
+                return sessionContext.createNodeOrNull(nd);
             }
         });
     }

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java Wed May 15 16:57:37 2013
@@ -461,10 +461,12 @@ public class NodeDelegate extends ItemDe
     }
 
     /**
-     * Remove the node if not root. Does nothing otherwise
+     * Remove this node. This operation never succeeds for the root node.
+     *
+     * @return {@code true} if the node was removed; {@code false} otherwise.
      */
-    public void remove() throws InvalidItemStateException {
-        getTree().remove();
+    public boolean remove() throws InvalidItemStateException {
+        return getTree().remove();
     }
 
     /**

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionDelegate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionDelegate.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionDelegate.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionDelegate.java Wed May 15 16:57:37 2013
@@ -19,7 +19,9 @@ package org.apache.jackrabbit.oak.jcr.de
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
 
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.Tree;
 
 /**
@@ -35,4 +37,14 @@ public class VersionDelegate extends Nod
                                   @Nonnull Tree tree) {
         return new VersionDelegate(sessionDelegate, tree);
     }
+
+    @Nonnull
+    NodeDelegate getFrozenNode() throws RepositoryException {
+        NodeDelegate frozenNode = getChild(JcrConstants.JCR_FROZENNODE);
+        if (frozenNode == null) {
+            throw new RepositoryException("Inconsistent version storage. " +
+                    "Version at " + getPath() + " does not have a jcr:frozenNode");
+        }
+        return frozenNode;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java Wed May 15 16:57:37 2013
@@ -17,6 +17,14 @@
 package org.apache.jackrabbit.oak.jcr.delegate;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION;
+import static org.apache.jackrabbit.JcrConstants.JCR_FROZENMIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_FROZENPRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.JCR_FROZENUUID;
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
+import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONHISTORY;
 
 import javax.annotation.Nonnull;
 import javax.jcr.InvalidItemStateException;
@@ -24,7 +32,9 @@ import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
 
 import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.jcr.version.ReadWriteVersionManager;
 
 /**
@@ -122,6 +132,37 @@ public class VersionManagerDelegate {
         return VersionDelegate.create(sessionDelegate, t);
     }
 
+    public void restore(@Nonnull NodeDelegate parent,
+                        @Nonnull String oakName,
+                        @Nonnull VersionDelegate vd)
+            throws RepositoryException {
+        NodeDelegate frozen = vd.getFrozenNode();
+        PropertyState primaryType = frozen.getProperty(
+                JCR_FROZENPRIMARYTYPE).getPropertyState();
+        PropertyState uuid = frozen.getProperty(
+                JCR_FROZENUUID).getPropertyState();
+        PropertyDelegate mixinTypes = frozen.getPropertyOrNull(JCR_FROZENMIXINTYPES);
+        if (parent.getChild(oakName) == null) {
+            // create a sentinel node with a jcr:baseVersion pointing
+            // to the version to restore
+            Tree t = parent.getTree().addChild(oakName);
+            t.setProperty(JCR_PRIMARYTYPE, primaryType.getValue(Type.NAME), Type.NAME);
+            t.setProperty(JCR_UUID, uuid.getValue(Type.STRING), Type.STRING);
+            if (mixinTypes != null && mixinTypes.getPropertyState().count() > 0) {
+                t.setProperty(JCR_MIXINTYPES,
+                        mixinTypes.getPropertyState().getValue(Type.NAMES),
+                        Type.NAMES);
+            }
+            t.setProperty(JCR_BASEVERSION, vd.getIdentifier(), Type.REFERENCE);
+            t.setProperty(JCR_VERSIONHISTORY, vd.getParent().getIdentifier(), Type.REFERENCE);
+        } else {
+            Tree t = parent.getChild(oakName).getTree();
+            t.setProperty(JCR_BASEVERSION, vd.getIdentifier(), Type.REFERENCE);
+            // TODO: what if node was checked-out and restore is for current
+            //       base version? -> will not trigger VersionEditor
+        }
+    }
+
     //----------------------------< internal >----------------------------------
 
     /**

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryImpl.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryImpl.java Wed May 15 16:57:37 2013
@@ -34,7 +34,6 @@ import javax.jcr.version.VersionExceptio
 
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.commons.PathUtils;
-import org.apache.jackrabbit.oak.jcr.NodeImpl;
 import org.apache.jackrabbit.oak.jcr.SessionContext;
 import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate;
 
@@ -140,7 +139,7 @@ public class QueryImpl implements Query 
         if (parentDelegate == null) {
             throw new PathNotFoundException("The specified path does not exist: " + parent);
         }
-        Node parentNode = new NodeImpl<NodeDelegate>(parentDelegate, sessionContext);
+        Node parentNode = sessionContext.createNodeOrNull(parentDelegate);
         if (!parentNode.isCheckedOut()) {
             throw new VersionException("Cannot store query. Node at " +
                     absPath + " is checked in.");

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java Wed May 15 16:57:37 2013
@@ -174,7 +174,7 @@ public class ReadWriteVersionManager ext
             return new AccessDeniedException(exception);
         } else if (exception.isOfType("State")) {
             return new InvalidItemStateException(exception);
-        } else if (exception.isOfType(CommitFailedException.VERSION) && exception.getCode() == 1) { // FIXME: hardcoded exception code
+        } else if (exception.isOfType(CommitFailedException.VERSION)) {
             return new VersionException(exception);
         } else if (exception.isOfType("Lock")) {
             return new LockException(exception);

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionImpl.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionImpl.java Wed May 15 16:57:37 2013
@@ -34,7 +34,6 @@ import javax.jcr.version.VersionHistory;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.jcr.NodeImpl;
 import org.apache.jackrabbit.oak.jcr.SessionContext;
-import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.PropertyDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.VersionDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.VersionManagerDelegate;
@@ -101,8 +100,8 @@ public class VersionImpl extends NodeImp
 
     @Override
     public Node getFrozenNode() throws RepositoryException {
-        return new NodeImpl<NodeDelegate>(
-                dlg.getChild(VersionConstants.JCR_FROZENNODE), sessionContext);
+        return sessionContext.createNodeOrNull(
+                dlg.getChild(VersionConstants.JCR_FROZENNODE));
     }
 
     //------------------------------< internal >--------------------------------

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionManagerImpl.java?rev=1482953&r1=1482952&r2=1482953&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionManagerImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionManagerImpl.java Wed May 15 16:57:37 2013
@@ -16,29 +16,43 @@
  */
 package org.apache.jackrabbit.oak.jcr.version;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
 import javax.annotation.Nonnull;
 import javax.jcr.InvalidItemStateException;
 import javax.jcr.ItemExistsException;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
 import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.lock.LockException;
 import javax.jcr.lock.LockManager;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.util.TraversingItemVisitor;
 import javax.jcr.version.Version;
 import javax.jcr.version.VersionException;
 import javax.jcr.version.VersionHistory;
 import javax.jcr.version.VersionManager;
 
 import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.jcr.SessionContext;
 import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.SessionOperation;
+import org.apache.jackrabbit.oak.jcr.delegate.VersionDelegate;
+import org.apache.jackrabbit.oak.jcr.delegate.VersionHistoryDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.VersionManagerDelegate;
 import org.apache.jackrabbit.oak.util.TODO;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 public class VersionManagerImpl implements VersionManager {
 
     private final SessionContext sessionContext;
@@ -62,31 +76,147 @@ public class VersionManagerImpl implemen
     }
 
     @Override
-    public void restore(
-            String absPath, Version version, boolean removeExisting)
+    public void restore(final String absPath,
+                        final Version version,
+                        final boolean removeExisting)
             throws RepositoryException {
-        TODO.unimplemented().doNothing();
+        final SessionDelegate sessionDelegate = sessionContext.getSessionDelegate();
+        sessionDelegate.perform(new SessionOperation<Void>() {
+            @Override
+            public Void perform() throws RepositoryException {
+                String oakPath = getOakPathOrThrowNotFound(absPath);
+                NodeDelegate nodeDelegate = sessionDelegate.getNode(oakPath);
+                if (nodeDelegate != null) {
+                    throw new VersionException(
+                            "VersionManager.restore(String, Version, boolean)"
+                                    + " not allowed on existing nodes; use"
+                                    + " VersionManager.restore(Version, boolean) instead: "
+                                    + absPath);
+                }
+                // check if parent exists
+                NodeDelegate parent = ensureParentExists(sessionDelegate, absPath);
+                // check for pending changes
+                checkPendingChangesForRestore(sessionDelegate);
+                // check lock status
+                checkNotLocked(parent.getPath());
+                // check for existing nodes
+                List<NodeDelegate> existing = getExisting(version,
+                        Collections.<String>emptySet());
+                boolean success = false;
+                try {
+                    if (!existing.isEmpty()) {
+                        if (removeExisting) {
+                            removeExistingNodes(existing);
+                        } else {
+                            List<String> paths = new ArrayList<String>();
+                            for (NodeDelegate nd : existing) {
+                                paths.add(nd.getPath());
+                            }
+                            throw new ItemExistsException("Unable to restore with " +
+                                    "removeExisting=false. Existing nodes in " +
+                                    "workspace: " + paths);
+                        }
+                    }
+                    // ready for restore
+                    VersionDelegate vd = versionManagerDelegate.getVersionByIdentifier(
+                            version.getIdentifier());
+                    versionManagerDelegate.restore(
+                            parent, PathUtils.getName(oakPath), vd);
+                    sessionDelegate.getRoot().commit();
+                    success = true;
+                } catch (CommitFailedException e) {
+                    throw new RepositoryException(e);
+                } finally {
+                    if (!success) {
+                        // refresh if one of the modifying operations fail
+                        sessionDelegate.refresh(false);
+                    }
+                }
+                return null;
+            }
+        });
     }
 
     @Override
-    public void restore(
-            String absPath, String versionName, boolean removeExisting)
+    public void restore(final String absPath,
+                        final String versionName,
+                        final boolean removeExisting)
             throws RepositoryException {
-        TODO.unimplemented().doNothing();
+        VersionHistory history = getVersionHistory(absPath);
+        restore(new Version[]{history.getVersion(versionName)}, removeExisting);
     }
 
     @Override
     public void restore(Version version, boolean removeExisting)
             throws RepositoryException {
-        TODO.unimplemented().doNothing();
+        restore(new Version[]{version}, removeExisting);
     }
 
     @Override
-    public void restore(Version[] versions, boolean removeExisting)
+    public void restore(final Version[] versions,
+                        final boolean removeExisting)
             throws ItemExistsException,
             UnsupportedRepositoryOperationException, VersionException,
             LockException, InvalidItemStateException, RepositoryException {
-        TODO.unimplemented().doNothing();
+        if (versions.length > 1) {
+            // TODO: implement restore of multiple versions
+            TODO.unimplemented().doNothing(); // TODO: RETURN
+        }
+        final Version version = versions[0];
+        VersionHistory history = (VersionHistory) version.getParent();
+        final String versionableId = history.getVersionableIdentifier();
+        if (history.getRootVersion().isSame(version)) {
+            throw new VersionException("Restore of root version not possible");
+        }
+        final SessionDelegate sessionDelegate = sessionContext.getSessionDelegate();
+        sessionDelegate.perform(new SessionOperation<Void>() {
+            @Override
+            public Void perform() throws RepositoryException {
+                // check for pending changes
+                checkPendingChangesForRestore(sessionDelegate);
+                NodeDelegate n = sessionDelegate.getNodeByIdentifier(versionableId);
+                if (n == null) {
+                    throw new VersionException("Unable to restore version. " +
+                            "No versionable node with identifier: " + versionableId);
+                }
+                // check lock status
+                checkNotLocked(n.getPath());
+                // check for existing nodes
+                List<NodeDelegate> existing = getExisting(version,
+                        Collections.singleton(n.getPath()));
+                boolean success = false;
+                try {
+                    if (!existing.isEmpty()) {
+                        if (removeExisting) {
+                            removeExistingNodes(existing);
+                        } else {
+                            List<String> paths = new ArrayList<String>();
+                            for (NodeDelegate nd : existing) {
+                                paths.add(nd.getPath());
+                            }
+                            throw new ItemExistsException("Unable to restore with " +
+                                    "removeExisting=false. Existing nodes in " +
+                                    "workspace: " + paths);
+                        }
+                    }
+                    // ready for restore
+                    VersionDelegate vd = versionManagerDelegate.getVersionByIdentifier(
+                            version.getIdentifier());
+                    versionManagerDelegate.restore(
+                            n.getParent(), n.getName(), vd);
+                    sessionDelegate.getRoot().commit();
+                    success = true;
+                } catch (CommitFailedException e) {
+                    throw new RepositoryException(e);
+                } finally {
+                    if (!success) {
+                        // refresh if one of the modifying operations fail
+                        sessionDelegate.refresh(false);
+                    }
+                }
+                return null;
+            }
+        });
     }
 
     @Override
@@ -142,13 +272,8 @@ public class VersionManagerImpl implemen
         return sessionDelegate.perform(new SessionOperation<VersionHistory>() {
             @Override
             public VersionHistory perform() throws RepositoryException {
-                String oakPath = getOakPathOrThrowNotFound(absPath);
-                NodeDelegate nodeDelegate = sessionDelegate.getNode(oakPath);
-                if (nodeDelegate == null) {
-                    throw new PathNotFoundException(absPath);
-                }
                 return new VersionHistoryImpl(
-                        versionManagerDelegate.getVersionHistory(nodeDelegate), sessionContext);
+                        internalGetVersionHistory(absPath), sessionContext);
             }
         });
     }
@@ -215,9 +340,7 @@ public class VersionManagerImpl implemen
                 if (nodeDelegate == null) {
                     throw new PathNotFoundException(absPath);
                 }
-                if (getLockManager().isLocked(absPath)) {
-                    throw new LockException("Node at " + absPath + " is locked");
-                }
+                checkNotLocked(absPath);
                 versionManagerDelegate.checkout(nodeDelegate);
                 return null;
             }
@@ -235,9 +358,7 @@ public class VersionManagerImpl implemen
                 if (nodeDelegate == null) {
                     throw new PathNotFoundException(absPath);
                 }
-                if (getLockManager().isLocked(absPath)) {
-                    throw new LockException("Node at " + absPath + " is locked");
-                }
+                checkNotLocked(absPath);
                 return new VersionImpl(versionManagerDelegate.checkin(nodeDelegate), sessionContext);
             }
         });
@@ -249,4 +370,133 @@ public class VersionManagerImpl implemen
         TODO.unimplemented().doNothing();
     }
 
+    //----------------------------< internal >----------------------------------
+
+    private void checkPendingChangesForRestore(SessionDelegate sessionDelegate)
+            throws InvalidItemStateException {
+        if (sessionDelegate.hasPendingChanges()) {
+            throw new InvalidItemStateException(
+                    "Unable to restore. Session has pending changes.");
+        }
+    }
+
+    private void checkNotLocked(String absPath) throws RepositoryException {
+        if (getLockManager().isLocked(absPath)) {
+            throw new LockException("Node at " + absPath + " is locked");
+        }
+    }
+
+    /**
+     * Returns the parent for the given <code>absPath</code> or throws a
+     * {@link PathNotFoundException} if it doesn't exist.
+     *
+     * @param sessionDelegate session delegate.
+     * @param absPath an absolute path
+     * @return the parent for the given <code>absPath</code>.
+     * @throws PathNotFoundException if the node does not exist.
+     */
+    @Nonnull
+    private NodeDelegate ensureParentExists(@Nonnull SessionDelegate sessionDelegate,
+                                            @Nonnull String absPath)
+            throws PathNotFoundException {
+        String oakParentPath = getOakPathOrThrowNotFound(
+                PathUtils.getParentPath(checkNotNull(absPath)));
+        NodeDelegate parent = checkNotNull(sessionDelegate).getNode(oakParentPath);
+        if (parent == null) {
+            throw new PathNotFoundException(PathUtils.getParentPath(absPath));
+        }
+        return parent;
+    }
+
+    /**
+     * Returns referenceable nodes outside of the versionable sub-graphs
+     * identified by <code>versionablePaths</code>, which are also present
+     * in the versionable state captured by <code>version</code>.
+     *
+     * @param version the version.
+     * @param versionablePaths identifies the starting points of the versionable
+     *                         sub-graphs.
+     * @return existing nodes in this workspace.
+     */
+    private List<NodeDelegate> getExisting(@Nonnull Version version,
+                                           @Nonnull Set<String> versionablePaths)
+            throws RepositoryException {
+        // collect uuids
+        final List<String> uuids = new ArrayList<String>();
+        version.getFrozenNode().accept(new TraversingItemVisitor.Default() {
+            @Override
+            protected void entering(Node node, int level)
+                    throws RepositoryException {
+                if (node.isNodeType(NodeType.NT_FROZEN_NODE)) {
+                    uuids.add(node.getProperty(Property.JCR_FROZEN_UUID).getString());
+                } else if (node.isNodeType(NodeType.NT_VERSIONED_CHILD)) {
+                    // TODO: handle?
+                }
+            }
+        });
+        SessionDelegate delegate = sessionContext.getSessionDelegate();
+        if (uuids.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<NodeDelegate> existing = new ArrayList<NodeDelegate>();
+        for (String uuid : uuids) {
+            NodeDelegate node = delegate.getNodeByIdentifier(uuid);
+            if (node != null) {
+                boolean inSubGraph = false;
+                for (String versionablePath : versionablePaths) {
+                    if (node.getPath().startsWith(versionablePath)) {
+                        inSubGraph = true;
+                        break;
+                    }
+                }
+                if (!inSubGraph) {
+                    existing.add(node);
+                }
+            }
+        }
+        return existing;
+    }
+
+    /**
+     * Removes existing nodes and throws a {@link RepositoryException} if
+     * removing one of them fails.
+     *
+     * @param existing nodes to remove.
+     * @throws RepositoryException if the operation fails.
+     */
+    private void removeExistingNodes(List<NodeDelegate> existing)
+            throws RepositoryException {
+        for (NodeDelegate nd : existing) {
+            if (!nd.remove()) {
+                throw new RepositoryException(
+                        "Unable to remove existing node: " + nd.getPath());
+            }
+        }
+    }
+
+    /**
+     * Returns the version history for the versionable node at the given path.
+     *
+     * @param absPathVersionable path to a versionable node.
+     * @return the version history.
+     * @throws PathNotFoundException if the given path does not reference an
+     *                               existing node.
+     * @throws UnsupportedRepositoryOperationException
+     *                               if the node at the given path is not
+     *                               mix:versionable.
+     * @throws RepositoryException if some other error occurs.
+     */
+    @Nonnull
+    private VersionHistoryDelegate internalGetVersionHistory(
+            @Nonnull String absPathVersionable)
+            throws RepositoryException, UnsupportedRepositoryOperationException {
+        SessionDelegate sessionDelegate = sessionContext.getSessionDelegate();
+        String oakPath = getOakPathOrThrowNotFound(checkNotNull(absPathVersionable));
+        NodeDelegate nodeDelegate = sessionDelegate.getNode(oakPath);
+        if (nodeDelegate == null) {
+            throw new PathNotFoundException(absPathVersionable);
+        }
+        return versionManagerDelegate.getVersionHistory(nodeDelegate);
+
+    }
 }

Added: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/RestoreTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/RestoreTest.java?rev=1482953&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/RestoreTest.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/RestoreTest.java Wed May 15 16:57:37 2013
@@ -0,0 +1,49 @@
+/*
+ * 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.version;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionManager;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * <code>RestoreTest</code>...
+ */
+public class RestoreTest extends AbstractJCRTest {
+
+    public void testSimpleRestore() throws RepositoryException {
+        VersionManager vMgr = superuser.getWorkspace().getVersionManager();
+        Node n = testRootNode.addNode(nodeName1, testNodeType);
+        n.addMixin(mixVersionable);
+        n.setProperty("prop", "a");
+        superuser.save();
+        String path = n.getPath();
+        Version v = vMgr.checkpoint(path); // 1.0
+        n.setProperty("prop", "b");
+        superuser.save();
+        vMgr.checkpoint(path); // 1.1
+        n.remove();
+        superuser.save();
+        vMgr.restore(path, v, true);
+        assertTrue(superuser.nodeExists(path));
+        n = superuser.getNode(path);
+        assertEquals("Property not restored", "a", n.getProperty("prop").getString());
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/RestoreTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/RestoreTest.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL