You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by th...@apache.org on 2019/10/31 07:34:34 UTC

svn commit: r1869202 - in /jackrabbit/oak/trunk: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/ oak-lucene/src/test/java/org/apache/jackrabbit/oak...

Author: thomasm
Date: Thu Oct 31 07:34:34 2019
New Revision: 1869202

URL: http://svn.apache.org/viewvc?rev=1869202&view=rev
Log:
OAK-8721 Automatically pick the latest active index version

Added:
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/CustomizedIndexTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/IndexUtils.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/Persistence.java
    jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexName.java
    jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNameTest.java
Modified:
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/LuceneIndexReaderFactory.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
    jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java
    jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java?rev=1869202&r1=1869201&r2=1869202&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java Thu Oct 31 07:34:34 2019
@@ -37,6 +37,7 @@ import org.apache.jackrabbit.oak.spi.com
 import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.commit.EditorDiff;
 import org.apache.jackrabbit.oak.spi.commit.SubtreeEditor;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
 import org.apache.jackrabbit.oak.spi.mount.Mounts;
 import org.apache.jackrabbit.oak.spi.state.EqualsDiff;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -97,6 +98,10 @@ public class IndexTracker {
         this.readerFactory = readerFactory;
         this.nrtFactory = nrtFactory;
     }
