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/23 10:37:19 UTC

svn commit: r1485613 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/core/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ oak-core/src/test/java/org/apache/jackrabbit/oak/core/ oak-jcr/

Author: mreutegg
Date: Thu May 23 08:37:19 2013
New Revision: 1485613

URL: http://svn.apache.org/r1485613
Log:
OAK-168: Basic JCR VersionManager support
- Restore of versionable nodes with OPV=Version

Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java
    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/VersionExceptionCode.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionableState.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/core/ImmutableRootTest.java
    jackrabbit/oak/trunk/oak-jcr/pom.xml

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java?rev=1485613&r1=1485612&r2=1485613&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java Thu May 23 08:37:19 2013
@@ -27,13 +27,17 @@ import org.apache.jackrabbit.oak.api.Blo
 import org.apache.jackrabbit.oak.api.ContentSession;
 import org.apache.jackrabbit.oak.api.QueryEngine;
 import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider;
+import org.apache.jackrabbit.oak.query.QueryEngineImpl;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
 /**
  * Simple implementation of the Root interface that only supports simple read
- * operations (excluding query) based on the {@code NodeState} (or {@code ImmutableTree})
- * passed to the constructor.
+ * operations based on the {@code NodeState} (or {@code ImmutableTree})
+ * passed to the constructor. This root implementation provides a query engine
+ * with index support limited to the {@link PropertyIndexProvider}.
  */
 public final class ImmutableRoot implements Root {
 
@@ -99,7 +103,17 @@ public final class ImmutableRoot impleme
     @Nonnull
     @Override
     public QueryEngine getQueryEngine() {
-        throw new UnsupportedOperationException();
+        return new QueryEngineImpl(new PropertyIndexProvider()) {
+            @Override
+            protected NodeState getRootState() {
+                return rootTree.state;
+            }
+
+            @Override
+            protected Tree getRootTree() {
+                return rootTree;
+            }
+        };
     }
 
     @Nonnull
@@ -107,10 +121,11 @@ public final class ImmutableRoot impleme
     public BlobFactory getBlobFactory() {
         throw new UnsupportedOperationException();
     }
-    
-	@Override
-	public ContentSession getContentSession() {
-		throw new UnsupportedOperationException();
-	}
-    
+
+    @Nonnull
+    @Override
+    public ContentSession getContentSession() {
+        throw new UnsupportedOperationException();
+    }
+
 }

