You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ja...@apache.org on 2015/10/10 21:01:59 UTC

svn commit: r1707907 - in /lucene/dev/trunk: lucene/suggest/src/java/org/apache/lucene/search/suggest/ solr/ solr/core/src/java/org/apache/solr/handler/component/ solr/core/src/java/org/apache/solr/spelling/suggest/ solr/core/src/java/org/apache/solr/s...

Author: janhoy
Date: Sat Oct 10 19:01:59 2015
New Revision: 1707907

URL: http://svn.apache.org/viewvc?rev=1707907&view=rev
Log:
SOLR-7888: Analyzing suggesters can now filter suggestions by a context field

Added:
    lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig-suggestercomponent-context-filter-query.xml   (with props)
    lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentContextFilterQueryTest.java   (with props)
Modified:
    lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/suggest/Lookup.java
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentDictionaryFactory.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterOptions.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterParams.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/fst/AnalyzingInfixLookupFactory.java
    lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentTest.java
    lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java

Modified: lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/suggest/Lookup.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/suggest/Lookup.java?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/suggest/Lookup.java (original)
+++ lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/suggest/Lookup.java Sat Oct 10 19:01:59 2015
@@ -24,6 +24,7 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.spell.Dictionary;
 import org.apache.lucene.store.DataInput;
 import org.apache.lucene.store.DataOutput;
@@ -252,6 +253,22 @@ public abstract class Lookup implements
   public abstract List<LookupResult> lookup(CharSequence key, Set<BytesRef> contexts, boolean onlyMorePopular, int num) throws IOException;
 
   /**
+   * Look up a key and return possible completion for this key.
+   * This needs to be overridden by all implementing classes as the default implementation just returns null
+   *
+   * @param key the lookup key
+   * @param contextFilerQuery A query for further filtering the result of the key lookup
+   * @param num maximum number of results to return
+   * @param allTermsRequired true is all terms are required
+   * @param doHighlight set to true if key should be highlighted
+   * @return a list of suggestions/completions. The default implementation returns null, meaning each @Lookup implementation should override this and provide their own implementation
+   * @throws IOException when IO exception occurs
+   */
+  public List<LookupResult> lookup(CharSequence key, BooleanQuery contextFilerQuery, int num, boolean allTermsRequired, boolean doHighlight) throws IOException{
+    return null;
+  }
+
+  /**
    * Persist the constructed lookup data to a directory. Optional operation.
    * @param output {@link DataOutput} to write the data to.
    * @return true if successful, false if unsuccessful or not supported.

Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Sat Oct 10 19:01:59 2015
@@ -176,6 +176,8 @@ New Features
 
 * SOLR-7858: Add links between original and new Admin UIs (Upayavira)
 
+* SOLR-7888: Analyzing suggesters can now filter suggestions by a context field (Arcadius Ahouansou, janhoy)
+
 Bug Fixes
 ----------------------
 

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java Sat Oct 10 19:01:59 2015
@@ -186,7 +186,7 @@ public class SuggestComponent extends Se
       rb.rsp.add("command", (!reloadAll) ? "reload" : "reloadAll");
     }
   }
-  
+
   /** Dispatch shard request in <code>STAGE_EXECUTE_QUERY</code> stage */
   @Override
   public int distributedProcess(ResponseBuilder rb) {
@@ -238,11 +238,21 @@ public class SuggestComponent extends Se
         query = params.get(CommonParams.Q);
       }
     }
