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 to...@apache.org on 2018/04/12 11:46:18 UTC

svn commit: r1828972 [4/5] - in /jackrabbit/oak/trunk/oak-search: ./ src/main/java/org/apache/jackrabbit/oak/plugins/index/search/ src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ src/test/java/org/apache/jackrabbit/oak/plugins/index/...

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java?rev=1828972&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java Thu Apr 12 11:46:17 2018
@@ -0,0 +1,525 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.search.Aggregate.NodeInclude;
+import org.apache.jackrabbit.oak.plugins.index.search.Aggregate.NodeIncludeResult;
+import org.apache.jackrabbit.oak.plugins.index.search.Aggregate.PropertyIncludeResult;
+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 org.junit.Test;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.collect.Iterables.toArray;
+import static com.google.common.collect.Maps.newHashMap;
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.oak.InitialContent.INITIAL_CONTENT;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INDEX_RULES;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.matchers.JUnitMatchers.hasItems;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+public class AggregateTest {
+
+    private final TestCollector col = new TestCollector();
+    private final SimpleMapper mapper = new SimpleMapper();
+    private final NodeState root = INITIAL_CONTENT;
+    private NodeBuilder builder = root.builder();
+
+    //~---------------------------------< Node Includes >
+
+    @Test
+    public void oneLevelAll() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("*")));
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a").child("c");
+        nb.child("b");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(2, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a", "b"));
+    }
+
+    @Test
+    public void oneLevelNamed() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("a")));
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a");
+        nb.child("b");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(1, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a"));
+    }
+
+    @Test
+    public void noOfChildNodeRead() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("a")));
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a");
+        for (int i = 0; i < 10; i++) {
+            nb.child("a"+i);
+        }
+
+        NodeState state = nb.getNodeState();
+        final AtomicInteger counter = new AtomicInteger();
+        Iterable<? extends ChildNodeEntry> countingIterator = Iterables.transform(state.getChildNodeEntries(),
+                new Function<ChildNodeEntry, ChildNodeEntry>() {
+            @Override
+            public ChildNodeEntry apply(ChildNodeEntry input) {
+                counter.incrementAndGet();
+                return input;
+            }
+        });
+        NodeState mocked = spy(state);
+        doReturn(countingIterator).when(mocked).getChildNodeEntries();
+        ag.collectAggregates(mocked, col);
+
+        //Here at max a single call should happen for reading child nodes
+        assertThat(counter.get(), is(lessThanOrEqualTo(1)));
+    }
+
+    @Test
+    public void oneLevelTyped() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("nt:resource","*", false)));
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a").setProperty(JCR_PRIMARYTYPE,"nt:resource");
+        nb.child("b");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(1, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a"));
+    }
+
+    @Test
+    public void oneLevelTypedMixin() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("mix:title","*", false)));
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a").setProperty(JcrConstants.JCR_MIXINTYPES, Collections.singleton("mix:title"), Type.NAMES);
+        nb.child("b");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(1, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a"));
+    }
+
+    @Test
+    public void multiLevelAll() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("*"), ni("*/*")));
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a").child("c");
+        nb.child("b");
+        nb.child("d").child("e").child("f");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(5, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a", "b", "d", "a/c", "d/e"));
+    }
+
+    @Test
+    public void multiLevelNamed() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("a"), ni("d/e")));
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a").child("c");
+        nb.child("b");
+        nb.child("d").child("e").child("f");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(2, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a", "d/e"));
+    }
+
+    @Test
+    public void multiLevelTyped() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("a"),
+                ni("nt:resource", "d/*/*", false)));
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a").child("c");
+        nb.child("b");
+        nb.child("d").child("e").child("f").setProperty(JCR_PRIMARYTYPE,"nt:resource");
+        nb.child("d").child("e").child("f2");
+        nb.child("d").child("e2").child("f3").setProperty(JCR_PRIMARYTYPE, "nt:resource");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(3, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a", "d/e/f", "d/e2/f3"));
+    }
+
+    @Test
+    public void multiLevelNamedSubAll() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("a"), ni("d/*/*")));
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a").child("c");
+        nb.child("b");
+        nb.child("d").child("e").child("f");
+        nb.child("d").child("e").child("f2");
+        nb.child("d").child("e2").child("f3");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(4, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a", "d/e/f", "d/e/f2", "d/e2/f3"));
+    }
+
+    //~---------------------------------< Node include recursive >
+
+    @Test
+    public void multiAggregateMapping() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni("*")));
+
+        Aggregate agFile = new Aggregate("nt:file", of(ni("*"), ni("*/*")));
+        mapper.add("nt:file", agFile);
+
+        NodeBuilder nb = newNode("nt:base");
+        nb.child("a").child("c");
+        nb.child("b").setProperty(JCR_PRIMARYTYPE, "nt:file");
+        nb.child("b").child("b1").child("b2");
+        nb.child("c");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(5, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a", "b", "c", "b/b1", "b/b1/b2"));
+    }
+
+    @Test
+    public void recursionEnabled() throws Exception{
+        Aggregate agFile = new Aggregate("nt:file", of(ni("*")), 5);
+        mapper.add("nt:file", agFile);
+
+        NodeBuilder nb = newNode("nt:file");
+        nb.child("a").child("c");
+        nb.child("b").setProperty(JCR_PRIMARYTYPE, "nt:file");
+        nb.child("b").child("b1").child("b2");
+        nb.child("c");
+
+        agFile.collectAggregates(nb.getNodeState(), col);
+        assertEquals(4, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("a", "b", "c", "b/b1"));
+    }
+
+    @Test
+    public void recursionEnabledWithLimitCheck() throws Exception{
+        int limit = 5;
+        Aggregate agFile = new Aggregate("nt:file", of(ni("*")), limit);
+        mapper.add("nt:file", agFile);
+
+        List<String> expectedPaths = Lists.newArrayList();
+        NodeBuilder nb = newNode("nt:file");
+        nb.child("a").child("c");
+
+        String path = "";
+        NodeBuilder fb = nb;
+        for (int i = 0; i < limit + 2; i++){
+            String name = "f "+ i;
+            path = PathUtils.concat(path, name);
+            fb = fb.child(name);
+            fb.setProperty(JCR_PRIMARYTYPE, "nt:file");
+
+            if (i < limit) {
+                expectedPaths.add(path);
+            }
+        }
+        expectedPaths.add("a");
+
+        agFile.collectAggregates(nb.getNodeState(), col);
+        assertEquals(expectedPaths.size(), col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems(toArray(expectedPaths, String.class)));
+    }
+
+    @Test
+    public void includeMatches() throws Exception{
+        Aggregate ag = new Aggregate("nt:base", of(ni(null, "*", true), ni(null, "*/*", true)));
+        assertTrue(ag.hasRelativeNodeInclude("foo"));
+        assertTrue(ag.hasRelativeNodeInclude("foo/bar"));
+        assertFalse(ag.hasRelativeNodeInclude("foo/bar/baz"));
+
+
+        Aggregate ag2 = new Aggregate("nt:base", of(ni(null, "foo", true), ni(null, "foo/*", true)));
+        assertTrue(ag2.hasRelativeNodeInclude("foo"));
+        assertFalse(ag2.hasRelativeNodeInclude("bar"));
+        assertTrue(ag2.hasRelativeNodeInclude("foo/bar"));
+        assertFalse(ag2.hasRelativeNodeInclude("foo/bar/baz"));
+    }
+
+    @Test
+    public void testReaggregate() throws Exception{
+        //Enable relative include for all child nodes of nt:folder
+        //So indexing would create fulltext field for each relative nodes
+        Aggregate agFolder = new Aggregate("nt:folder", of(ni("nt:file", "*", true)));
+
+        Aggregate agFile = new Aggregate("nt:file", of(ni(null, "jcr:content", true)));
+        mapper.add("nt:file", agFile);
+        mapper.add("nt:folder", agFolder);
+
+        NodeBuilder nb = newNode("nt:folder");
+        nb.child("a").child("c");
+        createFile(nb, "b", "hello world");
+        createFile(nb, "c", "hello world");
+
+        agFolder.collectAggregates(nb.getNodeState(), col);
+        assertEquals(4, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("b", "c", "b/jcr:content", "c/jcr:content"));
+
+        assertEquals(2, col.nodeResults.get("b/jcr:content").size());
+
+        //Check that a result is provided for relative node 'b'. Actual node provided
+        //is b/jcr:content
+        assertEquals(1, col.getRelativeNodeResults("b/jcr:content", "b").size());
+    }
+
+    @Test
+    public void testReaggregateMixin() throws Exception{
+        //A variant of testReaggregation but using mixin
+        //instead of normal nodetype. It abuses mix:title
+        //and treat it like nt:file. Test check if reaggregation
+        //works for mixins also
+
+        //Enable relative include for all child nodes of nt:folder
+        //So indexing would create fulltext field for each relative nodes
+        Aggregate agFolder = new Aggregate("nt:folder", of(ni("mix:title", "*", true)));
+
+        Aggregate agFile = new Aggregate("mix:title", of(ni(null, "jcr:content", true)));
+        mapper.add("mix:title", agFile);
+        mapper.add("nt:folder", agFolder);
+
+        NodeBuilder nb = newNode("nt:folder");
+        nb.child("a").child("c");
+        createFileMixin(nb, "b", "hello world");
+        createFileMixin(nb, "c", "hello world");
+
+        agFolder.collectAggregates(nb.getNodeState(), col);
+        assertEquals(4, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("b", "c", "b/jcr:content", "c/jcr:content"));
+
+        assertEquals(2, col.nodeResults.get("b/jcr:content").size());
+
+        //Check that a result is provided for relative node 'b'. Actual node provided
+        //is b/jcr:content
+        assertEquals(1, col.getRelativeNodeResults("b/jcr:content", "b").size());
+    }
+
+    @Test
+    public void testRelativeNodeInclude() throws Exception{
+        //Enable relative include for all child nodes of nt:folder
+        //So indexing would create fulltext field for each relative nodes
+        Aggregate agContent = new Aggregate("app:Page", of(ni(null, "jcr:content", true)));
+
+        mapper.add("app:Page", agContent);
+
+        NodeBuilder nb = newNode("app:Page");
+        nb.child("jcr:content").setProperty("foo", "bar");
+
+        agContent.collectAggregates(nb.getNodeState(), col);
+        assertEquals(1, col.getNodePaths().size());
+        assertThat(col.getNodePaths(), hasItems("jcr:content"));
+
+        assertEquals(2, col.nodeResults.get("jcr:content").size());
+
+        //Check that a result is provided for relative node 'b'. Actual node provided
+        //is b/jcr:content
+        assertEquals(1, col.getRelativeNodeResults("jcr:content", "jcr:content").size());
+    }
+
+    private static void createFile(NodeBuilder nb, String fileName, String content){
+        nb.child(fileName).setProperty(JCR_PRIMARYTYPE, "nt:file")
+                .child("jcr:content").setProperty("jcr:data", content.getBytes());
+    }
+
+    private static void createFileMixin(NodeBuilder nb, String fileName, String content){
+        //Abusing mix:title as its registered by default
+        nb.child(fileName).setProperty(JCR_MIXINTYPES, Collections.singleton("mix:title"), Type.NAMES)
+                .child("jcr:content").setProperty("jcr:data", content.getBytes());
+    }
+
+    //~---------------------------------< Prop Includes >
+
+    @Test
+    public void propOneLevelNamed() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        child(rules, "nt:folder/properties/p1")
+                .setProperty(FulltextIndexConstants.PROP_NAME, "a/p1");
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        Aggregate ag = defn.getApplicableIndexingRule("nt:folder").getAggregate();
+
+        NodeBuilder nb = newNode("nt:folder");
+        nb.child("a").setProperty("p1", "foo");
+        nb.child("a").setProperty("p2", "foo");
+        nb.child("b").setProperty("p2", "foo");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(1, col.getPropPaths().size());
+        assertThat(col.getPropPaths(), hasItems("a/p1"));
+    }
+
+    @Test
+    public void propOneLevelRegex() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        child(rules, "nt:folder/properties/p1")
+                .setProperty(FulltextIndexConstants.PROP_NAME, "a/foo.*")
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        Aggregate ag = defn.getApplicableIndexingRule("nt:folder").getAggregate();
+
+        NodeBuilder nb = newNode("nt:folder");
+        nb.child("a").setProperty("foo1", "foo");
+        nb.child("a").setProperty("foo2", "foo");
+        nb.child("a").setProperty("bar1", "foo");
+        nb.child("b").setProperty("p2", "foo");
+
+        ag.collectAggregates(nb.getNodeState(), col);
+        assertEquals(2, col.getPropPaths().size());
+        assertThat(col.getPropPaths(), hasItems("a/foo1", "a/foo2"));
+    }
+
+    //~---------------------------------< IndexingConfig >
+
+    @Test
+    public void simpleAggregateConfig() throws Exception{
+        NodeBuilder aggregates = builder.child(FulltextIndexConstants.AGGREGATES);
+        NodeBuilder aggFolder = aggregates.child("nt:folder");
+        aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PATH, "*");
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        Aggregate agg = defn.getAggregate("nt:folder");
+        assertNotNull(agg);
+        assertEquals(1, agg.getIncludes().size());
+    }
+
+    @Test
+    public void aggregateConfig2() throws Exception{
+        NodeBuilder aggregates = builder.child(FulltextIndexConstants.AGGREGATES);
+        NodeBuilder aggFolder = aggregates.child("nt:folder");
+        aggFolder.setProperty(FulltextIndexConstants.AGG_RECURSIVE_LIMIT, 42);
+        aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PATH, "*");
+        aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PRIMARY_TYPE, "nt:file");
+        aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_RELATIVE_NODE, true);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        Aggregate agg = defn.getAggregate("nt:folder");
+        assertNotNull(agg);
+        assertEquals(42, agg.reAggregationLimit);
+        assertEquals(1, agg.getIncludes().size());
+        assertEquals("nt:file", ((NodeInclude)agg.getIncludes().get(0)).primaryType);
+        assertTrue(((NodeInclude)agg.getIncludes().get(0)).relativeNode);
+    }
+
+    private static NodeBuilder newNode(String typeName){
+        NodeBuilder builder = EMPTY_NODE.builder();
+        builder.setProperty(JCR_PRIMARYTYPE, typeName);
+        return builder;
+    }
+
+    private static NodeBuilder child(NodeBuilder nb, String path) {
+        for (String name : PathUtils.elements(checkNotNull(path))) {
+            nb = nb.child(name);
+        }
+        return nb;
+    }
+
+    private Aggregate.Include ni(String pattern){
+        return new NodeInclude(mapper, pattern);
+    }
+
+    private Aggregate.Include ni(String type, String pattern, boolean relativeNode){
+        return new NodeInclude(mapper, type, pattern, relativeNode);
+    }
+
+    private static class TestCollector implements Aggregate.ResultCollector {
+        final ListMultimap<String, NodeIncludeResult> nodeResults = ArrayListMultimap.create();
+        final Map<String, PropertyIncludeResult> propResults = newHashMap();
+        @Override
+        public void onResult(NodeIncludeResult result) {
+            nodeResults.put(result.nodePath, result);
+        }
+
+        @Override
+        public void onResult(PropertyIncludeResult result) {
+            propResults.put(result.propertyPath, result);
+
+        }
+
+        public Collection<String> getNodePaths(){
+            return nodeResults.keySet();
+        }
+
+        public Collection<String> getPropPaths(){
+            return propResults.keySet();
+        }
+
+        public void reset(){
+            nodeResults.clear();
+            propResults.clear();
+        }
+
+        public List<NodeIncludeResult> getRelativeNodeResults(String path, String rootIncludePath){
+            List<NodeIncludeResult> result = Lists.newArrayList();
+
+            for (NodeIncludeResult nr : nodeResults.get(path)){
+                if (rootIncludePath.equals(nr.rootIncludePath)){
+                    result.add(nr);
+                }
+            }
+
+            return result;
+        }
+    }
+
+    private static class SimpleMapper implements Aggregate.AggregateMapper {
+        final Map<String, Aggregate> mapping = newHashMap();
+
+        @Override
+        public Aggregate getAggregate(String nodeTypeName) {
+            return mapping.get(nodeTypeName);
+        }
+
+        public void add(String type, Aggregate agg){
+            mapping.put(type, agg);
+        }
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java?rev=1828972&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java Thu Apr 12 11:46:17 2018
@@ -0,0 +1,1091 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.jcr.PropertyType;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.IndexingRule;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.IndexingMode;
+import org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Test;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static javax.jcr.PropertyType.TYPENAME_LONG;
+import static javax.jcr.PropertyType.TYPENAME_STRING;
+import static org.apache.jackrabbit.JcrConstants.NT_BASE;
+import static org.apache.jackrabbit.oak.InitialContent.INITIAL_CONTENT;
+import static org.apache.jackrabbit.oak.api.Type.NAMES;
+import static org.apache.jackrabbit.oak.api.Type.STRINGS;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_NAMES;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_TYPES;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INDEX_RULES;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_NODE;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.TIKA;
+import static org.apache.jackrabbit.oak.plugins.index.search.TestUtil.registerTestNodeType;
+import static org.apache.jackrabbit.oak.plugins.index.search.util.IndexHelper.newFTIndexDefinition;
+import static org.apache.jackrabbit.oak.plugins.index.search.util.IndexHelper.newFTPropertyIndexDefinition;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
+import static org.apache.jackrabbit.oak.plugins.tree.TreeConstants.OAK_CHILD_ORDER;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class IndexDefinitionTest {
+
+    private NodeState root = INITIAL_CONTENT;
+
+    private NodeBuilder builder = root.builder();
+
+    @Test
+    public void defaultConfig() throws Exception{
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        assertFalse(idxDefn.hasSyncPropertyDefinitions());
+    }
+
+    @Test
+    public void fullTextEnabled() throws Exception{
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        IndexDefinition.IndexingRule rule = idxDefn.getApplicableIndexingRule(NT_BASE);
+        assertTrue("By default fulltext is enabled", idxDefn.isFullTextEnabled());
+        assertTrue("By default everything is indexed", rule.isIndexed("foo"));
+        assertTrue("Property types need to be defined", rule.includePropertyType(PropertyType.DATE));
+        assertTrue("For fulltext storage is enabled", rule.getConfig("foo").stored);
+
+        assertFalse(rule.getConfig("foo").skipTokenization("foo"));
+        assertTrue(rule.getConfig("jcr:uuid").skipTokenization("jcr:uuid"));
+    }
+
+    @Test
+    public void propertyTypes() throws Exception{
+        builder.setProperty(createProperty(INCLUDE_PROPERTY_TYPES, of(TYPENAME_LONG), STRINGS));
+        builder.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo" , "bar"), STRINGS));
+        builder.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false);
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        IndexDefinition.IndexingRule rule = idxDefn.getApplicableIndexingRule(NT_BASE);
+        assertFalse(idxDefn.isFullTextEnabled());
+        assertFalse("If fulltext disabled then nothing stored", rule.getConfig("foo").stored);
+
+        assertTrue(rule.includePropertyType(PropertyType.LONG));
+        assertFalse(rule.includePropertyType(PropertyType.STRING));
+
+        assertTrue(rule.isIndexed("foo"));
+        assertTrue(rule.isIndexed("bar"));
+        assertFalse(rule.isIndexed("baz"));
+
+        assertTrue(rule.getConfig("foo").skipTokenization("foo"));
+    }
+
+    @Test
+    public void propertyDefinition() throws Exception{
+        builder.child(PROP_NODE).child("foo").setProperty(FulltextIndexConstants.PROP_TYPE, PropertyType.TYPENAME_DATE);
+        builder.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo" , "bar"), STRINGS));
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        IndexDefinition.IndexingRule rule = idxDefn.getApplicableIndexingRule(NT_BASE);
+
+        assertTrue(rule.isIndexed("foo"));
+        assertTrue(rule.isIndexed("bar"));
+
+        assertEquals(PropertyType.DATE, rule.getConfig("foo").getType());
+    }
+
+    @Test
+    public void propertyDefinitionWithExcludes() throws Exception{
+        builder.child(PROP_NODE).child("foo").setProperty(FulltextIndexConstants.PROP_TYPE, PropertyType.TYPENAME_DATE);
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        IndexDefinition.IndexingRule rule = idxDefn.getApplicableIndexingRule(NT_BASE);
+        assertTrue(rule.isIndexed("foo"));
+        assertTrue(rule.isIndexed("bar"));
+
+        assertEquals(PropertyType.DATE, rule.getConfig("foo").getType());
+    }
+
+    @Test
+    public void indexRuleSanity() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder").setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0);
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0)
+                .setProperty(FulltextIndexConstants.PROP_TYPE, PropertyType.TYPENAME_BOOLEAN);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:base"))));
+
+        IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule1);
+        assertEquals(2.0f, rule1.boost, 0);
+
+        assertTrue(rule1.isIndexed("prop1"));
+        assertFalse(rule1.isIndexed("prop2"));
+
+        PropertyDefinition pd = rule1.getConfig("prop1");
+        assertEquals(3.0f, pd.boost, 0);
+        assertEquals(PropertyType.BOOLEAN, pd.getType());
+    }
+
+    @Test
+    public void indexRuleInheritance() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        builder.setProperty(PROP_NAME, "testIndex");
+        rules.child("nt:hierarchyNode").setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:base"))));
+        assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:hierarchyNode"))));
+        assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder"))));
+    }
+
+    @Test
+    public void indexRuleMixin() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("mix:title");
+        TestUtil.child(rules, "mix:title/properties/jcr:title")
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder", "mix:title"))));
+        assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder"))));
+    }
+
+    @Test
+    public void indexRuleMixinInheritance() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("mix:mimeType");
+        TestUtil.child(rules, "mix:mimeType/properties/jcr:mimeType")
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder", "mix:mimeType"))));
+        assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder"))));
+
+        //nt:resource > mix:mimeType
+        assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:resource"))));
+    }
+
+    @Test
+    public void indexRuleInheritanceDisabled() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        builder.setProperty(PROP_NAME, "testIndex");
+        rules.child("nt:hierarchyNode")
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0)
+                .setProperty(FulltextIndexConstants.RULE_INHERITED, false);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:base"))));
+        assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:hierarchyNode"))));
+        assertNull("nt:folder should not be index as rule is not inheritable",
+                defn.getApplicableIndexingRule(asState(newNode("nt:folder"))));
+    }
+
+    @Test
+    public void indexRuleInheritanceOrdering() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.setProperty(OAK_CHILD_ORDER, ImmutableList.of("nt:hierarchyNode", "nt:base"),NAMES);
+        rules.child("nt:hierarchyNode").setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0);
+        rules.child("nt:base").setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        assertEquals(3.0, getRule(defn, "nt:base").boost, 0);
+        assertEquals(2.0, getRule(defn, "nt:hierarchyNode").boost, 0);
+        assertEquals(3.0, getRule(defn, "nt:query").boost, 0);
+    }
+    @Test
+    public void indexRuleInheritanceOrdering2() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.setProperty(OAK_CHILD_ORDER, ImmutableList.of("nt:base", "nt:hierarchyNode"),NAMES);
+        rules.child("nt:hierarchyNode").setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0);
+        rules.child("nt:base").setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        //As nt:base is defined earlier it would supercede everything
+        assertEquals(3.0, getRule(defn, "nt:base").boost, 0);
+        assertEquals(3.0, getRule(defn, "nt:hierarchyNode").boost, 0);
+        assertEquals(3.0, getRule(defn, "nt:file").boost, 0);
+    }
+
+    @Test
+    public void indexRuleWithPropertyRegEx() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0);
+        TestUtil.child(rules, "nt:folder/properties/prop2")
+                .setProperty(PROP_NAME, "foo.*")
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true)
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 4.0);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule1);
+
+        assertTrue(rule1.isIndexed("prop1"));
+        assertFalse(rule1.isIndexed("prop2"));
+        assertTrue(rule1.isIndexed("fooProp"));
+
+        PropertyDefinition pd = rule1.getConfig("fooProp2");
+        assertEquals(4.0f, pd.boost, 0);
+    }
+
+    @Test
+    public void indexRuleWithPropertyRegEx2() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(PROP_NAME, ".*")
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true);
+        TestUtil.child(rules, "nt:folder/properties/prop2")
+                .setProperty(PROP_NAME, "metadata/.*")
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true)
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 4.0);
+
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule1);
+
+        assertTrue(rule1.isIndexed("prop1"));
+        assertTrue(rule1.isIndexed("prop2"));
+        assertFalse(rule1.isIndexed("jcr:content/prop1"));
+
+        assertTrue(rule1.isIndexed("metadata/foo"));
+        assertFalse(rule1.isIndexed("metadata/foo/bar"));
+    }
+
+    @Test
+    public void indexRuleWithPropertyOrdering() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(PROP_NAME, "foo.*")
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true)
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0);
+        TestUtil.child(rules, "nt:folder/properties/prop2")
+                .setProperty(PROP_NAME, ".*")
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true)
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 4.0);
+
+        rules.child("nt:folder").child(PROP_NODE).setProperty(OAK_CHILD_ORDER, ImmutableList.of("prop2", "prop1"), NAMES);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule1);
+
+        assertTrue(rule1.isIndexed("prop1"));
+        assertTrue(rule1.isIndexed("fooProp"));
+
+        assertEquals(4.0f, rule1.getConfig("bazProp2").boost, 0);
+        //As prop2 is ordered before prop1 its regEx is evaluated first
+        //hence even with a specific regex of foo.* the defn used is from .*
+        assertEquals(4.0f, rule1.getConfig("fooProp").boost, 0);
+
+        //Order it correctly to get expected result
+        rules.child("nt:folder").child(PROP_NODE).setProperty(OAK_CHILD_ORDER, ImmutableList.of("prop1", "prop2"), NAMES);
+        defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertEquals(3.0f, rule1.getConfig("fooProp").boost, 0);
+    }
+
+    @Test
+    public void propertyConfigCaseInsensitive() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/foo")
+                .setProperty(PROP_NAME, "Foo")
+                .setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true);
+        TestUtil.child(rules, "nt:folder/properties/bar")
+                .setProperty(PROP_NAME, "BAR")
+                .setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule1);
+
+        assertTrue(rule1.isIndexed("Foo"));
+        assertTrue(rule1.isIndexed("foo"));
+        assertTrue(rule1.isIndexed("fOO"));
+        assertTrue(rule1.isIndexed("bar"));
+        assertFalse(rule1.isIndexed("baz"));
+    }
+
+    @Test
+    public void skipTokenization() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/prop2")
+                .setProperty(PROP_NAME, ".*")
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true)
+                .setProperty(FulltextIndexConstants.PROP_ANALYZED, true);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertFalse(rule.getConfig("foo").skipTokenization("foo"));
+        assertTrue(rule.getConfig(JcrConstants.JCR_UUID).skipTokenization(JcrConstants.JCR_UUID));
+    }
+
+    @Test
+    public void formatUpdate() throws Exception{
+        NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo",
+                "lucene", of(TYPENAME_STRING), of("foo", "Bar"), "async");
+        IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertTrue(defn.isOfOldFormat());
+
+        NodeBuilder updated = IndexDefinition.updateDefinition(defnb.getNodeState().builder());
+        IndexDefinition defn2 = new IndexDefinition(root, updated.getNodeState(), "/foo");
+
+        assertFalse(defn2.isOfOldFormat());
+        IndexingRule rule = defn2.getApplicableIndexingRule(asState(newNode("nt:base")));
+        assertNotNull(rule);
+        assertFalse(rule.getConfig("foo").index);
+        assertFalse(rule.getConfig("Bar").index);
+    }
+
+    @Test
+    public void propertyRegExAndRelativeProperty() throws Exception{
+        NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo",
+                "lucene", of(TYPENAME_STRING), of("foo"), "async");
+        IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertTrue(defn.isOfOldFormat());
+
+        NodeBuilder updated = IndexDefinition.updateDefinition(defnb.getNodeState().builder());
+        IndexDefinition defn2 = new IndexDefinition(root, updated.getNodeState(), "/foo");
+
+        IndexingRule rule = defn2.getApplicableIndexingRule(asState(newNode("nt:base")));
+        assertNotNull(rule.getConfig("foo"));
+        assertNull("Property regex used should not allow relative properties", rule.getConfig("foo/bar"));
+    }
+
+    @Test
+    public void fulltextEnabledAndAggregate() throws Exception{
+        NodeBuilder defnb = newFTPropertyIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo",
+                "lucene", of("foo"), "async");
+        IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertFalse(defn.isFullTextEnabled());
+
+        NodeBuilder aggregates = defnb.child(FulltextIndexConstants.AGGREGATES);
+        NodeBuilder aggFolder = aggregates.child("nt:base");
+        aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PATH, "*");
+
+        defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertTrue(defn.isFullTextEnabled());
+    }
+
+    @Test
+    public void costConfig() throws Exception {
+        NodeBuilder defnb = newFTPropertyIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo",
+                "lucene", of("foo"), "async");
+        IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertEquals(1.0, defn.getCostPerEntry(), 0);
+        assertEquals(1.0, defn.getCostPerExecution(), 0);
+        assertEquals(IndexDefinition.DEFAULT_ENTRY_COUNT, defn.getEntryCount());
+        assertFalse(defn.isEntryCountDefined());
+
+        defnb.setProperty(FulltextIndexConstants.COST_PER_ENTRY, 2.0);
+        defnb.setProperty(FulltextIndexConstants.COST_PER_EXECUTION, 3.0);
+        defnb.setProperty(IndexConstants.ENTRY_COUNT_PROPERTY_NAME, 500);
+
+        IndexDefinition defn2 = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertEquals(2.0, defn2.getCostPerEntry(), 0);
+        assertEquals(3.0, defn2.getCostPerExecution(), 0);
+        assertEquals(500, defn2.getEntryCount());
+    }
+
+    @Test
+    public void fulltextCost() throws Exception{
+        NodeBuilder defnb = newFTPropertyIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo",
+                "lucene", of("foo"), "async");
+        IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertEquals(300, defn.getFulltextEntryCount(300));
+        assertEquals(IndexDefinition.DEFAULT_ENTRY_COUNT + 100,
+                defn.getFulltextEntryCount(IndexDefinition.DEFAULT_ENTRY_COUNT + 100));
+
+        //Once count is explicitly defined then it would influence the cost
+        defnb.setProperty(IndexConstants.ENTRY_COUNT_PROPERTY_NAME, 100);
+        defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertEquals(100, defn.getFulltextEntryCount(300));
+        assertEquals(50, defn.getFulltextEntryCount(50));
+    }
+
+    @Test
+    public void customTikaConfig() throws Exception{
+        NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo",
+                "lucene", of(TYPENAME_STRING));
+        IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertFalse(defn.hasCustomTikaConfig());
+
+        defnb.child(FulltextIndexConstants.TIKA)
+                .child(FulltextIndexConstants.TIKA_CONFIG)
+                .child(JcrConstants.JCR_CONTENT)
+                .setProperty(JcrConstants.JCR_DATA, "hello".getBytes());
+        defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertTrue(defn.hasCustomTikaConfig());
+    }
+
+    @Test
+    public void customTikaMimeTypes() throws Exception{
+        NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo",
+                "lucene", of(TYPENAME_STRING));
+        IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertEquals("application/test", defn.getTikaMappedMimeType("application/test"));
+
+        NodeBuilder app =defnb.child(FulltextIndexConstants.TIKA)
+                .child(FulltextIndexConstants.TIKA_MIME_TYPES)
+                .child("application");
+        app.child("test").setProperty(FulltextIndexConstants.TIKA_MAPPED_TYPE, "text/plain");
+        app.child("test2").setProperty(FulltextIndexConstants.TIKA_MAPPED_TYPE, "text/plain");
+        defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertEquals("text/plain", defn.getTikaMappedMimeType("application/test"));
+        assertEquals("text/plain", defn.getTikaMappedMimeType("application/test2"));
+        assertEquals("application/test-unmapped", defn.getTikaMappedMimeType("application/test-unmapped"));
+    }
+
+    @Test
+    public void maxExtractLength() throws Exception{
+        NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo",
+                "lucene", of(TYPENAME_STRING));
+        IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertEquals(-IndexDefinition.DEFAULT_MAX_EXTRACT_LENGTH * IndexDefinition.DEFAULT_MAX_FIELD_LENGTH,
+                defn.getMaxExtractLength());
+
+
+        defnb.child(TIKA).setProperty(FulltextIndexConstants.TIKA_MAX_EXTRACT_LENGTH, 1000);
+
+        defn = new IndexDefinition(root, defnb.getNodeState(), "/foo");
+        assertEquals(1000, defn.getMaxExtractLength());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void nullCheckEnabledWithNtBase() throws Exception{
+        builder.child(PROP_NODE).child("foo").setProperty(FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, true);
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void nullCheckEnabledWithRegex() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child(TestUtil.NT_TEST);
+        TestUtil.child(rules, "oak:TestNode/properties/prop2")
+                .setProperty(PROP_NAME, ".*")
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true)
+                .setProperty(FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, true);
+        root = registerTestNodeType(builder).getNodeState();
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+    }
+
+    @Test
+    public void nullCheckEnabledWithTestNode() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        TestUtil.child(rules, "oak:TestNode/properties/prop2")
+                .setProperty(PROP_NAME, "foo")
+                .setProperty(FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, true);
+        root = registerTestNodeType(builder).getNodeState();
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        assertTrue(!idxDefn.getApplicableIndexingRule(TestUtil.NT_TEST).getNullCheckEnabledProperties().isEmpty());
+    }
+
+    @Test
+    public void notNullCheckEnabledWithTestNode() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        TestUtil.child(rules, "oak:TestNode/properties/prop2")
+                .setProperty(PROP_NAME, "foo")
+                .setProperty(FulltextIndexConstants.PROP_NOT_NULL_CHECK_ENABLED, true);
+        root = registerTestNodeType(builder).getNodeState();
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        assertTrue(!idxDefn.getApplicableIndexingRule(TestUtil.NT_TEST).getNotNullCheckEnabledProperties().isEmpty());
+    }
+
+    //OAK-2477
+    @Test
+    public void testSuggestFrequency() throws Exception {
+        int suggestFreq = 40;
+        //default config
+        NodeBuilder indexRoot = builder;
+        IndexDefinition idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo");
+        assertEquals("Default config", 10, idxDefn.getSuggesterUpdateFrequencyMinutes());
+
+        //namespaced config shadows old method
+        indexRoot = builder.child("shadowConfigRoot");
+        indexRoot.setProperty(FulltextIndexConstants.SUGGEST_UPDATE_FREQUENCY_MINUTES, suggestFreq);
+        indexRoot.child(FulltextIndexConstants.SUGGESTION_CONFIG);
+        idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo");
+        assertEquals("Namespaced config node should shadow global config",
+                10, idxDefn.getSuggesterUpdateFrequencyMinutes());
+
+        //config for backward config
+        indexRoot = builder.child("backwardCompatibilityRoot");
+        indexRoot.setProperty(FulltextIndexConstants.SUGGEST_UPDATE_FREQUENCY_MINUTES, suggestFreq);
+        idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo");
+        assertEquals("Backward compatibility config", suggestFreq, idxDefn.getSuggesterUpdateFrequencyMinutes());
+
+        indexRoot = builder.child("indexRoot");
+        indexRoot.child(FulltextIndexConstants.SUGGESTION_CONFIG)
+                .setProperty(FulltextIndexConstants.SUGGEST_UPDATE_FREQUENCY_MINUTES, suggestFreq);
+        idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo");
+        assertEquals("Set config", suggestFreq, idxDefn.getSuggesterUpdateFrequencyMinutes());
+    }
+
+    //OAK-2477
+    @Test
+    public void testSuggestAnalyzed() throws Exception {
+        //default config
+        NodeBuilder indexRoot = builder;
+        IndexDefinition idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo");
+        assertFalse("Default config", idxDefn.isSuggestAnalyzed());
+
+        //namespaced config shadows old method
+        indexRoot = builder.child("shadowConfigRoot");
+        indexRoot.setProperty(FulltextIndexConstants.SUGGEST_ANALYZED, true);
+        indexRoot.child(FulltextIndexConstants.SUGGESTION_CONFIG);
+        idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo");
+        assertFalse("Namespaced config node should shadow global config", idxDefn.isSuggestAnalyzed());
+
+        //config for backward config
+        indexRoot = builder.child("backwardCompatibilityRoot");
+        indexRoot.setProperty(FulltextIndexConstants.SUGGEST_ANALYZED, true);
+        idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo");
+        assertTrue("Backward compatibility config", idxDefn.isSuggestAnalyzed());
+
+        indexRoot = builder.child("indexRoot");
+        indexRoot.child(FulltextIndexConstants.SUGGESTION_CONFIG)
+                .setProperty(FulltextIndexConstants.SUGGEST_ANALYZED, true);
+        idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo");
+        assertTrue("Set config", idxDefn.isSuggestAnalyzed());
+    }
+
+    @Test
+    public void testSuggestEnabledOnNamedProp() throws Exception {
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        TestUtil.child(rules, "oak:TestNode/properties/prop2")
+                .setProperty(PROP_NAME, "foo")
+                .setProperty(FulltextIndexConstants.PROP_USE_IN_SUGGEST, true);
+        root = registerTestNodeType(builder).getNodeState();
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        assertTrue(idxDefn.isSuggestEnabled());
+    }
+
+    @Test
+    public void testSuggestEnabledOnRegexProp() throws Exception {
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child(TestUtil.NT_TEST);
+        TestUtil.child(rules, "oak:TestNode/properties/prop2")
+                .setProperty(PROP_NAME, ".*")
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true)
+                .setProperty(FulltextIndexConstants.PROP_USE_IN_SUGGEST, true);
+        root = registerTestNodeType(builder).getNodeState();
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        assertTrue(idxDefn.isSuggestEnabled());
+    }
+
+    @Test
+    public void testSuggestDisabled() throws Exception {
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        TestUtil.child(rules, "oak:TestNode/properties/prop2")
+                .setProperty(PROP_NAME, "foo");
+        root = registerTestNodeType(builder).getNodeState();
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        assertFalse(idxDefn.isSuggestEnabled());
+    }
+
+    @Test
+    public void analyzedEnabledForBoostedField() throws Exception {
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0)
+                .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true);
+        TestUtil.child(rules, "nt:folder/properties/prop2")
+                .setProperty(FulltextIndexConstants.PROP_ANALYZED, true)
+                .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true);
+        TestUtil.child(rules, "nt:folder/properties/prop3")
+                .setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true)
+                .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+
+        IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule1);
+
+        PropertyDefinition pd = rule1.getConfig("prop1");
+        assertEquals(3.0f, pd.boost, 0);
+        assertTrue("Analyzed should be assumed to be true for boosted fields", pd.analyzed);
+        assertFalse(rule1.getConfig("prop3").analyzed);
+
+        assertEquals(2, rule1.getNodeScopeAnalyzedProps().size());
+    }
+
+    @Test
+    public void nodeFullTextIndexed_Regex() throws Exception {
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(PROP_NAME, ".*")
+                .setProperty(FulltextIndexConstants.PROP_ANALYZED, true)
+                .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule);
+        assertFalse(rule.isNodeFullTextIndexed());
+
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true);
+        defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertTrue(rule.isNodeFullTextIndexed());
+        assertTrue(rule.indexesAllNodesOfMatchingType());
+    }
+
+    @Test
+    public void nodeFullTextIndexed_Simple() throws Exception {
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(PROP_NAME, "foo")
+                .setProperty(FulltextIndexConstants.PROP_ANALYZED, true);
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule);
+        assertFalse(rule.isNodeFullTextIndexed());
+
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true);
+        defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertTrue(rule.isNodeFullTextIndexed());
+        assertTrue(rule.indexesAllNodesOfMatchingType());
+    }
+
+    @Test
+    public void nodeFullTextIndexed_Aggregates() throws Exception {
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(PROP_NAME, "foo")
+                .setProperty(FulltextIndexConstants.PROP_ANALYZED, true);
+
+        NodeBuilder aggregates = builder.child(FulltextIndexConstants.AGGREGATES);
+        NodeBuilder aggFolder = aggregates.child("nt:folder");
+        aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PATH, "*");
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule);
+        assertTrue(rule.isNodeFullTextIndexed());
+        assertTrue(rule.indexesAllNodesOfMatchingType());
+    }
+
+    @Test
+    public void nonIndexPropShouldHaveAllOtherConfigDisabled() throws Exception{
+        NodeBuilder rules = builder.child(INDEX_RULES);
+        rules.child("nt:folder");
+        TestUtil.child(rules, "nt:folder/properties/prop1")
+                .setProperty(PROP_NAME, "foo")
+                .setProperty(FulltextIndexConstants.PROP_INDEX, false)
+                .setProperty(FulltextIndexConstants.PROP_USE_IN_SUGGEST, true)
+                .setProperty(FulltextIndexConstants.PROP_USE_IN_SPELLCHECK, true)
+                .setProperty(FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, true)
+                .setProperty(FulltextIndexConstants.PROP_NOT_NULL_CHECK_ENABLED, true)
+                .setProperty(FulltextIndexConstants.PROP_USE_IN_EXCERPT, true)
+                .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true)
+                .setProperty(FulltextIndexConstants.PROP_ORDERED, true)
+                .setProperty(FulltextIndexConstants.PROP_ANALYZED, true);
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder")));
+        assertNotNull(rule);
+
+        PropertyDefinition pd = rule.getConfig("foo");
+        //Assert that all other config is false if the index=false for any property
+        assertFalse(pd.index);
+        assertFalse(pd.nodeScopeIndex);
+        assertFalse(pd.useInSuggest);
+        assertFalse(pd.useInSpellcheck);
+        assertFalse(pd.nullCheckEnabled);
+        assertFalse(pd.notNullCheckEnabled);
+        assertFalse(pd.stored);
+        assertFalse(pd.ordered);
+        assertFalse(pd.analyzed);
+
+    }
+
+    @Test
+    public void costPerEntry() throws Exception{
+        builder.setProperty(FulltextIndexConstants.COMPAT_MODE, 2);
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        assertEquals(1.0, defn.getCostPerEntry(), 0.0);
+    }
+
+    @Test
+    public void sync() throws Exception{
+        TestUtil.enableIndexingMode(builder, IndexingMode.SYNC);
+        IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo");
+        assertTrue(idxDefn.isSyncIndexingEnabled());
+    }
+
+    @Test
+    public void hasPersistedIndex() throws Exception{
+        assertFalse(IndexDefinition.hasPersistedIndex(builder.getNodeState()));
+        builder.child(":status");
+        assertTrue(IndexDefinition.hasPersistedIndex(builder.getNodeState()));
+    }
+
+    @Test
+    public void uniqueIdForFreshIndex() throws Exception{
+        IndexDefinition defn = IndexDefinition.newBuilder(root, builder.getNodeState(), "/foo").build();
+        assertEquals("0", defn.getUniqueId());
+
+        builder.child(":status");
+        defn = IndexDefinition.newBuilder(root, builder.getNodeState(),"/foo").build();
+        assertNull(defn.getUniqueId());
+    }
+
+    @Test
+    public void nodeTypeChange() throws Exception{
+        IndexDefinition defn = IndexDefinition.newBuilder(root, builder.getNodeState(), "/foo").build();
+        NodeBuilder b2 = root.builder();
+        TestUtil.registerNodeType(b2, TestUtil.TEST_NODE_TYPE);
+        NodeState root2 = b2.getNodeState();
+
+
+        NodeBuilder b3 = root.builder();
+        b3.child("x");
+        NodeState root3 = b3.getNodeState();
+
+        assertFalse(defn.hasMatchingNodeTypeReg(root2));
+        assertTrue(defn.hasMatchingNodeTypeReg(root3));
+    }
+
+    @Test
+    public void uniqueIsSync() throws Exception{
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:base").property("foo").unique();
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+        assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").sync);
+        assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").unique);
+        assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").propertyIndex);
+    }
+
+    @Test
+    public void syncIsProperty() throws Exception{
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:base").property("foo").sync();
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+        assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").sync);
+        assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").propertyIndex);
+    }
+
+    @Test
+    public void syncPropertyDefinitions() throws Exception{
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:base").property("foo").sync();
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+        assertTrue(defn.hasSyncPropertyDefinitions());
+    }
+
+    //~----------------------------------< nodetype >
+
+
+    String testNodeTypeDefn = "[oak:TestMixA]\n" +
+            "  mixin\n" +
+            "\n" +
+            "[oak:TestSuperType]\n" +
+            "- * (UNDEFINED) multiple\n" +
+            "\n" +
+            "[oak:TestTypeA] > oak:TestSuperType\n" +
+            "- * (UNDEFINED) multiple\n" +
+            "\n" +
+            "[oak:TestTypeB] > oak:TestSuperType, oak:TestMixA\n" +
+            "- * (UNDEFINED) multiple";
+
+    @Test
+    public void nodeTypeIndexed() throws Exception{
+        TestUtil.registerNodeType(builder, testNodeTypeDefn);
+        root = builder.getNodeState();
+
+
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.nodeTypeIndex();
+        defnb.indexRule("oak:TestSuperType");
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+        assertFalse(defn.hasSyncPropertyDefinitions());
+
+        IndexingRule ruleSuper = getRule(defn, "oak:TestSuperType");
+        assertNotNull(ruleSuper);
+        assertTrue(defn.isPureNodeTypeIndex());
+        assertTrue(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE).propertyIndex);
+        assertTrue(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES).propertyIndex);
+        assertTrue(ruleSuper.indexesAllNodesOfMatchingType());
+
+        assertNotNull(getRule(defn, "oak:TestTypeA"));
+        assertTrue(getRule(defn, "oak:TestTypeA").indexesAllNodesOfMatchingType());
+        assertNotNull(getRule(defn, "oak:TestTypeB"));
+        assertNull(getRule(defn, "oak:TestMixA"));
+    }
+
+    @Test
+    public void nodeTypeIndexedSync() throws Exception{
+        TestUtil.registerNodeType(builder, testNodeTypeDefn);
+        root = builder.getNodeState();
+
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.nodeTypeIndex();
+        defnb.indexRule("oak:TestSuperType").sync();
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+        assertTrue(defn.hasSyncPropertyDefinitions());
+
+        IndexingRule ruleSuper = getRule(defn, "oak:TestSuperType");
+        assertNotNull(ruleSuper);
+        assertTrue(defn.isPureNodeTypeIndex());
+        assertTrue(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE).propertyIndex);
+        assertTrue(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE).sync);
+        assertTrue(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES).propertyIndex);
+        assertTrue(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES).sync);
+        assertTrue(ruleSuper.indexesAllNodesOfMatchingType());
+    }
+
+    @Test
+    public void nodeTypeIndexed_IgnoreOtherProps() throws Exception{
+        TestUtil.registerNodeType(builder, testNodeTypeDefn);
+        root = builder.getNodeState();
+
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.nodeTypeIndex();
+        defnb.indexRule("oak:TestSuperType").sync();
+        defnb.indexRule("oak:TestSuperType").property("foo").propertyIndex();
+
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+
+        IndexingRule ruleSuper = getRule(defn, "oak:TestSuperType");
+        assertNotNull(ruleSuper);
+
+        assertNull(ruleSuper.getConfig("foo"));
+        assertNotNull(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE));
+        assertNotNull(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES));
+    }
+
+    @Test
+    public void nodeTypeIndexed_IgnoreAggregates() throws Exception{
+        TestUtil.registerNodeType(builder, testNodeTypeDefn);
+        root = builder.getNodeState();
+
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.nodeTypeIndex();
+        defnb.indexRule("oak:TestSuperType").sync();
+        defnb.aggregateRule("oak:TestSuperType").include("*");
+
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+
+        IndexingRule ruleSuper = getRule(defn, "oak:TestSuperType");
+        assertNotNull(ruleSuper);
+
+        assertNull(ruleSuper.getConfig("foo"));
+        assertTrue(ruleSuper.getAggregate().getIncludes().isEmpty());
+        assertNotNull(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE));
+        assertNotNull(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES));
+    }
+
+    @Test
+    public void nodeTypeIndex_mixin() throws Exception{
+        TestUtil.registerNodeType(builder, testNodeTypeDefn);
+        root = builder.getNodeState();
+
+
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.nodeTypeIndex();
+        defnb.indexRule("oak:TestMixA");
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+        assertFalse(defn.hasSyncPropertyDefinitions());
+
+
+        assertNotNull(getRule(defn, "oak:TestTypeB"));
+        assertTrue(getRule(defn, "oak:TestTypeB").indexesAllNodesOfMatchingType());
+        assertNotNull(getRule(defn, "oak:TestMixA"));
+        assertTrue(getRule(defn, "oak:TestMixA").indexesAllNodesOfMatchingType());
+
+        assertNull(getRule(defn, "oak:TestTypeA"));
+        assertNull(getRule(defn, "oak:TestSuperType"));
+    }
+
+    @Test
+    public void mixinAndPrimaryType() throws Exception{
+        TestUtil.registerNodeType(builder, testNodeTypeDefn);
+        root = builder.getNodeState();
+
+
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("oak:TestMixA").property(JcrConstants.JCR_PRIMARYTYPE).propertyIndex();
+        defnb.indexRule("oak:TestSuperType").property(JcrConstants.JCR_PRIMARYTYPE).propertyIndex().sync();
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+
+        IndexingRule a = getRule(defn, "oak:TestMixA");
+        assertNotNull(a.getConfig(JcrConstants.JCR_PRIMARYTYPE));
+        assertNotNull(a.getConfig(JcrConstants.JCR_MIXINTYPES));
+        assertFalse(a.getConfig(JcrConstants.JCR_MIXINTYPES).sync);
+
+        IndexingRule b = getRule(defn, "oak:TestSuperType");
+        assertNotNull(b.getConfig(JcrConstants.JCR_PRIMARYTYPE));
+        assertNotNull(b.getConfig(JcrConstants.JCR_MIXINTYPES));
+        assertTrue(b.getConfig(JcrConstants.JCR_PRIMARYTYPE).sync);
+        assertTrue(b.getConfig(JcrConstants.JCR_MIXINTYPES).sync);
+    }
+
+    @Test
+    public void relativeNodeNames_None() {
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:base").property("foo").propertyIndex();
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+        assertTrue(defn.getRelativeNodeNames().isEmpty());
+        assertFalse(defn.indexesRelativeNodes());
+    }
+
+    @Test
+    public void relativeNodeNames_RelativeProp() {
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:base").property("jcr:content/foo").propertyIndex();
+        defnb.indexRule("nt:base").property("bar").propertyIndex();
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+        assertThat(defn.getRelativeNodeNames(), containsInAnyOrder("jcr:content"));
+        assertTrue(defn.indexesRelativeNodes());
+    }
+
+    @Test
+    public void relativeNodeNames_Aggregate() {
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:base").property("jcr:content/foo").propertyIndex();
+        defnb.aggregateRule("nt:base").include("jcr:content/metadata");
+        defnb.aggregateRule("nt:base").include("jcr:content/metadata/type/*");
+        defnb.aggregateRule("nt:base").include("*");
+
+        IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build();
+        assertThat(defn.getRelativeNodeNames(), containsInAnyOrder("jcr:content", "metadata", "type"));
+        assertTrue(defn.indexesRelativeNodes());
+    }
+
+    @Test
+    public void regexAllProps() {
+        IndexDefinitionBuilder builder = new IndexDefinitionBuilder();
+        builder.indexRule("nt:base").property("p");
+        builder.indexRule("nt:base").property("all", FulltextIndexConstants.REGEX_ALL_PROPS, true);
+
+        IndexDefinition def = IndexDefinition.newBuilder(root, builder.build(), "/foo").build();
+        IndexingRule rule = def.getApplicableIndexingRule(root);
+        assertNotNull(rule);
+
+        PropertyDefinition pd = rule.getConfig("p");
+        assertNotNull(pd);
+        assertFalse(pd.isRegexp);
+        assertFalse(pd.relative);
+        assertEquals(0, pd.ancestors.length);
+
+        pd = rule.getConfig("all");
+        assertNotNull(pd);
+        assertTrue(pd.isRegexp);
+        assertFalse(pd.relative);
+        assertEquals(0, pd.ancestors.length);
+
+        assertThat(rule.getAggregate().getIncludes(), is(empty()));
+        assertFalse(rule.getAggregate().hasNodeAggregates());
+        List<Aggregate.Matcher> matchers = rule.getAggregate()
+                .createMatchers(new TestRoot("/"));
+        assertThat(matchers, is(empty()));
+        assertThat(def.getRelativeNodeNames(), is(empty()));
+    }
+
+    //TODO indexesAllNodesOfMatchingType - with nullCheckEnabled
+
+    private static IndexingRule getRule(IndexDefinition defn, String typeName){
+        return defn.getApplicableIndexingRule(asState(newNode(typeName)));
+    }
+
+    private static NodeState asState(NodeBuilder nb){
+        return nb.getNodeState();
+    }
+
+    private static NodeBuilder newNode(String typeName){
+        NodeBuilder builder = EMPTY_NODE.builder();
+        builder.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName, Type.NAME);
+        return builder;
+    }
+
+    private static NodeBuilder newNode(String typeName, String mixins){
+        NodeBuilder builder = EMPTY_NODE.builder();
+        builder.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName);
+        builder.setProperty(JcrConstants.JCR_MIXINTYPES, Collections.singleton(mixins), Type.NAMES);
+        return builder;
+    }
+
+    private static class TestRoot implements Aggregate.AggregateRoot {
+
+        private final String path;
+
+        public TestRoot(String path) {
+            this.path = path;
+        }
+
+        @Override
+        public void markDirty() {
+        }
+
+        @Override
+        public String getPath() {
+            return path;
+        }
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java?rev=1828972&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java Thu Apr 12 11:46:17 2018
@@ -0,0 +1,274 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.IndexingMode;
+import org.apache.jackrabbit.oak.plugins.index.search.util.IndexHelper;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.plugins.memory.ModifiedNodeState;
+import org.apache.jackrabbit.oak.plugins.name.NamespaceEditorProvider;
+import org.apache.jackrabbit.oak.plugins.nodetype.TypeEditorProvider;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.NodeTypeRegistry;
+import org.apache.jackrabbit.oak.plugins.tree.factories.RootFactory;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.CompositeEditorProvider;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
+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 static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableSet.of;
+import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT;
+import static org.apache.jackrabbit.oak.api.Type.STRINGS;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
+
+public class TestUtil {
+    private static final AtomicInteger COUNTER = new AtomicInteger();
+
+    public static final String NT_TEST = "oak:TestNode";
+
+    public static final String TEST_NODE_TYPE = "[oak:TestNode]\n" +
+            " - * (UNDEFINED) multiple\n" +
+            " - * (UNDEFINED)\n" +
+            " + * (nt:base) = oak:TestNode VERSION";
+
+    static void useV2(NodeBuilder idxNb) {
+        if (!IndexFormatVersion.getDefault().isAtLeast(IndexFormatVersion.V2)) {
+            idxNb.setProperty(FulltextIndexConstants.COMPAT_MODE, IndexFormatVersion.V2.getVersion());
+        }
+    }
+
+    static void useV2(Tree idxTree) {
+        if (!IndexFormatVersion.getDefault().isAtLeast(IndexFormatVersion.V2)) {
+            idxTree.setProperty(FulltextIndexConstants.COMPAT_MODE, IndexFormatVersion.V2.getVersion());
+        }
+    }
+
+    public static NodeBuilder newFTIndexDefinitionV2(
+            @Nonnull NodeBuilder index, @Nonnull String name, String type,
+            @Nullable Set<String> propertyTypes) {
+        NodeBuilder nb = IndexHelper.newFTIndexDefinition(index, name, type, propertyTypes, null, null, null);
+        useV2(nb);
+        return nb;
+    }
+
+    public static Tree enableForFullText(Tree props, String propName) {
+        return enableForFullText(props, propName, false);
+    }
+
+    public static Tree enableForFullText(Tree props, String propName,  boolean regex) {
+        Tree prop = props.addChild(unique("prop"));
+        prop.setProperty(FulltextIndexConstants.PROP_NAME, propName);
+        prop.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true);
+        prop.setProperty(FulltextIndexConstants.PROP_IS_REGEX, regex);
+        prop.setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true);
+        prop.setProperty(FulltextIndexConstants.PROP_ANALYZED, true);
+        prop.setProperty(FulltextIndexConstants.PROP_USE_IN_EXCERPT, true);
+        prop.setProperty(FulltextIndexConstants.PROP_USE_IN_SPELLCHECK, true);
+        return prop;
+    }
+    
+    public static Tree enableForOrdered(Tree props, String propName) {
+        Tree prop = enablePropertyIndex(props, propName, false);
+        prop.setProperty("ordered", true);
+        return prop;
+    }
+
+    public static Tree enablePropertyIndex(Tree props, String propName,  boolean regex) {
+        Tree prop = props.addChild(unique("prop"));
+        prop.setProperty(FulltextIndexConstants.PROP_NAME, propName);
+        prop.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true);
+        prop.setProperty(FulltextIndexConstants.PROP_IS_REGEX, regex);
+        prop.setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, false);
+        prop.setProperty(FulltextIndexConstants.PROP_ANALYZED, false);
+        return prop;
+    }
+
+    public static Tree enableFunctionIndex(Tree props, String function) {
+        Tree prop = props.addChild(unique("prop"));
+        prop.setProperty(FulltextIndexConstants.PROP_FUNCTION, function);
+        return prop;
+    }
+
+    public static AggregatorBuilder newNodeAggregator(Tree indexDefn){
+        return new AggregatorBuilder(indexDefn);
+    }
+
+    public static Tree newRulePropTree(Tree indexDefn, String typeName){
+        Tree rules = indexDefn.addChild(FulltextIndexConstants.INDEX_RULES);
+        rules.setOrderableChildren(true);
+        Tree rule = rules.addChild(typeName);
+        Tree props = rule.addChild(FulltextIndexConstants.PROP_NODE);
+        props.setOrderableChildren(true);
+        return props;
+    }
+
+    public static NodeBuilder child(NodeBuilder nb, String path) {
+        for (String name : PathUtils.elements(checkNotNull(path))) {
+            nb = nb.child(name);
+        }
+        return nb;
+    }
+
+    static class AggregatorBuilder {
+        private final Tree aggs;
+
+        private AggregatorBuilder(Tree indexDefn) {
+            this.aggs = indexDefn.addChild(FulltextIndexConstants.AGGREGATES);
+        }
+
+        AggregatorBuilder newRuleWithName(String primaryType,
+                                          List<String> includes){
+            Tree agg = aggs.addChild(primaryType);
+            for (String include : includes){
+                agg.addChild(unique("include")).setProperty(FulltextIndexConstants.AGG_PATH, include);
+            }
+            return this;
+        }
+    }
+
+    static String unique(String name){
+        return name + COUNTER.getAndIncrement();
+    }
+
+    public static NodeBuilder registerTestNodeType(NodeBuilder builder){
+        registerNodeType(builder, TEST_NODE_TYPE);
+        return builder;
+    }
+
+    public static void registerNodeType(NodeBuilder builder, String nodeTypeDefn){
+        //Taken from org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent
+        NodeState base = ModifiedNodeState.squeeze(builder.getNodeState());
+        NodeStore store = new MemoryNodeStore(base);
+        Root root = RootFactory.createSystemRoot(
+                store, new EditorHook(new CompositeEditorProvider(
+                        new NamespaceEditorProvider(),
+                        new TypeEditorProvider())), null, null, null);
+        NodeTypeRegistry.register(root, IOUtils.toInputStream(nodeTypeDefn), "test node types");
+        NodeState target = store.getRoot();
+        target.compareAgainstBaseState(base, new ApplyDiff(builder));
+    }
+
+    public static Tree createNodeWithType(Tree t, String nodeName, String typeName){
+        t = t.addChild(nodeName);
+        t.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName, Type.NAME);
+        return t;
+    }
+
+    public static NodeBuilder createNodeWithType(NodeBuilder builder, String nodeName, String typeName){
+        builder = builder.child(nodeName);
+        builder.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName, Type.NAME);
+        return builder;
+    }
+
+    public static Tree createFileNode(Tree tree, String name, Blob content, String mimeType){
+        Tree fileNode = tree.addChild(name);
+        fileNode.setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE, Type.NAME);
+        Tree jcrContent = fileNode.addChild(JCR_CONTENT);
+        jcrContent.setProperty(JcrConstants.JCR_DATA, content);
+        jcrContent.setProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        return jcrContent;
+    }
+
+    public static Tree createFulltextIndex(Tree index, String name, String type) throws CommitFailedException {
+        Tree def = index.addChild(INDEX_DEFINITIONS_NAME).addChild(name);
+        def.setProperty(JcrConstants.JCR_PRIMARYTYPE,
+                INDEX_DEFINITIONS_NODE_TYPE, Type.NAME);
+        def.setProperty(TYPE_PROPERTY_NAME, type);
+        def.setProperty(REINDEX_PROPERTY_NAME, true);
+        def.setProperty(createProperty(FulltextIndexConstants.INCLUDE_PROPERTY_TYPES,
+                of(PropertyType.TYPENAME_STRING, PropertyType.TYPENAME_BINARY), STRINGS));
+        return index.getChild(INDEX_DEFINITIONS_NAME).getChild(name);
+    }
+
+    public static void shutdown(Repository repository) {
+        if (repository instanceof JackrabbitRepository) {
+            ((JackrabbitRepository) repository).shutdown();
+        }
+    }
+
+    public static NodeBuilder enableIndexingMode(NodeBuilder builder, IndexingMode indexingMode){
+        builder.setProperty(createAsyncProperty(indexingMode));
+        return builder;
+    }
+
+    public static Tree enableIndexingMode(Tree tree, IndexingMode indexingMode){
+        tree.setProperty(createAsyncProperty(indexingMode));
+        return tree;
+    }
+
+    private static PropertyState createAsyncProperty(String indexingMode) {
+        return createProperty(IndexConstants.ASYNC_PROPERTY_NAME, of(indexingMode , "async"), STRINGS);
+    }
+
+    private static PropertyState createAsyncProperty(IndexingMode indexingMode) {
+        switch(indexingMode) {
+            case NRT  :
+            case SYNC :
+                return createAsyncProperty(indexingMode.asyncValueName());
+            case ASYNC:
+                return createProperty(IndexConstants.ASYNC_PROPERTY_NAME, of("async"), STRINGS);
+            default:
+                throw new IllegalArgumentException("Unknown mode " + indexingMode);
+        }
+    }
+
+    public static class OptionalEditorProvider implements EditorProvider {
+        public EditorProvider delegate;
+
+        @Override
+        public Editor getRootEditor(NodeState before, NodeState after, NodeBuilder builder, CommitInfo info) throws CommitFailedException {
+            if (delegate != null){
+                return delegate.getRootEditor(before, after, builder, info);
+            }
+            return null;
+        }
+
+
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native