/*
 * 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.lucene;

import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.oak.Oak;
import org.apache.jackrabbit.oak.api.ContentRepository;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.CIHelper;
import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
import org.apache.jackrabbit.oak.plugins.index.lucene.spi.FulltextQueryTermsProvider;
import org.apache.jackrabbit.oak.plugins.index.lucene.spi.IndexFieldProvider;
import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
import org.apache.jackrabbit.oak.plugins.index.search.ExtractedTextCache;
import org.apache.jackrabbit.oak.plugins.nodetype.write.NodeTypeRegistry;
import org.apache.jackrabbit.oak.query.AbstractQueryTest;
import org.apache.jackrabbit.oak.spi.commit.Observer;
import org.apache.jackrabbit.oak.spi.mount.Mounts;
import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;

public class LuceneIndexAugmentTest extends AbstractQueryTest {
    private final SimpleIndexAugmentorFactory factory = new SimpleIndexAugmentorFactory();

    private IndexTracker tracker = new IndexTracker();

    private LuceneIndexNode indexNode;

    @Override
    protected void createTestIndexNode() {
        setTraversalEnabled(false);
    }

    @Override
    protected ContentRepository createRepository() {
        LuceneIndexEditorProvider editorProvider = new LuceneIndexEditorProvider(null,
            new ExtractedTextCache(0, 0),
            factory, Mounts.defaultMountInfoProvider());
        LuceneIndexProvider provider = new LuceneIndexProvider(tracker, factory);
        return new Oak()
            .with(new OpenSecurityProvider())
            .with((QueryIndexProvider) provider)
            .with((Observer) provider)
            .with(editorProvider)
            .createContentRepository();
    }

    //OAK-3576
    @Test public void queryHook() throws Exception {
        //setup repo and index
        NodeTypeRegistry.register(root, IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
        Tree props = createIndex(TestUtil.NT_TEST);
        TestUtil.enableForFullText(props, "foo");
        root.commit();

        //setup query augmentor
        final String testSearchText = "search this text";
        final String realSearchText = "bar";
        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                assertEquals("Full text term passed to provider isn't same as the one passed in query",
                    testSearchText, text);
                return new TermQuery(new Term(":fulltext", realSearchText));
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };

        //add content
        Tree test = root.getTree("/").addChild("test");
        Tree node = createNodeWithType(test, "item", TestUtil.NT_TEST);
        node.setProperty("foo", realSearchText);
        root.commit();

        //query (testSearchText doesn't have 'bar'... our augment would search for :fulltext:bar
        String query = "select [jcr:path] from [oak:TestNode] where CONTAINS(*, '" + testSearchText + "')";
        List<String> paths = executeQuery(query, SQL2);
        assertEquals("Augmented query wasn't used to search", 1, paths.size());
        assertEquals("/test/item", paths.get(0));
    }

    //OAK-3576
    @Test public void indexHookCallbackFrequency() throws Exception {
        //setup repo and index
        NodeTypeRegistry.register(root, IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
        Tree props = createIndex(TestUtil.NT_TEST);
        TestUtil.enablePropertyIndex(props, "foo1", false);
        TestUtil.enablePropertyIndex(props, "foo2", false);
        TestUtil.enablePropertyIndex(props, "subChild/foo3", false);
        root.commit();

        //setup index augmentor
        final AtomicInteger counter = new AtomicInteger(0);
        factory.indexFieldProvider = new IndexFieldProvider() {
            @NotNull
            @Override
            public Iterable<Field> getAugmentedFields(String path, NodeState document, NodeState indexDefinition) {
                counter.incrementAndGet();
                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, document, indexDefinition);
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };

        //add content
        counter.set(0);
        Tree test = root.getTree("/").addChild("test");
        Tree node = createNodeWithType(test, "item", TestUtil.NT_TEST);
        node.setProperty("foo1", "bar1");
        node.setProperty("foo2", "bar2");
        Tree subChild = node.addChild("subChild");
        subChild.setProperty("foo3", "bar3");
        root.commit();
        assertEquals("Number of callbacks should be same as number of changed properties", 1, counter.get());

        //change sub-property
        counter.set(0);
        subChild = root.getTree("/test/item/subChild");
        subChild.setProperty("foo3", "bar4");
        root.commit();
        assertEquals("Sub child property change should make call backs for all indexed properties", 1, counter.get());
    }

    //OAK-3576
    @Test public void indexHookCallbackAndStorage() throws Exception {
        final String propName = "subChild/foo";

        //setup repo and index
        NodeTypeRegistry.register(root, IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
        Tree props = createIndex(TestUtil.NT_TEST);
        TestUtil.enableForFullText(props, propName);
        root.commit();

        //setup index augmentor
        factory.indexFieldProvider = new IndexFieldProvider() {
            @NotNull
            @Override
            public Iterable<Field> getAugmentedFields(String path, NodeState document, NodeState indexDefinition) {
                assertEquals("/test/item", path);
                assertEquals(TestUtil.NT_TEST, document.getName(JcrConstants.JCR_PRIMARYTYPE));
                assertEquals(IndexConstants.INDEX_DEFINITIONS_NODE_TYPE,
                    indexDefinition.getName(JcrConstants.JCR_PRIMARYTYPE));
                return List.of(new StringField("barbar", "1", Field.Store.NO));
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };

        //add content
        Tree test = root.getTree("/").addChild("test");
        Tree node = createNodeWithType(test, "item", TestUtil.NT_TEST).addChild("subChild");
        node.setProperty("foo", "bar");
        root.commit();

        //Check document that made to the index
        IndexSearcher searcher = getSearcher();
        TopDocs docs = searcher.search(new TermQuery(new Term("barbar", "1")), 10);
        ScoreDoc[] scoreDocs = docs.scoreDocs;
        assertEquals("Number of results should be 1", 1, scoreDocs.length);
        Document doc = searcher.doc(scoreDocs[0].doc);
        String path = doc.get(":path");
        assertEquals("/test/item", path);
    }

    //OAK-3576
    @Test
    public void nullBehavior() throws Exception {
        // OAK-6833
        assumeFalse(CIHelper.windows());

        //setup repo and index
        NodeTypeRegistry.register(root, IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
        Tree props = createIndex(TestUtil.NT_TEST);
        TestUtil.enableForFullText(props, "foo");
        root.commit();

        Tree rootTree = root.getTree("/").addChild("test");

        //Note: augmentor behavior is tested elsewhere... we are just checking if default works

        int testIndex = 1;
        //both query and index augmentors are null (no exception expected)
        checkSimpleBehavior(rootTree, testIndex++);

        //Set a very sad query augmentor
        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                return null;
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return FulltextQueryTermsProvider.DEFAULT.getSupportedTypes();
            }
        };
        checkSimpleBehavior(rootTree, testIndex++);

        //Set query augmentor... with null query
        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                return null;
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };
        checkSimpleBehavior(rootTree, testIndex++);

        //Set query augmentor... with some query
        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                return new TermQuery(new Term("bar", "baz"));
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };
        checkSimpleBehavior(rootTree, testIndex++);

        factory.fulltextQueryTermsProvider = null;

        //Set a very sad index augmentor
        factory.indexFieldProvider = IndexFieldProvider.DEFAULT;
        checkSimpleBehavior(rootTree, testIndex++);

        //Set index augmentor... with null fields
        factory.indexFieldProvider = new IndexFieldProvider() {
            @NotNull
            @Override
            public Iterable<Field> getAugmentedFields(String path, NodeState document, NodeState indexDefinition) {
                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, document, indexDefinition);
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };
        checkSimpleBehavior(rootTree, testIndex++);

        //Set index augmentor... with some fields
        factory.fulltextQueryTermsProvider = null;
        factory.indexFieldProvider = new IndexFieldProvider() {
            @NotNull
            @Override
            public Iterable<Field> getAugmentedFields(String path, NodeState document, NodeState indexDefinition) {
                List<Field> fields = new ArrayList<>();
                fields.add(new StringField("bar", "baz", Field.Store.NO));
                return fields;
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };
        checkSimpleBehavior(rootTree, testIndex++);

        //setup composite query term provider with one returning null query
        factory.registerQueryTermsProvider(new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                return null;
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        });
        factory.useSuperBehavior = true;
        checkSimpleBehavior(rootTree, testIndex++);
    }

    //OAK-3576
    @Test
    public void skipDefaultOnlyUsingAugmentors() throws Exception {
        //setup repo and index
        NodeTypeRegistry.register(root, IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
        Tree props = createIndex(TestUtil.NT_TEST);
        Tree prop = props.addChild("foo1");
        prop.setProperty(FulltextIndexConstants.PROP_INDEX, true);
        prop = props.addChild("foo2");
        prop.setProperty(FulltextIndexConstants.PROP_NAME, "subChild/foo2");
        prop.setProperty(FulltextIndexConstants.PROP_INDEX, true);
        root.commit();

        //setup augmentors
        final AtomicInteger indexingCounter = new AtomicInteger(0);
        factory.indexFieldProvider = new IndexFieldProvider() {
            @NotNull
            @Override
            public Iterable<Field> getAugmentedFields(String path, NodeState document, NodeState indexDefinition) {
                indexingCounter.incrementAndGet();
                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, document, indexDefinition);
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };
        final AtomicInteger queryingCounter = new AtomicInteger(0);
        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                queryingCounter.set(1);
                return null;
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };

        //add content
        Tree node1 = createNodeWithType(root.getTree("/"), "node1", TestUtil.NT_TEST);
        node1.setProperty("foo1", "bar1");
        node1.addChild("subChild").setProperty("foo2", "bar2");
        root.commit();

        //indexing assertions
        assertEquals("Indexing augment should get called once", 1, indexingCounter.get());
        assertEquals("No docs should get indexed (augmentor hasn't added any field)",
            0, getSearcher().getIndexReader().numDocs());

        String query = "EXPLAIN SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] WHERE [foo1]='bar1'";
        List<String> paths = executeQuery(query, SQL2);
        assertTrue("indexed prop name shouldn't decide query plan (" + paths.get(0) + ")",
            paths.get(0).contains("/* no-index"));

        query = "EXPLAIN SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] WHERE [subChild/foo2]='bar2'";
        paths = executeQuery(query, SQL2);
        assertTrue("indexed prop name shouldn't decide query plan (" + paths.get(0) + ")",
            paths.get(0).contains("/* no-index"));
    }

    //OAK-3576
    @Test
    public void propertyIndexUsingAugmentors() throws Exception {
        //setup repo and index
        NodeTypeRegistry.register(root, IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
        Tree props = createIndex(TestUtil.NT_TEST);
        TestUtil.enablePropertyIndex(props, "foo1", false);
        TestUtil.enablePropertyIndex(props, "subChild/foo2", false);
        root.commit();

        //setup augmentors
        final AtomicInteger indexingCounter = new AtomicInteger(0);
        factory.indexFieldProvider = new IndexFieldProvider() {
            @NotNull
            @Override
            public Iterable<Field> getAugmentedFields(String path, NodeState document, NodeState indexDefinition) {
                indexingCounter.incrementAndGet();
                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, document, indexDefinition);
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };
        final AtomicInteger queryingCounter = new AtomicInteger(0);
        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                queryingCounter.set(1);
                return null;
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };

        //add content
        Tree node1 = createNodeWithType(root.getTree("/"), "node1", TestUtil.NT_TEST);
        node1.setProperty("foo1", "bar1");
        node1.addChild("subChild").setProperty("foo2", "bar2");
        root.commit();

        //indexing assertions
        assertEquals("Indexing augment should get called once", 1, indexingCounter.get());

        String query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] WHERE [foo1]='bar1'";
        executeQuery(query, SQL2);
        assertEquals("Query augmentor should not get called for property constraints", 0, queryingCounter.get());
        query = "EXPLAIN " + query;
        List<String> paths = executeQuery(query, SQL2, false);
        assertTrue("property index should have made the index selected (" + paths.get(0) + ")",
            paths.get(0).contains("/oak:index/test-index"));

        query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] WHERE [subChild/foo2]='bar2'";
        executeQuery(query, SQL2);
        assertEquals("Query augmentor should not get called for property constraints", 0, queryingCounter.get());
        query = "EXPLAIN " + query;
        paths = executeQuery(query, SQL2);
        assertTrue("property index should have made the index selected (" + paths.get(0) + ")",
            paths.get(0).contains("/oak:index/test-index"));
    }

    //OAK-3576
    @Test
    public void fulltextIndexUsingAugmentors() throws Exception {
        //setup repo and index
        NodeTypeRegistry.register(root, IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
        Tree props = createIndex(TestUtil.NT_TEST);
        TestUtil.enableForFullText(props, "foo1");
        TestUtil.enableForFullText(props, "subChild/foo2");
        root.commit();

        //setup augmentors
        final AtomicInteger indexingCounter = new AtomicInteger(0);
        factory.indexFieldProvider = new IndexFieldProvider() {
            @NotNull
            @Override
            public Iterable<Field> getAugmentedFields(String path, NodeState document, NodeState indexDefinition) {
                indexingCounter.incrementAndGet();
                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, document, indexDefinition);
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };
        final AtomicInteger queryingCounter = new AtomicInteger(0);
        factory.fulltextQueryTermsProvider = new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                queryingCounter.set(1);
                return null;
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        };

        //add content
        Tree node1 = createNodeWithType(root.getTree("/"), "node1", TestUtil.NT_TEST);
        node1.setProperty("foo1", "bar1");
        node1.addChild("subChild").setProperty("foo2", "bar2");
        root.commit();

        //indexing assertions
        assertEquals("Indexing augment should get called once", 1, indexingCounter.get());

        String query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] WHERE CONTAINS(*, 'bar1')";
        executeQuery(query, SQL2);
        assertEquals("Query augmentor should get called for full text constraints", 1, queryingCounter.get());
        queryingCounter.set(0);
        query = "EXPLAIN " + query;
        List<String> paths = executeQuery(query, SQL2, false);
        assertEquals("Query augmentor should get called for full text constraints", 1, queryingCounter.get());
        assertTrue("property index should have made the index selected (" + paths.get(0) + ")",
            paths.get(0).contains("/oak:index/test-index"));

        queryingCounter.set(0);
        query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] WHERE CONTAINS(*, 'bar2')";
        executeQuery(query, SQL2);
        assertEquals("Query augmentor should get called for full text constraints", 1, queryingCounter.get());
        queryingCounter.set(0);
        query = "EXPLAIN " + query;
        paths = executeQuery(query, SQL2, false);
        assertEquals("Query augmentor should get called for full text constraints", 1, queryingCounter.get());
        assertTrue("property index should have made the index selected (" + paths.get(0) + ")",
            paths.get(0).contains("/oak:index/test-index"));
    }

    @Test
    public void indexAugmentorMismatchedNodeType() throws Exception {
        //setup repo and index
        NodeTypeRegistry.register(root, IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
        Tree props = createIndex(TestUtil.NT_TEST);
        TestUtil.enableForFullText(props, "foo1");
        root.commit();

        //setup augmentors
        final AtomicInteger indexingCounter1 = new AtomicInteger(0);
        final AtomicInteger indexingCounter2 = new AtomicInteger(0);
        factory.registerIndexFieldProvider(new IndexFieldProvider() {
            @NotNull
            @Override
            public Iterable<Field> getAugmentedFields(String path, NodeState document, NodeState indexDefinition) {
                indexingCounter1.incrementAndGet();
                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, document, indexDefinition);
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(JcrConstants.NT_BASE);
            }
        });
        factory.registerIndexFieldProvider(new IndexFieldProvider() {
            @NotNull
            @Override
            public Iterable<Field> getAugmentedFields(String path, NodeState document, NodeState indexDefinition) {
                indexingCounter2.incrementAndGet();
                return IndexFieldProvider.DEFAULT.getAugmentedFields(path, document, indexDefinition);
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        });
        factory.useSuperBehavior = true;

        //add content
        createNodeWithType(root.getTree("/"), "node1", TestUtil.NT_TEST).setProperty("foo1", "bar1");
        root.commit();

        assertEquals("Mismatching node type should not let index augmentor called", 0, indexingCounter1.get());
        assertEquals("Matching node type should get augmentor called", 1, indexingCounter2.get());
    }

    @Test
    public void queryAugmentorMismatchedNodeType() throws Exception {
        //setup repo and index
        NodeTypeRegistry.register(root, IOUtils.toInputStream(TestUtil.TEST_NODE_TYPE), "test nodeType");
        Tree props = createIndex(TestUtil.NT_TEST);
        TestUtil.enableForFullText(props, "foo1", false);
        root.commit();

        //setup augmentors
        final AtomicInteger indexingCounter1 = new AtomicInteger(0);
        final AtomicInteger indexingCounter2 = new AtomicInteger(0);
        factory.registerQueryTermsProvider(new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                indexingCounter1.set(1);
                return null;
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(JcrConstants.NT_BASE);
            }
        });
        factory.registerQueryTermsProvider(new FulltextQueryTermsProvider() {
            @Override
            public Query getQueryTerm(String text, Analyzer analyzer, NodeState indexDefinition) {
                indexingCounter2.set(1);
                return null;
            }

            @NotNull
            @Override
            public Set<String> getSupportedTypes() {
                return Collections.singleton(TestUtil.NT_TEST);
            }
        });
        factory.useSuperBehavior = true;


        executeQuery("SELECT [jcr:path] FROM [" + TestUtil.NT_TEST + "] WHERE CONTAINS(*, 'test')", SQL2, false);

        assertEquals("Mismatching node type should not let index augmentor called", 0, indexingCounter1.get());
        assertEquals("Matching node type should get augmentor called", 1, indexingCounter2.get());
    }

    private static Tree createNodeWithType(Tree t, String nodeName, String typeName){
        t = t.addChild(nodeName);
        t.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName, Type.NAME);
        return t;
    }

    private Tree createIndex(String nodeType) throws Exception {
        Tree rootTree = root.getTree("/");
        return createIndex(rootTree, nodeType);
    }

    private Tree createIndex(Tree root, String nodeType) throws Exception {
        Tree index = createTestIndexNode(root, LuceneIndexConstants.TYPE_LUCENE);
        TestUtil.useV2(index);
        return TestUtil.newRulePropTree(index, nodeType);
    }

    private static class SimpleIndexAugmentorFactory extends IndexAugmentorFactory {
        IndexFieldProvider indexFieldProvider = null;
        FulltextQueryTermsProvider fulltextQueryTermsProvider = null;
        private boolean useSuperBehavior = false;

        void registerIndexFieldProvider(IndexFieldProvider provider) {
            bindIndexFieldProvider(provider);
        }

        void registerQueryTermsProvider(FulltextQueryTermsProvider provider) {
            bindFulltextQueryTermsProvider(provider);
        }

        @NotNull
        @Override
        public IndexFieldProvider getIndexFieldProvider(String nodeType) {
            return useSuperBehavior?
                super.getIndexFieldProvider(nodeType):
                (indexFieldProvider != null)?
                    indexFieldProvider:
                    IndexFieldProvider.DEFAULT;
        }

        @NotNull
        @Override
        public FulltextQueryTermsProvider getFulltextQueryTermsProvider(String nodeType) {
            return useSuperBehavior?
                super.getFulltextQueryTermsProvider(nodeType):
                (fulltextQueryTermsProvider != null)?
                    fulltextQueryTermsProvider:
                    FulltextQueryTermsProvider.DEFAULT;
        }
    }

    private IndexSearcher getSearcher(){
        if(indexNode == null){
            indexNode = tracker.acquireIndexNode("/oak:index/" + TEST_INDEX_NAME);
        }
        return indexNode.getSearcher();
    }

    private void checkSimpleBehavior(Tree rootTree, int testIndex) throws Exception {
        createNodeWithType(rootTree, "node" + testIndex, TestUtil.NT_TEST)
            .setProperty("foo", "bar" + testIndex);
        root.commit();

        String query = "SELECT [jcr:path] from [" + TestUtil.NT_TEST + "] WHERE contains(*, 'bar" + testIndex + "')";
        List<String> paths = executeQuery(query, SQL2);
        assertEquals(1, paths.size());
        assertEquals("/test/node" + testIndex, paths.get(0));
    }
}