-    
+
     if (query != null) {
       int count = params.getInt(SUGGEST_COUNT, 1);
-      SuggesterOptions options = new SuggesterOptions(new CharsRef(query), count);
-      Map<String, SimpleOrderedMap<NamedList<Object>>> namedListResults = 
+      boolean highlight = params.getBool(SUGGEST_HIGHLIGHT, false);
+      boolean allTermsRequired = params.getBool(SUGGEST_ALL_TERMS_REQUIRED, true);
+      String contextFilter = params.get(SUGGEST_CONTEXT_FILTER_QUERY);
+      if (contextFilter != null) {
+        contextFilter = contextFilter.trim();
+        if (contextFilter.length() == 0) {
+          contextFilter = null;
+        }
+      }
+
+      SuggesterOptions options = new SuggesterOptions(new CharsRef(query), count, contextFilter, allTermsRequired, highlight);
+      Map<String, SimpleOrderedMap<NamedList<Object>>> namedListResults =
           new HashMap<>();
       for (SolrSuggester suggester : querySuggesters) {
         SuggesterResult suggesterResult = suggester.getSuggestions(options);
@@ -251,7 +261,7 @@ public class SuggestComponent extends Se
       rb.rsp.add(SuggesterResultLabels.SUGGEST, namedListResults);
     }
   }
-  
+
   /** 
    * Used in Distributed Search, merges the suggestion results from every shard
    * */

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentDictionaryFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentDictionaryFactory.java?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentDictionaryFactory.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentDictionaryFactory.java Sat Oct 10 19:01:59 2015
@@ -33,6 +33,8 @@ public class DocumentDictionaryFactory e
   
   public static final String PAYLOAD_FIELD = "payloadField";
 
+  public static final String CONTEXT_FIELD = "contextField";
+
   @Override
   public Dictionary create(SolrCore core, SolrIndexSearcher searcher) {
     if(params == null) {
@@ -42,12 +44,13 @@ public class DocumentDictionaryFactory e
     String field = (String) params.get(FIELD);
     String weightField = (String) params.get(WEIGHT_FIELD);
     String payloadField = (String) params.get(PAYLOAD_FIELD);
-    
+    String contextField = (String) params.get(CONTEXT_FIELD);
+
     if (field == null) {
       throw new IllegalArgumentException(FIELD + " is a mandatory parameter");
     }
 
-    return new DocumentDictionary(searcher.getIndexReader(), field, weightField, payloadField);
+    return new DocumentDictionary(searcher.getIndexReader(), field, weightField, payloadField, contextField);
   }
   
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java Sat Oct 10 19:01:59 2015
@@ -23,13 +23,22 @@ import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
+import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
+import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
 import org.apache.lucene.search.spell.Dictionary;
 import org.apache.lucene.search.suggest.Lookup;
 import org.apache.lucene.search.suggest.Lookup.LookupResult;
 import org.apache.lucene.util.Accountable;
 import org.apache.lucene.util.IOUtils;
+import org.apache.solr.analysis.TokenizerChain;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.CloseHook;
 import org.apache.solr.core.SolrCore;
@@ -38,6 +47,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.spelling.suggest.fst.AnalyzingInfixLookupFactory.CONTEXTS_FIELD_NAME;
 
 /** 
  * Responsible for loading the lookup and dictionary Implementations specified by 
@@ -61,7 +71,7 @@ public class SolrSuggester implements Ac
   
   /** Fully-qualified class of the {@link Dictionary} implementation */
   public static final String DICTIONARY_IMPL = "dictionaryImpl";
-  
+
   /**
    * Name of the location where to persist the dictionary. If this location
    * is relative then the data will be stored under the core's dataDir. If this
@@ -81,8 +91,9 @@ public class SolrSuggester implements Ac
 
   private LookupFactory factory;
   private DictionaryFactory dictionaryFactory;
-  
-  /** 
+  private Analyzer contextFilterQueryAnalyzer;
+
+  /**
    * Uses the <code>config</code> and the <code>core</code> to initialize the underlying 
    * Lucene suggester
    * */
@@ -101,6 +112,9 @@ public class SolrSuggester implements Ac
       lookupImpl = LookupFactory.DEFAULT_FILE_BASED_DICT;
       LOG.info("No " + LOOKUP_IMPL + " parameter was provided falling back to " + lookupImpl);
     }
