blob: 7c64a7dc55b0b06cd8a028cdf2c45059b4a9ad07 [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.document.secondary;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
import org.apache.jackrabbit.oak.plugins.document.Collection;
import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider;
import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
import org.apache.jackrabbit.oak.plugins.document.NodeDocument;
import org.apache.jackrabbit.oak.plugins.document.Revision;
import org.apache.jackrabbit.oak.plugins.document.RevisionVector;
import org.apache.jackrabbit.oak.plugins.document.bundlor.BundledTypesRegistry;
import org.apache.jackrabbit.oak.plugins.document.bundlor.BundlingConfigHandler;
import org.apache.jackrabbit.oak.plugins.document.bundlor.BundlingConfigInitializer;
import org.apache.jackrabbit.oak.plugins.document.util.Utils;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
import org.apache.jackrabbit.oak.InitialContent;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
import org.apache.jackrabbit.oak.spi.filter.PathFilter;
import org.apache.jackrabbit.oak.spi.state.EqualsDiff;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import static com.google.common.collect.ImmutableList.of;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.oak.plugins.document.secondary.SecondaryStoreObserverTest.create;
import static org.apache.jackrabbit.oak.plugins.document.secondary.SecondaryStoreObserverTest.documentState;
import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class SecondaryStoreCacheTest {
private final List<String> empty = Collections.emptyList();
@Rule
public DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider();
private DocumentNodeStore primary;
private NodeStore secondary;
@Before
public void setUp() throws IOException {
primary = builderProvider.newBuilder().getNodeStore();
secondary = new MemoryNodeStore();
}
@Test
public void basicTest() throws Exception{
SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty));
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/b", "/a/c", "/x/y/z");
merge(nb);
RevisionVector rv1 = new RevisionVector(new Revision(1,0,1));
RevisionVector rv2 = new RevisionVector(new Revision(1,0,3));
assertNull(cache.getDocumentNodeState("/a/b", rv1, rv2));
assertNull(cache.getDocumentNodeState("/x", rv1, rv2));
}
@Test
public void updateAndReadAtReadRev() throws Exception{
SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty));
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/b", "/a/c", "/x/y/z");
AbstractDocumentNodeState r1 = merge(nb);
//Update some other part of tree i.e. which does not change lastRev for /a/c
nb = primary.getRoot().builder();
create(nb, "/a/e/d");
AbstractDocumentNodeState r2 = merge(nb);
//Lookup should work fine
AbstractDocumentNodeState a_r2 = documentState(r2, "/a/c");
AbstractDocumentNodeState result
= cache.getDocumentNodeState("/a/c", r2.getRootRevision(), a_r2.getLastRevision());
assertTrue(EqualsDiff.equals(a_r2, result));
//Child docs should only have lastRev and not root rev
assertTrue(result.hasProperty(DelegatingDocumentNodeState.PROP_LAST_REV));
assertFalse(result.hasProperty(DelegatingDocumentNodeState.PROP_REVISION));
//Root doc would have both meta props
assertTrue(secondary.getRoot().hasProperty(DelegatingDocumentNodeState.PROP_LAST_REV));
assertTrue(secondary.getRoot().hasProperty(DelegatingDocumentNodeState.PROP_REVISION));
nb = primary.getRoot().builder();
nb.child("a").child("c").remove();
AbstractDocumentNodeState r3 = merge(nb);
//Now look from older revision
result = cache.getDocumentNodeState("/a/c", r3.getRootRevision(), a_r2.getLastRevision());
//now as its not visible from head it would not be visible
assertNull(result);
}
@Test
public void updateAndReadAtPrevRevision() throws Exception {
SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty));
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/b", "/a/c");
AbstractDocumentNodeState r0 = merge(nb);
AbstractDocumentNodeState a_c_0 = documentState(primary.getRoot(), "/a/c");
//Update some other part of tree i.e. which does not change lastRev for /a/c
nb = primary.getRoot().builder();
create(nb, "/a/c/d");
AbstractDocumentNodeState r1 = merge(nb);
AbstractDocumentNodeState a_c_1 = documentState(primary.getRoot(), "/a/c");
AbstractDocumentNodeState result
= cache.getDocumentNodeState("/a/c", r1.getRootRevision(), a_c_1.getLastRevision());
assertTrue(EqualsDiff.equals(a_c_1, result));
//Read from older revision
result = cache.getDocumentNodeState("/a/c", r0.getRootRevision(), a_c_0.getLastRevision());
assertTrue(EqualsDiff.equals(a_c_0, result));
}
@Test
public void binarySearch() throws Exception{
SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty));
List<AbstractDocumentNodeState> roots = Lists.newArrayList();
List<RevisionVector> revs = Lists.newArrayList();
for (int i = 0; i < 50; i++) {
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/b"+i);
AbstractDocumentNodeState r = merge(nb);
roots.add(r);
revs.add(r.getRootRevision());
}
AbstractDocumentNodeState[] rootsArr = Iterables.toArray(roots, AbstractDocumentNodeState.class);
Collections.shuffle(revs);
for (RevisionVector rev : revs){
AbstractDocumentNodeState result = SecondaryStoreCache.findMatchingRoot(rootsArr, rev);
assertNotNull(result);
assertEquals(rev, result.getRootRevision());
}
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/m");
AbstractDocumentNodeState r = merge(nb);
AbstractDocumentNodeState result = SecondaryStoreCache.findMatchingRoot(rootsArr, r.getRootRevision());
assertNull(result);
}
@Test
public void readWithSecondaryLagging() throws Exception{
PathFilter pathFilter = new PathFilter(of("/a"), empty);
SecondaryStoreCache cache = createBuilder(pathFilter).buildCache();
SecondaryStoreObserver observer = createBuilder(pathFilter).buildObserver(cache);
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/b", "/a/c");
AbstractDocumentNodeState r0 = merge(nb);
AbstractDocumentNodeState a_c_0 = documentState(primary.getRoot(), "/a/c");
observer.contentChanged(r0, CommitInfo.EMPTY);
AbstractDocumentNodeState result = cache.getDocumentNodeState("/a/c", r0.getRootRevision(), a_c_0
.getLastRevision());
assertTrue(EqualsDiff.equals(a_c_0, result));
//Make change in some other part of tree i.e. /a/c is unmodified
nb = primary.getRoot().builder();
create(nb, "/a/e");
AbstractDocumentNodeState r1 = merge(nb);
//Change is yet not pushed to secondary i.e. observer not invoked
//but lookup with latest root should still work fine if lastRev matches
result = cache.getDocumentNodeState("/a/c", r1.getRootRevision(), a_c_0
.getLastRevision());
assertTrue(EqualsDiff.equals(a_c_0, result));
//Change which is not pushed would though not be visible
AbstractDocumentNodeState a_e_1 = documentState(primary.getRoot(), "/a/e");
result = cache.getDocumentNodeState("/a/e", r1.getRootRevision(), a_e_1
.getLastRevision());
assertNull(result);
}
@Test
public void isCached() throws Exception{
SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty));
assertTrue(cache.isCached("/a"));
assertTrue(cache.isCached("/a/b"));
assertFalse(cache.isCached("/x"));
}
@Test
public void bundledNodes() throws Exception{
SecondaryStoreCache cache = createCache(new PathFilter(of("/"), empty));
primary.setNodeStateCache(cache);
NodeBuilder builder = primary.getRoot().builder();
new InitialContent().initialize(builder);
BundlingConfigInitializer.INSTANCE.initialize(builder);
merge(builder);
BundledTypesRegistry registry = BundledTypesRegistry.from(NodeStateUtils.getNode(primary.getRoot(),
BundlingConfigHandler.CONFIG_PATH));
assertNotNull("DocumentBundling not found to be enabled for nt:file",
registry.getBundlor(newNode("nt:file").getNodeState()));
//1. Create a file node
builder = primary.getRoot().builder();
NodeBuilder fileNode = newNode("nt:file");
fileNode.child("jcr:content").setProperty("jcr:data", "foo");
builder.child("test").setChildNode("book.jpg", fileNode.getNodeState());
merge(builder);
//2. Assert that bundling is working
assertNull(getNodeDocument("/test/book.jpg/jcr:content"));
//3. Now update the file node
builder = primary.getRoot().builder();
builder.getChildNode("test").getChildNode("book.jpg").getChildNode("jcr:content").setProperty("foo", "bar");
merge(builder);
}
private SecondaryStoreCache createCache(PathFilter pathFilter){
SecondaryStoreBuilder builder = createBuilder(pathFilter);
builder.metaPropNames(DocumentNodeStore.META_PROP_NAMES);
SecondaryStoreCache cache = builder.buildCache();
SecondaryStoreObserver observer = builder.buildObserver(cache);
primary.addObserver(observer);
return cache;
}
private static NodeBuilder newNode(String typeName){
NodeBuilder builder = EMPTY_NODE.builder();
builder.setProperty(JCR_PRIMARYTYPE, typeName);
return builder;
}
private NodeDocument getNodeDocument(String path) {
return primary.getDocumentStore().find(Collection.NODES, Utils.getIdFromPath(path));
}
private SecondaryStoreBuilder createBuilder(PathFilter pathFilter) {
return new SecondaryStoreBuilder(secondary).pathFilter(pathFilter);
}
private AbstractDocumentNodeState merge(NodeBuilder nb) throws CommitFailedException {
return (AbstractDocumentNodeState) primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
}
}