You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ds...@apache.org on 2018/06/09 15:32:34 UTC

[2/2] lucene-solr:master: SOLR-11865: QueryElevationComponent changes * new useConfiguredElevatedOrder setting * more extensible (customizable via subclass) ** ElevationProvider ** handleInitializationException with cause enum * use BytesRef for uniqueKe

SOLR-11865: QueryElevationComponent changes
* new useConfiguredElevatedOrder setting
* more extensible (customizable via subclass)
** ElevationProvider
** handleInitializationException with cause enum
* use BytesRef for uniqueKey ID pervasively instead of String.
* ElevatorComparatorSource now reuses getBoostedDocs logic
* setSort will short-circuit if there are no elevated Ids
* extensive refactoring and affects some interrelated components


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/a06256cc
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/a06256cc
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/a06256cc

Branch: refs/heads/master
Commit: a06256ccee8742eaf47ac1f2c12ec8e68a70a163
Parents: 944b24f
Author: broustant <br...@salesforce.com>
Authored: Sat Jun 9 11:32:24 2018 -0400
Committer: David Smiley <ds...@apache.org>
Committed: Sat Jun 9 11:32:24 2018 -0400

----------------------------------------------------------------------
 solr/CHANGES.txt                                |    5 +-
 .../component/QueryElevationComponent.java      | 1312 ++++++++++++------
 .../transform/BaseEditorialTransformer.java     |   17 +-
 .../transform/ElevatedMarkerFactory.java        |    6 +-
 .../transform/ExcludedMarkerFactory.java        |    6 +-
 .../apache/solr/search/AbstractReRankQuery.java |    7 +-
 .../solr/search/CollapsingQParserPlugin.java    |   25 +-
 .../org/apache/solr/search/ReRankCollector.java |    7 +-
 .../component/QueryElevationComponentTest.java  |  315 +++--
 .../src/the-query-elevation-component.adoc      |   11 +
 .../common/params/QueryElevationParams.java     |    9 +-
 11 files changed, 1088 insertions(+), 632 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a06256cc/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 4c0bb66..993edcb 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -67,7 +67,10 @@ Upgrade Notes
 New Features
 ----------------------
 
-(no changes)
+* SOLR-11865: The QueryElevationComponent now has a useConfiguredElevatedOrder setting. When multiple docs are elevated,
+  this specifies whether their relative order should be the order in the configuration file or if not then should they
+  be subject to whatever the sort criteria is.  Additionally, QEC was extensively refactored to be more extensible.
+  (Bruno Roustant, David Smiley)
 
 Bug Fixes
 ----------------------

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a06256cc/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java b/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
index d7b8474..77d1e97 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.handler.component;
 
+import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpressionException;
@@ -26,40 +27,42 @@ import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.WeakHashMap;
 
 import com.carrotsearch.hppc.IntIntHashMap;
+import com.carrotsearch.hppc.cursors.IntIntCursor;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.IndexReaderContext;
 import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.PostingsEnum;
 import org.apache.lucene.index.Term;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.BoostQuery;
-import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.FieldComparator;
 import org.apache.lucene.search.FieldComparatorSource;
-import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SimpleFieldComparator;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
 import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.BytesRefBuilder;
-import org.apache.lucene.util.SentinelIntSet;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.QueryElevationParams;
@@ -73,7 +76,6 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.transform.ElevatedMarkerFactory;
 import org.apache.solr.response.transform.ExcludedMarkerFactory;
 import org.apache.solr.schema.FieldType;
-import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.QueryParsing;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -88,82 +90,55 @@ import org.slf4j.LoggerFactory;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.InputSource;
-
-import static org.apache.solr.common.params.CommonParams.ID;
+import org.xml.sax.SAXException;
 
 /**
  * A component to elevate some documents to the top of the result set.
  *
  * @since solr 1.3
  */
+@SuppressWarnings("WeakerAccess")
 public class QueryElevationComponent extends SearchComponent implements SolrCoreAware {
+
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   // Constants used in solrconfig.xml
+  @VisibleForTesting
   static final String FIELD_TYPE = "queryFieldType";
+  @VisibleForTesting
   static final String CONFIG_FILE = "config-file";
-  static final String EXCLUDE = "exclude";
-  public static final String BOOSTED = "BOOSTED";
-  public static final String BOOSTED_DOCIDS = "BOOSTED_DOCIDS";
-  public static final String BOOSTED_PRIORITY = "BOOSTED_PRIORITY";
+  private static final String EXCLUDE = "exclude";
 
+  /** @see #getBoostDocs(SolrIndexSearcher, Set, Map) */
+  private static final String BOOSTED_DOCIDS = "BOOSTED_DOCIDS";
 
+  /** Key to {@link SolrQueryRequest#getContext()} for a {@code Set<BytesRef>} of included IDs in configured
+   * order (so-called priority). */
+  public static final String BOOSTED = "BOOSTED";
+  /** Key to {@link SolrQueryRequest#getContext()} for a {@code Set<BytesRef>} of excluded IDs. */
   public static final String EXCLUDED = "EXCLUDED";
 
-  // Runtime param -- should be in common?
-
-  private SolrParams initArgs = null;
-  private Analyzer analyzer = null;
-  private String idField = null;
-  private FieldType idSchemaFT;
-
-  boolean forceElevation = false;
-  // For each IndexReader, keep a query->elevation map
-  // When the configuration is loaded from the data directory.
-  // The key is null if loaded from the config directory, and
-  // is never re-loaded.
-  final Map<IndexReader, Map<String, ElevationObj>> elevationCache =
-      new WeakHashMap<>();
-
-  class ElevationObj {
-    final String text;
-    final String analyzed;
-    final TermQuery [] exclude;//just keep the term query, b/c we will not always explicitly exclude the item based on markExcludes query time param
-    final BooleanQuery include;
-    final Map<BytesRef, Integer> priority;
-    final Set<String> ids;
-    final Set<String> excludeIds;
-
-    ElevationObj(String qstr, List<String> elevate, List<String> exclude) throws IOException {
-      this.text = qstr;
-      this.analyzed = getAnalyzedQuery(this.text);
-      this.ids = new HashSet<>();
-      this.excludeIds = new HashSet<>();
-
-      BooleanQuery.Builder include = new BooleanQuery.Builder();
-      this.priority = new HashMap<>();
-      int max = elevate.size() + 5;
-      for (String id : elevate) {
-        id = idSchemaFT.readableToIndexed(id);
-        ids.add(id);
-        TermQuery tq = new TermQuery(new Term(idField, id));
-        include.add(tq, BooleanClause.Occur.SHOULD);
-        this.priority.put(new BytesRef(id), max--);
-      }
-      this.include = include.build();
+  private static final boolean DEFAULT_FORCE_ELEVATION = false;
+  private static final boolean DEFAULT_USE_CONFIGURED_ELEVATED_ORDER = true;
+  private static final boolean DEFAULT_SUBSET_MATCH = false;
+  private static final String DEFAULT_EXCLUDE_MARKER_FIELD_NAME = "excluded";
+  private static final String DEFAULT_EDITORIAL_MARKER_FIELD_NAME = "elevated";
 
-      if (exclude == null || exclude.isEmpty()) {
-        this.exclude = null;
-      } else {
-        this.exclude = new TermQuery[exclude.size()];
-        for (int i = 0; i < exclude.size(); i++) {
-          String id = idSchemaFT.readableToIndexed(exclude.get(i));
-          excludeIds.add(id);
-          this.exclude[i] = new TermQuery(new Term(idField, id));
-        }
-      }
-    }
-  }
+  protected SolrParams initArgs;
+  protected Analyzer queryAnalyzer;
+  protected SchemaField uniqueKeyField;
+  /** @see QueryElevationParams#FORCE_ELEVATION */
+  protected boolean forceElevation;
+  /** @see QueryElevationParams#USE_CONFIGURED_ELEVATED_ORDER */
+  protected boolean useConfiguredElevatedOrder;
+
+  protected boolean initialized;
+
+  /**
+   * For each IndexReader, keep an ElevationProvider when the configuration is loaded from the data directory.
+   * The key is null if loaded from the config directory, and is never re-loaded.
+   */
+  private final Map<IndexReader, ElevationProvider> elevationProviderCache = new WeakHashMap<>();
 
   @Override
   public void init(NamedList args) {
@@ -172,128 +147,251 @@ public class QueryElevationComponent extends SearchComponent implements SolrCore
 
   @Override
   public void inform(SolrCore core) {
-    IndexSchema schema = core.getLatestSchema();
+    initialized = false;
+    try {
+      parseFieldType(core);
+      setUniqueKeyField(core);
+      parseExcludedMarkerFieldName(core);
+      parseEditorialMarkerFieldName(core);
+      parseForceElevation();
+      parseUseConfiguredOrderForElevations();
+      loadElevationConfiguration(core);
+      initialized = true;
+    } catch (InitializationException e) {
+      assert !initialized;
+      handleInitializationException(e, e.exceptionCause);
+    } catch (Exception e) {
+      assert !initialized;
+      handleInitializationException(e, InitializationExceptionCause.OTHER);
+    }
+  }
+
+  private void parseFieldType(SolrCore core) throws InitializationException {
     String a = initArgs.get(FIELD_TYPE);
     if (a != null) {
-      FieldType ft = schema.getFieldTypes().get(a);
+      FieldType ft = core.getLatestSchema().getFieldTypes().get(a);
       if (ft == null) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
-            "Unknown FieldType: '" + a + "' used in QueryElevationComponent");
+        throw new InitializationException("Parameter " + FIELD_TYPE + " defines an unknown field type \"" + a + "\"", InitializationExceptionCause.UNKNOWN_FIELD_TYPE);
       }
-      analyzer = ft.getQueryAnalyzer();
+      queryAnalyzer = ft.getQueryAnalyzer();
     }
+  }
 