+
+    contextFilterQueryAnalyzer = new TokenizerChain(new StandardTokenizerFactory(Collections.EMPTY_MAP), null);
+
     // initialize appropriate lookup instance
     factory = core.getResourceLoader().newInstance(lookupImpl, LookupFactory.class);
     lookup = factory.create(config, core);
@@ -146,7 +160,7 @@ public class SolrSuggester implements Ac
         DictionaryFactory.DEFAULT_FILE_BASED_DICT;
       LOG.info("No " + DICTIONARY_IMPL + " parameter was provided falling back to " + dictionaryImpl);
     }
-    
+
     dictionaryFactory = core.getResourceLoader().newInstance(dictionaryImpl, DictionaryFactory.class);
     dictionaryFactory.setParams(config);
     LOG.info("Dictionary loaded with params: " + config);
@@ -212,11 +226,41 @@ public class SolrSuggester implements Ac
     }
     
     SuggesterResult res = new SuggesterResult();
-    List<LookupResult> suggestions = lookup.lookup(options.token, false, options.count);
+    List<LookupResult> suggestions;
+    if(options.contextFilterQuery == null){
+      //TODO: this path needs to be fixed to accept query params to override configs such as allTermsRequired, highlight
+      suggestions = lookup.lookup(options.token, false, options.count);
+    } else {
+      BooleanQuery query = parseContextFilterQuery(options.contextFilterQuery);
+      suggestions = lookup.lookup(options.token, query, options.count, options.allTermsRequired, options.highlight);
+      if(suggestions == null){
+        // Context filtering not supported/configured by lookup
+        // Silently ignore filtering and serve a result by querying without context filtering
+        LOG.debug("Context Filtering Query not supported by {}", lookup.getClass());
+        suggestions = lookup.lookup(options.token, false, options.count);
+      }
+    }
     res.add(getName(), options.token.toString(), suggestions);
     return res;
   }
 