+    
+    public MountInfoProvider getMountInfoProvider() {
+        return readerFactory.getMountInfoProvider();
+    }
 
     public synchronized void close() {
         Map<String, LuceneIndexNodeManager> indices = this.indices;
@@ -304,4 +309,5 @@ public class IndexTracker {
     private static boolean isIndexDefinitionChanged(NodeState before, NodeState after) {
         return !EqualsDiff.equals(before.getChildNode(INDEX_DEFINITION_NODE), after.getChildNode(INDEX_DEFINITION_NODE));
     }
+
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java?rev=1869202&r1=1869201&r2=1869202&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java Thu Oct 31 07:34:34 2019
@@ -720,6 +720,11 @@ public class LucenePropertyIndex extends
     protected String getType() {
         return TYPE_LUCENE;
     }
+    
+    @Override
+    protected boolean filterReplacedIndexes() {
+        return tracker.getMountInfoProvider().hasNonDefaultMounts();
+    }
 
     @Override
     protected SizeEstimator getSizeEstimator(IndexPlan plan) {
@@ -1715,4 +1720,5 @@ public class LucenePropertyIndex extends
 
     static abstract class LuceneResultRowIterator extends AbstractIterator<FulltextResultRow> implements IteratorRewoundStateProvider {
     }
+
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java?rev=1869202&r1=1869201&r2=1869202&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java Thu Oct 31 07:34:34 2019
@@ -126,4 +126,9 @@ public class DefaultIndexReaderFactory i
         }
         return null;
     }
+
+    @Override
+    public MountInfoProvider getMountInfoProvider() {
+        return mountInfoProvider;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/LuceneIndexReaderFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/LuceneIndexReaderFactory.java?rev=1869202&r1=1869201&r2=1869202&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/LuceneIndexReaderFactory.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/LuceneIndexReaderFactory.java Thu Oct 31 07:34:34 2019
@@ -23,9 +23,13 @@ import java.io.IOException;
 import java.util.List;
 
 import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexDefinition;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
 public interface LuceneIndexReaderFactory {
 
     List<LuceneIndexReader> createReaders(LuceneIndexDefinition definition, NodeState definitionState, String indexPath) throws IOException;
+    
+    MountInfoProvider getMountInfoProvider();
+    
 }

Added: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/CustomizedIndexTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/CustomizedIndexTest.java?rev=1869202&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/CustomizedIndexTest.java (added)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/CustomizedIndexTest.java Thu Oct 31 07:34:34 2019
@@ -0,0 +1,129 @@
+/*
+ * 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.composite.blueGreen;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Tests customized indexes.
+ */
+public class CustomizedIndexTest {
+
+    private final Persistence.Config config = new Persistence.Config();
+
+    @Rule
+    public TemporaryFolder tempDir = new TemporaryFolder(new File("target"));
+    
+    private File globalDir;
+    private File libs1Dir;
+    private File libs2Dir;
+    private File datastoreDir;
+    private File indexDir;
+    
+    @Test
+    public void test() throws Exception {
+        createFolders();
+        config.blobStore = Persistence.getFileBlobStore(datastoreDir);
+        config.indexDir = indexDir;
+        initLibs1();
+        initGlobal();
+        compositeLibs1();
+        initLibs2();
+        compositeLibs2();
+    }
+    
+    private void initLibs1() throws Exception {
+        Persistence p = Persistence.open(libs1Dir, config);
+        p.session.getRootNode().addNode("libs").addNode("test").setProperty("foo", "a");
+        p.session.save();
+        IndexUtils.createIndex(p, "test-1", "foo", 10);
+        IndexUtils.assertQueryUsesIndexAndReturns(p, 
+                "/jcr:root//*[@foo]", 
+                "test-1", 
+                "[/libs/test]");
+        p.close();
+    }
+    
+    private void initGlobal() throws Exception {
+        Persistence p = Persistence.open(globalDir, config);
+        p.session.getRootNode().addNode("content").addNode("test").setProperty("foo", "a");
+        p.session.save(); 
+        p.close();
+    }
+    
+    private void compositeLibs1() throws Exception {
+        Persistence p = Persistence.openComposite(globalDir, libs1Dir, config);
+        IndexUtils.checkLibsIsReadOnly(p);
+        IndexUtils.createIndex(p, "test-1", "foo", 10);
+        IndexUtils.assertQueryUsesIndexAndReturns(p, 
+                "/jcr:root//*[@foo] order by @jcr:path", 
+                "test-1", 
+                "[/content/test, /libs/test]");
+        p.close();
+    }
+    
+    private void compositeLibs2() throws Exception {
+        Persistence p = Persistence.openComposite(globalDir, libs2Dir, config);
+        IndexUtils.checkLibsIsReadOnly(p);
+        IndexUtils.createIndex(p, "test-2", "foo", 20);
+        IndexUtils.assertQueryUsesIndexAndReturns(p, 
+                "/jcr:root//*[@foo] order by @jcr:path", 
+                "test-2", 
+                "[/content/test, /libs/test2]");
+        p.close();
+        // the new index must not be used in the old version (wiht libs1)
+        p = Persistence.openComposite(globalDir, libs1Dir, config);
+        IndexUtils.assertQueryUsesIndexAndReturns(p, 
+                "/jcr:root//*[@foo] order by @jcr:path", 
+                "test-1", 
+                "[/content/test, /libs/test]");
+        p.close();
+    }
+    
+    private void initLibs2() throws Exception {
+        Persistence p = Persistence.open(libs2Dir, config);
+        p.session.getRootNode().addNode("libs").addNode("test2").setProperty("foo", "a");
+        p.session.save();
+        IndexUtils.createIndex(p, "test-1", "foo", 10);
+        IndexUtils.assertQueryUsesIndexAndReturns(p, 
+                "/jcr:root//*[@foo]", 
+                "test-1", 
+                "[/libs/test2]");
+        IndexUtils.createIndex(p, "test-2", "foo", 20);
+        IndexUtils.assertQueryUsesIndexAndReturns(p, 
+                "/jcr:root//*[@foo]", 
+                "test-2", 
+                "[/libs/test2]");
+        p.close();        
+    }
+    
+    private void createFolders() throws IOException {
+        globalDir = tempDir.newFolder("global");
+        libs1Dir = tempDir.newFolder("libs1");
+        libs2Dir = tempDir.newFolder("libs2");
+        datastoreDir = tempDir.newFolder("datastore");
+        indexDir = tempDir.newFolder("index");
+    }
+    
+}

Added: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/IndexUtils.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/IndexUtils.java?rev=1869202&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/IndexUtils.java (added)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/IndexUtils.java Thu Oct 31 07:34:34 2019
@@ -0,0 +1,148 @@
+/*
+ * 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.composite.blueGreen;
+
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexFormatVersion;
+import org.junit.Assert;
+
+/**
+ * Utilities for indexing and query tests.
+ */
+public class IndexUtils {
+
+    /**
+     * Create an index and wait until it is ready.
+     * 
+     * @param p the persistence
+     * @param indexName the name of the index
+     * @param propertyName the property to index (on nt:base)
+     * @param cost the cost per execution (high means the index isn't used if possible)
+     */
+    public static void createIndex(Persistence p, String indexName, String propertyName, double cost) throws RepositoryException {
+        Node indexDef = p.session.getRootNode().getNode("oak:index");
+        Node index = indexDef.addNode(indexName, INDEX_DEFINITIONS_NODE_TYPE);
+        index.setProperty(TYPE_PROPERTY_NAME, LuceneIndexConstants.TYPE_LUCENE);
+        index.setProperty(IndexConstants.REINDEX_PROPERTY_NAME, true);
+        index.setProperty(FulltextIndexConstants.COMPAT_MODE, IndexFormatVersion.V2.getVersion());
+        index.setProperty(IndexConstants.ASYNC_PROPERTY_NAME, 
+                new String[] { "async", "nrt" });
+        index.setProperty(FulltextIndexConstants.COST_PER_EXECUTION, cost);
+        // index.setProperty("excludedPaths", "/jcr:system");
+        Node indexRules = index.addNode(FulltextIndexConstants.INDEX_RULES);
+        Node ntBase = indexRules.addNode("nt:base");
+        Node props = ntBase.addNode(FulltextIndexConstants.PROP_NODE);
+        Node foo = props.addNode(propertyName);
+        foo.setProperty(FulltextIndexConstants.PROP_NAME, propertyName);
+        foo.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true);
+        p.session.save();
+        for (int i = 0; i < 600; i++) {
+            index.refresh(false);
+            if (!index.getProperty("reindex").getBoolean()) {
+                break;
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+    }
+    
+    /**
+     * Run a query and return which index was used.
+     * 
+     * @param p the persistence
+     * @param xpath the xpath query
+     * @param expectedIndex the index that is expected to be used
+     * @param expectedResult the expected list of results
+     * @return the index name used
+     */
+    public static void assertQueryUsesIndexAndReturns(Persistence p, String xpath, String expectedIndex,
+            String expectedResult) throws RepositoryException {
+        QueryManager qm = p.session.getWorkspace().getQueryManager();
+        Query q = qm.createQuery("explain " + xpath, "xpath");
+        QueryResult result = q.execute();
+        Row r = result.getRows().nextRow();
+        String plan = r.getValue("plan").getString();
+        if (plan.indexOf(expectedIndex) <= 0) {
+            throw new AssertionError("Index " + expectedIndex + " not used for query " + xpath + ": " + plan);
+        }
+        q = qm.createQuery(xpath, "xpath");
+        NodeIterator it = q.execute().getNodes();
+        ArrayList<String> list = new ArrayList<>();
+        while (it.hasNext()) {
+            Node n = it.nextNode();
+            list.add(n.getPath());
+        }
+        Assert.assertEquals(expectedResult, list.toString());
+    }
+    
+    /**
+     * Utility method for debugging.
+     * 
+     * @param node the node to print
+     */
+    public static void debugPrintProperties(Node node) throws RepositoryException {
+        PropertyIterator it = node.getProperties();
+        while (it.hasNext()) {
+            Property pr = it.nextProperty();
+            if (pr.isMultiple()) {
+                System.out.println(pr.getName() + " " + Arrays.toString(pr.getValues()));
+            } else {
+                System.out.println(pr.getName() + " " + pr.getValue().getString());
+            }
+        }
+    }
+
+    /**
+     * Check if the /libs node is read-only in this repository.
+     * 
+     * @param p the persistence
+     */
+    public static void checkLibsIsReadOnly(Persistence p) throws RepositoryException {
+        // libs is read-only
+        Node libsNode = p.session.getRootNode().getNode("libs");
+        try {
+            libsNode.addNode("illegal");
+            Assert.fail();
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/Persistence.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/Persistence.java?rev=1869202&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/Persistence.java (added)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/blueGreen/Persistence.java Thu Oct 31 07:34:34 2019
@@ -0,0 +1,370 @@
+/*
+ * 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.composite.blueGreen;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+import javax.security.auth.Subject;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.oak.InitialContent;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.composite.CompositeNodeStore;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditorProvider;
+import org.apache.jackrabbit.oak.plugins.commit.ConflictValidatorProvider;
+import org.apache.jackrabbit.oak.plugins.commit.JcrConflictHandler;
+import org.apache.jackrabbit.oak.plugins.document.bundlor.BundlingConfigInitializer;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.WhiteboardIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.counter.NodeCounterEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.IndexCopier;
+import org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.reader.DefaultIndexReaderFactory;
+import org.apache.jackrabbit.oak.plugins.index.lucene.util.IndexDefinitionBuilder;
+import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider;
+import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider;
+import org.apache.jackrabbit.oak.plugins.index.reference.ReferenceEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.reference.ReferenceIndexProvider;
+import org.apache.jackrabbit.oak.plugins.name.NameValidatorProvider;
+import org.apache.jackrabbit.oak.plugins.name.NamespaceEditorProvider;
+import org.apache.jackrabbit.oak.plugins.nodetype.TypeEditorProvider;
+import org.apache.jackrabbit.oak.plugins.observation.ChangeCollectorProvider;
+import org.apache.jackrabbit.oak.plugins.version.VersionHook;
+import org.apache.jackrabbit.oak.security.authorization.AuthorizationConfigurationImpl;
+import org.apache.jackrabbit.oak.security.authorization.composite.CompositeAuthorizationConfiguration;
+import org.apache.jackrabbit.oak.security.internal.SecurityProviderBuilder;
+import org.apache.jackrabbit.oak.segment.SegmentNodeStore;
+import org.apache.jackrabbit.oak.segment.SegmentNodeStoreBuilders;
+import org.apache.jackrabbit.oak.segment.file.FileStore;
+import org.apache.jackrabbit.oak.segment.file.FileStoreBuilder;
+import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.blob.FileBlobStore;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.mount.Mounts;
+import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.query.WhiteboardIndexProvider;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.SystemSubject;
+import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.jetbrains.annotations.NotNull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Utility class to open a repository.
+ */
+public class Persistence {
+
+    private static final MountInfoProvider MOUNT_INFO_PROVIDER = createMountInfoProvider();
+
+    private final ArrayList<FileStore> fileStores = new ArrayList<>();
+    private JackrabbitRepository repository;
+    public Session session;
+
+    public static Persistence open(File directory, Config config) throws Exception {
+        Persistence result = new Persistence();
+        FileStore fs = openFileStore(directory, config.blobStore);
+        result.fileStores.add(fs);
+        SegmentNodeStore ns = openSegmentStore(fs);
+        result.repository = openRepsitory(ns, config.indexDir);
+        result.session = result.repository.login(createCredentials());
+        return result;
+    }
+
+    public static Persistence openComposite(File globalDir, File libsDir, Config config) throws Exception {
+        Persistence result = new Persistence();
+        FileStore libsFileStore = openFileStore(libsDir, config.blobStore);
+        result.fileStores.add(libsFileStore);
+        SegmentNodeStore libsStore = openSegmentStore(libsFileStore);
+        FileStore globalFileStore = openFileStore(globalDir, config.blobStore);
+        result.fileStores.add(globalFileStore);
+        SegmentNodeStore globalStore = openSegmentStore(globalFileStore);
+        NodeStore cn = new CompositeNodeStore.Builder(
+                MOUNT_INFO_PROVIDER,
+                globalStore).addMount("libs", libsStore).
+                setPartialReadOnly(true).build();
+        result.repository = openRepsitory(cn, config.indexDir);
+        result.session = result.repository.login(createCredentials());
+        return result;
+    }
+
+    public void close() {
+        session.logout();
+        repository.shutdown();
+        for(FileStore fs : fileStores) {
+            fs.close();
+        }
+    }
+    
+    private static MountInfoProvider createMountInfoProvider() {
+        return Mounts.newBuilder()
+                .mount("libs", true, Arrays.asList(
+                        // pathsSupportingFragments
+                        "/oak:index/*$" 
+                ), Arrays.asList(
+                        // mountedPaths
+                        "/libs",       
+                        "/apps",
+                        "/jcr:system/rep:permissionStore/oak:mount-libs-crx.default"))
+                .build();
+    }
+    
+    public static BlobStore getFileBlobStore(File directory) throws IOException {
+        return new FileBlobStore(directory.getAbsolutePath());
+    }
+    
+    private static SecurityProvider createSecurityProvider() {
+        Map<String, Object> userConfigMap = new HashMap<>();
+        userConfigMap.put(UserConstants.PARAM_GROUP_PATH, "/home/groups");
+        userConfigMap.put(UserConstants.PARAM_USER_PATH, "/home/users");
+        userConfigMap.put(UserConstants.PARAM_DEFAULT_DEPTH, 1);
+        ConfigurationParameters userConfig = ConfigurationParameters.of(ImmutableMap.of(
+                UserConfiguration.NAME,
+                ConfigurationParameters.of(userConfigMap)));
+        SecurityProvider securityProvider = SecurityProviderBuilder.newBuilder().with(userConfig).build();
+        AuthorizationConfiguration acConfig = securityProvider.getConfiguration(AuthorizationConfiguration.class);
+        ((AuthorizationConfigurationImpl) ((CompositeAuthorizationConfiguration) acConfig).getDefaultConfig()).bindMountInfoProvider(MOUNT_INFO_PROVIDER);
+        return securityProvider;
+    }
+    
+    private static FileStore openFileStore(File directory, BlobStore blobStore) throws IOException {
+        try {
+            return FileStoreBuilder
+                    .fileStoreBuilder(directory)
+                    .withBlobStore(blobStore)
+                    .build();
+        } catch (InvalidFileStoreVersionException e) {
+            throw new IOException(e);
+        }
+    }
+    
+    private static SegmentNodeStore openSegmentStore(FileStore fileStore) throws IOException {
+        return SegmentNodeStoreBuilders
+                .builder(fileStore)
+                .build();
+    }    
+
+    private static JackrabbitRepository openRepsitory(NodeStore nodeStore, File indexDirectory) throws RepositoryException {
+        ExecutorService executorService = Executors.newFixedThreadPool(3);
+        Oak oak = new Oak(nodeStore);
+        oak.withFailOnMissingIndexProvider();
+        InitialContent initialContent = new InitialContent();
+        EditorProvider atomicCounter = new AtomicCounterEditorProvider();
+        SecurityProvider securityProvider = createSecurityProvider();
+        Jcr jcr = new Jcr(oak, false).
+                with(new Executor() {
+                        @Override
+                        public void execute(Runnable command) {
+                            executorService.execute(command);
+                        }
+                    }).
+                // with(whiteboard)
+                with(initialContent).
+                with(new Content()).
+                with(JcrConflictHandler.createJcrConflictHandler()).
+                with(new VersionHook()).
+                with(securityProvider).
+                with(new NameValidatorProvider()).
+                with(new NamespaceEditorProvider()).
+                with(new TypeEditorProvider()).
+                with(new ConflictValidatorProvider()).
+                with(atomicCounter).
+                // one second delay
+                withAsyncIndexing("async", 1).
+                withAsyncIndexing("fulltext-async", 1);
+        ChangeCollectorProvider changeCollectorProvider = new ChangeCollectorProvider();
+        jcr.with(changeCollectorProvider);
+        
+        WhiteboardIndexProvider indexProvider = new WhiteboardIndexProvider();
+        WhiteboardIndexEditorProvider indexEditorProvider = new WhiteboardIndexEditorProvider();
+        boolean fastQueryResultSize = false;
+        jcr.with(indexProvider).
+            with(indexEditorProvider).
+            with("crx.default").
+            withFastQueryResultSize(fastQueryResultSize);
+        IndexTracker indexTracker;
+        IndexCopier indexCopier;
+        try {
+            indexCopier = new IndexCopier(executorService, indexDirectory);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        MountInfoProvider mip = MOUNT_INFO_PROVIDER;
+        DefaultIndexReaderFactory indexReaderFactory = new DefaultIndexReaderFactory(
+                mip, indexCopier);
+        indexTracker = new IndexTracker(indexReaderFactory);
+        LuceneIndexProvider luceneIndexProvider =
+                new LuceneIndexProvider(indexTracker);
+        LuceneIndexEditorProvider luceneIndexEditor =
+                new LuceneIndexEditorProvider(indexCopier, indexTracker, null, null, mip);
+        jcr.with(new PropertyIndexEditorProvider().with(mip)).
+            with(new NodeCounterEditorProvider().with(mip)).
+            with(new PropertyIndexProvider().with(mip)).
+            with(luceneIndexEditor).
+            with((QueryIndexProvider) luceneIndexProvider).
+            with((Observer) luceneIndexProvider).
+            with(new NodeTypeIndexProvider().with(mip)).
+            with(new ReferenceEditorProvider().with(mip)).
+            with(new ReferenceIndexProvider().
+            with(mip)).
+            with(BundlingConfigInitializer.INSTANCE);
+        ContentRepository contentRepository = jcr.createContentRepository();
+        setupPermissions(contentRepository, securityProvider);
+        JackrabbitRepository repository = (JackrabbitRepository) jcr.createRepository();
+        return repository;
+    }
+
+    private static void setupPermissions(ContentRepository repo,
+                                         SecurityProvider securityProvider) throws RepositoryException {
+        ContentSession cs = null;
+        try {
+            cs = Subject.doAsPrivileged(SystemSubject.INSTANCE, new PrivilegedExceptionAction<ContentSession>() {
+                @Override
+                public ContentSession run() throws Exception {
+                    return repo.login(null, null);
+                }
+            }, null);
+    
+            Root root = cs.getLatestRoot();
+            AuthorizationConfiguration config = securityProvider.getConfiguration(AuthorizationConfiguration.class);
+            AccessControlManager acMgr = config.getAccessControlManager(root, NamePathMapper.DEFAULT);
+            // protect /oak:index
+            setupPolicy("/" + IndexConstants.INDEX_DEFINITIONS_NAME, acMgr);
+            // protect /jcr:system
+            setupPolicy("/" + JcrConstants.JCR_SYSTEM, acMgr);
+            if (root.hasPendingChanges()) {
+                root.commit();
+            }
+        } catch (PrivilegedActionException | CommitFailedException e) {
+            throw new RepositoryException(e);
+        } finally {
+            if (cs != null) {
+                try {
+                    cs.close();
+                } catch (IOException e) {
+                    throw new RepositoryException(e);
+                }
+            }
+        }
+    }
+
+    private static void setupPolicy(String path, AccessControlManager acMgr) throws RepositoryException {
+        // only retrieve applicable policies thus leaving the setup untouched once
+        // it has been created and has potentially been modified on an existing
+        // instance
+        AccessControlPolicyIterator it = acMgr.getApplicablePolicies(path);
+        while (it.hasNext()) {
+            AccessControlPolicy policy = it.nextAccessControlPolicy();
+            if (policy instanceof JackrabbitAccessControlList) {
+                JackrabbitAccessControlList acl = (JackrabbitAccessControlList) policy;
+                Privilege[] jcrAll = AccessControlUtils.privilegesFromNames(acMgr, PrivilegeConstants.JCR_ALL);
+                acl.addEntry(EveryonePrincipal.getInstance(), jcrAll, false);
+                acMgr.setPolicy(path, acl);
+                break;
+            }
+        }
+    }
+    
+    private static SimpleCredentials createCredentials() {
+        return new SimpleCredentials("admin", "admin".toCharArray());
+    }
+
+    private static class Content implements RepositoryInitializer {
+        
+        private static final String FULLTEXT_ASYNC = "fulltext-async";
+        private NodeBuilder index;
+        
+        @Override
+        public void initialize(@NotNull NodeBuilder builder) {
+            if (builder.hasChildNode(IndexConstants.INDEX_DEFINITIONS_NAME)) {
+                index = builder.child(IndexConstants.INDEX_DEFINITIONS_NAME);
+                configureGlobalFullTextIndex();
+            }
+        }
+
+        private void configureGlobalFullTextIndex() {
+            String indexName = "lucene";
+            if (!index.hasChildNode(indexName)) {
+                Set<String> INCLUDE_PROPS = ImmutableSet.of("test");
+                IndexDefinitionBuilder indexBuilder = new IndexDefinitionBuilder(index.child(indexName))
+                        .codec("Lucene46")
+                        .excludedPaths("/libs");
+                indexBuilder.async(FULLTEXT_ASYNC, IndexConstants.INDEXING_MODE_NRT);
+                indexBuilder.aggregateRule("nt:file", "jcr:content");
+                indexBuilder.indexRule("rep:Token");
+                IndexDefinitionBuilder.IndexRule indexRules = indexBuilder.indexRule("nt:base");
+                indexRules.includePropertyTypes("String", "Binary");
+                for (String includeProp : INCLUDE_PROPS) {
+                    indexRules.property(includeProp).propertyIndex();
+                }
+                indexBuilder.build();
+            }
+        }        
+        
+    }
+    
+    public static class Config {
+        public BlobStore blobStore;
+        public File indexDir;
+    }
+
+}
+

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java?rev=1869202&r1=1869201&r2=1869202&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java Thu Oct 31 07:34:34 2019
@@ -85,6 +85,8 @@ import org.apache.jackrabbit.oak.spi.que
 import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextExpression;
 import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextParser;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.mount.Mounts;
 import org.apache.jackrabbit.oak.spi.query.Filter;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex.OrderEntry;
@@ -1931,6 +1933,11 @@ public class IndexPlannerTest {
             readers.add(new DefaultIndexReader(directory, null, definition.getAnalyzer()));
             return readers;
         }
+
+        @Override
+        public MountInfoProvider getMountInfoProvider() {
+            return Mounts.defaultMountInfoProvider();
+        }
     }
 
     /**

Modified: jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java?rev=1869202&r1=1869201&r2=1869202&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java (original)
+++ jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java Thu Oct 31 07:34:34 2019
@@ -124,4 +124,9 @@ public class ElasticsearchIndex extends
         estimators.putIfAbsent(path, new LMSEstimator());
         return estimators.get(path);
     }
+
+    @Override
+    protected boolean filterReplacedIndexes() {
+        return true;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java?rev=1869202&r1=1869201&r2=1869202&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java (original)
+++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java Thu Oct 31 07:34:34 2019
@@ -87,11 +87,22 @@ public abstract class FulltextIndex impl
     protected abstract Predicate<NodeState> getIndexDefinitionPredicate();
 
     protected abstract String getFulltextRequestString(IndexPlan plan, IndexNode indexNode);
+    
+    /**
+     * Whether replaced indexes (that is, if a new version of the index is
+     * available) should be filtered out.
+     * 
+     * @return true if yes (e.g. in a blue-green deployment model)
+     */
+    protected abstract boolean filterReplacedIndexes();
 
     @Override
     public List<IndexPlan> getPlans(Filter filter, List<OrderEntry> sortOrder, NodeState rootState) {
         Collection<String> indexPaths = new IndexLookup(rootState, getIndexDefinitionPredicate())
                 .collectIndexNodePaths(filter);
+        if (filterReplacedIndexes()) {
+            indexPaths = IndexName.filterReplacedIndexes(indexPaths, rootState);
+        }
         List<IndexPlan> plans = Lists.newArrayListWithCapacity(indexPaths.size());
         for (String path : indexPaths) {
             IndexNode indexNode = null;
@@ -115,7 +126,7 @@ public abstract class FulltextIndex impl
         }
         return plans;
     }
-
+    
     @Override
     public double getCost(Filter filter, NodeState root) {
         throw new UnsupportedOperationException("Not supported as implementing AdvancedQueryIndex");

Added: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexName.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexName.java?rev=1869202&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexName.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexName.java Thu Oct 31 07:34:34 2019
@@ -0,0 +1,197 @@
+/*
+ * 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.index.search.spi.query;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An index name, which possibly contains two version numbers: the product
+ * version number, and the customer version number.
+ * 
+ * The format of an index node name is: 
+ * - The name of the index, 
+ * - optionally a dash ('-') and the product version number, 
+ * - optionally "-custom-" and the customer version number.
+ * 
+ * If the node name doesn't contain version numbers / dashes, then version 0 is
+ * assumed (for both the product version number and customer version number).
+ */
+class IndexName implements Comparable<IndexName> {
+
+    private final static Logger LOG = LoggerFactory.getLogger(IndexName.class);
+    
+    // already logged index names
+    private static final HashSet<String> LOGGED_WARN = new HashSet<>();
+    // when LOGGED_WARN will be cleared
+    private static long nextLogWarnClear;
+
+    private final String nodeName;
+    private final String baseName;
+    private final boolean isVersioned;
+    private final int productVersion;
+    private final int customerVersion;
+    private final boolean isLegal;
+    
+    /**
+     * Parse the node name. Both node names with version and without version are
+     * supported.
+     * 
+     * @param nodeName the node name (starting from root; e.g. "/oak:index/lucene")
+     * @return the index name object
+     */
+    public static IndexName parse(final String nodeName) {
+        String baseName = nodeName;
+        int index = baseName.lastIndexOf('-');
+        if (index < 0) {
+            return new IndexName(nodeName, true);
+        }
+        String last = baseName.substring(index + 1);
+        baseName = baseName.substring(0, index);
+        try {
+            int v1 = Integer.parseInt(last);
+            if (!baseName.endsWith("-custom")) {
+                return new IndexName(nodeName, baseName, v1, 0);
+            }
+            baseName = baseName.substring(0, 
+                    baseName.length() - "-custom".length());
+            index = baseName.lastIndexOf('-');
+            if (index < 0) {
+                return new IndexName(nodeName, baseName, 0, v1);
+            }
+            last = baseName.substring(index + 1);
+            baseName = baseName.substring(0, index);
+            int v2 = Integer.parseInt(last);
+            return new IndexName(nodeName, baseName, v2, v1);
+        } catch (NumberFormatException e) {
+            long now = System.currentTimeMillis();
+            if (nextLogWarnClear < now) {
+                LOGGED_WARN.clear();
+                // clear again each 5 minutes
+                nextLogWarnClear = now + 5 * 60 * 1000;
+            }
+            if (LOGGED_WARN.add(nodeName)) {
+                LOG.warn("Index name format error: " + nodeName);
+            }
+            return new IndexName(nodeName, false);
+        }
+    }
+    
+    private IndexName(String nodeName, boolean isLegal) {
+        // not versioned
+        this.nodeName = nodeName;
+        this.baseName = nodeName;
+        this.isVersioned = false;
+        this.productVersion = 0;
+        this.customerVersion = 0;
+        this.isLegal = isLegal;
+    }
+
+    private IndexName(String nodeName, String baseName, int productVersion, int customerVersion) {
+        // versioned
+        this.nodeName = nodeName;
+        this.baseName = baseName;
+        this.isVersioned = true;
+        this.productVersion = productVersion;
+        this.customerVersion = customerVersion;
+        this.isLegal = true;
+    }
+    
+    public String toString() {
+        return nodeName + 
+                " base=" + baseName + 
+                (isVersioned ? " versioned": "") + 
+                " product=" + productVersion + 
+                " custom=" + customerVersion +
+                (isLegal ? "" : " illegal");
+    }
+    
+    @Override
+    public int compareTo(IndexName o) {
+        int comp = baseName.compareTo(o.baseName);
+        if (comp != 0) {
+            return comp;
+        }
+        comp = Integer.compare(productVersion, o.productVersion);
+        if (comp != 0) {
+            return comp;
+        }
+        return Integer.compare(customerVersion, o.customerVersion);
+    }
+
+    /**
+     * Filter out index that are replaced by another index with the same base
+     * name but newer version.
+     * 
+     * Indexes without a version number in the name are always used, except if
+     * there is an active index with the same base name but a newer version.
+     * 
+     * Active indexes have a hidden ":oak:mount-" node, which means they are
+     * indexed in the read-only node store.
+     * 
+     * @param indexPaths the set of index paths
+     * @param rootState the root node state (used to find hidden nodes)
+     * @return the filtered list
+     */
+    public static Collection<String> filterReplacedIndexes(Collection<String> indexPaths, NodeState rootState) {
+        HashMap<String, IndexName> latestVersions = new HashMap<String, IndexName>();
+        for (String p : indexPaths) {
+            IndexName indexName = IndexName.parse(p);
+            if (indexName.isVersioned) {
+                // which might not be a good idea - instead, it should check if the composite node store is used
+                // (but how?)
+                if (!isIndexActive(p, rootState)) {
+                    // the index is inactive, so not used
+                    continue;
+                }
+            }
+            IndexName stored = latestVersions.get(indexName.baseName);
+            if (stored == null || stored.compareTo(indexName) < 0) {
+                // no old version, or old version is smaller: replace
+                latestVersions.put(indexName.baseName, indexName);
+            }
+        }
+        ArrayList<String> result = new ArrayList<>(latestVersions.size());
+        for (IndexName n : latestVersions.values()) {
+            result.add(n.nodeName);
+        }
+        return result;
+    }
+
+    private static boolean isIndexActive(String indexPath, NodeState rootState) {
+        NodeState indexNode = rootState;
+        for(String e : PathUtils.elements(indexPath)) {
+            indexNode = indexNode.getChildNode(e);
+        }
+        for(String c : indexNode.getChildNodeNames()) {
+            if (c.startsWith(":oak:mount-")) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNameTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNameTest.java?rev=1869202&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNameTest.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNameTest.java Thu Oct 31 07:34:34 2019
@@ -0,0 +1,81 @@
+/*
+ * 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.index.search.spi.query;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Test the IndexName class
+ */
+public class IndexNameTest {
+
+    @Test
+    public void parse() {
+        assertEquals("/lucene base=/lucene product=0 custom=0", 
+                IndexName.parse("/lucene").toString());
+        assertEquals("/lucene-1 base=/lucene versioned product=1 custom=0", 
+                IndexName.parse("/lucene-1").toString());
+        assertEquals("/lucene-2 base=/lucene versioned product=2 custom=0", 
+                IndexName.parse("/lucene-2").toString());
+        assertEquals("/lucene-custom-3 base=/lucene versioned product=0 custom=3", 
+                IndexName.parse("/lucene-custom-3").toString());
+        assertEquals("/lucene-4-custom-5 base=/lucene versioned product=4 custom=5", 
+                IndexName.parse("/lucene-4-custom-5").toString());
+        assertEquals("/lucene-12-custom-34 base=/lucene versioned product=12 custom=34", 
+                IndexName.parse("/lucene-12-custom-34").toString());
+        // illegal
+        assertEquals("/lucene-abc base=/lucene-abc product=0 custom=0 illegal", 
+                IndexName.parse("/lucene-abc").toString());
+        assertEquals("/lucene-abc-custom-2 base=/lucene-abc-custom-2 product=0 custom=0 illegal", 
+                IndexName.parse("/lucene-abc-custom-2").toString());
+    }
+
+    @Test
+    public void compare() {
+        IndexName p0 = IndexName.parse("/lucene");
+        IndexName p0a = IndexName.parse("/lucene-0");
+        IndexName p0b = IndexName.parse("/lucene-0-custom-0");
+        IndexName p0c1 = IndexName.parse("/lucene-custom-1");
+        IndexName p0c1a = IndexName.parse("/lucene-0-custom-1");
+        IndexName p1 = IndexName.parse("/lucene-1");
+        IndexName p1c1 = IndexName.parse("/lucene-1-custom-1");
+        IndexName p1c2 = IndexName.parse("/lucene-1-custom-2");
+        
+        assertTrue(p0.compareTo(p0a) == 0);
+        assertTrue(p0.compareTo(p0b) == 0);
+        assertTrue(p0a.compareTo(p0b) == 0);
+        assertTrue(p0c1.compareTo(p0c1a) == 0);
+        
+        assertTrue(p0.compareTo(p0c1) < 0);
+        assertTrue(p0c1.compareTo(p1) < 0);
+        assertTrue(p1.compareTo(p1c1) < 0);
+        assertTrue(p1c1.compareTo(p1c2) < 0);
+        
+        IndexName a = IndexName.parse("/luceneA");
+        IndexName b = IndexName.parse("/luceneB");
+        IndexName c = IndexName.parse("/luceneC");
+        assertTrue(a.compareTo(b) < 0);
+        assertTrue(b.compareTo(c) < 0);
+        
+    }
+
+}