Author: chetanm
Date: Tue Jun 21 06:18:07 2016
New Revision: 1749426

URL: http://svn.apache.org/viewvc?rev=1749426&view=rev
Log:
OAK-4180 - Use another NodeStore as a local cache for a remote Document store

 Implementation for DocumentNodeStateCache which makes use of a secondary 
NodeStore to lookup paths which are configured for caching

Added:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheService.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserver.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreRootObserver.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeStateTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheServiceTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserverTest.java
   (with props)

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,219 @@
+/*
+ * 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 javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
+import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer;
+import org.apache.jackrabbit.oak.plugins.document.RevisionVector;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot;
+
+/**
+ * NodeState wrapper which wraps another NodeState (mostly SegmentNodeState)
+ * so as to expose it as an {@link AbstractDocumentNodeState} by extracting
+ * the meta properties which are stored as hidden properties
+ */
+class DelegatingDocumentNodeState extends AbstractDocumentNodeState {
+    //Hidden props holding DocumentNodeState meta properties
+    static final String PROP_PATH = ":doc-path";
+    static final String PROP_REVISION = ":doc-rev";
+    static final String PROP_LAST_REV = ":doc-lastRev";
+
+    private static final Predicate<PropertyState> NOT_META_PROPS = new 
Predicate<PropertyState>() {
+        @Override
+        public boolean apply(@Nullable PropertyState input) {
+            return !input.getName().startsWith(":doc-");
+        }
+    };
+
+    private final NodeState delegate;
+    private final RevisionVector rootRevision;
+    private final boolean fromExternalChange;
+    private final RevisionVector lastRevision;
+    private final RevisionVector readRevision;
+    private final String path;
+
+    /**
+     * Wraps a given NodeState as a {@link DelegatingDocumentNodeState} if
+     * it has required meta properties otherwise just returns the passed 
NodeState
+     *
+     * @param delegate nodeState to wrap
+     * @return wrapped state or original state
+     */
+    public static NodeState wrapIfPossible(NodeState delegate, NodeStateDiffer 
differ) {
+        if (hasMetaProps(delegate)) {
+            String revVector = getRequiredProp(delegate, PROP_REVISION);
+            return new DelegatingDocumentNodeState(delegate, 
RevisionVector.fromString(revVector), differ);
+        }
+        return delegate;
+    }
+
+    public static boolean hasMetaProps(NodeState delegate) {
+        return delegate.hasProperty(PROP_REVISION);
+    }
+
+    public static AbstractDocumentNodeState wrap(NodeState delegate, 
NodeStateDiffer differ) {
+        String revVector = getRequiredProp(delegate, PROP_REVISION);
+        return new DelegatingDocumentNodeState(delegate, 
RevisionVector.fromString(revVector), differ);
+    }
+
+    public DelegatingDocumentNodeState(NodeState delegate, RevisionVector 
rootRevision, NodeStateDiffer differ) {
+        this(delegate, rootRevision, false, differ);
+    }
+
+    public DelegatingDocumentNodeState(NodeState delegate, RevisionVector 
rootRevision,
+                                       boolean fromExternalChange, 
NodeStateDiffer differ) {
+        super(differ);
+        this.delegate = delegate;
+        this.rootRevision = rootRevision;
+        this.fromExternalChange = fromExternalChange;
+        this.path = getRequiredProp(PROP_PATH);
+        this.readRevision = 
RevisionVector.fromString(getRequiredProp(PROP_REVISION));
+        this.lastRevision = 
RevisionVector.fromString(getRequiredProp(PROP_LAST_REV));
+    }
+
+    private DelegatingDocumentNodeState(DelegatingDocumentNodeState original,
+                                        RevisionVector rootRevision, boolean 
fromExternalChange) {
+        super(original.differ);
+        this.delegate = original.delegate;
+        this.rootRevision = rootRevision;
+        this.fromExternalChange = fromExternalChange;
+        this.path = original.path;
+        this.readRevision = original.readRevision;
+        this.lastRevision = original.lastRevision;
+    }
+
+    //~----------------------------------< AbstractDocumentNodeState >
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public RevisionVector getRevision() {
+        return readRevision;
+    }
+
+    @Override
+    public RevisionVector getLastRevision() {
+        return lastRevision;
+    }
+
+    @Override
+    public RevisionVector getRootRevision() {
+        return rootRevision;
+    }
+
+    @Override
+    public boolean isFromExternalChange() {
+        return fromExternalChange;
+    }
+
+    @Override
+    public AbstractDocumentNodeState withRootRevision(@Nonnull RevisionVector 
root, boolean externalChange) {
+        if (rootRevision.equals(root) && fromExternalChange == externalChange) 
{
+            return this;
+        } else {
+            return new DelegatingDocumentNodeState(this, root, externalChange);
+        }
+    }
+
+    @Override
+    public boolean hasNoChildren() {
+        //Passing max as 1 so as to minimize any overhead.
+        return delegate.getChildNodeCount(1) == 0;
+    }
+
+    //~----------------------------------< NodeState >
+
+    @Override
+    public boolean exists() {
+        return true;
+    }
+
+    @Nonnull
+    @Override
+    public Iterable<? extends PropertyState> getProperties() {
+        return Iterables.filter(delegate.getProperties(), NOT_META_PROPS);
+    }
+
+    @Override
+    public boolean hasChildNode(@Nonnull String name) {
+        return delegate.hasChildNode(name);
+    }
+
+    @Nonnull
+    @Override
+    public NodeState getChildNode(@Nonnull String name) throws 
IllegalArgumentException {
+        return decorate(delegate.getChildNode(name));
+    }
+
+    @Nonnull
+    @Override
+    public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
+        return Iterables.transform(delegate.getChildNodeEntries(), new 
Function<ChildNodeEntry, ChildNodeEntry>() {
+            @Nullable
+            @Override
+            public ChildNodeEntry apply(@Nullable ChildNodeEntry input) {
+                return new MemoryChildNodeEntry(input.getName(), 
decorate(input.getNodeState()));
+            }
+        });
+    }
+
+    @Nonnull
+    @Override
+    public NodeBuilder builder() {
+        checkState(!denotesRoot(getPath()), "Builder cannot be opened for root 
" +
+                "path for state of type [%s]", delegate.getClass());
+        return new MemoryNodeBuilder(this);
+    }
+
+    //~--------------------------------------------< internal >
+
+    private NodeState decorate(NodeState childNode) {
+        if (childNode.exists()) {
+            return new DelegatingDocumentNodeState(childNode, rootRevision, 
fromExternalChange, differ);
+        }
+        return childNode;
+    }
+
+    private String getRequiredProp(String name){
+        return getRequiredProp(delegate, name);
+    }
+
+    private static String getRequiredProp(NodeState state, String name){
+        return checkNotNull(state.getString(name), "No property [%s] found in 
[%s]", name, state);
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,97 @@
+/*
+ * 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 org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
+import org.apache.jackrabbit.oak.plugins.document.RevisionVector;
+import org.apache.jackrabbit.oak.plugins.index.PathFilter;
+import org.apache.jackrabbit.oak.spi.state.ApplyDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import static 
org.apache.jackrabbit.oak.plugins.document.secondary.DelegatingDocumentNodeState.PROP_LAST_REV;
+import static 
org.apache.jackrabbit.oak.plugins.document.secondary.DelegatingDocumentNodeState.PROP_PATH;
+import static 
org.apache.jackrabbit.oak.plugins.document.secondary.DelegatingDocumentNodeState.PROP_REVISION;
+import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static 
org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
+
+class PathFilteringDiff extends ApplyDiff {
+    private final PathFilter pathFilter;
+
+    public PathFilteringDiff(NodeBuilder builder, PathFilter pathFilter) {
+        super(builder);
+        this.pathFilter = pathFilter;
+    }
+
+    @Override
+    public boolean childNodeAdded(String name, NodeState after) {
+        AbstractDocumentNodeState afterDoc = asDocumentState(after);
+        String nextPath = afterDoc.getPath();
+        PathFilter.Result result = pathFilter.filter(nextPath);
+        if (result == PathFilter.Result.EXCLUDE){
+            return true;
+        }
+
+        //We avoid this as we need to copy meta properties
+        //super.childNodeAdded(name, after);
+
+        NodeBuilder childBuilder = builder.child(name);
+        copyMetaProperties(afterDoc, childBuilder);
+        return after.compareAgainstBaseState(EMPTY_NODE,
+                new PathFilteringDiff(childBuilder, pathFilter));
+    }
+
+    @Override
+    public boolean childNodeChanged(String name, NodeState before, NodeState 
after) {
+        AbstractDocumentNodeState afterDoc = asDocumentState(after);
+        String nextPath = afterDoc.getPath();
+        if (pathFilter.filter(nextPath) != PathFilter.Result.EXCLUDE) {
+            NodeBuilder childBuilder = builder.getChildNode(name);
+            copyMetaProperties(afterDoc, childBuilder);
+            return after.compareAgainstBaseState(
+                    before, new PathFilteringDiff(builder.getChildNode(name), 
pathFilter));
+        }
+        return true;
+    }
+
+    @Override
+    public boolean childNodeDeleted(String name, NodeState before) {
+        String path = asDocumentState(before).getPath();
+        if (pathFilter.filter(path) != PathFilter.Result.EXCLUDE) {
+            return super.childNodeDeleted(name, before);
+        }
+        return true;
+    }
+
+    static void copyMetaProperties(AbstractDocumentNodeState state, 
NodeBuilder builder) {
+        builder.setProperty(asPropertyState(PROP_REVISION, 
state.getRevision()));
+        builder.setProperty(asPropertyState(PROP_LAST_REV, 
state.getLastRevision()));
+        builder.setProperty(createProperty(PROP_PATH, state.getPath()));
+    }
+
+    private static PropertyState asPropertyState(String name, RevisionVector 
revision) {
+        return createProperty(name, revision.asString());
+    }
+
+    private static AbstractDocumentNodeState asDocumentState(NodeState state){
+        return (AbstractDocumentNodeState) state;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,172 @@
+/*
+ * 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 javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.EvictingQueue;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache;
+import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer;
+import org.apache.jackrabbit.oak.plugins.document.RevisionVector;
+import org.apache.jackrabbit.oak.plugins.index.PathFilter;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.stats.MeterStats;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.apache.jackrabbit.oak.stats.StatsOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+class SecondaryStoreCache implements DocumentNodeStateCache, 
SecondaryStoreRootObserver {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private static final AbstractDocumentNodeState[] EMPTY = new 
AbstractDocumentNodeState[0];
+    private final NodeStore store;
+    private final PathFilter pathFilter;
+    private final NodeStateDiffer differ;
+    private final MeterStats unknownPaths;
+    private final MeterStats knownMissed;
+    private final MeterStats headRevMatched;
+    private final MeterStats prevRevMatched;
+    private final int maxSize = 10000;
+    private final EvictingQueue<AbstractDocumentNodeState> queue;
+    private volatile AbstractDocumentNodeState[] previousRoots = EMPTY;
+
+    public SecondaryStoreCache(NodeStore store, PathFilter pathFilter, 
NodeStateDiffer differ) {
+        this(store, pathFilter, StatisticsProvider.NOOP, differ);
+    }
+
+    public SecondaryStoreCache(NodeStore store, PathFilter pathFilter, 
StatisticsProvider statisticsProvider,
+                               NodeStateDiffer differ) {
+        this.differ = differ;
+        this.store = store;
+        this.pathFilter = pathFilter;
+        this.unknownPaths = 
statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_UNKNOWN", StatsOptions.DEFAULT);
+        this.knownMissed = 
statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_KNOWN_MISSED", 
StatsOptions.DEFAULT);
+        this.headRevMatched = 
statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_HEAD", StatsOptions.DEFAULT);
+        this.prevRevMatched = 
statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_OLD", StatsOptions.DEFAULT);
+        this.queue = EvictingQueue.create(maxSize);
+    }
+
+    @Nonnull
+    @Override
+    public NodeStateCacheEntry getDocumentNodeState(String path, 
RevisionVector rootRevision,
+                                                    RevisionVector 
parentLastRev) {
+        //TODO We might need skip the calls if they occur due to 
SecondaryStoreObserver
+        //doing the diff or in the startup when we try to sync the state
+        PathFilter.Result result = pathFilter.filter(path);
+        if (result != PathFilter.Result.INCLUDE) {
+            unknownPaths.mark();
+            return DocumentNodeStateCache.UNKNOWN;
+        }
+
+        if (!DelegatingDocumentNodeState.hasMetaProps(store.getRoot())){
+            return DocumentNodeStateCache.UNKNOWN;
+        }
+
+        AbstractDocumentNodeState currentRoot = 
DelegatingDocumentNodeState.wrap(store.getRoot(), differ);
+
+        NodeStateCacheEntry cacheEntryResult = 
findByMatchingParentLastRev(currentRoot, path,
+                rootRevision, parentLastRev);
+        if (cacheEntryResult != DocumentNodeStateCache.UNKNOWN){
+            headRevMatched.mark();
+            return cacheEntryResult;
+        }
+
+        AbstractDocumentNodeState matchingRoot = 
findMatchingRoot(rootRevision);
+        if (matchingRoot != null){
+            NodeState state = NodeStateUtils.getNode(matchingRoot, path);
+            if (state.exists()){
+                AbstractDocumentNodeState docState = 
(AbstractDocumentNodeState) state;
+                prevRevMatched.mark();
+                return new NodeStateCacheEntry(docState);
+            } else {
+                return DocumentNodeStateCache.MISSING;
+            }
+        }
+
+        //TODO Check in tail if rootRevision is not in our maintained list of 
root
+        knownMissed.mark();
+        return DocumentNodeStateCache.UNKNOWN;
+    }
+
+    @Nonnull
+    private NodeStateCacheEntry 
findByMatchingParentLastRev(AbstractDocumentNodeState root, String path,
+                                                            RevisionVector 
rootRevision, RevisionVector parentLastRev){
+        NodeState parentNodeState = root;
+        NodeState state = root;
+
+        //Get the parent node state
+        for (String name : PathUtils.elements(checkNotNull(path))) {
+            parentNodeState = state;
+            state = state.getChildNode(checkNotNull(name));
+        }
+
+        if (parentNodeState.exists()) {
+            AbstractDocumentNodeState parentDocState = 
(AbstractDocumentNodeState) parentNodeState;
+            //So parent state exists and matches the expected revision
+            if (parentLastRev.equals(parentDocState.getLastRevision())) {
+                headRevMatched.mark();
+                if (state.exists()) {
+                    AbstractDocumentNodeState stateAtExpectedRootRev =
+                            ((AbstractDocumentNodeState) 
state).withRootRevision(rootRevision, false);
+                    return new NodeStateCacheEntry(stateAtExpectedRootRev);
+                } else {
+                    return DocumentNodeStateCache.MISSING;
+                }
+            }
+        }
+
+        return DocumentNodeStateCache.UNKNOWN;
+    }
+
+    @CheckForNull
+    private AbstractDocumentNodeState findMatchingRoot(RevisionVector rr) {
+        if (previousRoots.length == 0){
+            return null;
+        }
+        //TODO Binary search
+        AbstractDocumentNodeState latest = previousRoots[previousRoots.length 
- 1];
+        AbstractDocumentNodeState oldest = previousRoots[0];
+        if (rr.compareTo(latest.getRootRevision()) <= 0
+                && rr.compareTo(oldest.getRootRevision()) >= 0){
+            for (AbstractDocumentNodeState s : previousRoots){
+                if (s.getRootRevision().equals(rr)){
+                    return s;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void contentChanged(@Nonnull AbstractDocumentNodeState root) {
+        synchronized (queue){
+            //TODO Possibly can be improved
+            queue.add(root);
+            previousRoots = queue.toArray(EMPTY);
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheService.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheService.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheService.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheService.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,220 @@
+/*
+ * 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.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.Lists;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
+import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer;
+import org.apache.jackrabbit.oak.plugins.index.PathFilter;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.commit.BackgroundObserver;
+import org.apache.jackrabbit.oak.spi.commit.BackgroundObserverMBean;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.state.SecondaryNodeStoreProvider;
+import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.util.Arrays.asList;
+import static org.apache.jackrabbit.oak.commons.PropertiesUtil.toBoolean;
+import static org.apache.jackrabbit.oak.commons.PropertiesUtil.toInteger;
+import static org.apache.jackrabbit.oak.commons.PropertiesUtil.toStringArray;
+import static 
org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean;
+
+@Component(label = "Apache Jackrabbit Oak DocumentNodeStateCache Provider",
+        metatype = true,
+        policy = ConfigurationPolicy.REQUIRE,
+        description = "Configures a DocumentNodeStateCache based on a 
secondary NodeStore"
+)
+public class SecondaryStoreCacheService {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    /**
+     * Having a reference to BlobStore ensures that DocumentNodeStoreService 
does register a BlobStore
+     */
+    @Reference
+    private BlobStore blobStore;
+
+    @Reference
+    private SecondaryNodeStoreProvider secondaryNodeStoreProvider;
+
+    @Reference
+    private Executor executor;
+
+    @Reference
+    private StatisticsProvider statisticsProvider;
+
+    /*
+     * Have an optional dependency on DocumentNodeStore such that we do not 
have hard dependency
+     * on it and DocumentNodeStore can make use of this service even after it 
has unregistered
+     */
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY,
+            policy = ReferencePolicy.DYNAMIC)
+    private DocumentNodeStore documentNodeStore;
+
+    @Property(unbounded = PropertyUnbounded.ARRAY,
+            label = "Included Paths",
+            description = "List of paths which are to be included in the 
secondary store",
+            value = {"/"}
+    )
+    private static final String PROP_INCLUDES = "includedPaths";
+
+    @Property(unbounded = PropertyUnbounded.ARRAY,
+            label = "Excluded Paths",
+            description = "List of paths which are to be excluded in the 
secondary store",
+            value = {}
+    )
+    private static final String PROP_EXCLUDES = "excludedPaths";
+
+
+    private static final boolean PROP_ASYNC_OBSERVER_DEFAULT = true;
+    @Property(
+            boolValue = PROP_ASYNC_OBSERVER_DEFAULT,
+            label = "Async Observation",
+            description = "Enable async observation processing"
+    )
+    private static final String PROP_ASYNC_OBSERVER = "enableAsyncObserver";
+
+    private static final int PROP_OBSERVER_QUEUE_SIZE_DEFAULT = 1000;
+    @Property(
+            intValue = PROP_OBSERVER_QUEUE_SIZE_DEFAULT,
+            label = "Observer queue size",
+            description = "Observer queue size. Used if 'enableAsyncObserver' 
is set to true"
+    )
+    private static final String PROP_OBSERVER_QUEUE_SIZE = "observerQueueSize";
+
+    private final List<Registration> oakRegs = Lists.newArrayList();
+
+    private final List<ServiceRegistration> regs = Lists.newArrayList();
+
+    private Whiteboard whiteboard;
+
+    private BundleContext bundleContext;
+
+    private PathFilter pathFilter;
+
+    private final MultiplexingNodeStateDiffer differ = new 
MultiplexingNodeStateDiffer();
+
+    @Activate
+    private void activate(BundleContext context, Map<String, Object> config){
+        bundleContext = context;
+        whiteboard = new OsgiWhiteboard(context);
+        String[] includedPaths = toStringArray(config.get(PROP_INCLUDES), new 
String[]{"/"});
+        String[] excludedPaths = toStringArray(config.get(PROP_EXCLUDES), new 
String[]{""});
+
+        pathFilter = new PathFilter(asList(includedPaths), 
asList(excludedPaths));
+        NodeStore segStore = secondaryNodeStoreProvider.getSecondaryStore();
+
+        SecondaryStoreCache cache = new SecondaryStoreCache(segStore, 
pathFilter, statisticsProvider, differ);
+        SecondaryStoreObserver observer = new SecondaryStoreObserver(segStore, 
pathFilter,
+                cache, differ, statisticsProvider);
+        registerObserver(observer, config);
+
+        
regs.add(bundleContext.registerService(DocumentNodeStateCache.class.getName(), 
cache, null));
+
+        //TODO Need to see OSGi dynamics. Its possible that DocumentNodeStore 
works after the cache
+        //gets deregistered but the SegmentNodeState instances might still be 
in use and that would cause
+        //failure
+    }
+
+    @Deactivate
+    private void deactivate(){
+        for (Registration r : oakRegs){
+            r.unregister();
+        }
+        for (ServiceRegistration r : regs){
+            r.unregister();
+        }
+    }
+
+    PathFilter getPathFilter() {
+        return pathFilter;
+    }
+
+    protected void bindDocumentNodeStore(DocumentNodeStore documentNodeStore){
+        log.info("Registering DocumentNodeStore as the differ");
+        differ.setDelegate(documentNodeStore);
+    }
+
+    protected void unbindDocumentNodeStore(DocumentNodeStore 
documentNodeStore){
+        differ.setDelegate(NodeStateDiffer.DEFAULT_DIFFER);
+    }
+
+    //~----------------------------------------------------< internal >
+
+    private void registerObserver(Observer observer, Map<String, Object> 
config) {
+        boolean enableAsyncObserver = 
toBoolean(config.get(PROP_ASYNC_OBSERVER), PROP_ASYNC_OBSERVER_DEFAULT);
+        int  queueSize = toInteger(config.get(PROP_OBSERVER_QUEUE_SIZE), 
PROP_OBSERVER_QUEUE_SIZE_DEFAULT);
+        if (enableAsyncObserver){
+            BackgroundObserver bgObserver = new BackgroundObserver(observer, 
executor, queueSize);
+            oakRegs.add(registerMBean(whiteboard,
+                    BackgroundObserverMBean.class,
+                    bgObserver.getMBean(),
+                    BackgroundObserverMBean.TYPE,
+                    "Secondary NodeStore observer stats"));
+            observer = bgObserver;
+            log.info("Configuring the observer for secondary NodeStore as " +
+                    "Background Observer with queue size {}", queueSize);
+        }
+
+        //Ensure that our observer comes first in processing
+        Hashtable<String, Object> props = new Hashtable<>();
+        props.put(Constants.SERVICE_RANKING, 10000);
+        regs.add(bundleContext.registerService(Observer.class.getName(), 
observer, props));
+    }
+
+    private static class MultiplexingNodeStateDiffer implements 
NodeStateDiffer {
+        private volatile NodeStateDiffer delegate = 
NodeStateDiffer.DEFAULT_DIFFER;
+        @Override
+        public boolean compare(@Nonnull AbstractDocumentNodeState node,
+                               @Nonnull AbstractDocumentNodeState base, 
@Nonnull NodeStateDiff diff) {
+            return delegate.compare(node, base, diff);
+        }
+
+        public void setDelegate(NodeStateDiffer delegate) {
+            this.delegate = delegate;
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheService.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserver.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserver.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserver.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserver.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,107 @@
+/*
+ * 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.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.base.Stopwatch;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
+import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer;
+import org.apache.jackrabbit.oak.plugins.index.PathFilter;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.state.ApplyDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.apache.jackrabbit.oak.stats.StatsOptions;
+import org.apache.jackrabbit.oak.stats.TimerStats;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class SecondaryStoreObserver implements Observer {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final NodeStore nodeStore;
+    private final PathFilter pathFilter;
+    private final SecondaryStoreRootObserver secondaryObserver;
+    private final NodeStateDiffer differ;
+    private final TimerStats local;
+    private final TimerStats external;
+    private boolean firstEventProcessed;
+
+    public SecondaryStoreObserver(NodeStore nodeStore, PathFilter pathFilter, 
NodeStateDiffer differ) {
+        this(nodeStore, pathFilter, SecondaryStoreRootObserver.NOOP, differ, 
StatisticsProvider.NOOP);
+    }
+
+    public SecondaryStoreObserver(NodeStore nodeStore, PathFilter pathFilter,
+                                  SecondaryStoreRootObserver secondaryObserver,
+                                  NodeStateDiffer differ, StatisticsProvider 
statisticsProvider) {
+        this.nodeStore = nodeStore;
+        this.pathFilter = pathFilter;
+        this.secondaryObserver = secondaryObserver;
+        this.differ = differ;
+        this.local = statisticsProvider.getTimer("DOCUMENT_CACHE_SEC_LOCAL", 
StatsOptions.DEFAULT);
+        this.external = 
statisticsProvider.getTimer("DOCUMENT_CACHE_SEC_EXTERNAL", 
StatsOptions.DEFAULT);
+    }
+
+    @Override
+    public void contentChanged(@Nonnull NodeState root, @Nullable CommitInfo 
info) {
+        //Diff here would also be traversing non visible areas and there
+        //diffManyChildren might pose problem for e.g. data under uuid index
+        if (!firstEventProcessed){
+            log.info("Starting initial sync");
+        }
+
+        Stopwatch w = Stopwatch.createStarted();
+        NodeState target = root;
+        NodeState secondaryRoot = nodeStore.getRoot();
+        NodeState base = 
DelegatingDocumentNodeState.wrapIfPossible(secondaryRoot, differ);
+        NodeBuilder builder = secondaryRoot.builder();
+        ApplyDiff diff = new PathFilteringDiff(builder, pathFilter);
+
+        //Copy the root node meta properties
+        PathFilteringDiff.copyMetaProperties((AbstractDocumentNodeState) 
target, builder);
+
+        //Apply the rest of properties
+        target.compareAgainstBaseState(base, diff);
+        try {
+            NodeState updatedSecondaryRoot = nodeStore.merge(builder, 
EmptyHook.INSTANCE, CommitInfo.EMPTY);
+            
secondaryObserver.contentChanged(DelegatingDocumentNodeState.wrap(updatedSecondaryRoot,
 differ));
+
+            TimerStats timer = info == null ? external : local;
+            timer.update(w.elapsed(TimeUnit.NANOSECONDS), 
TimeUnit.NANOSECONDS);
+
+            if (!firstEventProcessed){
+                log.info("Time taken for initial sync {}", w);
+                firstEventProcessed = true;
+            }
+        } catch (CommitFailedException e) {
+            //TODO
+            log.warn("Commit to secondary store failed", e);
+        }
+    }
+
+}

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserver.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreRootObserver.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreRootObserver.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreRootObserver.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreRootObserver.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,35 @@
+/*
+ * 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 javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
+
+interface SecondaryStoreRootObserver {
+    SecondaryStoreRootObserver NOOP = new SecondaryStoreRootObserver(){
+        @Override
+        public void contentChanged(@Nonnull AbstractDocumentNodeState root) {
+
+        }
+    };
+
+    void contentChanged(@Nonnull AbstractDocumentNodeState root);
+}

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreRootObserver.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeStateTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeStateTest.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeStateTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeStateTest.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,146 @@
+/*
+ * 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 com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
+import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer;
+import org.apache.jackrabbit.oak.plugins.document.Revision;
+import org.apache.jackrabbit.oak.plugins.document.RevisionVector;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.EqualsDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Test;
+
+import static 
org.apache.jackrabbit.oak.plugins.document.secondary.DelegatingDocumentNodeState.PROP_LAST_REV;
+import static 
org.apache.jackrabbit.oak.plugins.document.secondary.DelegatingDocumentNodeState.PROP_PATH;
+import static 
org.apache.jackrabbit.oak.plugins.document.secondary.DelegatingDocumentNodeState.PROP_REVISION;
+import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static 
org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class DelegatingDocumentNodeStateTest {
+    private NodeBuilder builder = EMPTY_NODE.builder();
+
+    @Test
+    public void basicWorking() throws Exception{
+        RevisionVector rv1 = new RevisionVector(new Revision(1,0,1));
+        RevisionVector rv2 = new RevisionVector(new Revision(1,0,3));
+        builder.setProperty(asPropertyState(PROP_REVISION, rv1));
+        builder.setProperty(asPropertyState(PROP_LAST_REV, rv2));
+        builder.setProperty(createProperty(PROP_PATH, "foo"));
+        AbstractDocumentNodeState state = 
DelegatingDocumentNodeState.wrap(builder.getNodeState(), 
NodeStateDiffer.DEFAULT_DIFFER);
+
+        assertEquals(rv1, state.getRevision());
+        assertEquals(rv1, state.getRootRevision());
+        assertEquals(rv2, state.getLastRevision());
+        assertEquals("foo", state.getPath());
+        assertTrue(state.hasNoChildren());
+        assertTrue(state.exists());
+        assertFalse(state.isFromExternalChange());
+    }
+    
+    @Test
+    public void metaPropertiesFilteredOut() throws Exception{
+        setMetaProps(builder);
+        builder.setProperty("foo", "bar");
+
+        AbstractDocumentNodeState state = 
DelegatingDocumentNodeState.wrap(builder.getNodeState(), 
NodeStateDiffer.DEFAULT_DIFFER);
+        assertEquals(1, Iterables.size(state.getProperties()));
+    }
+
+    @Test
+    public void childNodeDecorated() throws Exception{
+        setMetaProps(builder);
+        setMetaProps(builder.child("a"));
+        setMetaProps(builder.child("b"));
+
+        AbstractDocumentNodeState state = 
DelegatingDocumentNodeState.wrap(builder.getNodeState(), 
NodeStateDiffer.DEFAULT_DIFFER);
+        assertTrue(state.getChildNode("a") instanceof 
AbstractDocumentNodeState);
+        assertTrue(state.getChildNode("b") instanceof 
AbstractDocumentNodeState);
+        assertFalse(state.hasChildNode("c"));
+        assertFalse(state.getChildNode("c").exists());
+
+        assertFalse(state.hasNoChildren());
+
+        for(ChildNodeEntry cne : state.getChildNodeEntries()){
+            assertTrue(cne.getNodeState() instanceof 
AbstractDocumentNodeState);
+        }
+
+        assertEquals(2, state.getChildNodeCount(100));
+    }
+
+    @Test
+    public void withRootRevision() throws Exception{
+        RevisionVector rv1 = new RevisionVector(new Revision(1,0,1));
+        RevisionVector rv2 = new RevisionVector(new Revision(1,0,3));
+        builder.setProperty(asPropertyState(PROP_REVISION, rv1));
+        builder.setProperty(asPropertyState(PROP_LAST_REV, rv2));
+        builder.setProperty(createProperty(PROP_PATH, "foo"));
+        AbstractDocumentNodeState state = 
DelegatingDocumentNodeState.wrap(builder.getNodeState(), 
NodeStateDiffer.DEFAULT_DIFFER);
+
+        AbstractDocumentNodeState state2 = state.withRootRevision(rv1, false);
+        assertSame(state, state2);
+
+        RevisionVector rv4 = new RevisionVector(new Revision(1,0,4));
+        AbstractDocumentNodeState state3 = state.withRootRevision(rv4, true);
+        assertEquals(rv4, state3.getRootRevision());
+        assertTrue(state3.isFromExternalChange());
+    }
+
+    @Test
+    public void wrapIfPossible() throws Exception{
+        assertFalse(DelegatingDocumentNodeState.wrapIfPossible(EMPTY_NODE, 
NodeStateDiffer.DEFAULT_DIFFER)
+                instanceof AbstractDocumentNodeState);
+
+        setMetaProps(builder);
+        
assertTrue(DelegatingDocumentNodeState.wrapIfPossible(builder.getNodeState(), 
NodeStateDiffer.DEFAULT_DIFFER) instanceof
+                AbstractDocumentNodeState);
+    }
+
+    @Test
+    public void equals1() throws Exception{
+        setMetaProps(builder);
+        builder.setProperty("foo", "bar");
+
+        NodeBuilder b2 = EMPTY_NODE.builder();
+        b2.setProperty("foo", "bar");
+
+        
assertTrue(EqualsDiff.equals(DelegatingDocumentNodeState.wrap(builder.getNodeState(),
 NodeStateDiffer.DEFAULT_DIFFER),
+                b2.getNodeState()));
+        assertTrue(EqualsDiff.equals(b2.getNodeState(),
+                DelegatingDocumentNodeState.wrap(builder.getNodeState(), 
NodeStateDiffer.DEFAULT_DIFFER)));
+    }
+
+    private static void setMetaProps(NodeBuilder nb){
+        nb.setProperty(asPropertyState(PROP_REVISION, new RevisionVector(new 
Revision(1,0,1))));
+        nb.setProperty(asPropertyState(PROP_LAST_REV, new RevisionVector(new 
Revision(1,0,1))));
+        nb.setProperty(createProperty(PROP_PATH, "foo"));
+    }
+
+    private static PropertyState asPropertyState(String name, RevisionVector 
revision) {
+        return createProperty(name, revision.asString());
+    }
+
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeStateTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheServiceTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheServiceTest.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheServiceTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheServiceTest.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,109 @@
+/*
+ * 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.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache;
+import org.apache.jackrabbit.oak.plugins.index.PathFilter;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.blob.MemoryBlobStore;
+import org.apache.jackrabbit.oak.spi.commit.BackgroundObserverMBean;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.state.SecondaryNodeStoreProvider;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class SecondaryStoreCacheServiceTest {
+    @Rule
+    public final OsgiContext context = new OsgiContext();
+
+    @Rule
+    public DocumentMKBuilderProvider builderProvider = new 
DocumentMKBuilderProvider();
+
+    private SecondaryStoreCacheService cacheService = new 
SecondaryStoreCacheService();
+    private NodeStore secondaryStore = new MemoryNodeStore();
+
+    @Before
+    public void configureDefaultServices(){
+        context.registerService(BlobStore.class, new MemoryBlobStore());
+        context.registerService(SecondaryNodeStoreProvider.class, new 
SecondaryNodeStoreProvider() {
+            @Override
+            public NodeStore getSecondaryStore() {
+                return secondaryStore;
+            }
+        });
+        context.registerService(Executor.class, 
Executors.newSingleThreadExecutor());
+        context.registerService(StatisticsProvider.class, 
StatisticsProvider.NOOP);
+        MockOsgi.injectServices(cacheService, context.bundleContext());
+    }
+
+    @Test
+    public void defaultSetup() throws Exception{
+        MockOsgi.activate(cacheService, context.bundleContext(), new 
HashMap<String, Object>());
+
+        assertNotNull(context.getService(Observer.class));
+        assertNotNull(context.getService(BackgroundObserverMBean.class));
+        assertNotNull(context.getService(DocumentNodeStateCache.class));
+
+        MockOsgi.deactivate(cacheService);
+
+        assertNull(context.getService(Observer.class));
+        assertNull(context.getService(BackgroundObserverMBean.class));
+        assertNull(context.getService(DocumentNodeStateCache.class));
+    }
+
+    @Test
+    public void disableBackground() throws Exception{
+        Map<String, Object> config = new HashMap<>();
+        config.put("enableAsyncObserver", "false");
+        MockOsgi.activate(cacheService, context.bundleContext(), config);
+
+        assertNotNull(context.getService(Observer.class));
+        assertNull(context.getService(BackgroundObserverMBean.class));
+        assertNotNull(context.getService(DocumentNodeStateCache.class));
+    }
+
+    @Test
+    public void configurePathFilter() throws Exception{
+        Map<String, Object> config = new HashMap<>();
+        config.put("includedPaths", new String[] {"/a"});
+        config.put("excludedPaths", new String[] {"/a/b"});
+        MockOsgi.activate(cacheService, context.bundleContext(), config);
+
+        assertEquals(PathFilter.Result.INCLUDE, 
cacheService.getPathFilter().filter("/a"));
+        assertEquals(PathFilter.Result.EXCLUDE, 
cacheService.getPathFilter().filter("/a/b/c"));
+    }
+
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheServiceTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,151 @@
+/*
+ * 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 org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache;
+import 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache.NodeStateCacheEntry;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer;
+import org.apache.jackrabbit.oak.plugins.document.Revision;
+import org.apache.jackrabbit.oak.plugins.document.RevisionVector;
+import org.apache.jackrabbit.oak.plugins.index.PathFilter;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.state.EqualsDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+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.oak.plugins.document.NodeStateDiffer.DEFAULT_DIFFER;
+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.junit.Assert.*;
+
+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{
+        PathFilter pathFilter = new PathFilter(of("/a"), empty);
+        SecondaryStoreObserver observer = new 
SecondaryStoreObserver(secondary, pathFilter, DEFAULT_DIFFER);
+        primary.addObserver(observer);
+
+        SecondaryStoreCache cache = new SecondaryStoreCache(secondary, 
pathFilter, DEFAULT_DIFFER);
+
+        NodeBuilder nb = primary.getRoot().builder();
+        create(nb, "/a/b", "/a/c", "/x/y/z");
+        primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        RevisionVector rv1 = new RevisionVector(new Revision(1,0,1));
+        RevisionVector rv2 = new RevisionVector(new Revision(1,0,3));
+        assertSame(DocumentNodeStateCache.UNKNOWN, 
cache.getDocumentNodeState("/a/b", rv1, rv2));
+        assertSame(DocumentNodeStateCache.UNKNOWN, 
cache.getDocumentNodeState("/x", rv1, rv2));
+    }
+
+    @Test
+    public void updateAndReadAtReadRev() throws Exception{
+        PathFilter pathFilter = new PathFilter(of("/a"), empty);
+        SecondaryStoreObserver observer = new 
SecondaryStoreObserver(secondary, pathFilter, DEFAULT_DIFFER);
+        primary.addObserver(observer);
+
+        SecondaryStoreCache cache = new SecondaryStoreCache(secondary, 
pathFilter, DEFAULT_DIFFER);
+
+        NodeBuilder nb = primary.getRoot().builder();
+        create(nb, "/a/b", "/a/c", "/x/y/z");
+        AbstractDocumentNodeState r1 =
+                (AbstractDocumentNodeState) primary.merge(nb, 
EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        //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 =
+                (AbstractDocumentNodeState)primary.merge(nb, 
EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        //Lookup should work fine
+        AbstractDocumentNodeState a_r2 = documentState(r2, "/a");
+        NodeStateCacheEntry result
+                = cache.getDocumentNodeState("/a/c", r2.getRootRevision(), 
a_r2.getLastRevision());
+        assertTrue(EqualsDiff.equals(a_r2.getChildNode("c"), 
result.getState()));
+
+        nb = primary.getRoot().builder();
+        nb.child("a").child("c").remove();
+        primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        //Now look from older revision
+        result = cache.getDocumentNodeState("/a/c", r1.getRootRevision(), 
a_r2.getLastRevision());
+
+        //now as its not visible from head it would not be visible
+        assertSame(DocumentNodeStateCache.UNKNOWN, result);
+    }
+
+    @Test
+    public void updateAndReadAtPrevRevision() throws Exception {
+        PathFilter pathFilter = new PathFilter(of("/a"), empty);
+        SecondaryStoreCache cache = new SecondaryStoreCache(secondary, 
pathFilter, DEFAULT_DIFFER);
+        SecondaryStoreObserver observer = new 
SecondaryStoreObserver(secondary, pathFilter, cache,
+                DEFAULT_DIFFER, StatisticsProvider.NOOP);
+        primary.addObserver(observer);
+
+        NodeBuilder nb = primary.getRoot().builder();
+        create(nb, "/a/b", "/a/c");
+        AbstractDocumentNodeState r0 =
+                (AbstractDocumentNodeState) primary.merge(nb, 
EmptyHook.INSTANCE, CommitInfo.EMPTY);
+        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 =
+                (AbstractDocumentNodeState)primary.merge(nb, 
EmptyHook.INSTANCE, CommitInfo.EMPTY);
+        AbstractDocumentNodeState a_c_1 = documentState(primary.getRoot(), 
"/a/c");
+
+        NodeStateCacheEntry result
+                = cache.getDocumentNodeState("/a/c", r1.getRootRevision(), 
a_c_1.getLastRevision());
+        assertTrue(EqualsDiff.equals(a_c_1, result.getState()));
+
+        //Read from older revision
+        result
+                = cache.getDocumentNodeState("/a/c", r0.getRootRevision(), 
a_c_0.getLastRevision());
+        assertTrue(EqualsDiff.equals(a_c_0, result.getState()));
+    }
+
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserverTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserverTest.java?rev=1749426&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserverTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserverTest.java
 Tue Jun 21 06:18:07 2016
@@ -0,0 +1,163 @@
+/*
+ * 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 org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer;
+import org.apache.jackrabbit.oak.plugins.index.PathFilter;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+public class SecondaryStoreObserverTest {
+    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 basicSetup() throws Exception{
+        PathFilter pathFilter = new PathFilter(of("/a"), empty);
+        SecondaryStoreObserver observer = new 
SecondaryStoreObserver(secondary, pathFilter, NodeStateDiffer.DEFAULT_DIFFER);
+        primary.addObserver(observer);
+
+        NodeBuilder nb = primary.getRoot().builder();
+        create(nb, "/a/b", "/a/c", "/x/y/z");
+        primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        dump(secondaryRoot(), "/a");
+        dump(primary.getRoot(), "/a");
+        assertEquals(secondaryRoot().getChildNode("a"),
+                primary.getRoot().getChildNode("a"));
+    }
+
+    @Test
+    public void childNodeAdded() throws Exception{
+        PathFilter pathFilter = new PathFilter(of("/a"), empty);
+        SecondaryStoreObserver observer = new 
SecondaryStoreObserver(secondary, pathFilter, NodeStateDiffer.DEFAULT_DIFFER);
+        primary.addObserver(observer);
+
+        NodeBuilder nb = primary.getRoot().builder();
+        create(nb, "/a/b", "/a/c", "/x/y/z");
+        primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        nb = primary.getRoot().builder();
+        create(nb, "/a/d");
+        primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        assertMetaState(primary.getRoot(), secondaryRoot(), "/a/d");
+        assertMetaState(primary.getRoot(), secondaryRoot(), "/a");
+    }
+
+    @Test
+    public void childNodeChangedAndExclude() throws Exception{
+        PathFilter pathFilter = new PathFilter(of("/a"), of("a/b"));
+        SecondaryStoreObserver observer = new 
SecondaryStoreObserver(secondary, pathFilter, NodeStateDiffer.DEFAULT_DIFFER);
+        primary.addObserver(observer);
+
+        NodeBuilder nb = primary.getRoot().builder();
+        create(nb, "/a/b", "/a/c", "/x/y/z");
+        primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        nb = primary.getRoot().builder();
+        create(nb, "/a/d", "/a/b/e");
+        primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        assertMetaState(primary.getRoot(), secondaryRoot(), "/a/d");
+    }
+
+    @Test
+    public void childNodeDeleted() throws Exception{
+        PathFilter pathFilter = new PathFilter(of("/a"), empty);
+        SecondaryStoreObserver observer = new 
SecondaryStoreObserver(secondary, pathFilter, NodeStateDiffer.DEFAULT_DIFFER);
+        primary.addObserver(observer);
+
+        NodeBuilder nb = primary.getRoot().builder();
+        create(nb, "/a/b", "/a/c", "/x/y/z");
+        primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        nb = primary.getRoot().builder();
+        nb.child("a").child("c").remove();
+        primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        assertFalse(NodeStateUtils.getNode(secondaryRoot(), "/a/c").exists());
+    }
+
+    private NodeState secondaryRoot() {
+        return DelegatingDocumentNodeState.wrap(secondary.getRoot(), 
NodeStateDiffer.DEFAULT_DIFFER);
+    }
+
+    private static void assertMetaState(NodeState root1, NodeState root2, 
String path){
+        assertMetaState(documentState(root1, path), documentState(root2, 
path));
+    }
+
+    private static void assertMetaState(AbstractDocumentNodeState a, 
AbstractDocumentNodeState b){
+        assertEquals(a.getRevision(), b.getRevision());
+        assertEquals(a.getRootRevision(), b.getRootRevision());
+        assertEquals(a.getPath(), b.getPath());
+    }
+
+    static AbstractDocumentNodeState documentState(NodeState root, String 
path){
+        return (AbstractDocumentNodeState) NodeStateUtils.getNode(root, path);
+    }
+
+    private static void dump(NodeState root, String path){
+        NodeState state = NodeStateUtils.getNode(root, path);
+        System.out.println(NodeStateUtils.toString(state));
+    }
+
+    static NodeState create(NodeBuilder b, String ... paths){
+        for (String path : paths){
+            NodeBuilder cb = b;
+            for (String pathElement : PathUtils.elements(path)){
+                cb = cb.child(pathElement);
+            }
+        }
+        return b.getNodeState();
+    }
+
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreObserverTest.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to