+  private BooleanQuery parseContextFilterQuery(String contextFilter) {
+    if(contextFilter == null){
+      return null;
+    }
+
+    Query query = null;
+    try {
+      query = new StandardQueryParser(contextFilterQueryAnalyzer).parse(contextFilter, CONTEXTS_FIELD_NAME);
+      if (query instanceof BooleanQuery) {
+        return (BooleanQuery) query;
+      }
+      return new BooleanQuery.Builder().add(query, BooleanClause.Occur.MUST).build();
+    } catch (QueryNodeException e) {
+      throw new IllegalArgumentException("Failed to parse query: " + query);
+    }
+  }
+
   /** Returns the unique name of the suggester */
   public String getName() {
     return name;

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterOptions.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterOptions.java?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterOptions.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterOptions.java Sat Oct 10 19:01:59 2015
@@ -30,9 +30,21 @@ public class SuggesterOptions {
   
   /** Number of suggestions requested */
   int count;
-  
-  public SuggesterOptions(CharsRef token, int count) {
+
+  /** A Solr or Lucene query for filtering suggestions*/
+  String contextFilterQuery;
+
+  /** Are all terms required?*/
+  boolean allTermsRequired;
+
+  /** Highlight term in results?*/
+  boolean highlight;
+
+  public SuggesterOptions(CharsRef token, int count, String contextFilterQuery, boolean allTermsRequired, boolean highlight) {
     this.token = token;
     this.count = count;
+    this.contextFilterQuery = contextFilterQuery;
+    this.allTermsRequired = allTermsRequired;
+    this.highlight = highlight;
   }
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterParams.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterParams.java?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterParams.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/SuggesterParams.java Sat Oct 10 19:01:59 2015
@@ -66,4 +66,21 @@ public interface SuggesterParams {
    * This parameter does not need any suggest dictionary names to be specified
    */
   public static final String SUGGEST_RELOAD_ALL = SUGGEST_PREFIX + "reloadAll";
+
+  /**
+   * contextFilterQuery to use for filtering the result of the suggestion
+   */
+  public static final String SUGGEST_CONTEXT_FILTER_QUERY = SUGGEST_PREFIX + "cfq";
+
+  /**
+   * Whether keyword should be highlighted in result or not
+   */
+  public static final String SUGGEST_HIGHLIGHT = SUGGEST_PREFIX + "highlight";
+
+
+  /**
+   * Whether all terms are required or not
+   */
+  public static final String SUGGEST_ALL_TERMS_REQUIRED = SUGGEST_PREFIX + "allTermsRequired";
+
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/fst/AnalyzingInfixLookupFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/fst/AnalyzingInfixLookupFactory.java?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/fst/AnalyzingInfixLookupFactory.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/spelling/suggest/fst/AnalyzingInfixLookupFactory.java Sat Oct 10 19:01:59 2015
@@ -75,6 +75,11 @@ public class AnalyzingInfixLookupFactory
    * File name for the automaton.
    */
   private static final String FILENAME = "iwfsta.bin";
+
+  /**
+   * Clone of CONTEXTS_FIELD_NAME in AnalyzingInfixSuggester
+   */
+  public static final String CONTEXTS_FIELD_NAME = "contexts";
   
   
   @Override
@@ -110,7 +115,7 @@ public class AnalyzingInfixLookupFactory
     
     boolean highlight = params.get(HIGHLIGHT) != null
     ? Boolean.getBoolean(params.get(HIGHLIGHT).toString())
-    : AnalyzingInfixSuggester.DEFAULT_HIGHLIGHT; 
+    : AnalyzingInfixSuggester.DEFAULT_HIGHLIGHT;
 
     try {
       return new AnalyzingInfixSuggester(FSDirectory.open(new File(indexPath).toPath()), indexAnalyzer,

Added: lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig-suggestercomponent-context-filter-query.xml
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig-suggestercomponent-context-filter-query.xml?rev=1707907&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig-suggestercomponent-context-filter-query.xml (added)
+++ lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig-suggestercomponent-context-filter-query.xml Sat Oct 10 19:01:59 2015
@@ -0,0 +1,121 @@
+<?xml version="1.0" ?>
+<!--
+ 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.
+-->
+<config>
+  <xi:include href="solrconfig.snippet.randomindexconfig.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
+   <luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
+  <!--  The DirectoryFactory to use for indexes.
+        solr.StandardDirectoryFactory, the default, is filesystem based.
+        solr.RAMDirectoryFactory is memory based and not persistent. -->
+  <dataDir>${solr.data.dir:}</dataDir>
+  <directoryFactory name="DirectoryFactory" class="solr.NRTCachingDirectoryFactory"/>
+
+  <updateHandler class="solr.DirectUpdateHandler2"/>
+
+  <requestHandler name="standard" class="solr.StandardRequestHandler" />
+
+  <searchComponent class="solr.SuggestComponent" name="suggest">
+    <!--Suggest Component for context filtering test -->
+    <lst name="suggester">
+      <str name="name">suggest_blended_infix_suggester</str>
+      <str name="lookupImpl">BlendedInfixLookupFactory</str>
+      <str name="dictionaryImpl">DocumentDictionaryFactory</str>
+      <str name="field">cat</str>
+      <str name="weightField">price</str>
+      <str name="contextField">my_contexts_t</str>
+      <str name="suggestAnalyzerFieldType">text</str>
+      <str name="buildOnCommit">false</str>
+      <str name="buildOnStartup">false</str>
+      <str name="storeDir">suggest_blended_infix_suggester</str>
+      <str name="indexPath">suggest_blended_infix_suggester</str>
+      <str name="highlight">false</str>
+    </lst>
+
+    <lst name="suggester">
+      <str name="name">suggest_blended_infix_suggester_string</str>
+      <str name="lookupImpl">BlendedInfixLookupFactory</str>
+      <str name="dictionaryImpl">DocumentDictionaryFactory</str>
+      <str name="field">cat</str>
+      <str name="weightField">price</str>
+      <str name="contextField">my_contexts_s</str>
+      <str name="suggestAnalyzerFieldType">text</str>
+      <str name="buildOnCommit">false</str>
+      <str name="buildOnStartup">false</str>
+      <str name="storeDir">suggest_blended_infix_suggester_string</str>
+      <str name="indexPath">suggest_blended_infix_suggester_string</str>
+      <str name="highlight">false</str>
+    </lst>
+
+    <lst name="suggester">
+      <str name="name">suggest_lookup_has_no_context_implementation</str>
+      <str name="lookupImpl">AnalyzingLookupFactory</str>
+      <str name="dictionaryImpl">DocumentDictionaryFactory</str>
+      <str name="field">cat</str>
+      <str name="weightField">price</str>
+      <str name="suggestAnalyzerFieldType">text</str>
+      <str name="buildOnCommit">false</str>
+      <str name="buildOnStartup">false</str>
+      <str name="storeDir">suggest_lookup_has_no_context_implementation</str>
+      <str name="indexPath">suggest_lookup_has_no_context_implementation</str>
+      <str name="highlight">false</str>
+    </lst>
+
+    <lst name="suggester">
+      <str name="name">suggest_context_filtering_not_implemented</str>
+      <str name="lookupImpl">AnalyzingLookupFactory</str>
+      <str name="dictionaryImpl">DocumentDictionaryFactory</str>
+      <str name="field">cat</str>
+      <str name="weightField">price</str>
+      <str name="suggestAnalyzerFieldType">text</str>
+      <str name="buildOnCommit">false</str>
+      <str name="buildOnStartup">false</str>
+      <str name="contextField">my_contexts_t</str>
+      <str name="storeDir">suggest_context_filtering_not_implemented</str>
+      <str name="indexPath">suggest_context_filtering_not_implemented</str>
+      <str name="highlight">false</str>
+    </lst>
+
+
+    <lst name="suggester">
+      <str name="name">suggest_context_implemented_but_not_configured</str>
+      <str name="lookupImpl">BlendedInfixLookupFactory</str>
+      <str name="dictionaryImpl">DocumentDictionaryFactory</str>
+      <str name="field">cat</str>
+      <str name="weightField">price</str>
+      <str name="suggestAnalyzerFieldType">text</str>
+      <str name="buildOnCommit">false</str>
+      <str name="buildOnStartup">false</str>
+      <str name="storeDir">suggest_context_implemented_but_not_configured</str>
+      <str name="indexPath">suggest_context_implemented_but_not_configured</str>
+      <str name="highlight">false</str>
+    </lst>
+
+  </searchComponent>
+  <requestHandler name="/suggest" class="org.apache.solr.handler.component.SearchHandler">
+    <lst name="defaults">
+      <str name="suggest">true</str>
+      <str name="suggest.count">5</str>
+    </lst>
+    <arr name="components">
+      <str>suggest</str>
+    </arr>
+  </requestHandler>
+
+  <query><useColdSearcher>false</useColdSearcher></query>
+
+</config>
+

Added: lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentContextFilterQueryTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentContextFilterQueryTest.java?rev=1707907&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentContextFilterQueryTest.java (added)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentContextFilterQueryTest.java Sat Oct 10 19:01:59 2015
@@ -0,0 +1,258 @@
+package org.apache.solr.handler.component;
+
+/*
+ * 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.
+ */
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.spelling.suggest.SuggesterParams;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+
+public class SuggestComponentContextFilterQueryTest extends SolrTestCaseJ4 {
+
+  static String rh = "/suggest";
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    initCore("solrconfig-suggestercomponent-context-filter-query.xml", "schema.xml");
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+
+    assertU(delQ("*:*"));
+    // id, cat, price, weight, contexts
+    assertU(adoc("id", "0", "cat", "This is a title", "price", "5", "weight", "10", "my_contexts_t", "ctx1"));
+    assertU(adoc("id", "1", "cat", "This is another title", "price", "10", "weight", "10", "my_contexts_t", "ctx1"));
+    assertU(adoc("id", "7", "cat", "example with ctx1 at 40", "price", "40", "weight", "30", "my_contexts_t", "ctx1"));
+    assertU(adoc("id", "8", "cat", "example with ctx2 and ctx3 at 45", "price", "45", "weight", "30", "my_contexts_t", "CTX2", "my_contexts_t", "CTX3"));
+    assertU(adoc("id", "9", "cat", "example with ctx4 at 50 using my_contexts_s", "price", "50", "weight", "40", "my_contexts_s", "ctx4"));
+    assertU((commit()));
+    waitForWarming();
+  }
+
+  @Test
+  public void testContextFilterParamIsIgnoredWhenContextIsNotImplemented() throws Exception {
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_lookup_has_no_context_implementation",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx1",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_lookup_has_no_context_implementation']/lst[@name='examp']/int[@name='numFound'][.='3']",
+        "//lst[@name='suggest']/lst[@name='suggest_lookup_has_no_context_implementation']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx4 at 50 using my_contexts_s']",
+        "//lst[@name='suggest']/lst[@name='suggest_lookup_has_no_context_implementation']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
+        "//lst[@name='suggest']/lst[@name='suggest_lookup_has_no_context_implementation']/lst[@name='examp']/arr[@name='suggestions']/lst[3]/str[@name='term'][.='example with ctx1 at 40']"
+    );
+  }
+
+
+  @Test
+  public void testContextFilteringIsIgnoredWhenContextIsImplementedButNotConfigured() throws Exception {
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_context_implemented_but_not_configured",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_context_implemented_but_not_configured']/lst[@name='examp']/int[@name='numFound'][.='3']",
+        "//lst[@name='suggest']/lst[@name='suggest_context_implemented_but_not_configured']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx4 at 50 using my_contexts_s']",
+        "//lst[@name='suggest']/lst[@name='suggest_context_implemented_but_not_configured']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
+        "//lst[@name='suggest']/lst[@name='suggest_context_implemented_but_not_configured']/lst[@name='examp']/arr[@name='suggestions']/lst[3]/str[@name='term'][.='example with ctx1 at 40']"
+    );
+  }
+
+  @Test
+  public void testBuildThrowsIllegalArgumentExceptionWhenContextIsConfiguredButNotImplemented() throws Exception {
+    try {
+      assertQ(
+          req("qt", rh,
+              SuggesterParams.SUGGEST_BUILD, "true",
+              SuggesterParams.SUGGEST_DICT, "suggest_context_filtering_not_implemented",
+              SuggesterParams.SUGGEST_Q, "examp")
+          ,
+          ""
+      );
+      fail("Expecting exception because ");
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      assertTrue(cause instanceof IllegalArgumentException);
+      assertThat(cause.getMessage(), is("this suggester doesn't support contexts"));
+    }
+
+    // When not building, no exception is thrown
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "false",
+            SuggesterParams.SUGGEST_DICT, "suggest_context_filtering_not_implemented",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_context_filtering_not_implemented']/lst[@name='examp']/int[@name='numFound'][.='0']"
+    );
+  }
+
+
+  @Test
+  public void testContextFilterIsTrimmed() throws Exception {
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "     ", //trimmed to null... just as if there was no context filter param
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='3']"
+    );
+  }
+
+  public void testExplicitFieldedQuery() throws Exception {
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "contexts:ctx1",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx1 at 40']"
+    );
+  }
+
+  public void testContextFilterOK() throws Exception {
+    //No filtering
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='3']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx4 at 50 using my_contexts_s']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[3]/str[@name='term'][.='example with ctx1 at 40']"
+    );
+
+    //TermQuery
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx1",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx1 at 40']"
+    );
+
+    //OR BooleanQuery
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx1 OR CTX2",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='2']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx1 at 40']"
+    );
+
+    //AND BooleanQuery
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "CTX2 AND CTX3",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx2 and ctx3 at 45']");
+
+
+    //PrefixQuery
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx*",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx1 at 40']"
+    );
+
+    //RangeQuery
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "[* TO *]",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='2']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx1 at 40']"
+    );
+
+    //WildcardQuery
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "c*1",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx1 at 40']");
+  }
+
+  @Test
+  public void testStringContext(){
+    //Here, the context field is a string, so it's case sensitive
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester_string",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "Ctx4",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester_string']/lst[@name='examp']/int[@name='numFound'][.='0']");
+
+   assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester_string",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx4",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester_string']/lst[@name='examp']/int[@name='numFound'][.='1']");
+  }
+
+  @Test
+  public void testContextFilterOnInvalidFieldGivesNoSuggestions() throws Exception {
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "some_invalid_context_field:some_invalid_value",
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='0']");
+  }
+
+
+  @Test
+  public void testContextFilterUsesAnalyzer() throws Exception {
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "CTx1", // Will not match due to case
+            SuggesterParams.SUGGEST_Q, "examp"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='0']");
+  }
+
+  @Ignore// TODO: SOLR-7964
+  @Test
+  public void testContextFilterWithHighlight() throws Exception {
+    assertQ(req("qt", rh,
+            SuggesterParams.SUGGEST_BUILD, "true",
+            SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
+            SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx1",
+            SuggesterParams.SUGGEST_HIGHLIGHT, "true",
+            SuggesterParams.SUGGEST_Q, "example"),
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='example']/int[@name='numFound'][.='1']",
+        "//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='example']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='<b>example</b> data']"
+    );
+  }
+
+}
+

Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentTest.java?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentTest.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/SuggestComponentTest.java Sat Oct 10 19:01:59 2015
@@ -522,20 +522,4 @@ public class SuggestComponentTest extend
         );
   }
 
