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 md...@apache.org on 2012/06/14 15:27:38 UTC

svn commit: r1350229 - in /jackrabbit/oak/trunk: oak-core/ oak-core/src/main/java/org/apache/jackrabbit/oak/core/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/ oak-core/src/main/java/org/apache/jackrabbit/oak/util/ oak-jcr/src/main/jav...

Author: mduerig
Date: Thu Jun 14 13:27:38 2012
New Revision: 1350229

URL: http://svn.apache.org/viewvc?rev=1350229&view=rev
Log:
OAK-133: Session.refresh(true) should allow for manual conflict reconciliation
Add ConflictHandler which annotates conflicts on nodes

Modified:
    jackrabbit/oak/trunk/oak-core/pom.xml
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidatorProvider.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/util/Iterators.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionDelegate.java
    jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/RepositoryTest.java

Modified: jackrabbit/oak/trunk/oak-core/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/pom.xml?rev=1350229&r1=1350228&r2=1350229&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-core/pom.xml Thu Jun 14 13:27:38 2012
@@ -44,11 +44,12 @@
               org.apache.jackrabbit.oak.util,
               org.apache.jackrabbit.oak.namepath,
               org.apache.jackrabbit.oak.plugins.name,
+              org.apache.jackrabbit.oak.spi.state,
               org.apache.jackrabbit.oak.spi.security.authentication,
               org.apache.jackrabbit.oak.spi.security.principal,
               org.apache.jackrabbit.oak.spi.security.privilege,
-              org.apache.jackrabbit.oak.security.privilege,
-              org.apache.jackrabbit.oak.spi.security.user
+              org.apache.jackrabbit.oak.spi.security.user,
+              org.apache.jackrabbit.oak.security.privilege
             </Export-Package>
             <Bundle-Activator>
               org.apache.jackrabbit.oak.osgi.Activator

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java?rev=1350229&r1=1350228&r2=1350229&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java Thu Jun 14 13:27:38 2012
@@ -26,6 +26,7 @@ import javax.annotation.Nonnull;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.ConflictHandler;
 import org.apache.jackrabbit.oak.api.ConflictHandler.Resolution;
+import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
@@ -38,10 +39,12 @@ import org.apache.jackrabbit.oak.spi.sta
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.apache.jackrabbit.oak.api.ConflictHandler.Resolution.*;
+import static org.apache.jackrabbit.oak.api.ConflictHandler.Resolution.MERGED;
+import static org.apache.jackrabbit.oak.api.ConflictHandler.Resolution.OURS;
 import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
 import static org.apache.jackrabbit.oak.commons.PathUtils.getName;
 import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
