@Override
  public void search(Query query, Collector collector) throws IOException {
    // Wrap the caller's collector with various wrappers e.g. those used to siphon
    // matches off for aggregation or to impose a time-limit on collection.
    final boolean timeoutSet = searchContext.timeoutInMillis() != -1;
    final boolean terminateAfterSet =
        searchContext.terminateAfter() != SearchContext.DEFAULT_TERMINATE_AFTER;

    if (timeoutSet) {
      // TODO: change to use our own counter that uses the scheduler in ThreadPool
      // throws TimeLimitingCollector.TimeExceededException when timeout has reached
      collector =
          Lucene.wrapTimeLimitingCollector(
              collector, searchContext.timeEstimateCounter(), searchContext.timeoutInMillis());
    }
    if (terminateAfterSet) {
      // throws Lucene.EarlyTerminationException when given count is reached
      collector =
          Lucene.wrapCountBasedEarlyTerminatingCollector(collector, searchContext.terminateAfter());
    }
    if (currentState == Stage.MAIN_QUERY) {
      if (searchContext.parsedPostFilter() != null) {
        // this will only get applied to the actual search collector and not
        // to any scoped collectors, also, it will only be applied to the main collector
        // since that is where the filter should only work
        final Weight filterWeight =
            createNormalizedWeight(searchContext.parsedPostFilter().query(), false);
        collector = new FilteredCollector(collector, filterWeight);
      }
      if (queryCollectors != null && !queryCollectors.isEmpty()) {
        ArrayList<Collector> allCollectors = new ArrayList<>(queryCollectors.values());
        allCollectors.add(collector);
        collector = MultiCollector.wrap(allCollectors);
      }

      // apply the minimum score after multi collector so we filter aggs as well
      if (searchContext.minimumScore() != null) {
        collector = new MinimumScoreCollector(collector, searchContext.minimumScore());
      }
    }
    super.search(query, collector);
  }
  @Override
  public void search(List<LeafReaderContext> leaves, Weight weight, Collector collector)
      throws IOException {
    final boolean timeoutSet = searchContext.timeoutInMillis() != -1;
    final boolean terminateAfterSet =
        searchContext.terminateAfter() != SearchContext.DEFAULT_TERMINATE_AFTER;

    if (timeoutSet) {
      // TODO: change to use our own counter that uses the scheduler in ThreadPool
      // throws TimeLimitingCollector.TimeExceededException when timeout has reached
      collector =
          Lucene.wrapTimeLimitingCollector(
              collector, searchContext.timeEstimateCounter(), searchContext.timeoutInMillis());
    }
    if (terminateAfterSet) {
      // throws Lucene.EarlyTerminationException when given count is reached
      collector =
          Lucene.wrapCountBasedEarlyTerminatingCollector(collector, searchContext.terminateAfter());
    }
    if (currentState == Stage.MAIN_QUERY) {
      if (searchContext.parsedPostFilter() != null) {
        // this will only get applied to the actual search collector and not
        // to any scoped collectors, also, it will only be applied to the main collector
        // since that is where the filter should only work
        collector = new FilteredCollector(collector, searchContext.parsedPostFilter().filter());
      }
      if (queryCollectors != null && !queryCollectors.isEmpty()) {
        ArrayList<Collector> allCollectors = new ArrayList<>(queryCollectors.values());
        allCollectors.add(collector);
        collector = MultiCollector.wrap(allCollectors);
      }

      // apply the minimum score after multi collector so we filter aggs as well
      if (searchContext.minimumScore() != null) {
        collector = new MinimumScoreCollector(collector, searchContext.minimumScore());
      }
    }

    // we only compute the doc id set once since within a context, we execute the same query
    // always...
    try {
      if (timeoutSet || terminateAfterSet) {
        try {
          super.search(leaves, weight, collector);
        } catch (TimeLimitingCollector.TimeExceededException e) {
          assert timeoutSet : "TimeExceededException thrown even though timeout wasn't set";
          searchContext.queryResult().searchTimedOut(true);
        } catch (Lucene.EarlyTerminationException e) {
          assert terminateAfterSet
              : "EarlyTerminationException thrown even though terminateAfter wasn't set";
          searchContext.queryResult().terminatedEarly(true);
        }
        if (terminateAfterSet && searchContext.queryResult().terminatedEarly() == null) {
          searchContext.queryResult().terminatedEarly(false);
        }
      } else {
        super.search(leaves, weight, collector);
      }
    } finally {
      searchContext.clearReleasables(Lifetime.COLLECTION);
    }
  }