Author: mkataria
Date: Tue Jul 28 14:53:46 2020
New Revision: 1880375

URL: http://svn.apache.org/viewvc?rev=1880375&view=rev
Log:
OAK-9151: Support term suggestion in Oak ES

Added:
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexSuggestionCommonTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java.orig
   (with props)
    
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSuggestIterator.java
   (with props)
    
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexSuggestionCommonTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexSuggestionCommonTest.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java
    
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
    
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
    
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java

Added: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexSuggestionCommonTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexSuggestionCommonTest.java?rev=1880375&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexSuggestionCommonTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexSuggestionCommonTest.java
 Tue Jul 28 14:53:46 2020
@@ -0,0 +1,46 @@
+/*
+ * 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.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.plugins.index.IndexSuggestionCommonTest;
+import org.apache.jackrabbit.oak.plugins.index.LuceneIndexOptions;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import java.io.File;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class LuceneIndexSuggestionCommonTest extends IndexSuggestionCommonTest 
{
+    private ExecutorService executorService = Executors.newFixedThreadPool(2);
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder(new 
File("target"));
+
+    @Override
+    protected Repository createJcrRepository() throws RepositoryException {
+        indexOptions = new LuceneIndexOptions();
+        repositoryOptionsUtil = new 
LuceneTestRepositoryBuilder(executorService, temporaryFolder).build();
+        Oak oak = repositoryOptionsUtil.getOak();
+        Jcr jcr = new Jcr(oak);
+        Repository repository = jcr.createRepository();
+        return repository;
+    }
+}

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

Modified: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java?rev=1880375&r1=1880374&r2=1880375&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java
 Tue Jul 28 14:53:46 2020
@@ -95,15 +95,15 @@ class ElasticDocument {
                 if (fulltext.size() > 0) {
                     builder.field(FieldNames.FULLTEXT, fulltext);
                 }
-            if (suggest.size() > 0) {
-                builder.startObject(FieldNames.SUGGEST).field("input", 
suggest).endObject();
-            }
-            if (notNullProps.size() > 0) {
-                builder.field(FieldNames.NOT_NULL_PROPS, notNullProps);
-            }
-            if (nullProps.size() > 0) {
-                builder.field(FieldNames.NULL_PROPS, nullProps);
-            }
+                if (suggest.size() > 0) {
+                    
builder.startObject(FieldNames.SUGGEST).field("suggestion", 
suggest).endObject();
+                }
+                if (notNullProps.size() > 0) {
+                    builder.field(FieldNames.NOT_NULL_PROPS, notNullProps);
+                }
+                if (nullProps.size() > 0) {
+                    builder.field(FieldNames.NULL_PROPS, nullProps);
+                }
                 for (Map.Entry<String, Object> prop : properties.entrySet()) {
                     builder.field(prop.getKey(), prop.getValue());
                 }

Modified: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java?rev=1880375&r1=1880374&r2=1880375&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
 Tue Jul 28 14:53:46 2020
@@ -157,13 +157,31 @@ class ElasticIndexHelper {
 
             Type<?> type = null;
             boolean useInSpellCheck = false;
-            for (PropertyDefinition pd: propertyDefinitions) {
+            boolean useInSuggest = false;
+            for (PropertyDefinition pd : propertyDefinitions) {
                 type = Type.fromTag(pd.getType(), false);
                 if (pd.useInSpellcheck) {
                     useInSpellCheck = true;
-                    break;
                 }
+                if (pd.useInSuggest) {
+                    useInSuggest = true;
+                }
+            }
 
+            if (useInSuggest) {
+                mappingBuilder.startObject(FieldNames.SUGGEST);
+                {
+                    mappingBuilder.field("type", "nested");
+                    mappingBuilder.startObject("properties");
+                    {
+                        mappingBuilder.startObject("suggestion")
+                                .field("type", "text")
+                                .field("analyzer", "oak_analyzer")
+                                .endObject();
+                    }
+                    mappingBuilder.endObject();
+                }
+                mappingBuilder.endObject();
             }
 
             mappingBuilder.startObject(name);

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java.orig
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java.orig?rev=1880375&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java.orig
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java.orig
 Tue Jul 28 14:53:46 2020
@@ -0,0 +1,164 @@
+/*
+ * 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.elastic.query;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticConnection;
+import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResultRowAsyncIterator;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexNode;
+import org.apache.jackrabbit.oak.plugins.index.search.SizeEstimator;
+import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
+import 
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner;
+import org.apache.jackrabbit.oak.plugins.index.search.util.LMSEstimator;
+import org.apache.jackrabbit.oak.spi.query.Cursor;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.elasticsearch.common.Strings;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+
+import static 
org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static 
org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition.TYPE_ELASTICSEARCH;
+
+class ElasticIndex extends FulltextIndex {
+    private static final Predicate<NodeState> 
ELASTICSEARCH_INDEX_DEFINITION_PREDICATE =
+            state -> 
TYPE_ELASTICSEARCH.equals(state.getString(TYPE_PROPERTY_NAME));
+    private static final Map<String, LMSEstimator> ESTIMATORS = new 
WeakHashMap<>();
+
+    // no concept of rewound in ES (even if it might be doing it internally, 
we can't do much about it
+    private static final IteratorRewoundStateProvider 
REWOUND_STATE_PROVIDER_NOOP = () -> 0;
+
+    // higher than some threshold below which the query should rather be 
answered by something else if possible
+    private static final double MIN_COST = 100.1;
+
+    private final ElasticConnection elasticConnection;
+    private final NodeState root;
+
+    ElasticIndex(@NotNull ElasticConnection elasticConnection, @NotNull 
NodeState root) {
+        this.elasticConnection = elasticConnection;
+        this.root = root;
+    }
+
+    @Override
+    protected String getType() {
+        return TYPE_ELASTICSEARCH;
+    }
+
+    @Override
+    protected FulltextIndexPlanner getPlanner(IndexNode indexNode, String 
path, Filter filter, List<OrderEntry> sortOrder) {
+        return new ElasticIndexPlanner(indexNode, path, filter, sortOrder);
+    }
+
+    @Override
+    protected SizeEstimator getSizeEstimator(IndexPlan plan) {
+        return () -> 
getEstimator(plan.getPlanName()).estimate(plan.getFilter());
+    }
+
+    @Override
+    protected Predicate<NodeState> getIndexDefinitionPredicate() {
+        return ELASTICSEARCH_INDEX_DEFINITION_PREDICATE;
+    }
+
+    @Override
+    public double getMinimumCost() {
+        return MIN_COST;
+    }
+
+    @Override
+    public String getIndexName() {
+        return "elasticsearch";
+    }
+
+    @Override
+    protected ElasticIndexNode acquireIndexNode(IndexPlan plan) {
+        return (ElasticIndexNode) super.acquireIndexNode(plan);
+    }
+
+    @Override
+    protected IndexNode acquireIndexNode(String indexPath) {
+        return new ElasticIndexNode(root, indexPath, elasticConnection);
+    }
+
+    @Override
+    protected String getFulltextRequestString(IndexPlan plan, IndexNode 
indexNode) {
+        return Strings.toString(new ElasticRequestHandler(plan, 
getPlanResult(plan)).baseQuery());
+    }
+
+    @Override
+    public Cursor query(IndexPlan plan, NodeState rootState) {
+        final Filter filter = plan.getFilter();
+        final FulltextIndexPlanner.PlanResult planResult = getPlanResult(plan);
+
+        final ElasticRequestHandler requestHandler = new 
ElasticRequestHandler(plan, planResult);
+        final ElasticResponseHandler responseHandler = new 
ElasticResponseHandler(planResult, filter);
+
+        final Iterator<FulltextResultRow> itr;
+        if (requestHandler.requiresSpellCheck()) {
+            itr = new ElasticSpellcheckIterator(acquireIndexNode(plan), 
requestHandler, responseHandler);
+        } else {
+            // this function is called for each extracted row. Passing 
FulltextIndex::shouldInclude means that for each
+            // row we evaluate getPathRestriction(plan) & 
plan.getFilter().getPathRestriction(). Providing a partial
+            // function (https://en.wikipedia.org/wiki/Partial_function) we 
can evaluate them once and still use a predicate as before
+            BiFunction<String, Filter.PathRestriction, Predicate<String>> 
partialShouldInclude = (path, pathRestriction) -> docPath ->
+                    shouldInclude(path, pathRestriction, docPath);
+
+            itr = new ElasticResultRowAsyncIterator(
+                    acquireIndexNode(plan),
+                    requestHandler,
+                    responseHandler,
+                    plan,
+                    partialShouldInclude.apply(getPathRestriction(plan), 
filter.getPathRestriction()),
+                    getEstimator(plan.getPlanName())
+            );
+
+        }
+        return new FulltextPathCursor(itr, REWOUND_STATE_PROVIDER_NOOP, plan, 
filter.getQueryLimits(), getSizeEstimator(plan));
+    }
+
+    private LMSEstimator getEstimator(String path) {
+        ESTIMATORS.putIfAbsent(path, new LMSEstimator());
+        return ESTIMATORS.get(path);
+    }
+
+    private static boolean shouldInclude(String path, Filter.PathRestriction 
pathRestriction, String docPath) {
+        boolean include = true;
+        switch (pathRestriction) {
+            case EXACT:
+                include = path.equals(docPath);
+                break;
+            case DIRECT_CHILDREN:
+                include = PathUtils.getParentPath(docPath).equals(path);
+                break;
+            case ALL_CHILDREN:
+                include = PathUtils.isAncestor(path, docPath);
+                break;
+        }
+
+        return include;
+    }
+
+    @Override
+    protected boolean filterReplacedIndexes() {
+        return true;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java.orig
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java?rev=1880375&r1=1880374&r2=1880375&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
 Tue Jul 28 14:53:46 2020
@@ -39,10 +39,14 @@ import org.apache.jackrabbit.oak.spi.que
 import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextTerm;
 import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextVisitor;
 import org.apache.lucene.search.WildcardQuery;
+import org.apache.lucene.search.join.ScoreMode;
 import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.InnerHitBuilder;
+import org.elasticsearch.index.query.MatchBoolPrefixQueryBuilder;
 import org.elasticsearch.index.query.MatchPhraseQueryBuilder;
 import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
 import org.elasticsearch.index.query.MultiMatchQueryBuilder;
+import org.elasticsearch.index.query.NestedQueryBuilder;
 import org.elasticsearch.index.query.Operator;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
@@ -59,6 +63,7 @@ import org.jetbrains.annotations.NotNull
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
 import javax.jcr.PropertyType;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -68,6 +73,7 @@ import java.util.concurrent.atomic.Atomi
 import java.util.function.BiPredicate;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
+
 import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot;
@@ -87,12 +93,13 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.spi.query.QueryConstants.JCR_PATH;
 import static org.apache.jackrabbit.oak.spi.query.QueryConstants.JCR_SCORE;
 import static org.apache.jackrabbit.util.ISO8601.parse;
+import static org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item;
 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
 import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
+import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
 import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
-import static org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item;
 
 /**
  * Class to map query plans into Elastic request objects.
@@ -101,6 +108,7 @@ public class ElasticRequestHandler {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(ElasticRequestHandler.class);
     private final static String SPELLCHECK_PREFIX = "spellcheck?term=";
+    protected final static String SUGGEST_PREFIX = "suggest?term=";
     private static final String ES_TRIGRAM_SUFFIX = ".trigram";
     private static final List<FieldSortBuilder> DEFAULT_SORTS = Arrays.asList(
             SortBuilders.fieldSort("_score").order(SortOrder.DESC),
@@ -210,6 +218,10 @@ public class ElasticRequestHandler {
         return propertyRestrictionQuery != null && 
propertyRestrictionQuery.startsWith(SPELLCHECK_PREFIX);
     }
 
+    public boolean requiresSuggestion() {
+        return propertyRestrictionQuery != null && 
propertyRestrictionQuery.startsWith(SUGGEST_PREFIX);
+    }
+
     public ElasticFacetProvider getAsyncFacetProvider(ElasticResponseHandler 
responseHandler) {
         return requiresFacets() ?
                 ElasticFacetProvider.getProvider(
@@ -530,6 +542,17 @@ public class ElasticRequestHandler {
         return queries;
     }
 
+    public BoolQueryBuilder suggestionMatchQuery(String suggestion) {
+        QueryBuilder qb = new MatchBoolPrefixQueryBuilder(FieldNames.SUGGEST + 
".suggestion", suggestion).operator(Operator.AND);
+        NestedQueryBuilder nestedQueryBuilder = 
nestedQuery(FieldNames.SUGGEST, qb, ScoreMode.Max);
+        InnerHitBuilder in = new InnerHitBuilder().setSize(100);
+        nestedQueryBuilder.innerHit(in);
+        BoolQueryBuilder query = boolQuery()
+                .must(nestedQueryBuilder);
+        nonFullTextConstraints(indexPlan, planResult).forEach(query::must);
+        return query;
+    }
+
     private static QueryBuilder 
nodeTypeConstraints(IndexDefinition.IndexingRule defn, Filter filter) {
         final BoolQueryBuilder bq = boolQuery();
         PropertyDefinition primaryType = defn.getConfig(JCR_PRIMARYTYPE);

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSuggestIterator.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSuggestIterator.java?rev=1880375&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSuggestIterator.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSuggestIterator.java
 Tue Jul 28 14:53:46 2020
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elastic.query;
+
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
+import 
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex.FulltextResultRow;
+import org.elasticsearch.action.search.SearchRequest;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.RequestOptions;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.elasticsearch.search.sort.SortBuilders;
+import org.elasticsearch.search.sort.SortOrder;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.PriorityQueue;
+
+/**
+ * This class is in charge to extract suggestions for a given query. 
Suggestion is more like
+ * a completion result.
+ */
+class ElasticSuggestIterator implements Iterator<FulltextResultRow> {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(ElasticSuggestIterator.class);
+
+    private final ElasticIndexNode indexNode;
+    private final ElasticRequestHandler requestHandler;
+    private final ElasticResponseHandler responseHandler;
+    private final String suggestQuery;
+
+    private Iterator<? extends FulltextResultRow> internalIterator;
+    private boolean loaded;
+
+    ElasticSuggestIterator(@NotNull ElasticIndexNode indexNode,
+                           @NotNull ElasticRequestHandler requestHandler,
+                           @NotNull ElasticResponseHandler responseHandler) {
+        this.indexNode = indexNode;
+        this.requestHandler = requestHandler;
+        this.responseHandler = responseHandler;
+        this.suggestQuery = 
requestHandler.getPropertyRestrictionQuery().replace(ElasticRequestHandler.SUGGEST_PREFIX,
 "");
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (!loaded) {
+            try {
+                loadSuggestions();
+            } catch (IOException e) {
+                LOG.error("Failed loading suggestions", e);
+                throw new RuntimeException(e);
+            }
+            loaded = true;
+        }
+        return internalIterator != null && internalIterator.hasNext();
+    }
+
+    @Override
+    public FulltextResultRow next() {
+        return internalIterator.next();
+    }
+
+    private void loadSuggestions() throws IOException {
+        BoolQueryBuilder suggestionQuery = 
requestHandler.suggestionMatchQuery(suggestQuery);
+        SearchSourceBuilder searchSourceBuilder = 
SearchSourceBuilder.searchSource()
+                .query(suggestionQuery)
+                .size(100)
+                .fetchSource(FieldNames.PATH, null);
+        SearchRequest searchRequest = new 
SearchRequest(indexNode.getDefinition().getRemoteIndexAlias())
+                .source(searchSourceBuilder);
+        SearchResponse res = 
indexNode.getConnection().getClient().search(searchRequest, 
RequestOptions.DEFAULT);
+        PriorityQueue<ElasticSuggestion> suggestionPriorityQueue = new 
PriorityQueue<>((a, b) -> Double.compare(b.score, a.score));
+        for (SearchHit doc : res.getHits()) {
+            if (responseHandler.isAccessible(responseHandler.getPath(doc))) {
+                for (SearchHit suggestion : 
doc.getInnerHits().get(FieldNames.SUGGEST).getHits()) {
+                    suggestionPriorityQueue.add(new 
ElasticSuggestion(((List<String>) 
suggestion.getSourceAsMap().get("suggestion")).get(0), suggestion.getScore()));
+                }
+            }
+        }
+        this.internalIterator = suggestionPriorityQueue.iterator();
+    }
+
+    private final static class ElasticSuggestion extends FulltextResultRow{
+        private ElasticSuggestion(String suggestion, double score) {
+            super(suggestion, score);
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSuggestIterator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexSuggestionCommonTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexSuggestionCommonTest.java?rev=1880375&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexSuggestionCommonTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexSuggestionCommonTest.java
 Tue Jul 28 14:53:46 2020
@@ -0,0 +1,63 @@
+/*
+ * 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.elastic;
+
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.plugins.index.ElasticTestRepositoryBuilder;
+import org.apache.jackrabbit.oak.plugins.index.IndexSuggestionCommonTest;
+import org.apache.jackrabbit.oak.plugins.index.TestUtils;
+import org.junit.After;
+import org.junit.ClassRule;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import java.io.IOException;
+
+public class ElasticIndexSuggestionCommonTest extends 
IndexSuggestionCommonTest {
+
+    // Set this connection string as
+    // <scheme>://<hostname>:<port>?key_id=<>,key_secret=<>
+    // key_id and key_secret are optional in case the ES server
+    // needs authentication
+    // Do not set this if docker is running and you want to run the tests on 
docker instead.
+    private static String elasticConnectionString = 
System.getProperty("elasticConnectionString");
+    @ClassRule
+    public static ElasticConnectionRule elasticRule = new 
ElasticConnectionRule(elasticConnectionString);
+
+    /*
+    Close the ES connection after every test method execution
+     */
+    @After
+    public void cleanup() throws IOException {
+        elasticRule.closeElasticConnection();
+    }
+
+    protected Repository createJcrRepository() throws RepositoryException {
+        indexOptions = new ElasticIndexOptions();
+        repositoryOptionsUtil = new 
ElasticTestRepositoryBuilder(elasticRule).build();
+        Oak oak = repositoryOptionsUtil.getOak();
+        Jcr jcr = new Jcr(oak);
+        Repository repository = jcr.createRepository();
+        return repository;
+    }
+
+    protected void assertEventually(Runnable r) {
+        TestUtils.assertEventually(r,
+                ((repositoryOptionsUtil.isAsync() ? 
repositoryOptionsUtil.defaultAsyncIndexingTimeInSeconds : 0) + 
ElasticIndexDefinition.BULK_FLUSH_INTERVAL_MS_DEFAULT) * 5);
+    }
+}

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

Modified: 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java?rev=1880375&r1=1880374&r2=1880375&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java
 Tue Jul 28 14:53:46 2020
@@ -341,6 +341,10 @@ public abstract class FulltextIndex impl
         }
 
         public FulltextResultRow(String suggestion, long weight) {
+            this(suggestion, (double)weight);
+        }
+
+        public FulltextResultRow(String suggestion, double weight) {
             this.isVirutal = true;
             this.path = "/";
             this.score = weight;

Added: 
jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexSuggestionCommonTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexSuggestionCommonTest.java?rev=1880375&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexSuggestionCommonTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexSuggestionCommonTest.java
 Tue Jul 28 14:53:46 2020
@@ -0,0 +1,369 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import 
org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexFormatVersion;
+import org.apache.jackrabbit.oak.query.AbstractJcrTest;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
+import javax.jcr.security.Privilege;
+
+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.index.search.FulltextIndexConstants.INDEX_RULES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public abstract class IndexSuggestionCommonTest extends AbstractJcrTest {
+    protected TestRepository repositoryOptionsUtil;
+    protected Node indexNode;
+    protected IndexOptions indexOptions;
+
+    private JackrabbitSession session = null;
+    private Node root = null;
+
+    @Before
+    public void settingup() throws RepositoryException {
+        session = (JackrabbitSession) adminSession;
+        root = session.getRootNode();
+    }
+
+    private void createSuggestIndex(String name, String indexedNodeType, 
String indexedPropertyName)
+            throws Exception {
+        createSuggestIndex(name, indexedNodeType, indexedPropertyName, false, 
false);
+    }
+
+    private void createSuggestIndex(String name, String indexedNodeType, 
String indexedPropertyName, boolean addFullText, boolean suggestAnalyzed)
+            throws Exception {
+        Node def = root.getNode(INDEX_DEFINITIONS_NAME)
+                .addNode(name, INDEX_DEFINITIONS_NODE_TYPE);
+        def.setProperty(TYPE_PROPERTY_NAME, indexOptions.getIndexType());
+        def.setProperty(REINDEX_PROPERTY_NAME, true);
+        def.setProperty("name", name);
+        def.setProperty(FulltextIndexConstants.COMPAT_MODE, 
IndexFormatVersion.V2.getVersion());
+        if (suggestAnalyzed) {
+            
def.addNode(FulltextIndexConstants.SUGGESTION_CONFIG).setProperty("suggestAnalyzed",
 suggestAnalyzed);
+        }
+
+
+        Node propertyIdxDef = def.addNode(INDEX_RULES, 
JcrConstants.NT_UNSTRUCTURED)
+                .addNode(indexedNodeType, JcrConstants.NT_UNSTRUCTURED)
+                .addNode(FulltextIndexConstants.PROP_NODE, 
JcrConstants.NT_UNSTRUCTURED)
+                .addNode("indexedProperty", JcrConstants.NT_UNSTRUCTURED);
+        propertyIdxDef.setProperty("propertyIndex", true);
+        propertyIdxDef.setProperty("analyzed", true);
+        propertyIdxDef.setProperty("useInSuggest", true);
+        if (addFullText) {
+            propertyIdxDef.setProperty("nodeScopeIndex", true);
+        }
+        propertyIdxDef.setProperty("name", indexedPropertyName);
+    }
+
+    /**
+     * Utility method to check suggestion over {@code nodeType} when the index 
definition also created for
+     * the same type
+     */
+    private void checkSuggestions(final String nodeType,
+                                  final String indexPropName, final String 
indexPropValue,
+                                  final boolean addFullText, final boolean 
useUserSession,
+                                  final String suggestQueryText, final boolean 
shouldSuggest, final boolean suggestAnalyzed)
+            throws Exception {
+        checkSuggestions(nodeType, nodeType,
+                indexPropName, indexPropValue,
+                addFullText, useUserSession,
+                suggestQueryText, shouldSuggest, suggestAnalyzed);
+    }
+
+    /**
+     * Utility method to check suggestion over {@code queryNodeType} when the 
index definition is created for
+     * {@code indexNodeType}
+     */
+    private void checkSuggestions(final String indexNodeType, final String 
queryNodeType,
+                                  final String indexPropName, final String 
indexPropValue,
+                                  final boolean addFullText, final boolean 
useUserSession,
+                                  final String suggestQueryText, final boolean 
shouldSuggest,
+                                  final boolean suggestAnalyzed)
+            throws Exception {
+        createSuggestIndex("lucene-suggest", indexNodeType, indexPropName, 
addFullText, suggestAnalyzed);
+
+        Node indexedNode = root.addNode("indexedNode1", queryNodeType);
+
+        if (indexPropValue != null) {
+            indexedNode.setProperty(indexPropName, indexPropValue + " 1");
+            indexedNode = root.addNode("indexedNode2", queryNodeType);
+            indexedNode.setProperty(indexPropName, indexPropValue + " 2");
+        }
+
+        session.save();
+
+        Session userSession = session;
+
+        if (useUserSession) {
+            allow(indexedNode);
+            session.save();
+            userSession = anonymousSession;//repository.login(new 
SimpleCredentials(TEST_USER_NAME, TEST_USER_NAME.toCharArray()));
+        }
+
+        String suggQuery = createSuggestQuery(queryNodeType, suggestQueryText);
+        QueryManager queryManager = 
userSession.getWorkspace().getQueryManager();
+        if (shouldSuggest) {
+            assertEventually(() -> {
+                try {
+                    assertNotNull("There should be some suggestion", 
getResult(queryManager, suggQuery));
+                } catch (RepositoryException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+
+        } else {
+            assertEventually(() -> {
+                try {
+                    assertNull("There shouldn't be any suggestion", 
getResult(queryManager, suggQuery));
+                } catch (RepositoryException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+
+        }
+        userSession.logout();
+    }
+
+    private String getResult(QueryManager queryManager, String suggQuery) 
throws RepositoryException {
+
+        QueryResult result = queryManager.createQuery(suggQuery, 
Query.JCR_SQL2).execute();
+        RowIterator rows = result.getRows();
+
+        String value = null;
+        while (rows.hasNext()) {
+            Row firstRow = rows.nextRow();
+            value = firstRow.getValue("suggestion").getString();
+            break;
+        }
+        return value;
+    }
+
+    private Node allow(Node node) throws RepositoryException {
+        AccessControlUtils.allow(node, "anonymous", Privilege.JCR_READ);
+        return node;
+    }
+
+    private String createSuggestQuery(String nodeTypeName, String suggestFor) {
+        return "SELECT [rep:suggest()] as suggestion, [jcr:score] as score  
FROM [" + nodeTypeName + "] WHERE suggest('" + suggestFor + "')";
+    }
+
+    @Test
+    public void suggestNodeName() throws Exception {
+        final String nodeType = "nt:unstructured";
+
+        createSuggestIndex("lucene-suggest", nodeType, 
FulltextIndexConstants.PROPDEF_PROP_NODE_NAME);
+
+        root.addNode("indexedNode", nodeType);
+        adminSession.save();
+
+        String suggQuery = createSuggestQuery(nodeType, "indexedn");
+        QueryManager queryManager = 
adminSession.getWorkspace().getQueryManager();
+        assertEventually(() -> {
+            try {
+                assertEquals("Node name should be suggested", "indexedNode", 
getResult(queryManager, suggQuery));
+            } catch (RepositoryException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    //OAK-3157
+    @Test
+    public void testSuggestQuery() throws Exception {
+        final String nodeType = "nt:unstructured";
+        final String indexPropName = "description";
+        final String indexPropValue = "this is just a sample text which should 
get some response in suggestions";
+        final String suggestQueryText = "th";
+        final boolean shouldSuggest = true;
+
+        checkSuggestions(nodeType,
+                indexPropName, indexPropValue,
+                false, false,
+                suggestQueryText, shouldSuggest, false);
+    }
+
+    //OAK-4126
+    @Test
+    public void testSuggestQuerySpecialChars() throws Exception {
+        final String nodeType = "nt:unstructured";
+        final String indexPropName = "description";
+        final String indexPropValue = "DD~@#$%^&*()_+{}\":?><`1234567890-=[]";
+        final String suggestQueryText = "dd";
+        final boolean shouldSuggest = true;
+
+        checkSuggestions(nodeType,
+                indexPropName, indexPropValue,
+                false, false,
+                suggestQueryText, shouldSuggest, false);
+    }
+
+    @Test
+    public void avoidInfiniteSuggestions() throws Exception {
+        final String nodeType = "nt:unstructured";
+        final String indexPropName = "description";
+        final String higherRankPropValue = "DD DD DD DD";
+        final String exceptionThrowingPropValue = 
"DD~@#$%^&*()_+{}\":?><`1234567890-=[]";
+        final String suggestQueryText = "dd";
+
+        createSuggestIndex("lucene-suggest", nodeType, indexPropName);
+
+        root.addNode("higherRankNode", nodeType).setProperty(indexPropName, 
higherRankPropValue);
+        root.addNode("exceptionThrowingNode", 
nodeType).setProperty(indexPropName, exceptionThrowingPropValue);
+        adminSession.save();
+
+        String suggQuery = createSuggestQuery(nodeType, suggestQueryText);
+        QueryManager queryManager = 
adminSession.getWorkspace().getQueryManager();
+        QueryResult result = queryManager.createQuery(suggQuery, 
Query.JCR_SQL2).execute();
+        RowIterator rows = result.getRows();
+
+        int count = 0;
+        while (count < 3 && rows.hasNext()) {
+            count++;
+            rows.nextRow();
+        }
+
+        assertTrue("There must not be more than 2 suggestions", count <= 2);
+    }
+
+    //OAK-3156
+    @Test
+    public void testSuggestQueryWithUserAccess() throws Exception {
+        final String nodeType = "nt:unstructured";
+        final String indexPropName = "description";
+        final String indexPropValue = "this is just a sample text which should 
get some response in suggestions";
+        final String suggestQueryText = "this is jus";
+        final boolean shouldSuggest = true;
+
+        checkSuggestions(nodeType,
+                indexPropName, indexPropValue,
+                false, true,
+                suggestQueryText, shouldSuggest, false);
+    }
+
+    //OAK-3156
+    @Test
+    public void testSuggestQueryFromMoreGeneralNodeType() throws Exception {
+        final String indexNodeType = "nt:base";
+        final String queryNodeType = "oak:Unstructured";
+        final String indexPropName = "description";
+        final String indexPropValue = "this is just a sample text which should 
get some response in suggestions";
+        final String suggestQueryText = "th";
+        final boolean shouldSuggest = false;
+
+        checkSuggestions(indexNodeType, queryNodeType,
+                indexPropName, indexPropValue,
+                true, false,
+                suggestQueryText, shouldSuggest, false);
+    }
+
+    //OAK-3156
+    @Test
+    public void testSuggestQueryOnNonNtBase() throws Exception {
+        final String nodeType = "oak:Unstructured";
+        final String indexPropName = "description";
+        final String indexPropValue = "this is just a sample text which should 
get some response in suggestions";
+        final String suggestQueryText = "th";
+        final boolean shouldSuggest = true;
+
+        checkSuggestions(nodeType,
+                indexPropName, indexPropValue,
+                true, false,
+                suggestQueryText, shouldSuggest, false);
+    }
+
+    //OAK-3509
+    @Test
+    public void testMultipleSuggestions() throws Exception {
+        final String nodeType = "oak:Unstructured";
+        final String indexPropName = "description";
+        final String indexPropValue = "this is just a sample text which should 
get some response in suggestions";
+        final String suggestQueryText = "th";
+        final boolean shouldSuggest = true;
+
+        checkSuggestions(nodeType,
+                indexPropName, indexPropValue,
+                true, false,
+                suggestQueryText, shouldSuggest, false);
+    }
+
+    //OAK-3407
+    @Test
+    public void testSuggestQueryAnalyzed() throws Exception {
+        final String nodeType = "nt:unstructured";
+        final String indexPropName = "description";
+        final String indexPropValue = "this is just a sample text which should 
get some response in suggestions";
+        final String suggestQueryText = "sa";
+
+        checkSuggestions(nodeType,
+                indexPropName, indexPropValue,
+                true, true,
+                suggestQueryText, true, true);
+    }
+
+    //OAK-3149
+    @Test
+    public void testSuggestQueryInfix() throws Exception {
+        final String nodeType = "nt:unstructured";
+        final String indexPropName = "description";
+        final String indexPropValue = "this is just a sample text which should 
get some response in suggestions";
+        final String suggestQueryText = "sa";
+
+        checkSuggestions(nodeType,
+                indexPropName, indexPropValue,
+                true, true,
+                suggestQueryText, true, false);
+    }
+
+    //OAK-4067
+    @Test
+    public void emptySuggestWithNothingIndexed() throws Exception {
+        final String nodeType = "nt:unstructured";
+        final String indexPropName = "description";
+        final String indexPropValue = null;
+        final String suggestQueryText = null;
+
+        checkSuggestions(nodeType,
+                indexPropName, indexPropValue,
+                true, true,
+                suggestQueryText, false, false);
+    }
+
+    private static void assertEventually(Runnable r) {
+        TestUtils.assertEventually(r, 3000 * 3);
+    }
+}

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


Reply via email to