+import static org.apache.jackrabbit.oak.util.Iterators.toList;
 
 public class RootImpl implements Root {
     static final Logger log = LoggerFactory.getLogger(RootImpl.class);
@@ -317,7 +320,7 @@ public class RootImpl implements Root {
 
                 switch (resolution) {
                     case OURS:
-                        setProperty(after, target);
+                        setProperty(target, after);
                         break;
                     case THEIRS:
                     case MERGED:
@@ -347,7 +350,7 @@ public class RootImpl implements Root {
 
                 switch (resolution) {
                     case OURS:
-                        setProperty(after, target);
+                        setProperty(target, after);
                         break;
                     case THEIRS:
                     case MERGED:
@@ -394,7 +397,7 @@ public class RootImpl implements Root {
 
                 switch (resolution) {
                     case OURS:
-                        addChild(name, after, target);
+                        addChild(target, name, after);
                         break;
                     case THEIRS:
                     case MERGED:
@@ -417,7 +420,7 @@ public class RootImpl implements Root {
 
                 switch (resolution) {
                     case OURS:
-                        addChild(name, after, target);
+                        addChild(target, name, after);
                         break;
                     case THEIRS:
                     case MERGED:
@@ -450,19 +453,20 @@ public class RootImpl implements Root {
                 }
             }
 
-            private void addChild(String name, NodeState state, Tree target) {
+            private void addChild(Tree target, String name, NodeState state) {
                 Tree child = target.addChild(name);
                 for (PropertyState property : state.getProperties()) {
-                    setProperty(property, child);
+                    setProperty(child, property);
                 }
                 for (ChildNodeEntry entry : state.getChildNodeEntries()) {
-                    addChild(entry.getName(), entry.getNodeState(), child);
+                    addChild(child, entry.getName(), entry.getNodeState());
                 }
             }
 
-            private void setProperty(PropertyState property, Tree target) {
+            private void setProperty(Tree target, PropertyState property) {
                 if (property.isArray()) {
-                    target.setProperty(property.getName(), toList(property.getValues()));
+                    target.setProperty(property.getName(),
+                            toList(property.getValues(), new ArrayList<CoreValue>()));
                 }
                 else {
                     target.setProperty(property.getName(), property.getValue());
@@ -472,12 +476,4 @@ public class RootImpl implements Root {
         });
     }
 
-    private static <T> List<T> toList(Iterable<T> values) {
-        List<T> l = new ArrayList<T>();
-        for (T value : values) {
-            l.add(value);
-        }
-        return l;
-    }
-
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidatorProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidatorProvider.java?rev=1350229&r1=1350228&r2=1350229&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidatorProvider.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidatorProvider.java Thu Jun 14 13:27:38 2012
@@ -16,14 +16,14 @@
  */
 package org.apache.jackrabbit.oak.plugins.type;
 
+import java.util.HashSet;
+import java.util.Set;
+
 import org.apache.jackrabbit.oak.spi.commit.Validator;
 import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
 import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
-import java.util.HashSet;
-import java.util.Set;
-
 public class TypeValidatorProvider implements ValidatorProvider {
 
     @Override
@@ -88,6 +88,9 @@ public class TypeValidatorProvider imple
         types.add("rep:Members");
         types.add("rep:RetentionManageable");
 
+        // Oak types are always available
+        types.add("mix:mergeConflict");
+
         // Find any extra types from /jcr:system/jcr:nodeTypes
         NodeState system = after.getChildNode("jcr:system");
         if (system != null) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/util/Iterators.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/util/Iterators.java?rev=1350229&r1=1350228&r2=1350229&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/util/Iterators.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/util/Iterators.java Thu Jun 14 13:27:38 2012
@@ -16,6 +16,14 @@
  */
 package org.apache.jackrabbit.oak.util;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.annotation.Nonnull;
+
 import org.apache.commons.collections.iterators.ArrayIterator;
 import org.apache.commons.collections.iterators.EmptyIterator;
 import org.apache.commons.collections.iterators.FilterIterator;
@@ -23,13 +31,6 @@ import org.apache.commons.collections.it
 import org.apache.commons.collections.iterators.SingletonIterator;
 import org.apache.commons.collections.iterators.TransformIterator;
 
-import javax.annotation.Nonnull;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
 /**
  * Utility class containing type safe adapters for some of the iterators of
  * commons-collections.
@@ -242,4 +243,19 @@ public final class Iterators {
         };
     }
 
+    /**
+     * Spools the values of an iterator into a list.
+     * @param values  the values to spool
+     * @param list  the target list to receive the values
+     * @param <T>
+     * @return  {@code list}
+     */
+    @Nonnull
+    public static <T> List<T> toList(Iterable<? extends T> values, List<T> list) {
+        for (T value : values) {
+            list.add(value);
+        }
+        return list;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionDelegate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionDelegate.java?rev=1350229&r1=1350228&r2=1350229&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionDelegate.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionDelegate.java Thu Jun 14 13:27:38 2012
@@ -18,7 +18,9 @@ package org.apache.jackrabbit.oak.jcr;
 
 import java.io.IOException;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 import javax.annotation.CheckForNull;
@@ -26,6 +28,8 @@ import javax.annotation.Nonnull;
 import javax.jcr.ItemExistsException;
 import javax.jcr.Node;
 import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -38,8 +42,11 @@ import javax.jcr.version.VersionManager;
 
 import org.apache.jackrabbit.oak.api.AuthInfo;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.ConflictHandler;
 import org.apache.jackrabbit.oak.api.ContentSession;
 import org.apache.jackrabbit.oak.api.CoreValue;
+import org.apache.jackrabbit.oak.api.CoreValueFactory;
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.QueryEngine;
 import org.apache.jackrabbit.oak.api.Result;
 import org.apache.jackrabbit.oak.api.ResultRow;
@@ -52,9 +59,14 @@ import org.apache.jackrabbit.oak.namepat
 import org.apache.jackrabbit.oak.namepath.NameMapper;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.namepath.NamePathMapperImpl;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.util.Iterators;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.jackrabbit.oak.util.Iterators.toList;
+
 public class SessionDelegate {
     static final Logger log = LoggerFactory.getLogger(SessionDelegate.class);
 
@@ -66,6 +78,7 @@ public class SessionDelegate {
     private final Workspace workspace;
     private final Session session;
     private final Root root;
+    private final ConflictHandler conflictHandler;
 
     private boolean isAlive = true;
 
@@ -79,6 +92,7 @@ public class SessionDelegate {
         this.workspace = new WorkspaceImpl(this);
         this.session = new SessionImpl(this);
         this.root = contentSession.getCurrentRoot();
+        this.conflictHandler = new AnnotatingConflictHandler();
     }
 
     public boolean isAlive() {
@@ -168,7 +182,7 @@ public class SessionDelegate {
 
     public void save() throws RepositoryException {
         try {
-            root.commit(DefaultConflictHandler.OURS);
+            root.commit(conflictHandler);
         }
         catch (CommitFailedException e) {
             throw new RepositoryException(e);
@@ -177,7 +191,7 @@ public class SessionDelegate {
 
     public void refresh(boolean keepChanges) {
         if (keepChanges) {
-            root.rebase(DefaultConflictHandler.OURS);
+            root.rebase(conflictHandler);
         }
         else {
             root.refresh();
@@ -441,4 +455,137 @@ public class SessionDelegate {
             return true;
         }
     }
+
+    //------------------------------------------------------------< AnnotatingConflictHandler >---
+
+    /**
+     * This {@link ConflictHandler} implementation resolves conflicts to
+     * {@link Resolution#THEIRS} and in addition marks nodes where a conflict
+     * occurred with {@code mix:mergeConflict}:
+     *
+     * <pre>
+     * [mix:mergeConflict]
+     *   mixin
+     *   primaryitem jcr:ours
+     *   + jcr:ours (nt:unstructured)
+     * </pre>
+     *
+     * The {@code jcr:ours} sub node contains our version of the node prior to
+     * the conflict.
+     *
+     * TODO: add corresponding commit hook which fails the commit on existence of mix:mergeConflict
+     */
+    private class AnnotatingConflictHandler implements ConflictHandler {
+        // TODO: move these constants to some common location for repository internal node types
+        public static final String MIX_MERGE_CONFLICT = "mix:mergeConflict";
+        public static final String JCR_OURS = "jcr:ours";
+        public static final String ADD_EXISTING = "addExisting";
+        public static final String CHANGE_DELETED = "changeDeleted";
+        public static final String CHANGE_CHANGED = "changeChanged";
+        public static final String DELETE_CHANGED = "deleteChanged";
+
+        private final CoreValueFactory valueFactory;
+        private final String jcrMixinTypes;
+
+        AnnotatingConflictHandler() throws RepositoryException {
+            valueFactory = contentSession.getCoreValueFactory();
+            jcrMixinTypes = getOakPathOrThrow(Property.JCR_MIXIN_TYPES);
+        }
+
+        @Override
+        public Resolution addExistingProperty(Tree parent, PropertyState ours, PropertyState theirs) {
+            Tree marker = addConflictMarker(parent);
+            setProperty(getOrCreateNode(marker, ADD_EXISTING), ours);
+            return Resolution.THEIRS;
+        }
+
+        @Override
+        public Resolution changeDeletedProperty(Tree parent, PropertyState ours) {
+            Tree marker = addConflictMarker(parent);
+            setProperty(getOrCreateNode(marker, CHANGE_DELETED), ours);
+            return Resolution.THEIRS;
+        }
+
+        @Override
+        public Resolution changeChangedProperty(Tree parent, PropertyState ours, PropertyState theirs) {
+            Tree marker = addConflictMarker(parent);
+            setProperty(getOrCreateNode(marker, CHANGE_CHANGED), ours);
+            return Resolution.THEIRS;
+        }
+
+        @Override
+        public Resolution deleteChangedProperty(Tree parent, PropertyState theirs) {
+            Tree marker = addConflictMarker(parent);
+            setProperty(getOrCreateNode(marker, DELETE_CHANGED), theirs);
+            return Resolution.THEIRS;
+        }
+
+        @Override
+        public Resolution addExistingNode(Tree parent, String name, NodeState ours, NodeState theirs) {
+            Tree marker = addConflictMarker(parent);
+            addChild(getOrCreateNode(marker, ADD_EXISTING), name, ours);
+            return Resolution.THEIRS;
+        }
+
+        @Override
+        public Resolution changeDeletedNode(Tree parent, String name, NodeState ours) {
+            Tree marker = addConflictMarker(parent);
+            addChild(getOrCreateNode(marker, CHANGE_DELETED), name, ours);
+            return Resolution.THEIRS;
+        }
+
+        @Override
+        public Resolution deleteChangedNode(Tree parent, String name, NodeState theirs) {
+            Tree marker = addConflictMarker(parent);
+            markChild(getOrCreateNode(marker, DELETE_CHANGED), name);
+            return Resolution.THEIRS;
+        }
+
+        private Tree addConflictMarker(Tree parent) {
+            PropertyState jcrMixin = parent.getProperty(jcrMixinTypes);
+            List<CoreValue> mixins = new ArrayList<CoreValue>();
+            if (jcrMixin != null) {
+                mixins = Iterators.toList(jcrMixin.getValues(), mixins);
+            }
+            if (!mixins.contains(MIX_MERGE_CONFLICT)) {
+                mixins.add(valueFactory.createValue(MIX_MERGE_CONFLICT, PropertyType.NAME));
+                parent.setProperty(jcrMixinTypes, mixins);
+            }
+
+            return getOrCreateNode(parent, JCR_OURS);
+        }
+
+        private Tree getOrCreateNode(Tree parent, String name) {
+            Tree child = parent.getChild(name);
+            if (child == null) {
+                child = parent.addChild(name);
+            }
+            return child;
+        }
+
+        private void addChild(Tree parent, String name, NodeState state) {
+            Tree child = parent.addChild(name);
+            for (PropertyState property : state.getProperties()) {
+                setProperty(child, property);
+            }
+            for (ChildNodeEntry entry : state.getChildNodeEntries()) {
+                addChild(child, entry.getName(), entry.getNodeState());
+            }
+        }
+
+        private void markChild(Tree parent, String name) {
+            parent.addChild(name);
+        }
+
+        private void setProperty(Tree parent, PropertyState property) {
+            if (property.isArray()) {
+                parent.setProperty(property.getName(),
+                        toList(property.getValues(), new ArrayList<CoreValue>()));
+            }
+            else {
+                parent.setProperty(property.getName(), property.getValue());
+            }
+        }
+
+    }
 }

Modified: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/RepositoryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/RepositoryTest.java?rev=1350229&r1=1350228&r2=1350229&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/RepositoryTest.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/RepositoryTest.java Thu Jun 14 13:27:38 2012
@@ -1230,12 +1230,7 @@ public class RepositoryTest extends Abst
 
             session2.save();
             assertFalse(session1.getRootNode().hasNode("node"));
-            assertTrue(session2.getRootNode().hasNode("node"));
-            assertTrue(session2.getRootNode().getNode("node").hasNode("2"));
-
-            session1.refresh(true);
-            assertTrue(session1.getRootNode().hasNode("node"));
-            assertTrue(session1.getRootNode().getNode("node").hasNode("2"));
+            assertFalse(session2.getRootNode().hasNode("node"));
         }
         finally {
             session1.logout();