-  private void waitForWarming() throws InterruptedException {
-    RefCounted<SolrIndexSearcher> registeredSearcher = h.getCore().getRegisteredSearcher();
-    RefCounted<SolrIndexSearcher> newestSearcher = h.getCore().getNewestSearcher(false);;
-    while (registeredSearcher == null || registeredSearcher.get() != newestSearcher.get()) {
-      if (registeredSearcher != null) {
-        registeredSearcher.decref();
-      }
-      newestSearcher.decref();
-      Thread.sleep(50);
-      registeredSearcher = h.getCore().getRegisteredSearcher();
-      newestSearcher = h.getCore().getNewestSearcher(false);
-    }
-    registeredSearcher.decref();
-    newestSearcher.decref();
-  }
-  
 }

Modified: lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java?rev=1707907&r1=1707906&r2=1707907&view=diff
==============================================================================
--- lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java (original)
+++ lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java Sat Oct 10 19:01:59 2015
@@ -98,6 +98,7 @@ import org.apache.solr.search.SolrIndexS
 import org.apache.solr.servlet.DirectSolrConnection;
 import org.apache.solr.util.AbstractSolrTestCase;
 import org.apache.solr.util.DateFormatUtil;
+import org.apache.solr.util.RefCounted;
 import org.apache.solr.util.RevertDefaultThreadHandlerRule;
 import org.apache.solr.util.SSLTestConfig;
 import org.apache.solr.util.TestHarness;
@@ -2117,4 +2118,20 @@ public abstract class SolrTestCaseJ4 ext
     return result;
   }
 
+  protected void waitForWarming() throws InterruptedException {
+    RefCounted<SolrIndexSearcher> registeredSearcher = h.getCore().getRegisteredSearcher();
+    RefCounted<SolrIndexSearcher> newestSearcher = h.getCore().getNewestSearcher(false);
+    ;
+    while (registeredSearcher == null || registeredSearcher.get() != newestSearcher.get()) {
+      if (registeredSearcher != null) {
+        registeredSearcher.decref();
+      }
+      newestSearcher.decref();
+      Thread.sleep(50);
+      registeredSearcher = h.getCore().getRegisteredSearcher();
+      newestSearcher = h.getCore().getNewestSearcher(false);
+    }
+    registeredSearcher.decref();
+    newestSearcher.decref();
+  }
 }