blob: 71492b927a1516aacfe342bc657f80f135048ed6 [file] [log] [blame]
/*
* 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.oak.api.Type.STRING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.jackrabbit.oak.Oak;
import org.apache.jackrabbit.oak.api.ContentRepository;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler;
import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
import org.apache.jackrabbit.oak.spi.state.ConflictAnnotatingRebaseDiff;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.Test;
public class ThreeWayConflictHandlerTest {
@Test
public void addExistingProperty() throws Exception {
AtomicBoolean called = new AtomicBoolean(false);
ThreeWayConflictHandler handler = new ErrorThreeWayConflictHandler() {
@NotNull
@Override
public Resolution addExistingProperty(@NotNull NodeBuilder parent, @NotNull PropertyState ours, @NotNull PropertyState theirs) {
called.set(true);
assertEquals("ours", ours.getValue(STRING));
assertEquals("theirs", theirs.getValue(STRING));
return Resolution.IGNORED;
}
};
ContentRepository repo = newRepo(handler);
Root root = login(repo);
setup(root);
Root ourRoot = login(repo);
Root theirRoot = login(repo);
theirRoot.getTree("/c").setProperty("p0", "theirs");
ourRoot.getTree("/c").setProperty("p0", "ours");
theirRoot.commit();
ourRoot.commit();
assertTrue(called.get());
}
@Test
public void changeDeletedProperty() throws Exception {
AtomicBoolean called = new AtomicBoolean(false);
ThreeWayConflictHandler handler = new ErrorThreeWayConflictHandler() {
@NotNull
@Override
public Resolution changeDeletedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState ours, @NotNull PropertyState base) {
called.set(true);
assertEquals("ours", ours.getValue(STRING));
assertEquals("base", base.getValue(STRING));
return Resolution.IGNORED;
}
};
ContentRepository repo = newRepo(handler);
Root root = login(repo);
setup(root);
Root ourRoot = login(repo);
Root theirRoot = login(repo);
theirRoot.getTree("/c").removeProperty("p");
ourRoot.getTree("/c").setProperty("p", "ours");
theirRoot.commit();
ourRoot.commit();
assertTrue(called.get());
}
@Test
public void changeChangedProperty() throws Exception {
AtomicBoolean called = new AtomicBoolean(false);
ThreeWayConflictHandler handler = new ErrorThreeWayConflictHandler() {
@NotNull
@Override
public Resolution changeChangedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState ours, @NotNull PropertyState theirs,
@NotNull PropertyState base) {
called.set(true);
assertEquals("ours", ours.getValue(STRING));
assertEquals("theirs", theirs.getValue(STRING));
assertEquals("base", base.getValue(STRING));
return Resolution.IGNORED;
}
};
ContentRepository repo = newRepo(handler);
Root root = login(repo);
setup(root);
Root ourRoot = login(repo);
Root theirRoot = login(repo);
theirRoot.getTree("/c").setProperty("p", "theirs");
ourRoot.getTree("/c").setProperty("p", "ours");
theirRoot.commit();
ourRoot.commit();
assertTrue(called.get());
}
@Test
public void deleteDeletedProperty() throws Exception {
AtomicBoolean called = new AtomicBoolean(false);
ThreeWayConflictHandler handler = new ErrorThreeWayConflictHandler() {
@NotNull
@Override
public Resolution deleteDeletedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState base) {
called.set(true);
assertEquals("base", base.getValue(STRING));
return Resolution.IGNORED;
}
};
ContentRepository repo = newRepo(handler);
Root root = login(repo);
setup(root);
Root ourRoot = login(repo);
Root theirRoot = login(repo);
theirRoot.getTree("/c").removeProperty("p");
ourRoot.getTree("/c").removeProperty("p");
theirRoot.commit();
ourRoot.commit();
assertTrue(called.get());
}
@Test
public void deleteChangedProperty() throws Exception {
AtomicBoolean called = new AtomicBoolean(false);
ThreeWayConflictHandler handler = new ErrorThreeWayConflictHandler() {
@NotNull
@Override
public Resolution deleteChangedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState theirs, @NotNull PropertyState base) {
called.set(true);
assertEquals("theirs", theirs.getValue(STRING));
assertEquals("base", base.getValue(STRING));
return Resolution.IGNORED;
}
};
ContentRepository repo = newRepo(handler);
Root root = login(repo);
setup(root);
Root ourRoot = login(repo);
Root theirRoot = login(repo);
theirRoot.getTree("/c").setProperty("p", "theirs");
ourRoot.getTree("/c").removeProperty("p");
theirRoot.commit();
ourRoot.commit();
assertTrue(called.get());
}
@Test
public void changeDeletedNode() throws Exception {
AtomicBoolean called = new AtomicBoolean(false);
ThreeWayConflictHandler handler = new ErrorThreeWayConflictHandler() {
@NotNull
@Override
public Resolution changeDeletedNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState ours, @NotNull NodeState base) {
called.set(true);
assertTrue(ours.hasProperty("p"));
assertTrue(base.hasProperty("p"));
assertEquals("ours", ours.getProperty("p").getValue(STRING));
assertEquals("base", base.getProperty("p").getValue(STRING));
return Resolution.IGNORED;
}
};
ContentRepository repo = newRepo(handler);
Root root = login(repo);
setup(root);
Root ourRoot = login(repo);
Root theirRoot = login(repo);
theirRoot.getTree("/c").remove();
ourRoot.getTree("/c").setProperty("p", "ours");
theirRoot.commit();
ourRoot.commit();
assertTrue(called.get());
}
@Test
public void deleteChangedNode() throws Exception {
AtomicBoolean called = new AtomicBoolean(false);
ThreeWayConflictHandler handler = new ErrorThreeWayConflictHandler() {
@NotNull
@Override
public Resolution deleteChangedNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState theirs, @NotNull NodeState base) {
called.set(true);
assertTrue(theirs.hasProperty("p"));
assertTrue(base.hasProperty("p"));
assertEquals("theirs", theirs.getProperty("p").getValue(STRING));
assertEquals("base", base.getProperty("p").getValue(STRING));
return Resolution.IGNORED;
}
};
ContentRepository repo = newRepo(handler);
Root root = login(repo);
setup(root);
Root ourRoot = login(repo);
Root theirRoot = login(repo);
theirRoot.getTree("/c").setProperty("p", "theirs");
ourRoot.getTree("/c").remove();
theirRoot.commit();
ourRoot.commit();
assertTrue(called.get());
}
@Test
public void deleteDeletedNode() throws Exception {
AtomicBoolean called = new AtomicBoolean(false);
ThreeWayConflictHandler handler = new ErrorThreeWayConflictHandler() {
@NotNull
@Override
public Resolution deleteDeletedNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState base) {
called.set(true);
assertTrue(base.hasProperty("p"));
assertEquals("base", base.getProperty("p").getValue(STRING));
return Resolution.IGNORED;
}
};
ContentRepository repo = newRepo(handler);
Root root = login(repo);
setup(root);
Root ourRoot = login(repo);
Root theirRoot = login(repo);
theirRoot.getTree("/c").remove();
ourRoot.getTree("/c").remove();
theirRoot.commit();
ourRoot.commit();
assertTrue(called.get());
}
@Test
public void deletedNodesShouldNotBeRecreated() throws Exception {
NodeState root = EmptyNodeState.EMPTY_NODE;
NodeState withProperty;
{
NodeBuilder builder = root.builder();
builder.child("c").setProperty("foo", "bar");
withProperty = builder.getNodeState();
}
NodeState withUpdatedProperty;
{
NodeBuilder builder = withProperty.builder();
builder.child("c").setProperty("foo", "baz");
withUpdatedProperty = builder.getNodeState();
}
NodeState withRemovedChild;
{
NodeBuilder builder = withProperty.builder();
builder.child("c").remove();
withRemovedChild = builder.getNodeState();
}
NodeBuilder mergedBuilder = withUpdatedProperty.builder();
withRemovedChild.compareAgainstBaseState(withProperty, new ConflictAnnotatingRebaseDiff(mergedBuilder));
NodeState merged = ConflictHook.of(DefaultThreeWayConflictHandler.OURS).processCommit(
mergedBuilder.getBaseState(),
mergedBuilder.getNodeState(),
CommitInfo.EMPTY
);
assertFalse(merged.hasChildNode("c"));
}
private static ContentRepository newRepo(ThreeWayConflictHandler handler) {
return new Oak().with(new OpenSecurityProvider()).with(handler).createContentRepository();
}
private static Root login(ContentRepository repo) throws Exception {
return repo.login(null, null).getLatestRoot();
}
private static void setup(Root root) throws Exception {
Tree tree = root.getTree("/");
tree.addChild("c").setProperty("p", "base");
root.commit();
}
private static class ErrorThreeWayConflictHandler implements ThreeWayConflictHandler {
@NotNull
@Override
public Resolution addExistingProperty(@NotNull NodeBuilder parent, @NotNull PropertyState ours, @NotNull PropertyState theirs) {
Assert.fail("method should not be called");
return Resolution.IGNORED;
}
@NotNull
@Override
public Resolution changeDeletedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState ours, @NotNull PropertyState base) {
Assert.fail("method should not be called");
return Resolution.IGNORED;
}
@NotNull
@Override
public Resolution changeChangedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState ours, @NotNull PropertyState theirs,
@NotNull PropertyState base) {
Assert.fail("method should not be called");
return Resolution.IGNORED;
}
@NotNull
@Override
public Resolution deleteDeletedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState base) {
Assert.fail("method should not be called");
return Resolution.IGNORED;
}
@NotNull
@Override
public Resolution deleteChangedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState theirs, @NotNull PropertyState base) {
Assert.fail("method should not be called");
return Resolution.IGNORED;
}
@NotNull
@Override
public Resolution addExistingNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState ours, @NotNull NodeState theirs) {
Assert.fail("method should not be called");
return Resolution.IGNORED;
}
@NotNull
@Override
public Resolution changeDeletedNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState ours, @NotNull NodeState base) {
Assert.fail("method should not be called");
return Resolution.IGNORED;
}
@NotNull
@Override
public Resolution deleteChangedNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState theirs, @NotNull NodeState base) {
Assert.fail("method should not be called");
return Resolution.IGNORED;
}
@NotNull
@Override
public Resolution deleteDeletedNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState base) {
Assert.fail("method should not be called");
return Resolution.IGNORED;
}
}
}