blob: 60e5c912d87faba589789f565fe8f0a012a879e8 [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.spi.state;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
import org.apache.jackrabbit.oak.spi.commit.DefaultMoveValidator;
import org.apache.jackrabbit.oak.spi.commit.EditorDiff;
import org.junit.Before;
import org.junit.Test;
public class MoveDetectorTest {
private NodeState root;
@Before
public void setup() {
NodeBuilder rootBuilder = EmptyNodeState.EMPTY_NODE.builder();
NodeBuilder test = rootBuilder.child("test");
test.setProperty("a", 1);
test.setProperty("b", 2);
test.setProperty("c", 3);
test.child("x");
test.child("y");
test.child("z").child("zz");
root = rootBuilder.getNodeState();
}
/**
* Test whether we can detect a single move
* @throws CommitFailedException
*/
@Test
public void simpleMove() throws CommitFailedException {
NodeState moved1 = move(root.builder(), "/test/x", "/test/y/xx").getNodeState();
MoveExpectation moveExpectation1 = new MoveExpectation(
ImmutableMap.of("/test/x", "/test/y/xx"));
MoveDetector moveDetector1 = new MoveDetector(moveExpectation1);
CommitFailedException exception1 = EditorDiff.process(moveDetector1, root, moved1);
if (exception1 != null) {
throw exception1;
}
moveExpectation1.assertAllFound();
// Test whether we can also detect the move back on top of the previous, persisted move
NodeState moved2 = move(moved1.builder(), "/test/y/xx", "/test/x").getNodeState();
MoveExpectation moveExpectation2 = new MoveExpectation(
ImmutableMap.of("/test/y/xx", "/test/x"));
MoveDetector moveDetector2 = new MoveDetector(moveExpectation2);
CommitFailedException exception2 = EditorDiff.process(moveDetector2, moved1, moved2);
if (exception2 != null) {
throw exception2;
}
moveExpectation2.assertAllFound();
}
/**
* Moving a moved node is reported as a single move from the original source
* to the final destination.
* @throws CommitFailedException
*/
@Test
public void moveMoved() throws CommitFailedException {
NodeBuilder rootBuilder = root.builder();
move(rootBuilder, "/test/x", "/test/y/xx");
NodeState moved = move(rootBuilder, "/test/y/xx", "/test/z/xxx").getNodeState();
MoveExpectation moveExpectation = new MoveExpectation(
ImmutableMap.of("/test/x", "/test/z/xxx"));
MoveDetector moveDetector = new MoveDetector(moveExpectation);
CommitFailedException exception = EditorDiff.process(moveDetector, root, moved);
if (exception != null) {
throw exception;
}
moveExpectation.assertAllFound();
}
/**
* Moving a transiently added node doesn't generate a move event
* @throws CommitFailedException
*/
@Test
public void moveAddedNode() throws CommitFailedException {
NodeBuilder rootBuilder = root.builder();
rootBuilder.getChildNode("test").setChildNode("added");
NodeState moved = move(rootBuilder, "/test/added", "/test/y/added").getNodeState();
MoveExpectation moveExpectation = new MoveExpectation(
ImmutableMap.<String, String>of());
MoveDetector moveDetector = new MoveDetector(moveExpectation);
CommitFailedException exception = EditorDiff.process(moveDetector, root, moved);
if (exception != null) {
throw exception;
}
moveExpectation.assertAllFound();
}
/**
* Moving a node from a moved subtree doesn't generate a move event.
* @throws CommitFailedException
*/
@Test
public void moveFromMovedSubtree() throws CommitFailedException {
NodeBuilder rootBuilder = root.builder();
move(rootBuilder, "/test/z", "/test/y/z");
NodeState moved = move(rootBuilder, "/test/y/z/zz", "/test/x/zz").getNodeState();
MoveExpectation moveExpectation = new MoveExpectation(
ImmutableMap.of("/test/z", "/test/y/z", "/test/z/zz", "/test/x/zz"));
MoveDetector moveDetector = new MoveDetector(moveExpectation);
CommitFailedException exception = EditorDiff.process(moveDetector, root, moved);
if (exception != null) {
throw exception;
}
moveExpectation.assertAllFound();
}
/**
* Moving a transiently added node from a moved subtree doesn't generate a move event.
* @throws CommitFailedException
*/
@Test
public void moveAddedFromMovedSubtree() throws CommitFailedException {
NodeBuilder rootBuilder = root.builder();
rootBuilder.getChildNode("test").getChildNode("z").setChildNode("added");
move(rootBuilder, "/test/z", "/test/y/z");
NodeState moved = move(rootBuilder, "/test/y/z/added", "/test/x/added").getNodeState();
MoveExpectation moveExpectation = new MoveExpectation(
ImmutableMap.of("/test/z", "/test/y/z"));
MoveDetector moveDetector = new MoveDetector(moveExpectation);
CommitFailedException exception = EditorDiff.process(moveDetector, root, moved);
if (exception != null) {
throw exception;
}
moveExpectation.assertAllFound();
}
/**
* Moving a node forth and back again should not generate a move event.
* @throws CommitFailedException
*/
@Test
public void moveForthAndBack() throws CommitFailedException {
NodeBuilder rootBuilder = root.builder();
move(rootBuilder, "/test/x", "/test/y/xx");
NodeState moved = move(rootBuilder, "/test/y/xx", "/test/x").getNodeState();
MoveExpectation moveExpectation = new MoveExpectation(
ImmutableMap.<String, String>of());
MoveDetector moveDetector = new MoveDetector(moveExpectation);
CommitFailedException exception = EditorDiff.process(moveDetector, root, moved);
if (exception != null) {
throw exception;
}
moveExpectation.assertAllFound();
}
//------------------------------------------------------------< private >---
private static NodeBuilder move(NodeBuilder builder, String source, String dest) {
NodeBuilder sourceBuilder = getBuilder(builder, source);
NodeBuilder destParentBuilder = getBuilder(builder, PathUtils.getParentPath(dest));
assertTrue(sourceBuilder.moveTo(destParentBuilder, PathUtils.getName(dest)));
return builder;
}
private static NodeBuilder getBuilder(NodeBuilder builder, String path) {
for (String name : PathUtils.elements(path)) {
builder = builder.getChildNode(name);
}
return builder;
}
private static class MoveExpectation extends DefaultMoveValidator {
private final Map<String, String> moves;
private final String path;
private MoveExpectation(Map<String, String> moves, String path) {
this.moves = moves;
this.path = path;
}
public MoveExpectation(Map<String, String> moves) {
this(Maps.newHashMap(moves), "/");
}
@Override
public void move(String name, String sourcePath, NodeState moved) throws CommitFailedException {
String actualDestPath = PathUtils.concat(path, name);
String expectedDestPath = moves.remove(sourcePath);
assertEquals("Unexpected move. " +
"Expected: " + (expectedDestPath == null
? "None"
: '>' + sourcePath + ':' + expectedDestPath) +
" Found: >" + sourcePath + ':' + actualDestPath,
expectedDestPath, actualDestPath);
}
@Override
public MoveValidator childNodeChanged(String name, NodeState before, NodeState after) {
return new MoveExpectation(moves, PathUtils.concat(path, name));
}
public void assertAllFound() {
assertTrue("Missing moves: " + moves, moves.isEmpty());
}
}
}