Index: oak-core/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java (revision cd5ff6d6e7d9e5229037bdf79a83bab3c8941c57) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java (revision ) @@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.spi.state; import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.jackrabbit.JcrConstants.JCR_LASTMODIFIED; import static org.apache.jackrabbit.oak.api.Type.LONG; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -29,6 +30,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -116,6 +118,55 @@ b1.setChildNode("n" + k); b2.setChildNode("m" + k); } + + b1.setChildNode("conflict"); + b2.setChildNode("conflict"); + + store.merge(b1, hook, CommitInfo.EMPTY); + store.merge(b2, hook, CommitInfo.EMPTY); + } + + @Test + public void addExistingNodeJCRLastModified() throws CommitFailedException { + CommitHook hook = new CompositeHook( + new ConflictHook(JcrConflictHandler.createJcrConflictHandler()), + new EditorHook(new ConflictValidatorProvider()) + ); + + NodeBuilder b1 = store.getRoot().builder(); + NodeBuilder b2 = store.getRoot().builder(); + + Calendar calendar = Calendar.getInstance(); + b1.setChildNode("addExistingNodeJCRLastModified").setProperty(JCR_LASTMODIFIED, calendar); + calendar.add(Calendar.MINUTE, 1); + b2.setChildNode("addExistingNodeJCRLastModified").setProperty(JCR_LASTMODIFIED, calendar); + + b1.setChildNode("conflict"); + b2.setChildNode("conflict"); + + store.merge(b1, hook, CommitInfo.EMPTY); + store.merge(b2, hook, CommitInfo.EMPTY); + } + + @Test + public void addChangeChangedJCRLastModified() throws CommitFailedException { + CommitHook hook = new CompositeHook( + new ConflictHook(JcrConflictHandler.createJcrConflictHandler()), + new EditorHook(new ConflictValidatorProvider()) + ); + + NodeBuilder b = store.getRoot().builder(); + Calendar calendar = Calendar.getInstance(); + b.setChildNode("addExistingNodeJCRLastModified").setProperty(JCR_LASTMODIFIED, calendar); + store.merge(b, hook, CommitInfo.EMPTY); + + NodeBuilder b1 = store.getRoot().builder(); + NodeBuilder b2 = store.getRoot().builder(); + + calendar.add(Calendar.MINUTE, 1); + b1.setChildNode("addExistingNodeJCRLastModified").setProperty(JCR_LASTMODIFIED, calendar); + calendar.add(Calendar.MINUTE, 1); + b2.setChildNode("addExistingNodeJCRLastModified").setProperty(JCR_LASTMODIFIED, calendar); b1.setChildNode("conflict"); b2.setChildNode("conflict"); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrConflictHandler.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrConflictHandler.java (revision cd5ff6d6e7d9e5229037bdf79a83bab3c8941c57) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrConflictHandler.java (revision ) @@ -32,6 +32,7 @@ */ public static CompositeConflictHandler createJcrConflictHandler() { return new CompositeConflictHandler(ImmutableList.of( + new JcrLastModifiedConflictHandler(), new ChildOrderConflictHandler(), new AnnotatingConflictHandler() )); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrLastModifiedConflictHandler.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrLastModifiedConflictHandler.java (revision ) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrLastModifiedConflictHandler.java (revision ) @@ -0,0 +1,105 @@ +/* + * 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.commit; + +import static org.apache.jackrabbit.JcrConstants.JCR_LASTMODIFIED; +import static org.apache.jackrabbit.util.ISO8601.parse; + +import java.util.Calendar; + +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.spi.commit.PartialConflictHandler; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +public class JcrLastModifiedConflictHandler implements PartialConflictHandler { + + @Override + public Resolution addExistingProperty(NodeBuilder parent, PropertyState ours, + PropertyState theirs) { + if (isLastModified(ours)) { + merge(parent, ours, theirs); + return Resolution.MERGED; + } + return null; + } + + @Override + public Resolution changeDeletedProperty(NodeBuilder parent, PropertyState ours) { + return null; + } + + @Override + public Resolution changeChangedProperty(NodeBuilder parent, PropertyState ours, + PropertyState theirs) { + if (isLastModified(ours)) { + merge(parent, ours, theirs); + return Resolution.MERGED; + } + return null; + } + + private static void merge(NodeBuilder parent, PropertyState ours, + PropertyState theirs) { + Calendar o = parse(ours.getValue(Type.DATE)); + Calendar t = parse(theirs.getValue(Type.DATE)); + // pick & set newer one + if (o.before(t)) { + parent.setProperty(JCR_LASTMODIFIED, t); + } else { + parent.setProperty(JCR_LASTMODIFIED, o); + } + } + + private static boolean isLastModified(PropertyState p) { + return JCR_LASTMODIFIED.equals(p.getName()); + } + + @Override + public Resolution deleteDeletedProperty(NodeBuilder parent, PropertyState ours) { + return null; + } + + @Override + public Resolution deleteChangedProperty(NodeBuilder parent, PropertyState theirs) { + return null; + } + + @Override + public Resolution addExistingNode(NodeBuilder parent, String name, NodeState ours, NodeState theirs) { + return null; + } + + @Override + public Resolution changeDeletedNode(NodeBuilder parent, String name, NodeState ours) { + return null; + } + + @Override + public Resolution deleteChangedNode(NodeBuilder parent, String name, NodeState theirs) { + return null; + } + + @Override + public Resolution deleteDeletedNode(NodeBuilder parent, String name) { + return null; + } +}