| /* |
| * 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.document; |
| |
| import java.util.List; |
| import java.util.Map; |
| |
| import com.google.common.collect.Lists; |
| |
| import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Key; |
| import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation; |
| import org.apache.jackrabbit.oak.spi.state.NodeBuilder; |
| import org.apache.jackrabbit.oak.stats.Clock; |
| import org.junit.AfterClass; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| |
| import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES; |
| import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.COMMIT_ROOT; |
| import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.setCommitRoot; |
| import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.setModified; |
| import static org.apache.jackrabbit.oak.plugins.document.TestUtils.merge; |
| import static org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation.Type.REMOVE_MAP_ENTRY; |
| import static org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation.Type.SET_MAP_ENTRY; |
| import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| |
| public class NodeDocumentSweeperTest { |
| |
| @Rule |
| public DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider(); |
| |
| private Clock clock; |
| private DocumentNodeStore ns; |
| private DocumentMK mk; |
| private DocumentStore store; |
| private MissingLastRevSeeker seeker; |
| |
| @Before |
| public void before() throws Exception { |
| clock = new Clock.Virtual(); |
| clock.waitUntil(System.currentTimeMillis()); |
| DocumentMK.Builder builder = builderProvider.newBuilder(); |
| builder.clock(clock).setAsyncDelay(0); |
| Revision.setClock(clock); |
| mk = builder.open(); |
| ns = builder.getNodeStore(); |
| store = ns.getDocumentStore(); |
| seeker = builder.createMissingLastRevSeeker(); |
| } |
| |
| @AfterClass |
| public static void resetClock() { |
| Revision.resetClockToDefault(); |
| } |
| |
| @Test |
| public void sweepUncommittedBeforeHead() throws Exception { |
| Revision uncommitted = ns.newRevision(); |
| NodeBuilder b = ns.getRoot().builder(); |
| b.child("test"); |
| merge(ns, b); |
| ns.runBackgroundUpdateOperations(); |
| |
| UpdateOp op = new UpdateOp(getIdFromPath("/test"), false); |
| op.setMapEntry("foo", uncommitted, "value"); |
| setCommitRoot(op, uncommitted, 0); |
| setModified(op, uncommitted); |
| assertNotNull(store.findAndUpdate(NODES, op)); |
| |
| List<UpdateOp> ops = Lists.newArrayList(); |
| Revision nextSweepStart = sweep(ops); |
| |
| assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart); |
| assertEquals(1, ops.size()); |
| op = ops.get(0); |
| Map<Key, Operation> changes = op.getChanges(); |
| assertEquals(2, changes.size()); |
| Operation o = changes.get(new Key(COMMIT_ROOT, uncommitted)); |
| assertNotNull(o); |
| assertEquals(REMOVE_MAP_ENTRY, o.type); |
| o = changes.get(new Key("foo", uncommitted)); |
| assertNotNull(o); |
| assertEquals(REMOVE_MAP_ENTRY, o.type); |
| } |
| |
| @Test |
| public void sweepUncommittedAfterHead() throws Exception { |
| NodeBuilder b = ns.getRoot().builder(); |
| b.child("test"); |
| merge(ns, b); |
| ns.runBackgroundUpdateOperations(); |
| |
| Revision uncommitted = ns.newRevision(); |
| UpdateOp op = new UpdateOp(getIdFromPath("/test"), false); |
| op.setMapEntry("foo", uncommitted, "value"); |
| setCommitRoot(op, uncommitted, 0); |
| setModified(op, uncommitted); |
| assertNotNull(store.findAndUpdate(NODES, op)); |
| |
| List<UpdateOp> ops = Lists.newArrayList(); |
| Revision nextSweepStart = sweep(ops); |
| |
| assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart); |
| // must not sweep change newer than head |
| assertEquals(0, ops.size()); |
| } |
| |
| @Test |
| public void sweepUnmergedBranchCommit() throws Exception { |
| NodeBuilder b = ns.getRoot().builder(); |
| b.child("test"); |
| merge(ns, b); |
| |
| String branchRev = mk.branch(null); |
| mk.commit("/test", "^\"foo\":\"value\"", branchRev, null); |
| |
| // force a new head revision newer than branch commit |
| b = ns.getRoot().builder(); |
| b.child("bar"); |
| merge(ns, b); |
| ns.runBackgroundUpdateOperations(); |
| |
| List<UpdateOp> ops = Lists.newArrayList(); |
| Revision nextSweepStart = sweep(ops); |
| |
| // must not touch branch |
| assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart); |
| assertEquals(0, ops.size()); |
| } |
| |
| @Test |
| public void sweepMergedBranch() throws Exception { |
| String branchRev = mk.branch(null); |
| branchRev = mk.commit("/", "+\"foo\":{}", branchRev, null); |
| branchRev = mk.commit("/", "+\"bar\":{}", branchRev, null); |
| branchRev = mk.commit("/", "+\"baz\":{}", branchRev, null); |
| mk.merge(branchRev, null); |
| ns.runBackgroundUpdateOperations(); |
| |
| List<UpdateOp> ops = Lists.newArrayList(); |
| Revision nextSweepStart = sweep(ops); |
| |
| assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart); |
| assertEquals(0, ops.size()); |
| } |
| |
| @Test |
| public void updatePre18Branch() throws Exception { |
| String branchRev = mk.branch(null); |
| branchRev = mk.commit("/", "+\"foo\":{}", branchRev, null); |
| mk.merge(branchRev, null); |
| ns.runBackgroundUpdateOperations(); |
| |
| // simulate a pre 1.8 branch commit by removing the branch commit entry |
| NodeDocument doc = store.find(NODES, getIdFromPath("/foo")); |
| assertNotNull(doc); |
| assertEquals(1, doc.getLocalBranchCommits().size()); |
| UpdateOp op = new UpdateOp(doc.getId(), false); |
| for (Revision r : doc.getLocalBranchCommits()) { |
| NodeDocument.removeBranchCommit(op, r); |
| } |
| assertNotNull(store.findAndUpdate(NODES, op)); |
| |
| List<UpdateOp> ops = Lists.newArrayList(); |
| Revision nextSweepStart = sweep(ops); |
| |
| assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart); |
| assertEquals(1, ops.size()); |
| op = ops.get(0); |
| Map<Key, Operation> changes = op.getChanges(); |
| assertEquals(1, changes.size()); |
| Key k = changes.keySet().iterator().next(); |
| assertEquals("_bc", k.getName()); |
| assertEquals(SET_MAP_ENTRY, changes.get(k).type); |
| } |
| |
| private Revision sweep(final List<UpdateOp> ops) throws Exception { |
| NodeDocumentSweeper sweeper = new NodeDocumentSweeper(ns, false); |
| Revision startRev = ns.getSweepRevisions().getRevision(ns.getClusterId()); |
| assertNotNull(startRev); |
| Iterable<NodeDocument> docs = seeker.getCandidates(startRev.getTimestamp()); |
| return sweeper.sweep(docs, new NodeDocumentSweepListener() { |
| @Override |
| public void sweepUpdate(Map<Path, UpdateOp> updates) |
| throws DocumentStoreException { |
| ops.addAll(updates.values()); |
| } |
| }); |
| } |
| } |