-    SchemaField sf = schema.getUniqueKeyField();
-    if( sf == null) {
-      throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, 
-          "QueryElevationComponent requires the schema to have a uniqueKeyField." );
-    }
-    idSchemaFT = sf.getType();
-    idField = sf.getName();
-    //register the EditorialMarkerFactory
-    String excludeName = initArgs.get(QueryElevationParams.EXCLUDE_MARKER_FIELD_NAME, "excluded");
-    if (excludeName == null || excludeName.equals("") == true){
-      excludeName = "excluded";
-    }
-    ExcludedMarkerFactory excludedMarkerFactory = new ExcludedMarkerFactory();
-    core.addTransformerFactory(excludeName, excludedMarkerFactory);
-    ElevatedMarkerFactory elevatedMarkerFactory = new ElevatedMarkerFactory();
-    String markerName = initArgs.get(QueryElevationParams.EDITORIAL_MARKER_FIELD_NAME, "elevated");
-    if (markerName == null || markerName.equals("") == true) {
-      markerName = "elevated";
+  private void setUniqueKeyField(SolrCore core) throws InitializationException {
+    uniqueKeyField = core.getLatestSchema().getUniqueKeyField();
+    if (uniqueKeyField == null) {
+      throw new InitializationException("This component requires the schema to have a uniqueKeyField", InitializationExceptionCause.MISSING_UNIQUE_KEY_FIELD);
     }
-    core.addTransformerFactory(markerName, elevatedMarkerFactory);
-    forceElevation = initArgs.getBool(QueryElevationParams.FORCE_ELEVATION, forceElevation);
+  }
 
-    String f = initArgs.get(CONFIG_FILE);
-    if (f != null) {
-      try {
-        synchronized (elevationCache) {
-          elevationCache.clear();
-          boolean exists = false;
-
-          // check if using ZooKeeper
-          ZkController zkController = core.getCoreContainer().getZkController();
-          if (zkController != null) {
-            // TODO : shouldn't have to keep reading the config name when it has been read before
-            exists = zkController.configFileExists(zkController.getZkStateReader().readConfigName(core.getCoreDescriptor().getCloudDescriptor().getCollectionName()), f);
+  private void parseExcludedMarkerFieldName(SolrCore core) {
+    String markerName = initArgs.get(QueryElevationParams.EXCLUDE_MARKER_FIELD_NAME, DEFAULT_EXCLUDE_MARKER_FIELD_NAME);
+    core.addTransformerFactory(markerName, new ExcludedMarkerFactory());
+  }
+
+  private void parseEditorialMarkerFieldName(SolrCore core) {
+    String markerName = initArgs.get(QueryElevationParams.EDITORIAL_MARKER_FIELD_NAME, DEFAULT_EDITORIAL_MARKER_FIELD_NAME);
+    core.addTransformerFactory(markerName, new ElevatedMarkerFactory());
+  }
+
+  private void parseForceElevation() {
+    forceElevation = initArgs.getBool(QueryElevationParams.FORCE_ELEVATION, DEFAULT_FORCE_ELEVATION);
+  }
+
+  private void parseUseConfiguredOrderForElevations() {
+    useConfiguredElevatedOrder = initArgs.getBool(QueryElevationParams.USE_CONFIGURED_ELEVATED_ORDER, DEFAULT_USE_CONFIGURED_ELEVATED_ORDER);
+  }
+
+  /**
+   * (Re)Loads elevation configuration.
+   *
+   * @param core The core holding this component.
+   * @return The number of elevation rules parsed.
+   */
+  @SuppressWarnings("WeakerAccess")
+  protected int loadElevationConfiguration(SolrCore core) throws Exception {
+    synchronized (elevationProviderCache) {
+      elevationProviderCache.clear();
+      String configFileName = initArgs.get(CONFIG_FILE);
+      if (configFileName == null) {
+        // Throw an exception which is handled by handleInitializationException().
+        // If not overridden handleInitializationException() simply skips this exception.
+        throw new InitializationException("Missing component parameter " + CONFIG_FILE + " - it has to define the path to the elevation configuration file", InitializationExceptionCause.NO_CONFIG_FILE_DEFINED);
+      }
+      boolean configFileExists = false;
+      ElevationProvider elevationProvider = NO_OP_ELEVATION_PROVIDER;
+
+      // check if using ZooKeeper
+      ZkController zkController = core.getCoreContainer().getZkController();
+      if (zkController != null) {
+        // TODO : shouldn't have to keep reading the config name when it has been read before
+        configFileExists = zkController.configFileExists(zkController.getZkStateReader().readConfigName(core.getCoreDescriptor().getCloudDescriptor().getCollectionName()), configFileName);
+      } else {
+        File fC = new File(core.getResourceLoader().getConfigDir(), configFileName);
+        File fD = new File(core.getDataDir(), configFileName);
+        if (fC.exists() == fD.exists()) {
+          InitializationException e = new InitializationException("Missing config file \"" + configFileName + "\" - either " + fC.getAbsolutePath() + " or " + fD.getAbsolutePath() + " must exist, but not both", InitializationExceptionCause.MISSING_CONFIG_FILE);
+          elevationProvider = handleConfigLoadingException(e, true);
+          elevationProviderCache.put(null, elevationProvider);
+        } else if (fC.exists()) {
+          if (fC.length() == 0) {
+            InitializationException e = new InitializationException("Empty config file \"" + configFileName + "\" - " + fC.getAbsolutePath(), InitializationExceptionCause.EMPTY_CONFIG_FILE);
+            elevationProvider = handleConfigLoadingException(e, true);
           } else {
-            File fC = new File(core.getResourceLoader().getConfigDir(), f);
-            File fD = new File(core.getDataDir(), f);
-            if (fC.exists() == fD.exists()) {
-              throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
-                  "QueryElevationComponent missing config file: '" + f + "\n"
-                      + "either: " + fC.getAbsolutePath() + " or " + fD.getAbsolutePath() + " must exist, but not both.");
-            }
-            if (fC.exists()) {
-              exists = true;
-              log.info("Loading QueryElevation from: " + fC.getAbsolutePath());
-              Config cfg = new Config(core.getResourceLoader(), f);
-              elevationCache.put(null, loadElevationMap(cfg));
-            }
+            configFileExists = true;
+            log.info("Loading QueryElevation from: " + fC.getAbsolutePath());
+            Config cfg = new Config(core.getResourceLoader(), configFileName);
+            elevationProvider = loadElevationProvider(cfg);
           }
-          //in other words, we think this is in the data dir, not the conf dir
-          if (!exists) {
-            // preload the first data
-            RefCounted<SolrIndexSearcher> searchHolder = null;
-            try {
-              searchHolder = core.getNewestSearcher(false);
-              IndexReader reader = searchHolder.get().getIndexReader();
-              getElevationMap(reader, core);
-            } finally {
-              if (searchHolder != null) searchHolder.decref();
-            }
+          elevationProviderCache.put(null, elevationProvider);
+        }
+      }
+      //in other words, we think this is in the data dir, not the conf dir
+      if (!configFileExists) {
+        // preload the first data
+        RefCounted<SolrIndexSearcher> searchHolder = null;
+        try {
+          searchHolder = core.getNewestSearcher(false);
+          if (searchHolder == null) {
+            elevationProvider = NO_OP_ELEVATION_PROVIDER;
+          } else {
+            IndexReader reader = searchHolder.get().getIndexReader();
+            elevationProvider = getElevationProvider(reader, core);
           }
+        } finally {
+          if (searchHolder != null) searchHolder.decref();
         }
-      } catch (Exception ex) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
-            "Error initializing QueryElevationComponent.", ex);
       }
+      return elevationProvider.size();
+    }
+  }
+
+  /**
+   * Handles the exception that occurred while initializing this component.
+   * If this method does not throw an exception, this component silently fails to initialize
+   * and is muted with field {@link #initialized} which becomes {@code false}.
+   */
+  protected void handleInitializationException(Exception exception, InitializationExceptionCause cause) {
+    if (cause != InitializationExceptionCause.NO_CONFIG_FILE_DEFINED) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+          "Error initializing " + QueryElevationComponent.class.getSimpleName(), exception);
     }
   }
 
-  //get the elevation map from the data dir
-  Map<String, ElevationObj> getElevationMap(IndexReader reader, SolrCore core) throws Exception {
-    synchronized (elevationCache) {
-      Map<String, ElevationObj> map = elevationCache.get(null);
-      if (map != null) return map;
+  /**
+   * Handles an exception that occurred while loading the configuration resource.
+   *
+   * @param e                   The exception caught.
+   * @param resourceAccessIssue <code>true</code> if the exception has been thrown
+   *                            because the resource could not be accessed (missing or cannot be read)
+   *                            or the config file is empty; <code>false</code> if the resource has
+   *                            been found and accessed but the error occurred while loading the resource
+   *                            (invalid format, incomplete or corrupted).
+   * @return The {@link ElevationProvider} to use if the exception is absorbed. If {@code null}
+   *         is returned, the {@link #NO_OP_ELEVATION_PROVIDER} is used but not cached in
+   *         the {@link ElevationProvider} cache.
+   * @throws E If the exception is not absorbed.
+   */
+  protected <E extends Exception> ElevationProvider handleConfigLoadingException(E e, boolean resourceAccessIssue) throws E {
+    throw e;
+  }
 
-      map = elevationCache.get(reader);
-      if (map == null) {
-        String f = initArgs.get(CONFIG_FILE);
-        if (f == null) {
-          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
-              "QueryElevationComponent must specify argument: " + CONFIG_FILE);
+  /**
+   * Gets the {@link ElevationProvider} from the data dir or from the cache.
+   *
+   * @return The cached or loaded {@link ElevationProvider}.
+   * @throws java.io.IOException                  If the configuration resource cannot be found, or if an I/O error occurs while analyzing the triggering queries.
+   * @throws org.xml.sax.SAXException                 If the configuration resource is not a valid XML content.
+   * @throws javax.xml.parsers.ParserConfigurationException If the configuration resource is not a valid XML configuration.
+   * @throws RuntimeException             If the configuration resource is not an XML content of the expected format
+   *                                      (either {@link RuntimeException} or {@link org.apache.solr.common.SolrException}).
+   */
+  @VisibleForTesting
+  ElevationProvider getElevationProvider(IndexReader reader, SolrCore core) throws Exception {
+    synchronized (elevationProviderCache) {
+      ElevationProvider elevationProvider;
+      elevationProvider = elevationProviderCache.get(null);
+      if (elevationProvider != null) return elevationProvider;
+
+      elevationProvider = elevationProviderCache.get(reader);
+      if (elevationProvider == null) {
+        Exception loadingException = null;
+        boolean resourceAccessIssue = false;
+        try {
+          elevationProvider = loadElevationProvider(core);
+        } catch (IOException e) {
+          loadingException = e;
+          resourceAccessIssue = true;
+        } catch (Exception e) {
+          loadingException = e;
         }
-        log.info("Loading QueryElevation from data dir: " + f);
-        
-        Config cfg;
-        
-        ZkController zkController = core.getCoreContainer().getZkController();
-        if (zkController != null) {
-          cfg = new Config(core.getResourceLoader(), f, null, null);
-        } else {
-          InputStream is = VersionedFile.getLatestFile(core.getDataDir(), f);
-          cfg = new Config(core.getResourceLoader(), f, new InputSource(is), null);
+        boolean shouldCache = true;
+        if (loadingException != null) {
+          elevationProvider = handleConfigLoadingException(loadingException, resourceAccessIssue);
+          if (elevationProvider == null) {
+            elevationProvider = NO_OP_ELEVATION_PROVIDER;
+            shouldCache = false;
+          }
+        }
+        if (shouldCache) {
+          elevationProviderCache.put(reader, elevationProvider);
         }
-  
-        map = loadElevationMap(cfg);
-        elevationCache.put(reader, map);
       }
-      return map;
+      assert elevationProvider != null;
+      return elevationProvider;
+    }
+  }
+
+  /**
+   * Loads the {@link ElevationProvider} from the data dir.
+   *
+   * @return The loaded {@link ElevationProvider}.
+   * @throws java.io.IOException                  If the configuration resource cannot be found, or if an I/O error occurs while analyzing the triggering queries.
+   * @throws org.xml.sax.SAXException                 If the configuration resource is not a valid XML content.
+   * @throws javax.xml.parsers.ParserConfigurationException If the configuration resource is not a valid XML configuration.
+   * @throws RuntimeException             If the configuration resource is not an XML content of the expected format
+   *                                      (either {@link RuntimeException} or {@link org.apache.solr.common.SolrException}).
+   */
+  private ElevationProvider loadElevationProvider(SolrCore core) throws IOException, SAXException, ParserConfigurationException {
+    String configFileName = initArgs.get(CONFIG_FILE);
+    if (configFileName == null) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+          "QueryElevationComponent must specify argument: " + CONFIG_FILE);
+    }
+    log.info("Loading QueryElevation from data dir: " + configFileName);
+
+    Config cfg;
+    ZkController zkController = core.getCoreContainer().getZkController();
+    if (zkController != null) {
+      cfg = new Config(core.getResourceLoader(), configFileName, null, null);
+    } else {
+      InputStream is = VersionedFile.getLatestFile(core.getDataDir(), configFileName);
+      cfg = new Config(core.getResourceLoader(), configFileName, new InputSource(is), null);
     }
+    ElevationProvider elevationProvider = loadElevationProvider(cfg);
+    assert elevationProvider != null;
+    return elevationProvider;
   }
 
-  //load up the elevation map
-  private Map<String, ElevationObj> loadElevationMap(Config cfg) throws IOException {
+  /**
+   * Loads the {@link ElevationProvider}.
+   *
+   * @throws java.io.IOException      If an I/O error occurs while analyzing the triggering queries.
+   * @throws RuntimeException If the config does not provide an XML content of the expected format
+   *                          (either {@link RuntimeException} or {@link org.apache.solr.common.SolrException}).
+   */
+  @SuppressWarnings("WeakerAccess")
+  protected ElevationProvider loadElevationProvider(Config config) throws IOException {
+    Map<ElevatingQuery, ElevationBuilder> elevationBuilderMap = new LinkedHashMap<>();
     XPath xpath = XPathFactory.newInstance().newXPath();
-    Map<String, ElevationObj> map = new HashMap<>();
-    NodeList nodes = (NodeList) cfg.evaluate("elevate/query", XPathConstants.NODESET);
+    NodeList nodes = (NodeList) config.evaluate("elevate/query", XPathConstants.NODESET);
     for (int i = 0; i < nodes.getLength(); i++) {
       Node node = nodes.item(i);
-      String qstr = DOMUtil.getAttr(node, "text", "missing query 'text'");
+      String queryString = DOMUtil.getAttr(node, "text", "missing query 'text'");
+      String matchString = DOMUtil.getAttr(node, "match");
+      ElevatingQuery elevatingQuery = new ElevatingQuery(queryString, parseMatchPolicy(matchString));
 
-      NodeList children = null;
+      NodeList children;
       try {
         children = (NodeList) xpath.evaluate("doc", node, XPathConstants.NODESET);
       } catch (XPathExpressionException e) {
@@ -301,67 +399,45 @@ public class QueryElevationComponent extends SearchComponent implements SolrCore
             "query requires '<doc .../>' child");
       }
 
-      ArrayList<String> include = new ArrayList<>();
-      ArrayList<String> exclude = new ArrayList<>();
+      if (children.getLength() == 0) { // weird
+        continue;
+      }
+      ElevationBuilder elevationBuilder = new ElevationBuilder();
       for (int j = 0; j < children.getLength(); j++) {
         Node child = children.item(j);
-        String id = DOMUtil.getAttr(child, ID, "missing 'id'");
+        String id = DOMUtil.getAttr(child, "id", "missing 'id'");
         String e = DOMUtil.getAttr(child, EXCLUDE, null);
         if (e != null) {
           if (Boolean.valueOf(e)) {
-            exclude.add(id);
+            elevationBuilder.addExcludedIds(Collections.singleton(id));
             continue;
           }
         }
-        include.add(id);
+        elevationBuilder.addElevatedIds(Collections.singletonList(id));
       }
 
-      ElevationObj elev = new ElevationObj(qstr, include, exclude);
-      if (map.containsKey(elev.analyzed)) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
-            "Boosting query defined twice for query: '" + elev.text + "' (" + elev.analyzed + "')");
+      // It is allowed to define multiple times different elevations for the same query. In this case the elevations
+      // are merged in the ElevationBuilder (they will be triggered at the same time).
+      ElevationBuilder previousElevationBuilder = elevationBuilderMap.get(elevatingQuery);
+      if (previousElevationBuilder == null) {
+        elevationBuilderMap.put(elevatingQuery, elevationBuilder);
+      } else {
+        previousElevationBuilder.merge(elevationBuilder);
       }
-      map.put(elev.analyzed, elev);
     }
-    return map;
+    return createElevationProvider(queryAnalyzer, elevationBuilderMap);
   }
 
-  /**
-   * Helpful for testing without loading config.xml
-   *
-   * @throws IOException If there is a low-level I/O error.
-   */
-  void setTopQueryResults(IndexReader reader, String query, String[] ids, String[] ex) throws IOException {
-    if (ids == null) {
-      ids = new String[0];
-    }
-    if (ex == null) {
-      ex = new String[0];
-    }
-
-    Map<String, ElevationObj> elev = elevationCache.get(reader);
-    if (elev == null) {
-      elev = new HashMap<>();
-      elevationCache.put(reader, elev);
-    }
-    ElevationObj obj = new ElevationObj(query, Arrays.asList(ids), Arrays.asList(ex));
-    elev.put(obj.analyzed, obj);
-  }
-
-  String getAnalyzedQuery(String query) throws IOException {
-    if (analyzer == null) {
-      return query;
-    }
-    StringBuilder norm = new StringBuilder();
-    try (TokenStream tokens = analyzer.tokenStream("", query)) {
-      tokens.reset();
-
-      CharTermAttribute termAtt = tokens.addAttribute(CharTermAttribute.class);
-      while (tokens.incrementToken()) {
-        norm.append(termAtt.buffer(), 0, termAtt.length());
-      }
-      tokens.end();
-      return norm.toString();
+  private boolean parseMatchPolicy(String matchString) {
+    if (matchString == null) {
+      return DEFAULT_SUBSET_MATCH;
+    } else if (matchString.equalsIgnoreCase("exact")) {
+      return false;
+    } else if (matchString.equalsIgnoreCase("subset")) {
+      return true;
+    } else {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+          "invalid value \"" + matchString + "\" for query match attribute");
     }
   }
 
@@ -371,125 +447,126 @@ public class QueryElevationComponent extends SearchComponent implements SolrCore
 
   @Override
   public void prepare(ResponseBuilder rb) throws IOException {
-    SolrQueryRequest req = rb.req;
-    SolrParams params = req.getParams();
-    // A runtime param can skip 
-    if (!params.getBool(QueryElevationParams.ENABLE, true)) {
+    if (!initialized || !rb.req.getParams().getBool(QueryElevationParams.ENABLE, true)) {
       return;
     }
 
-    boolean exclusive = params.getBool(QueryElevationParams.EXCLUSIVE, false);
-    // A runtime parameter can alter the config value for forceElevation
-    boolean force = params.getBool(QueryElevationParams.FORCE_ELEVATION, forceElevation);
-    boolean markExcludes = params.getBool(QueryElevationParams.MARK_EXCLUDES, false);
-    String boostStr = params.get(QueryElevationParams.IDS);
-    String exStr = params.get(QueryElevationParams.EXCLUDE);
+    Elevation elevation = getElevation(rb);
+    if (elevation != null) {
+      setQuery(rb, elevation);
+      setSort(rb, elevation);
+    }
 
-    Query query = rb.getQuery();
+    if (rb.isDebug() && rb.isDebugQuery()) {
+      addDebugInfo(rb, elevation);
+    }
+  }
+
+  @Override
+  public void process(ResponseBuilder rb) throws IOException {
+    // Do nothing -- the real work is modifying the input query
+  }
+
+  protected Elevation getElevation(ResponseBuilder rb) {
     SolrParams localParams = rb.getQparser().getLocalParams();
-    String qstr = localParams == null ? rb.getQueryString() : localParams.get(QueryParsing.V);
-    if (query == null || qstr == null) {
-      return;
+    String queryString = localParams == null ? rb.getQueryString() : localParams.get(QueryParsing.V);
+    if (queryString == null || rb.getQuery() == null) {
+      return null;
     }
 
-    ElevationObj booster = null;
+    SolrParams params = rb.req.getParams();
+    String paramElevatedIds = params.get(QueryElevationParams.IDS);
+    String paramExcludedIds = params.get(QueryElevationParams.EXCLUDE);
     try {
-      if(boostStr != null || exStr != null) {
-        List<String> boosts = (boostStr != null) ? StrUtils.splitSmart(boostStr,",", true) : new ArrayList<String>(0);
-        List<String> excludes = (exStr != null) ? StrUtils.splitSmart(exStr, ",", true) : new ArrayList<String>(0);
-        booster = new ElevationObj(qstr, boosts, excludes);
+      if (paramElevatedIds != null || paramExcludedIds != null) {
+        List<String> elevatedIds = paramElevatedIds != null ? StrUtils.splitSmart(paramElevatedIds,",", true) : Collections.emptyList();
+        List<String> excludedIds = paramExcludedIds != null ? StrUtils.splitSmart(paramExcludedIds, ",", true) : Collections.emptyList();
+        return new ElevationBuilder().addElevatedIds(elevatedIds).addExcludedIds(excludedIds).build();
       } else {
-        IndexReader reader = req.getSearcher().getIndexReader();
-        qstr = getAnalyzedQuery(qstr);
-        booster = getElevationMap(reader, req.getCore()).get(qstr);
+        IndexReader reader = rb.req.getSearcher().getIndexReader();
+        return getElevationProvider(reader, rb.req.getCore()).getElevationForQuery(queryString);
       }
-    } catch (Exception ex) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
-          "Error loading elevation", ex);
+    } catch (Exception e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error loading elevation", e);
     }
+  }
 
-    if (booster != null) {
-      rb.req.getContext().put(BOOSTED, booster.ids);
-      rb.req.getContext().put(BOOSTED_PRIORITY, booster.priority);
-
-      // Change the query to insert forced documents
-      if (exclusive == true) {
-        //we only want these results
-        rb.setQuery(new BoostQuery(booster.include, 0f));
-      } else {
-        BooleanQuery.Builder newq = new BooleanQuery.Builder();
-        newq.add(query, BooleanClause.Occur.SHOULD);
-        newq.add(new BoostQuery(booster.include, 0f), BooleanClause.Occur.SHOULD);
-        if (booster.exclude != null) {
-          if (markExcludes == false) {
-            for (TermQuery tq : booster.exclude) {
-              newq.add(new BooleanClause(tq, BooleanClause.Occur.MUST_NOT));
-            }
-          } else {
-            //we are only going to mark items as excluded, not actually exclude them.  This works
-            //with the EditorialMarkerFactory
-            rb.req.getContext().put(EXCLUDED, booster.excludeIds);
+  private void setQuery(ResponseBuilder rb, Elevation elevation) {
+    rb.req.getContext().put(BOOSTED, elevation.elevatedIds);
+
+    // Change the query to insert forced documents
+    SolrParams params = rb.req.getParams();
+    if (params.getBool(QueryElevationParams.EXCLUSIVE, false)) {
+      // We only want these elevated results
+      rb.setQuery(new BoostQuery(elevation.includeQuery, 0f));
+    } else {
+      BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder();
+      queryBuilder.add(rb.getQuery(), BooleanClause.Occur.SHOULD);
+      queryBuilder.add(new BoostQuery(elevation.includeQuery, 0f), BooleanClause.Occur.SHOULD);
+      if (elevation.excludeQueries != null) {
+        if (params.getBool(QueryElevationParams.MARK_EXCLUDES, false)) {
+          // We are only going to mark items as excluded, not actually exclude them.
+          // This works with the EditorialMarkerFactory.
+          rb.req.getContext().put(EXCLUDED, elevation.excludedIds);
+        } else {
+          for (TermQuery tq : elevation.excludeQueries) {
+            queryBuilder.add(tq, BooleanClause.Occur.MUST_NOT);
           }
         }
-        rb.setQuery(newq.build());
       }
+      rb.setQuery(queryBuilder.build());
+    }
+  }
 
-      ElevationComparatorSource comparator = new ElevationComparatorSource(booster);
-      // if the sort is 'score desc' use a custom sorting method to 
-      // insert documents in their proper place 
-      SortSpec sortSpec = rb.getSortSpec();
-      if (sortSpec.getSort() == null) {
-        sortSpec.setSortAndFields(new Sort(new SortField[]{
-              new SortField("_elevate_", comparator, true),
-              new SortField(null, SortField.Type.SCORE, false)
-            }),
-          Arrays.asList(new SchemaField[2]));
-      } else {
-        // Check if the sort is based on score
-        SortSpec modSortSpec = this.modifySortSpec(sortSpec, force, comparator);
-        if (null != modSortSpec) {
-          rb.setSortSpec(modSortSpec);
-        }
-      }
+  private void setSort(ResponseBuilder rb, Elevation elevation) throws IOException {
+    if (elevation.elevatedIds.isEmpty()) {
+      return;
+    }
+    boolean forceElevation = rb.req.getParams().getBool(QueryElevationParams.FORCE_ELEVATION, this.forceElevation);
+    boolean useConfigured = rb.req.getParams().getBool(QueryElevationParams.USE_CONFIGURED_ELEVATED_ORDER, this.useConfiguredElevatedOrder);
+    final IntIntHashMap elevatedWithPriority = getBoostDocs(rb.req.getSearcher(), elevation.elevatedIds, rb.req.getContext());
+    ElevationComparatorSource comparator = new ElevationComparatorSource(elevatedWithPriority, useConfigured);
+    setSortSpec(rb, forceElevation, comparator);
+    setGroupingSpec(rb, forceElevation, comparator);
+  }
 
-      // alter the sorting in the grouping specification if there is one
-      GroupingSpecification groupingSpec = rb.getGroupingSpec();
-      if(groupingSpec != null) {
-        SortSpec groupSortSpec = groupingSpec.getGroupSortSpec();
-        SortSpec modGroupSortSpec = this.modifySortSpec(groupSortSpec, force, comparator);
-        if (modGroupSortSpec != null) {
-          groupingSpec.setGroupSortSpec(modGroupSortSpec);
-        }
-        SortSpec withinGroupSortSpec = groupingSpec.getWithinGroupSortSpec();
-        SortSpec modWithinGroupSortSpec = this.modifySortSpec(withinGroupSortSpec, force, comparator);
-        if (modWithinGroupSortSpec != null) {
-          groupingSpec.setWithinGroupSortSpec(modWithinGroupSortSpec);
-        }
+  private void setSortSpec(ResponseBuilder rb, boolean forceElevation, ElevationComparatorSource comparator) {
+    // if the sort is 'score desc' use a custom sorting method to
+    // insert documents in their proper place
+    SortSpec sortSpec = rb.getSortSpec();
+    if (sortSpec.getSort() == null) {
+      sortSpec.setSortAndFields(
+              new Sort(
+                      new SortField("_elevate_", comparator, true),
+                      new SortField(null, SortField.Type.SCORE, false)),
+              Arrays.asList(new SchemaField[2]));
+    } else {
+      // Check if the sort is based on score
+      SortSpec modSortSpec = this.modifySortSpec(sortSpec, forceElevation, comparator);
+      if (null != modSortSpec) {
+        rb.setSortSpec(modSortSpec);
       }
     }
+  }
 
-    // Add debugging information
-    if (rb.isDebug()) {
-      List<String> match = null;
-      if (booster != null) {
-        // Extract the elevated terms into a list
-        match = new ArrayList<>(booster.priority.size());
-        for (Object o : booster.include.clauses()) {
-          TermQuery tq = (TermQuery) ((BooleanClause) o).getQuery();
-          match.add(tq.getTerm().text());
-        }
+  private void setGroupingSpec(ResponseBuilder rb, boolean forceElevation, ElevationComparatorSource comparator) {
+    // alter the sorting in the grouping specification if there is one
+    GroupingSpecification groupingSpec = rb.getGroupingSpec();
+    if(groupingSpec != null) {
+      SortSpec groupSortSpec = groupingSpec.getGroupSortSpec();
+      SortSpec modGroupSortSpec = this.modifySortSpec(groupSortSpec, forceElevation, comparator);
+      if (modGroupSortSpec != null) {
+        groupingSpec.setGroupSortSpec(modGroupSortSpec);
       }
-
-      SimpleOrderedMap<Object> dbg = new SimpleOrderedMap<>();
-      dbg.add("q", qstr);
-      dbg.add("match", match);
-      if (rb.isDebugQuery()) {
-        rb.addDebugInfo("queryBoosting", dbg);
+      SortSpec withinGroupSortSpec = groupingSpec.getWithinGroupSortSpec();
+      SortSpec modWithinGroupSortSpec = this.modifySortSpec(withinGroupSortSpec, forceElevation, comparator);
+      if (modWithinGroupSortSpec != null) {
+        groupingSpec.setWithinGroupSortSpec(modWithinGroupSortSpec);
       }
     }
   }
 
-  private SortSpec modifySortSpec(SortSpec current, boolean force, ElevationComparatorSource comparator) {
+  private SortSpec modifySortSpec(SortSpec current, boolean forceElevation, ElevationComparatorSource comparator) {
     boolean modify = false;
     SortField[] currentSorts = current.getSort().getSort();
     List<SchemaField> currentFields = current.getSchemaFields();
@@ -498,7 +575,7 @@ public class QueryElevationComponent extends SearchComponent implements SolrCore
     List<SchemaField> fields = new ArrayList<>(currentFields.size() + 1);
 
     // Perhaps force it to always sort by score
-    if (force && currentSorts[0].getType() != SortField.Type.SCORE) {
+    if (forceElevation && currentSorts[0].getType() != SortField.Type.SCORE) {
       sorts.add(new SortField("_elevate_", comparator, true));
       fields.add(null);
       modify = true;
@@ -513,84 +590,81 @@ public class QueryElevationComponent extends SearchComponent implements SolrCore
       sorts.add(sf);
       fields.add(currentFields.get(i));
     }
-    if (modify) {
-      SortSpec newSpec = new SortSpec(new Sort(sorts.toArray(new SortField[sorts.size()])),
-                                      fields,
-                                      current.getCount(),
-                                      current.getOffset());
-      return newSpec;
+    return modify ?
+            new SortSpec(new Sort(sorts.toArray(new SortField[sorts.size()])),
+                    fields,
+                    current.getCount(),
+                    current.getOffset())
+            : null;
+  }
+
+  private void addDebugInfo(ResponseBuilder rb, Elevation elevation) {
+    List<String> match = null;
+    if (elevation != null) {
+      // Extract the elevated terms into a list
+      match = new ArrayList<>(elevation.includeQuery.clauses().size());
+      for (BooleanClause clause : elevation.includeQuery.clauses()) {
+        TermQuery tq = (TermQuery) clause.getQuery();
+        match.add(tq.getTerm().text());
+      }
     }
-    return null;
+    SimpleOrderedMap<Object> dbg = new SimpleOrderedMap<>();
+    dbg.add("q", rb.getQueryString());
+    dbg.add("match", match);
+    rb.addDebugInfo("queryBoosting", dbg);
   }
 
+  //---------------------------------------------------------------------------------
+  // Boosted docs helper
+  //---------------------------------------------------------------------------------
 
-  public static IntIntHashMap getBoostDocs(SolrIndexSearcher indexSearcher, Map<BytesRef, Integer>boosted, Map context) throws IOException {
+  /**
+   * Resolves a set of boosted docs by uniqueKey to a map of docIds mapped to a priority value &gt; 0.
+   * @param indexSearcher the SolrIndexSearcher; required
+   * @param boosted are the set of uniqueKey values to be boosted in priority order.  If null; returns null.
+   * @param context the {@link SolrQueryRequest#getContext()} or null if none.  We'll cache our results here.
+   */
+  //TODO consider simplifying to remove "boosted" arg which can be looked up in context via BOOSTED key?
+  public static IntIntHashMap getBoostDocs(SolrIndexSearcher indexSearcher, Set<BytesRef> boosted, Map context) throws IOException {
 
     IntIntHashMap boostDocs = null;
 
-    if(boosted != null) {
+    if (boosted != null) {
 
-      //First see if it's already in the request context. Could have been put there
-      //by another caller.
-      if(context != null) {
+      //First see if it's already in the request context. Could have been put there by another caller.
+      if (context != null) {
         boostDocs = (IntIntHashMap) context.get(BOOSTED_DOCIDS);
+        if (boostDocs != null) {
+          return boostDocs;
+        }
       }
 
-      if(boostDocs != null) {
-        return boostDocs;
-      }
       //Not in the context yet so load it.
-
-      SchemaField idField = indexSearcher.getSchema().getUniqueKeyField();
-      String fieldName = idField.getName();
-      HashSet<BytesRef> localBoosts = new HashSet<>(boosted.size()*2);
-      Iterator<BytesRef> boostedIt = boosted.keySet().iterator();
-      while(boostedIt.hasNext()) {
-        localBoosts.add(boostedIt.next());
-      }
-
-      boostDocs = new IntIntHashMap(boosted.size());
-
-      List<LeafReaderContext>leaves = indexSearcher.getTopReaderContext().leaves();
-      PostingsEnum postingsEnum = null;
-      for(LeafReaderContext leaf : leaves) {
-        LeafReader reader = leaf.reader();
-        int docBase = leaf.docBase;
-        Bits liveDocs = reader.getLiveDocs();
-        Terms terms = reader.terms(fieldName);
-        TermsEnum termsEnum = terms.iterator();
-        Iterator<BytesRef> it = localBoosts.iterator();
-        while(it.hasNext()) {
-          BytesRef ref = it.next();
-          if(termsEnum.seekExact(ref)) {
-            postingsEnum = termsEnum.postings(postingsEnum);
-            int doc = postingsEnum.nextDoc();
-            while (doc != PostingsEnum.NO_MORE_DOCS && liveDocs != null && liveDocs.get(doc) == false) {
-              doc = postingsEnum.nextDoc();
-            }
-            if(doc != PostingsEnum.NO_MORE_DOCS) {
-              //Found the document.
-              int p = boosted.get(ref);
-              boostDocs.put(doc+docBase, p);
-              it.remove();
-            }
-          }
+      boostDocs = new IntIntHashMap(boosted.size()); // docId to boost
+      int priority = boosted.size() + 1; // the corresponding priority for each boosted key (starts at this; decrements down)
+      for (BytesRef uniqueKey : boosted) {
+        priority--; // therefore first == bosted.size(); last will be 1
+        long segAndId = indexSearcher.lookupId(uniqueKey); // higher 32 bits == segment ID, low 32 bits == doc ID
+        if (segAndId == -1) { // not found
+          continue;
         }
+        int seg = (int) (segAndId >> 32);
+        int localDocId = (int) segAndId;
+        final IndexReaderContext indexReaderContext = indexSearcher.getTopReaderContext().children().get(seg);
+        int docId = indexReaderContext.docBaseInParent + localDocId;
+        boostDocs.put(docId, priority);
       }
+      assert priority == 1; // the last priority (lowest)
     }
 
-    if(context != null) {
+    if (context != null) {
+      //noinspection unchecked
       context.put(BOOSTED_DOCIDS, boostDocs);
     }
 
     return boostDocs;
   }
 
-  @Override
-  public void process(ResponseBuilder rb) throws IOException {
-    // Do nothing -- the real work is modifying the input query
-  }
-
   //---------------------------------------------------------------------------------
   // SolrInfoBean
   //---------------------------------------------------------------------------------
@@ -600,101 +674,461 @@ public class QueryElevationComponent extends SearchComponent implements SolrCore
     return "Query Boosting -- boost particular documents for a given query";
   }
 
-  class ElevationComparatorSource extends FieldComparatorSource {
-  private QueryElevationComponent.ElevationObj elevations;
-  private SentinelIntSet ordSet; //the key half of the map
-  private BytesRef[] termValues;//the value half of the map
+  //---------------------------------------------------------------------------------
+  // Overrides
+  //---------------------------------------------------------------------------------
 
-  public ElevationComparatorSource(final QueryElevationComponent.ElevationObj elevations) {
-    this.elevations = elevations;
-    int size = elevations.ids.size();
-    ordSet = new SentinelIntSet(size, -1);
-    termValues = new BytesRef[ordSet.keys.length];
+  /**
+   * Creates the {@link ElevationProvider} to set during configuration loading. The same instance will be used later
+   * when elevating results for queries.
+   *
+   * @param queryAnalyzer to analyze and tokenize the query.
+   * @param elevationBuilderMap map of all {@link ElevatingQuery} and their corresponding {@link ElevationBuilder}.
+   * @return The created {@link ElevationProvider}.
+   */
+  protected ElevationProvider createElevationProvider(Analyzer queryAnalyzer, Map<ElevatingQuery, ElevationBuilder> elevationBuilderMap) {
+    return new MapElevationProvider(elevationBuilderMap);
   }
 
-  @Override
-  public FieldComparator<Integer> newComparator(String fieldname, final int numHits, int sortPos, boolean reversed) {
-    return new SimpleFieldComparator<Integer>() {
-      private final int[] values = new int[numHits];
-      private int bottomVal;
-      private int topVal;
-      private PostingsEnum postingsEnum;
-      Set<String> seen = new HashSet<>(elevations.ids.size());
-
-      @Override
-      public int compare(int slot1, int slot2) {
-        return values[slot1] - values[slot2];  // values will be small enough that there is no overflow concern
+  //---------------------------------------------------------------------------------
+  // Query analysis and tokenization
+  //---------------------------------------------------------------------------------
+
+  /**
+   * Analyzes the provided query string and returns a space concatenation of the analyzed tokens.
+   */
+  public String analyzeQuery(String query) {
+    //split query terms with analyzer then join
+    StringBuilder norm = new StringBuilder();
+    try (TokenStream tokens = queryAnalyzer.tokenStream("", query)) {
+      tokens.reset();
+      CharTermAttribute termAtt = tokens.addAttribute(CharTermAttribute.class);
+      while (tokens.incrementToken()) {
+        norm.append(termAtt);
+      }
+      tokens.end();
+    } catch (IOException e) {
+      Throwables.propagate(e);
+    }
+    return norm.toString();
+  }
+
+  //---------------------------------------------------------------------------------
+  // Testing
+  //---------------------------------------------------------------------------------
+
+  /**
+   * Helpful for testing without loading config.xml.
+   *
+   * @param reader      The {@link org.apache.lucene.index.IndexReader}.
+   * @param queryString The query for which to elevate some documents. If the query has already been defined an
+   *                    elevation, this method overwrites it.
+   * @param subsetMatch <code>true</code> for query subset match; <code>false</code> for query exact match.
+   * @param elevatedIds The readable ids of the documents to set as top results for the provided query.
+   * @param excludedIds The readable ids of the document to exclude from results for the provided query.
+   * @throws java.io.IOException If there is a low-level I/O error.
+   */
+  @VisibleForTesting
+  void setTopQueryResults(IndexReader reader, String queryString, boolean subsetMatch,
+                          String[] elevatedIds, String[] excludedIds) throws IOException {
+    clearElevationProviderCache();
+    ElevatingQuery elevatingQuery = new ElevatingQuery(queryString, subsetMatch);
+    ElevationBuilder elevationBuilder = new ElevationBuilder();
+    elevationBuilder.addElevatedIds(elevatedIds == null ? Collections.emptyList() : Arrays.asList(elevatedIds));
+    elevationBuilder.addExcludedIds(excludedIds == null ? Collections.emptyList() : Arrays.asList(excludedIds));
+    Map<ElevatingQuery, ElevationBuilder> elevationBuilderMap = ImmutableMap.of(elevatingQuery, elevationBuilder);
+    synchronized (elevationProviderCache) {
+      elevationProviderCache.computeIfAbsent(reader, k -> createElevationProvider(queryAnalyzer, elevationBuilderMap));
+    }
+  }
+
+  @VisibleForTesting
+  void clearElevationProviderCache() {
+    synchronized (elevationProviderCache) {
+        elevationProviderCache.clear();
+    }
+  }
+
+  //---------------------------------------------------------------------------------
+  // Exception
+  //---------------------------------------------------------------------------------
+
+  private static class InitializationException extends Exception {
+
+    private final InitializationExceptionCause exceptionCause;
+
+    InitializationException(String message, InitializationExceptionCause exceptionCause) {
+      super(message);
+      this.exceptionCause = exceptionCause;
+    }
+  }
+
+  protected enum InitializationExceptionCause {
+    /**
+     * The component parameter {@link #FIELD_TYPE} defines an unknown field type.
+     */
+    UNKNOWN_FIELD_TYPE,
+    /**
+     * This component requires the schema to have a uniqueKeyField, which it does not have.
+     */
+    MISSING_UNIQUE_KEY_FIELD,
+    /**
+     * Missing component parameter {@link #CONFIG_FILE} - it has to define the path to the elevation configuration file (e.g. elevate.xml).
+     */
+    NO_CONFIG_FILE_DEFINED,
+    /**
+     * The elevation configuration file (e.g. elevate.xml) cannot be found, or is defined in both conf/ and data/ directories.
+     */
+    MISSING_CONFIG_FILE,
+    /**
+     * The elevation configuration file (e.g. elevate.xml) is empty.
+     */
+    EMPTY_CONFIG_FILE,
+    /**
+     * Unclassified exception cause.
+     */
+    OTHER,
+  }
+
+  //---------------------------------------------------------------------------------
+  // Elevation classes
+  //---------------------------------------------------------------------------------
+
+  /**
+   * Provides the elevations defined for queries.
+   */
+  protected interface ElevationProvider {
+    /**
+     * Gets the elevation associated to the provided query.
+     * <p>
+     * By contract and by design, only one elevation may be associated
+     * to a given query (this can be safely verified by an assertion).
+     *
+     * @param queryString The query string (not {@link #analyzeQuery(String) analyzed} yet,
+     *              this {@link ElevationProvider} is in charge of analyzing it).
+     * @return The elevation associated with the query; or <code>null</code> if none.
+     */
+    Elevation getElevationForQuery(String queryString);
+
+    /**
+     * Gets the number of query elevations in this {@link ElevationProvider}.
+     */
+    @VisibleForTesting
+    int size();
+  }
+
+  /**
+   * {@link ElevationProvider} that returns no elevation.
+   */
+  @SuppressWarnings("WeakerAccess")
+  protected static final ElevationProvider NO_OP_ELEVATION_PROVIDER = new ElevationProvider() {
+    @Override
+    public Elevation getElevationForQuery(String queryString) {
+      return null;
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+  };
+
+  /**
+   * Simple query exact match {@link ElevationProvider}.
+   * <p>
+   * It does not support subset matching (see {@link #parseMatchPolicy(String)}).
+   * <p>
+   * Immutable.
+   */
+  @SuppressWarnings("WeakerAccess")
+  protected class MapElevationProvider implements ElevationProvider {
+
+    private final Map<String, Elevation> elevationMap;
+
+    public MapElevationProvider(Map<ElevatingQuery, ElevationBuilder> elevationBuilderMap) {
+      elevationMap = buildElevationMap(elevationBuilderMap);
+    }
+
+    private Map<String, Elevation> buildElevationMap(Map<ElevatingQuery, ElevationBuilder> elevationBuilderMap) {
+      Map<String, Elevation> elevationMap = Maps.newHashMapWithExpectedSize(elevationBuilderMap.size());
+      for (Map.Entry<ElevatingQuery, ElevationBuilder> entry : elevationBuilderMap.entrySet()) {
+        ElevatingQuery elevatingQuery = entry.getKey();
+        if (elevatingQuery.subsetMatch) {
+          throw new UnsupportedOperationException("Subset matching is not supported by " + getClass().getName());
+        }
+        String analyzedQuery = analyzeQuery(elevatingQuery.queryString);
+        Elevation elevation = entry.getValue().build();
+        Elevation duplicateElevation = elevationMap.put(analyzedQuery, elevation);
+        if (duplicateElevation != null) {
+          throw new IllegalArgumentException("Duplicate elevation for query.  Analyzed: \"" + analyzedQuery + "\"" +
+              " Original: \"" + elevatingQuery.queryString + "\"");
+        }
+      }
+      return Collections.unmodifiableMap(elevationMap);
+    }
+
+    @Override
+    public Elevation getElevationForQuery(String queryString) {
+      String analyzedQuery = analyzeQuery(queryString);
+      return elevationMap.get(analyzedQuery);
+    }
+
+    @Override
+    public int size() {
+      return elevationMap.size();
+    }
+  }
+
+  /**
+   * Query triggering elevation.
+   */
+  @SuppressWarnings("WeakerAccess")
+  protected static class ElevatingQuery {
+
+    public final String queryString;
+    public final boolean subsetMatch;
+
+    /**
+     * @param queryString The query to elevate documents for (not the analyzed form).
+     * @param subsetMatch Whether to match a subset of query terms.
+     */
+    protected ElevatingQuery(String queryString, boolean subsetMatch) throws IOException {
+      this.queryString = queryString;
+      this.subsetMatch = subsetMatch;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof ElevatingQuery)) {
+        return false;
+      }
+      ElevatingQuery eq = (ElevatingQuery) o;
+      return queryString.equals(eq.queryString) && subsetMatch == eq.subsetMatch;
+    }
+
+    @Override
+    public int hashCode() {
+      return queryString.hashCode() + (subsetMatch ? 1 : 0);
+    }
+  }
+
+  /**
+   * Builds an {@link Elevation}. This class is used to start defining query elevations, but allowing the merge of
+   * multiple elevations for the same query.
+   */
+  @SuppressWarnings("WeakerAccess")
+  public class ElevationBuilder {
+
+    /**
+     * The ids of the elevated documents that should appear on top of search results; can be <code>null</code>.
+     * The order is retained.
+     */
+    private LinkedHashSet<BytesRef> elevatedIds;
+    /**
+     * The ids of the excluded documents that should not appear in search results; can be <code>null</code>.
+     */
+    private Set<BytesRef> excludedIds;
+
+    // for temporary/transient use when adding an elevated or excluded ID
+    private final BytesRefBuilder scratch = new BytesRefBuilder();
+
+    public ElevationBuilder addElevatedIds(List<String> ids) {
+      if (elevatedIds == null) {
+        elevatedIds = new LinkedHashSet<>(Math.max(10, ids.size()));
+      }
+      for (String id : ids) {
+        elevatedIds.add(toBytesRef(id));
       }
+      return this;
+    }
 
-      @Override
-      public void setBottom(int slot) {
-        bottomVal = values[slot];
+    public ElevationBuilder addExcludedIds(Collection<String> ids) {
+      if (excludedIds == null) {
+        excludedIds = new LinkedHashSet<>(Math.max(10, ids.size()));
+      }
+      for (String id : ids) {
+        excludedIds.add(toBytesRef(id));
       }
+      return this;
+    }
 
-      @Override
-      public void setTopValue(Integer value) {
-        topVal = value.intValue();
+    public BytesRef toBytesRef(String id) {
+      uniqueKeyField.getType().readableToIndexed(id, scratch);
+      return scratch.toBytesRef();
+    }
+
+    public ElevationBuilder merge(ElevationBuilder elevationBuilder) {
+      if (elevatedIds == null) {
+        elevatedIds = elevationBuilder.elevatedIds;
+      } else if (elevationBuilder.elevatedIds != null) {
+        elevatedIds.addAll(elevationBuilder.elevatedIds);
+      }
+      if (excludedIds == null) {
+        excludedIds = elevationBuilder.excludedIds;
+      } else if (elevationBuilder.excludedIds != null) {
+        excludedIds.addAll(elevationBuilder.excludedIds);
       }
+      return this;
+    }
 
-      private int docVal(int doc) {
-        if (ordSet.size() > 0) {
-          int slot = ordSet.find(doc);
-          if (slot >= 0) {
-            BytesRef id = termValues[slot];
-            Integer prio = elevations.priority.get(id);
-            return prio == null ? 0 : prio.intValue();
-          }
+    public Elevation build() {
+      return new Elevation(elevatedIds, excludedIds, uniqueKeyField.getName());
+    }
+
+  }
+
+  /**
+   * Elevation of some documents in search results, with potential exclusion of others.
+   */
+  protected static class Elevation {
+
+    private static final BooleanQuery EMPTY_QUERY = new BooleanQuery.Builder().build();
+
+    public final Set<BytesRef> elevatedIds; // in configured order; not null
+    public final BooleanQuery includeQuery; // not null
+    public final Set<BytesRef> excludedIds; // not null
+    //just keep the term query, b/c we will not always explicitly exclude the item based on markExcludes query time param
+    public final TermQuery[] excludeQueries; //may be null
+
+    /**
+     * Constructs an elevation.
+     * @param elevatedIds           The ids of the elevated documents that should appear on top of search results; can be <code>null</code>.
+     *                              In configured order.
+     * @param excludedIds           The ids of the excluded documents that should not appear in search results; can be <code>null</code>.
+     * @param queryFieldName        The field name to use to create query terms.
+     */
+    public Elevation(Set<BytesRef> elevatedIds, Set<BytesRef> excludedIds,
+                      String queryFieldName) {
+      if (elevatedIds == null || elevatedIds.isEmpty()) {
+        includeQuery = EMPTY_QUERY;
+        this.elevatedIds = Collections.emptySet();
+      } else {
+        this.elevatedIds = new LinkedHashSet<>(elevatedIds);
+        BooleanQuery.Builder includeQueryBuilder = new BooleanQuery.Builder();
+        for (BytesRef elevatedId : elevatedIds) {
+          includeQueryBuilder.add(new TermQuery(new Term(queryFieldName, elevatedId)), BooleanClause.Occur.SHOULD);
         }
-        return 0;
+        includeQuery = includeQueryBuilder.build();
       }
 
-      @Override
-      public int compareBottom(int doc) {
-        return bottomVal - docVal(doc);
+      if (excludedIds == null || excludedIds.isEmpty()) {
+        this.excludedIds = Collections.emptySet();
+        excludeQueries = null;
+      } else {
+        this.excludedIds = ImmutableSet.copyOf(excludedIds);
+        List<TermQuery> excludeQueriesBuilder = new ArrayList<>(excludedIds.size());
+        for (BytesRef excludedId : excludedIds) {
+          excludeQueriesBuilder.add(new TermQuery(new Term(queryFieldName, excludedId)));
+        }
+        excludeQueries = excludeQueriesBuilder.toArray(new TermQuery[excludeQueriesBuilder.size()]);
       }
+    }
+
+    @Override
+    public String toString() {
+      return "{elevatedIds=" + Collections2.transform(elevatedIds, BytesRef::utf8ToString) +
+          ", excludedIds=" + Collections2.transform(excludedIds, BytesRef::utf8ToString) + "}";
+    }
+  }
 
-      @Override
-      public void copy(int slot, int doc) {
-        values[slot] = docVal(doc);
+  /** Elevates certain docs to the top. */
+  private class ElevationComparatorSource extends FieldComparatorSource {
+
+    private final IntIntHashMap elevatedWithPriority;
+    private final boolean useConfiguredElevatedOrder;
+    private final int[] sortedElevatedDocIds;
+
+    private ElevationComparatorSource(IntIntHashMap elevatedWithPriority, boolean useConfiguredElevatedOrder) {
+      this.elevatedWithPriority = elevatedWithPriority;
+      this.useConfiguredElevatedOrder = useConfiguredElevatedOrder;
+
+      // copy elevatedWithPriority keys (doc IDs) into sortedElevatedDocIds, sorted
+      sortedElevatedDocIds = new int[elevatedWithPriority.size()];
+      final Iterator<IntIntCursor> iterator = elevatedWithPriority.iterator();
+      for (int i = 0; i < sortedElevatedDocIds.length; i++) {
+        IntIntCursor next = iterator.next();
+        sortedElevatedDocIds[i] = next.key;
       }
+      assert iterator.hasNext() == false;
+      Arrays.sort(sortedElevatedDocIds);
+    }
 
-      @Override
-      protected void doSetNextReader(LeafReaderContext context) throws IOException {
-        //convert the ids to Lucene doc ids, the ordSet and termValues needs to be the same size as the number of elevation docs we have
-        ordSet.clear();
-        Terms terms = context.reader().terms(idField);
-        if (terms == null) return;
-        TermsEnum termsEnum = terms.iterator();
-        BytesRefBuilder term = new BytesRefBuilder();
-        Bits liveDocs = context.reader().getLiveDocs();
-
-        for (String id : elevations.ids) {
-          term.copyChars(id);
-          if (seen.contains(id) == false  && termsEnum.seekExact(term.get())) {
-            postingsEnum = termsEnum.postings(postingsEnum, PostingsEnum.NONE);
-            int docId = postingsEnum.nextDoc();
-            while (docId != DocIdSetIterator.NO_MORE_DOCS && liveDocs != null && liveDocs.get(docId) == false) {
-              docId = postingsEnum.nextDoc();
+    @Override
+    public FieldComparator<Integer> newComparator(String fieldName, final int numHits, int sortPos, boolean reversed) {
+      return new SimpleFieldComparator<Integer>() {
+        final int[] values = new int[numHits];
+        int bottomVal;
+        int topVal;
+
+        int docBase;
+        boolean hasElevatedDocsThisSegment;
+
+        @Override
+        protected void doSetNextReader(LeafReaderContext context) throws IOException {
+          docBase = context.docBase;
+          // ascertain if hasElevatedDocsThisSegment
+          final int idx = Arrays.binarySearch(sortedElevatedDocIds, docBase);
+          if (idx < 0) {
+            //first doc in segment isn't elevated (typical).  Maybe another is?
+            int nextIdx = -idx - 1;
+            if (nextIdx < sortedElevatedDocIds.length) {
+              int nextElevatedDocId = sortedElevatedDocIds[nextIdx];
+              if (nextElevatedDocId > docBase + context.reader().maxDoc()) {
+                hasElevatedDocsThisSegment = false;
+                return;
+              }
             }
-            if (docId == DocIdSetIterator.NO_MORE_DOCS ) continue;  // must have been deleted
-            termValues[ordSet.put(docId)] = term.toBytesRef();
-            seen.add(id);
-            assert postingsEnum.nextDoc() == DocIdSetIterator.NO_MORE_DOCS;
           }
+          hasElevatedDocsThisSegment = true;
         }
-      }
 
-      @Override
-      public Integer value(int slot) {
-        return values[slot];
-      }
+        @Override
+        public int compare(int slot1, int slot2) {
+          return values[slot1] - values[slot2];  // values will be small enough that there is no overflow concern
+        }
 
-      @Override
-      public int compareTop(int doc) {
-        final int docValue = docVal(doc);
-        return topVal - docValue;  // values will be small enough that there is no overflow concern
-      }
-    };
-  }
+        @Override
+        public void setBottom(int slot) {
+          bottomVal = values[slot];
+        }
+
+        @Override
+        public void setTopValue(Integer value) {
+          topVal = value;
+        }
+
+        private int docVal(int doc) {
+          if (!hasElevatedDocsThisSegment) {
+            assert elevatedWithPriority.containsKey(docBase + doc) == false;
+            return -1;
+          } else if (useConfiguredElevatedOrder) {
+            return elevatedWithPriority.getOrDefault(docBase + doc, -1);
+          } else {
+            return elevatedWithPriority.containsKey(docBase + doc) ? 1 : -1;
+          }
+        }
+
+        @Override
+        public int compareBottom(int doc) {
+          return bottomVal - docVal(doc);
+        }
+
+        @Override
+        public void copy(int slot, int doc) {
+          values[slot] = docVal(doc);
+        }
+
+        @Override
+        public Integer value(int slot) {
+          return values[slot];
+        }
+
+        @Override
+        public int compareTop(int doc) {
+          final int docValue = docVal(doc);
+          return topVal - docValue;  // values will be small enough that there is no overflow concern
+        }
+      };
+    }
   }
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a06256cc/solr/core/src/java/org/apache/solr/response/transform/BaseEditorialTransformer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/transform/BaseEditorialTransformer.java b/solr/core/src/java/org/apache/solr/response/transform/BaseEditorialTransformer.java
index d646ee4..6324d8a 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/BaseEditorialTransformer.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/BaseEditorialTransformer.java
@@ -20,6 +20,8 @@ package org.apache.solr.response.transform;
 import java.util.Set;
 
 import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.schema.FieldType;
 
@@ -47,9 +49,9 @@ public abstract class BaseEditorialTransformer extends DocTransformer {
   @Override
   public void transform(SolrDocument doc, int docid) {
     //this only gets added if QueryElevationParams.MARK_EXCLUDED is true
-    Set<String> ids = getIdSet();
+    Set<BytesRef> ids = getIdSet();
     if (ids != null && ids.isEmpty() == false) {
-      String key = getKey(doc);
+      BytesRef key = getKey(doc);
       doc.setField(name, ids.contains(key));
     } else {
       //if we have no ids, that means we weren't marking, but the user still asked for the field to be added, so just mark everything as false
@@ -57,17 +59,20 @@ public abstract class BaseEditorialTransformer extends DocTransformer {
     }
   }
 
-  protected abstract Set<String> getIdSet();
+  protected abstract Set<BytesRef> getIdSet();
 
-  protected String getKey(SolrDocument doc) {
+  protected BytesRef getKey(SolrDocument doc) {
     Object obj = doc.get(idFieldName);
     if (obj instanceof IndexableField) {
       IndexableField f = (IndexableField) obj;
+      BytesRefBuilder bytesRefBuilder = new BytesRefBuilder();
       Number n = f.numericValue();
       if (n != null) {
-        return ft.readableToIndexed(n.toString());
+        ft.readableToIndexed(n.toString(), bytesRefBuilder);
+      } else {
+        ft.readableToIndexed(f.stringValue(), bytesRefBuilder);
       }
-      return ft.readableToIndexed(f.stringValue());
+      return bytesRefBuilder.get();
     }
     throw new AssertionError("Expected an IndexableField but got: " + obj.getClass());
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a06256cc/solr/core/src/java/org/apache/solr/response/transform/ElevatedMarkerFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/transform/ElevatedMarkerFactory.java b/solr/core/src/java/org/apache/solr/response/transform/ElevatedMarkerFactory.java
index 51f3cff..e5fb414 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/ElevatedMarkerFactory.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/ElevatedMarkerFactory.java
@@ -18,6 +18,7 @@ package org.apache.solr.response.transform;
 
 import java.util.Set;
 
+import org.apache.lucene.util.BytesRef;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.handler.component.QueryElevationComponent;
 import org.apache.solr.request.SolrQueryRequest;
@@ -44,9 +45,10 @@ class MarkTransformer extends BaseEditorialTransformer {
     super(name, idFieldName, ft);
   }
 
+  @SuppressWarnings("unchecked")
   @Override
-  protected Set<String> getIdSet() {
-    return (Set<String>) context.getRequest().getContext().get(QueryElevationComponent.BOOSTED);
+  protected Set<BytesRef> getIdSet() {
+    return (Set<BytesRef>) context.getRequest().getContext().get(QueryElevationComponent.BOOSTED);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a06256cc/solr/core/src/java/org/apache/solr/response/transform/ExcludedMarkerFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/transform/ExcludedMarkerFactory.java b/solr/core/src/java/org/apache/solr/response/transform/ExcludedMarkerFactory.java
index 2d670d7..2036c48 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/ExcludedMarkerFactory.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/ExcludedMarkerFactory.java
@@ -18,6 +18,7 @@ package org.apache.solr.response.transform;
 
 import java.util.Set;
 
+import org.apache.lucene.util.BytesRef;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.handler.component.QueryElevationComponent;
 import org.apache.solr.request.SolrQueryRequest;
@@ -46,9 +47,10 @@ class ExcludedTransformer extends BaseEditorialTransformer {
     super(name, idFieldName, ft);
   }
 
+  @SuppressWarnings("unchecked")
   @Override
-  protected Set<String> getIdSet() {
-    return (Set<String>)context.getRequest().getContext().get(QueryElevationComponent.EXCLUDED);
+  protected Set<BytesRef> getIdSet() {
+    return (Set<BytesRef>)context.getRequest().getContext().get(QueryElevationComponent.EXCLUDED);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a06256cc/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java b/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java
index f7679b0..0c2fb82 100644
--- a/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java
+++ b/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java
@@ -18,6 +18,7 @@ package org.apache.solr.search;
 
 import java.io.IOException;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.IndexSearcher;
@@ -35,7 +36,7 @@ public abstract class AbstractReRankQuery extends RankQuery {
   protected Query mainQuery;
   final protected int reRankDocs;
   final protected Rescorer reRankQueryRescorer;
-  protected Map<BytesRef, Integer> boostedPriority;
+  protected Set<BytesRef> boostedPriority;
 
   public AbstractReRankQuery(Query mainQuery, int reRankDocs, Rescorer reRankQueryRescorer) {
     this.mainQuery = mainQuery;
@@ -54,13 +55,13 @@ public abstract class AbstractReRankQuery extends RankQuery {
     return null;
   }
 
+  @SuppressWarnings("unchecked")
   public TopDocsCollector getTopDocsCollector(int len, QueryCommand cmd, IndexSearcher searcher) throws IOException {
-
     if(this.boostedPriority == null) {
       SolrRequestInfo info = SolrRequestInfo.getRequestInfo();
       if(info != null) {
         Map context = info.getReq().getContext();
-        this.boostedPriority = (Map<BytesRef, Integer>)context.get(QueryElevationComponent.BOOSTED_PRIORITY);
+        this.boostedPriority = (Set<BytesRef>)context.get(QueryElevationComponent.BOOSTED);
       }
     }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a06256cc/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
index d0f8cd4..5a6d48c 100644
--- a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
@@ -24,7 +24,14 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
+import com.carrotsearch.hppc.FloatArrayList;
+import com.carrotsearch.hppc.IntArrayList;
+import com.carrotsearch.hppc.IntIntHashMap;
+import com.carrotsearch.hppc.IntLongHashMap;
+import com.carrotsearch.hppc.cursors.IntIntCursor;
+import com.carrotsearch.hppc.cursors.IntLongCursor;
 import org.apache.commons.lang.StringUtils;
 import org.apache.lucene.codecs.DocValuesProducer;
 import org.apache.lucene.index.DocValues;
@@ -69,13 +76,6 @@ import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.StrField;
 import org.apache.solr.uninverting.UninvertingReader;
 
-import com.carrotsearch.hppc.FloatArrayList;
-import com.carrotsearch.hppc.IntArrayList;
-import com.carrotsearch.hppc.IntIntHashMap;
-import com.carrotsearch.hppc.IntLongHashMap;
-import com.carrotsearch.hppc.cursors.IntIntCursor;
-import com.carrotsearch.hppc.cursors.IntLongCursor;
-
 import static org.apache.solr.common.params.CommonParams.SORT;
 
 /**
@@ -215,7 +215,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
     public String hint;
     private boolean needsScores = true;
     private int nullPolicy;
-    private Map<BytesRef, Integer> boosted;
+    private Set<BytesRef> boosted; // ordered by "priority"
     public static final int NULL_POLICY_IGNORE = 0;
     public static final int NULL_POLICY_COLLAPSE = 1;
     public static final int NULL_POLICY_EXPAND = 2;
@@ -338,11 +338,6 @@ public class CollapsingQParserPlugin extends QParserPlugin {
       }
     }
 
-    private IntIntHashMap getBoostDocs(SolrIndexSearcher indexSearcher, Map<BytesRef, Integer> boosted, Map context) throws IOException {
-      IntIntHashMap boostDocs = QueryElevationComponent.getBoostDocs(indexSearcher, boosted, context);
-      return boostDocs;
-    }
-
     public DelegatingCollector getFilterCollector(IndexSearcher indexSearcher) {
       try {
 
@@ -360,10 +355,10 @@ public class CollapsingQParserPlugin extends QParserPlugin {
         }
 
         if(this.boosted == null && context != null) {
-          this.boosted = (Map<BytesRef, Integer>)context.get(QueryElevationComponent.BOOSTED_PRIORITY);
+          this.boosted = (Set<BytesRef>)context.get(QueryElevationComponent.BOOSTED);
         }
 
-        boostDocsMap = getBoostDocs(searcher, this.boosted, context);
+        boostDocsMap = QueryElevationComponent.getBoostDocs(searcher, this.boosted, context);
         return collectorFactory.getCollector(this.collapseField,
                                              this.groupHeadSelector,
                                              this.sortSpec,

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a06256cc/solr/core/src/java/org/apache/solr/search/ReRankCollector.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/ReRankCollector.java b/solr/core/src/java/org/apache/solr/search/ReRankCollector.java
index 0447053..a1689dd 100644
--- a/solr/core/src/java/org/apache/solr/search/ReRankCollector.java
+++ b/solr/core/src/java/org/apache/solr/search/ReRankCollector.java
@@ -20,9 +20,10 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Map;
+import java.util.Set;
+
 import com.carrotsearch.hppc.IntFloatHashMap;
 import com.carrotsearch.hppc.IntIntHashMap;
-
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.LeafCollector;
@@ -46,7 +47,7 @@ public class ReRankCollector extends TopDocsCollector {
   final private IndexSearcher searcher;
   final private int reRankDocs;
   final private int length;
-  final private Map<BytesRef, Integer> boostedPriority;
+  final private Set<BytesRef> boostedPriority; // order is the "priority"
   final private Rescorer reRankQueryRescorer;
 
 
@@ -55,7 +56,7 @@ public class ReRankCollector extends TopDocsCollector {
       Rescorer reRankQueryRescorer,
       QueryCommand cmd,
       IndexSearcher searcher,
-      Map<BytesRef, Integer> boostedPriority) throws IOException {
+      Set<BytesRef> boostedPriority) throws IOException {
     super(null);
     this.reRankDocs = reRankDocs;
     this.length = length;