Modified: 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=1485613&r1=1485612&r2=1485613&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/DateVersionSelector.java Thu May 23 08:37:19 2013
@@ -69,8 +69,9 @@ public class DateVersionSelector impleme
         NodeBuilder latestVersion = null;
         for (String name: history.getChildNodeNames()) {
             NodeBuilder v = history.getChildNode(name);
-            if (name.equals(JcrConstants.JCR_ROOTVERSION)) {
-                // ignore root version
+            if (name.equals(JcrConstants.JCR_ROOTVERSION)
+                    || name.equals(JcrConstants.JCR_VERSIONLABELS)) {
+                // ignore root version and labels node
                 continue;
             }
             long c = v.getProperty(JcrConstants.JCR_CREATED).getValue(Type.DATE);

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=1485613&r1=1485612&r2=1485613&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 Thu May 23 08:37:19 2013
@@ -18,6 +18,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.version;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION;
@@ -36,6 +37,7 @@ import static org.apache.jackrabbit.JcrC
 import static org.apache.jackrabbit.JcrConstants.NT_VERSIONLABELS;
 import static org.apache.jackrabbit.oak.plugins.version.Utils.uuidFromNode;
 import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.REP_VERSIONSTORAGE;
+import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.VERSION_STORE_PATH;
 
 import java.util.Collections;
 import java.util.Iterator;
@@ -43,6 +45,8 @@ import java.util.List;
 import java.util.Set;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
@@ -58,7 +62,6 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
 /**
@@ -161,18 +164,16 @@ class ReadWriteVersionManager extends Re
     }
 
     public void restore(@Nonnull NodeBuilder versionable,
-                        @Nonnull String versionUUID)
+                        @Nonnull String versionUUID,
+                        @Nullable VersionSelector selector)
             throws CommitFailedException {
+        String versionPath = getIdentifierManager().getPath(versionUUID);
         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 (versionPath != null) {
+            String versionName = PathUtils.getName(versionPath);
+            if (history.hasChildNode(versionName)) {
+                version = history.getChildNode(versionName);
             }
         }
         if (version == null) {
@@ -182,8 +183,40 @@ class ReadWriteVersionManager extends Re
                             " does not have a Version with UUID: " + versionUUID);
         }
         VersionableState versionableState = VersionableState.forRestore(
-                version, versionable, ntMgr);
-        versionableState.restore();
+                version, history, versionable, this, ntMgr);
+        versionableState.restore(selector);
+    }
+
+    /**
+     * Restores a version from the history identified by <code>historyIdentifier</code>
+     * using the given version <code>selector</code>.
+     *
+     * @param historyIdentifier identifier of the version history node.
+     * @param selector the version selector.
+     * @param versionable the versionable node where the version is restored to.
+     * @throws CommitFailedException if an error occurs while restoring.
+     */
+    void restore(@Nonnull String historyIdentifier,
+                 @Nonnull VersionSelector selector,
+                 @Nonnull NodeBuilder versionable)
+            throws CommitFailedException, RepositoryException {
+        String historyPath = getIdentifierManager().getPath(historyIdentifier);
+        String historyRelPath = PathUtils.relativize(VERSION_STORE_PATH, historyPath);
+        NodeBuilder history = resolve(versionStorageNode, historyRelPath);
+        checkState(history.exists(), "Version history does not exist: " + historyPath);
+        NodeBuilder version = selector.select(history);
+        if (version == null) {
+            throw new CommitFailedException(CommitFailedException.VERSION,
+                    VersionExceptionCode.NO_VERSION_TO_RESTORE.ordinal(),
+                    "VersionSelector did not select any version from " +
+                            "history: " + historyPath);
+        }
+        // make sure versionable nodes has a jcr:uuid
+        // (required to identify its version history)
+        String versionableUUUID = history.getProperty(
+                JCR_VERSIONABLEUUID).getValue(Type.STRING);
+        versionable.setProperty(JCR_UUID, versionableUUUID, Type.STRING);
+        restore(versionable, uuidFromNode(version), selector);
     }
 
     // TODO: more methods that modify versions
@@ -191,6 +224,23 @@ class ReadWriteVersionManager extends Re
     //------------------------------< internal >--------------------------------
 
     /**
+     * Resolves the <code>relPath</code> based on the given <code>node</code>
+     * and returns the resulting node, possibly non-existing.
+     *
+     * @param node the resolved node.
+     * @param relPath a relative path.
+     * @return the resolved node.
+     */
+    @Nonnull
+    private NodeBuilder resolve(NodeBuilder node, String relPath) {
+        checkArgument(!PathUtils.isAbsolute(relPath), "Not a relative path");
+        for (String name : PathUtils.elements(relPath)) {
+            node = node.getChildNode(name);
+        }
+        return node;
+    }
+
+    /**
      * Creates a version in the given version history. If the given version
      * history does not yet have a version, then a root version is created and
      * the versionable node is in a checked out state. Otherwise a version is
@@ -229,27 +279,22 @@ class ReadWriteVersionManager extends Re
         version.setProperty(JCR_SUCCESSORS, Collections.<String>emptyList(), Type.REFERENCES);
 
         // update successors of versions identified by predecessors
-        // FIXME: inefficient because it iterates over all versions
-        for (String name : vHistory.getChildNodeNames()) {
-            if (name.equals(JCR_VERSIONLABELS)) {
-                continue;
-            }
+        for (String id : predecessors) {
+            String name = PathUtils.getName(getIdentifierManager().getPath(id));
             NodeBuilder predecessor = vHistory.getChildNode(name);
-            if (predecessors.contains(uuidFromNode(predecessor))) {
-                PropertyState state = predecessor.getProperty(JCR_SUCCESSORS);
-                if (state == null) {
-                    throw new IllegalStateException("Missing " + JCR_SUCCESSORS +
-                            " property on " + predecessor);
-                }
-                Set<String> refs = Sets.newHashSet(state.getValue(Type.REFERENCES));
-                refs.add(versionUUID);
-                predecessor.setProperty(JCR_SUCCESSORS, refs, Type.REFERENCES);
+            PropertyState state = predecessor.getProperty(JCR_SUCCESSORS);
+            if (state == null) {
+                throw new IllegalStateException("Missing " + JCR_SUCCESSORS +
+                        " property on " + predecessor);
             }
+            Set<String> refs = Sets.newHashSet(state.getValue(Type.REFERENCES));
+            refs.add(versionUUID);
+            predecessor.setProperty(JCR_SUCCESSORS, refs, Type.REFERENCES);
         }
 
         // jcr:frozenNode of created version
         VersionableState versionableState = VersionableState.fromVersion(
-                version, versionable, ntMgr);
+                version, vHistory, versionable, this, ntMgr);
         if (!isRootVersion) {
             versionableState.create();
         }
@@ -331,18 +376,11 @@ class ReadWriteVersionManager extends Re
             throw new IllegalStateException(message);
         }
 
-        // FIXME: inefficient because it iterates over all versions!
-        Set<String> uuids = ImmutableSet.copyOf(predecessors.getValue(Type.REFERENCES));
         String best = null;
-        for (String name : history.getChildNodeNames()) {
-            if (name.equals(JCR_VERSIONLABELS)) {
-                continue;
-            }
-            NodeBuilder child = history.getChildNode(name);
-            if (uuids.contains(uuidFromNode(child))) {
-                if (best == null || name.length() < best.length()) {
-                    best = name;
-                }
+        for (String id : predecessors.getValue(Type.REFERENCES)) {
+            String name = PathUtils.getName(getIdentifierManager().getPath(id));
+            if (best == null || name.length() < best.length()) {
+                best = name;
             }
         }
 

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=1485613&r1=1485612&r2=1485613&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 Thu May 23 08:37:19 2013
@@ -62,7 +62,7 @@ public class Utils {
     @Nonnull
     static String primaryTypeOf(@Nonnull NodeBuilder node)
             throws IllegalStateException {
-        String primaryType = node.getName(JCR_PRIMARYTYPE);
+        String primaryType = checkNotNull(node).getName(JCR_PRIMARYTYPE);
         if (primaryType == null) {
             throw new IllegalStateException("Node does not have a jcr: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=1485613&r1=1485612&r2=1485613&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 Thu May 23 08:37:19 2013
@@ -91,7 +91,7 @@ class VersionEditor implements Editor {
                 && !this.after.hasProperty(JcrConstants.JCR_ISCHECKEDOUT)
                 && !this.before.exists()) {
             // sentinel node for restore
-            vMgr.restore(node, after.getValue(Type.REFERENCE));
+            vMgr.restore(node, after.getValue(Type.REFERENCE), null);
             return;
         }
         if (!wasReadOnly) {
@@ -124,7 +124,7 @@ class VersionEditor implements Editor {
                 vMgr.checkin(node);
             }
         } else if (propName.equals(VersionConstants.JCR_BASEVERSION)) {
-            vMgr.restore(node, after.getValue(Type.REFERENCE));
+            vMgr.restore(node, after.getValue(Type.REFERENCE), null);
         } else if (isVersionProperty(after)) {
             throwProtected(after.getName());
         } else if (wasReadOnly) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java?rev=1485613&r1=1485612&r2=1485613&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java Thu May 23 08:37:19 2013
@@ -27,7 +27,8 @@ public enum VersionExceptionCode {
     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");
+    OPV_ABORT_ITEM_PRESENT("Item with OPV ABORT action present"),
+    NO_VERSION_TO_RESTORE("No suitable version to restore");
 
     private final String desc;
 

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=1485613&r1=1485612&r2=1485613&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 Thu May 23 08:37:19 2013
@@ -20,9 +20,11 @@ package org.apache.jackrabbit.oak.plugin
 
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
 import javax.jcr.nodetype.PropertyDefinition;
@@ -64,6 +66,8 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.util.TODO;
 
+import com.google.common.collect.Lists;
+
 /**
  * <code>VersionableState</code> provides methods to create a versionable state
  * for a version based on a versionable node.
@@ -84,22 +88,29 @@ class VersionableState {
         BASIC_PROPERTIES.add(JCR_PRIMARYTYPE);
         BASIC_PROPERTIES.add(JCR_UUID);
         BASIC_PROPERTIES.add(JCR_MIXINTYPES);
+        BASIC_FROZEN_PROPERTIES.addAll(BASIC_PROPERTIES);
         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 history;
     private final NodeBuilder frozenNode;
     private final NodeBuilder versionable;
+    private final ReadWriteVersionManager vMgr;
     private final ReadOnlyNodeTypeManager ntMgr;
 
     private VersionableState(@Nonnull NodeBuilder version,
+                             @Nonnull NodeBuilder history,
                              @Nonnull NodeBuilder versionable,
+                             @Nonnull ReadWriteVersionManager vMgr,
                              @Nonnull ReadOnlyNodeTypeManager ntMgr) {
         this.version = checkNotNull(version);
+        this.history = checkNotNull(history);
         this.frozenNode = version.child(JCR_FROZENNODE);
         this.versionable = checkNotNull(versionable);
+        this.vMgr = checkNotNull(vMgr);
         this.ntMgr = checkNotNull(ntMgr);
     }
 
@@ -109,15 +120,19 @@ class VersionableState {
      * jcr:frozenUuid) from the given versionable node.
      *
      * @param version the parent node of the frozen node.
+     * @param history the history node of the version.
      * @param versionable the versionable node.
+     * @param vMgr the version manager.
      * @param ntMgr the node type manager.
      * @return a versionable state
      */
     @Nonnull
     static VersionableState fromVersion(@Nonnull NodeBuilder version,
+                                        @Nonnull NodeBuilder history,
                                         @Nonnull NodeBuilder versionable,
+                                        @Nonnull ReadWriteVersionManager vMgr,
                                         @Nonnull ReadOnlyNodeTypeManager ntMgr) {
-        VersionableState state = new VersionableState(version, versionable, ntMgr);
+        VersionableState state = new VersionableState(version, history, versionable, vMgr, ntMgr);
         return state.initFrozen(version.child(JCR_FROZENNODE), versionable);
     }
 
@@ -125,14 +140,18 @@ class VersionableState {
      * Creates a versionable state for a restore.
      *
      * @param version the version to restore.
+     * @param history the history node of the version.
      * @param versionable the versionable node.
+     * @param vMgr the version manager.
      * @param ntMgr the node type manager.
      * @return a versionable state.
      */
     static VersionableState forRestore(@Nonnull NodeBuilder version,
+                                       @Nonnull NodeBuilder history,
                                        @Nonnull NodeBuilder versionable,
+                                       @Nonnull ReadWriteVersionManager vMgr,
                                        @Nonnull ReadOnlyNodeTypeManager ntMgr) {
-        return new VersionableState(version, versionable, ntMgr);
+        return new VersionableState(version, history, versionable, vMgr, ntMgr);
     }
 
     /**
@@ -147,15 +166,19 @@ class VersionableState {
         // initialize jcr:frozenNode
         frozen.setProperty(JCR_UUID, IdentifierManager.generateUUID(), Type.STRING);
         frozen.setProperty(JCR_PRIMARYTYPE, NT_FROZENNODE, Type.NAME);
-        Iterable<String> mixinTypes;
+        List<String> mixinTypes;
         if (referenceable.hasProperty(JCR_MIXINTYPES)) {
-            mixinTypes = referenceable.getNames(JCR_MIXINTYPES);
+            mixinTypes = Lists.newArrayList(referenceable.getNames(JCR_MIXINTYPES));
         } else {
             mixinTypes = Collections.emptyList();
         }
-        frozen.setProperty(JCR_FROZENMIXINTYPES, mixinTypes, Type.NAMES);
-        frozen.setProperty(JCR_FROZENPRIMARYTYPE, primaryTypeOf(referenceable), Type.NAME);
         frozen.setProperty(JCR_FROZENUUID, uuidFromNode(referenceable), Type.STRING);
+        frozen.setProperty(JCR_FROZENPRIMARYTYPE, primaryTypeOf(referenceable), Type.NAME);
+        if (mixinTypes.isEmpty()) {
+            frozen.removeProperty(JCR_FROZENMIXINTYPES);
+        } else {
+            frozen.setProperty(JCR_FROZENMIXINTYPES, mixinTypes, Type.NAMES);
+        }
         return this;
     }
 
@@ -180,13 +203,18 @@ class VersionableState {
     /**
      * Restore the versionable node to the given version.
      *
+     * @param selector an optional version selector. If none is passed, this
+     *                 method will use a date based version selector.
      * @return the versionable node.
      * @throws CommitFailedException if the operation fails.
      */
-    public NodeBuilder restore() throws CommitFailedException {
+    public NodeBuilder restore(@Nullable VersionSelector selector)
+            throws CommitFailedException {
         try {
-            long created = version.getProperty(JCR_CREATED).getValue(Type.DATE);
-            VersionSelector selector = new DateVersionSelector(created);
+            if (selector == null) {
+                long created = version.getProperty(JCR_CREATED).getValue(Type.DATE);
+                selector = new DateVersionSelector(created);
+            }
             restoreFrozen(frozenNode, versionable, selector);
             restoreVersionable(versionable, version);
             return versionable;
@@ -199,37 +227,49 @@ class VersionableState {
 
     //--------------------------< internal >------------------------------------
 
+    /**
+     * Restores the state from <code>src</code> to a child node of
+     * <code>destParent</code> with the same name as <code>src</code>.
+     *
+     * @param src the source node.
+     * @param destParent the parent of the destination node.
+     * @param name the name of the source node.
+     * @param selector the version selector.
+     */
     private void restoreState(@Nonnull NodeBuilder src,
-                              @Nonnull NodeBuilder dest,
+                              @Nonnull NodeBuilder destParent,
+                              @Nonnull String name,
                               @Nonnull VersionSelector selector)
             throws RepositoryException, CommitFailedException {
+        checkNotNull(name);
+        checkNotNull(destParent);
         String primaryType = primaryTypeOf(src);
         if (primaryType.equals(NT_FROZENNODE)) {
-            restoreFrozen(src, dest, selector);
+            // replace with frozen state
+            destParent.removeChildNode(name);
+            restoreFrozen(src, destParent.child(name), selector);
         } else if (primaryType.equals(NT_VERSIONEDCHILD)) {
-            restoreVersionedChild(src, dest, selector);
+            // only perform chained restore if the node didn't exist
+            // before. see 15.7.5 and RestoreTest#testRestoreName
+            if (!destParent.hasChildNode(name)) {
+                restoreVersionedChild(src, destParent.child(name), selector);
+            }
         } else {
-            restoreNode(src, dest, selector);
+            // replace
+            destParent.removeChildNode(name);
+            restoreNode(src, destParent.child(name), selector);
         }
     }
 
     /**
      * Restore a nt:frozenNode.
      */
-    private void restoreFrozen(NodeBuilder frozen,
-                               NodeBuilder dest,
-                               VersionSelector selector)
+    private void restoreFrozen(@Nonnull NodeBuilder frozen,
+                               @Nonnull NodeBuilder dest,
+                               @Nonnull 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);
-        }
+        restoreFrozen(frozen, dest);
         // 15.7.3 Restoring Properties
         for (PropertyState p : frozen.getProperties()) {
             if (BASIC_FROZEN_PROPERTIES.contains(p.getName())) {
@@ -265,13 +305,35 @@ class VersionableState {
     }
 
     /**
+     * Restores the basic frozen properties (jcr:primaryType, jcr:mixinTypes
+     * and jcr:uuid).
+     */
+    private void restoreFrozen(@Nonnull NodeBuilder frozen,
+                               @Nonnull NodeBuilder dest) {
+        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);
+        }
+    }
+
+    /**
      * Restore a copied node.
      */
     private void restoreNode(NodeBuilder src,
                              NodeBuilder dest,
                              VersionSelector selector)
             throws RepositoryException, CommitFailedException {
-        copyProperties(src, dest, OPVForceCopy.INSTANCE, false);
+        if (primaryTypeOf(src).equals(NT_FROZENNODE)) {
+            restoreFrozen(src, dest);
+            copyProperties(src, dest, OPVForceCopy.INSTANCE, true);
+        } else {
+            copyProperties(src, dest, OPVForceCopy.INSTANCE, false);
+        }
         restoreChildren(src, dest, selector);
     }
 
@@ -281,11 +343,14 @@ class VersionableState {
     private void restoreVersionedChild(NodeBuilder versionedChild,
                                        NodeBuilder dest,
                                        VersionSelector selector)
-            throws RepositoryException {
+            throws RepositoryException, CommitFailedException {
         // 15.7.5 Chained Versions on Restore
-        TODO.unimplemented().doNothing();
-        // ...
-        // restoreVersionable(dest, selector.select(history));
+        PropertyState id = versionedChild.getProperty(JCR_CHILDVERSIONHISTORY);
+        if (id == null) {
+            throw new RepositoryException("Mandatory property " +
+                    JCR_CHILDVERSIONHISTORY + " is missing.");
+        }
+        vMgr.restore(id.getValue(Type.REFERENCE), selector, dest);
     }
 
     /**
@@ -299,10 +364,12 @@ class VersionableState {
         for (String name : src.getChildNodeNames()) {
             NodeBuilder srcChild = src.getChildNode(name);
             int action = getOPV(dest, srcChild, name);
-            if (action == COPY || action == VERSION) {
+            if (action == COPY) {
                 // replace on destination
                 dest.removeChildNode(name);
-                restoreState(srcChild, dest.child(name), selector);
+                restoreNode(srcChild, dest.child(name), selector);
+            } else if (action == VERSION) {
+                restoreState(srcChild, dest, name, selector);
             }
         }
         for (String name : dest.getChildNodeNames()) {
@@ -331,6 +398,7 @@ class VersionableState {
                                     @Nonnull NodeBuilder version) {
         checkNotNull(versionable).setProperty(JCR_ISCHECKEDOUT,
                 false, Type.BOOLEAN);
+        versionable.setProperty(JCR_VERSIONHISTORY, uuidFromNode(history));
         versionable.setProperty(JCR_BASEVERSION,
                 uuidFromNode(version), Type.REFERENCE);
         versionable.setProperty(JCR_PREDECESSORS,
@@ -380,18 +448,12 @@ class VersionableState {
                 if (ntMgr.isNodeType(new ReadOnlyTree(child.getNodeState()), MIX_VERSIONABLE)) {
                     // create frozen versionable child
                     versionedChild(child, dest.child(name));
-                } else if (isReferenceable(child)) {
-                    createFrozen(child, dest.child(name));
                 } else {
                     // else copy
                     copy(child, dest.child(name));
                 }
             } else if (opv == COPY) {
-                if (isReferenceable(child)) {
-                    createFrozen(child, dest.child(name));
-                } else {
-                    copy(child, dest.child(name));
-                }
+                copy(child, dest.child(name));
             }
         }
     }
@@ -405,14 +467,15 @@ class VersionableState {
     private void copy(NodeBuilder src,
                       NodeBuilder dest)
             throws RepositoryException, CommitFailedException {
-        copyProperties(src, dest, OPVForceCopy.INSTANCE, false);
+        if (isReferenceable(src)) {
+            initFrozen(dest, src);
+            copyProperties(src, dest, OPVForceCopy.INSTANCE, true);
+        } else {
+            copyProperties(src, dest, OPVForceCopy.INSTANCE, false);
+        }
         for (String name : src.getChildNodeNames()) {
             NodeBuilder child = src.getChildNode(name);
-            if (isReferenceable(child)) {
-                createFrozen(child, dest.child(name));
-            } else {
-                copy(child, dest.child(name));
-            }
+            copy(child, dest.child(name));
         }
     }
 

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/core/ImmutableRootTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/core/ImmutableRootTest.java?rev=1485613&r1=1485612&r2=1485613&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/core/ImmutableRootTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/core/ImmutableRootTest.java Thu May 23 08:37:19 2013
@@ -86,13 +86,6 @@ public class ImmutableRootTest extends O
         }
 
         try {
-            root.getQueryEngine();
-            fail();
-        } catch (UnsupportedOperationException e) {
-            // success
-        }
-
-        try {
             root.move("/x", "/b");
             fail();
         } catch (UnsupportedOperationException e) {

Modified: jackrabbit/oak/trunk/oak-jcr/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/pom.xml?rev=1485613&r1=1485612&r2=1485613&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-jcr/pom.xml Thu May 23 08:37:19 2013
@@ -267,21 +267,8 @@
       org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrderJcr2_2
       org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrderJcr2_3
       org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrderJcr2_4
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreChild1
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreChild1Jcr2
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreChild1Jcr2_2
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreChild1Jcr2_3
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreChild1Jcr2_4
       org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreLabel
       org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreLabelJcr2
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreName
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreNameJcr2
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder2
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder2Jcr2
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder2Jcr2_2
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder2Jcr2_3
-      org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder2Jcr2_4
       org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest
       org.apache.jackrabbit.test.api.version.OnParentVersionComputeTest
       org.apache.jackrabbit.test.api.version.OnParentVersionCopyTest