protected ShardIterator shards(ClusterState state, ExplainRequest request)
     throws ElasticSearchException {
   return clusterService
       .operationRouting()
       .getShards(
           clusterService.state(),
           request.index(),
           request.getType(),
           request.getId(),
           request.getRouting(),
           request.getPreference());
 }
  private ParsedQuery parseQuery(ExplainRequest request, IndexService indexService) {
    try {
      XContentParser parser = XContentHelper.createParser(request.getSource());
      for (XContentParser.Token token = parser.nextToken();
          token != XContentParser.Token.END_OBJECT;
          token = parser.nextToken()) {
        if (token == XContentParser.Token.FIELD_NAME) {
          String fieldName = parser.currentName();
          if ("query".equals(fieldName)) {
            return indexService.queryParserService().parse(parser);
          } else if ("query_binary".equals(fieldName)) {
            byte[] querySource = parser.binaryValue();
            XContentParser qSourceParser =
                XContentFactory.xContent(querySource).createParser(querySource);
            return indexService.queryParserService().parse(qSourceParser);
          }
        }
      }
    } catch (Exception e) {
      throw new ElasticSearchException("Couldn't parse query from source.", e);
    }

    throw new ElasticSearchException("No query specified");
  }
  @Override
  public void handleRequest(final RestRequest request, final RestChannel channel) {
    final ExplainRequest explainRequest =
        new ExplainRequest(request.param("index"), request.param("type"), request.param("id"));
    explainRequest.parent(request.param("parent"));
    explainRequest.routing(request.param("routing"));
    explainRequest.preference(request.param("preference"));
    String sourceString = request.param("source");
    String queryString = request.param("q");
    if (request.hasContent()) {
      explainRequest.source(request.content(), request.contentUnsafe());
    } else if (sourceString != null) {
      explainRequest.source(new BytesArray(request.param("source")), false);
    } else if (queryString != null) {
      QueryStringQueryBuilder queryStringBuilder = QueryBuilders.queryString(queryString);
      queryStringBuilder.defaultField(request.param("df"));
      queryStringBuilder.analyzer(request.param("analyzer"));
      queryStringBuilder.analyzeWildcard(request.paramAsBoolean("analyze_wildcard", false));
      queryStringBuilder.lowercaseExpandedTerms(
          request.paramAsBoolean("lowercase_expanded_terms", true));
      queryStringBuilder.lenient(request.paramAsBooleanOptional("lenient", null));
      String defaultOperator = request.param("default_operator");
      if (defaultOperator != null) {
        if ("OR".equals(defaultOperator)) {
          queryStringBuilder.defaultOperator(QueryStringQueryBuilder.Operator.OR);
        } else if ("AND".equals(defaultOperator)) {
          queryStringBuilder.defaultOperator(QueryStringQueryBuilder.Operator.AND);
        } else {
          throw new ElasticSearchIllegalArgumentException(
              "Unsupported defaultOperator [" + defaultOperator + "], can either be [OR] or [AND]");
        }
      }

      ExplainSourceBuilder explainSourceBuilder = new ExplainSourceBuilder();
      explainSourceBuilder.query(queryStringBuilder);
      explainRequest.source(explainSourceBuilder);
    }

    client.explain(
        explainRequest,
        new ActionListener<ExplainResponse>() {

          @Override
          public void onResponse(ExplainResponse response) {
            try {
              XContentBuilder builder = restContentBuilder(request);
              builder.startObject();
              builder.field(Fields.OK, response.exists());
              builder.field(Fields.MATCHES, response.match());
              if (response.hasExplanation()) {
                builder.startObject(Fields.EXPLANATION);
                buildExplanation(builder, response.explanation());
                builder.endObject();
              }
              builder.endObject();
              channel.sendResponse(
                  new XContentRestResponse(request, response.exists() ? OK : NOT_FOUND, builder));
            } catch (Exception e) {
              onFailure(e);
            }
          }

          private void buildExplanation(XContentBuilder builder, Explanation explanation)
              throws IOException {
            builder.field(Fields.VALUE, explanation.getValue());
            builder.field(Fields.DESCRIPTION, explanation.getDescription());
            Explanation[] innerExps = explanation.getDetails();
            if (innerExps != null) {
              builder.startArray(Fields.DETAILS);
              for (Explanation exp : innerExps) {
                builder.startObject();
                buildExplanation(builder, exp);
                builder.endObject();
              }
              builder.endArray();
            }
          }

          @Override
          public void onFailure(Throwable e) {
            try {
              channel.sendResponse(new XContentThrowableRestResponse(request, e));
            } catch (IOException e1) {
              logger.error("Failed to send failure response", e1);
            }
          }
        });
  }
  protected ExplainResponse shardOperation(ExplainRequest request, int shardId)
      throws ElasticSearchException {
    IndexService indexService = indicesService.indexService(request.index());
    IndexShard indexShard = indexService.shardSafe(shardId);
    Term uidTerm =
        new Term(UidFieldMapper.NAME, Uid.createUidAsBytes(request.getType(), request.getId()));
    Engine.GetResult result = indexShard.get(new Engine.Get(false, uidTerm));
    if (!result.exists()) {
      return new ExplainResponse(false);
    }

    SearchContext context =
        new SearchContext(
            0,
            new ShardSearchRequest()
                .types(new String[] {request.getType()})
                .filteringAliases(request.getFilteringAlias()),
            null,
            result.searcher(),
            indexService,
            indexShard,
            scriptService);
    SearchContext.setCurrent(context);

    try {
      context.parsedQuery(parseQuery(request, indexService));
      context.preProcess();
      int topLevelDocId = result.docIdAndVersion().docId + result.docIdAndVersion().reader.docBase;
      Explanation explanation;
      if (context.rescore() != null) {
        RescoreSearchContext ctx = context.rescore();
        Rescorer rescorer = ctx.rescorer();
        explanation = rescorer.explain(topLevelDocId, context, ctx);
      } else {
        explanation = context.searcher().explain(context.query(), topLevelDocId);
      }
      if (request.getFields() != null) {
        if (request.getFields().length == 1 && "_source".equals(request.getFields()[0])) {
          request.setFields(null); // Load the _source field
        }
        // Advantage is that we're not opening a second searcher to retrieve the _source. Also
        // because we are working in the same searcher in engineGetResult we can be sure that a
        // doc isn't deleted between the initial get and this call.
        GetResult getResult =
            indexShard
                .getService()
                .get(result, request.getId(), request.getType(), request.getFields());
        return new ExplainResponse(true, explanation, getResult);
      } else {
        return new ExplainResponse(true, explanation);
      }
    } catch (IOException e) {
      throw new ElasticSearchException("Could not explain", e);
    } finally {
      context.release();
      SearchContext.removeCurrent();
    }
  }
 @Override
 protected void resolveRequest(ClusterState state, ExplainRequest request) {
   String concreteIndex = state.metaData().concreteIndex(request.index());
   request.setFilteringAlias(state.metaData().filteringAliases(concreteIndex, request.index()));
   request.index(state.metaData().concreteIndex(request.index()));
 }
 protected ClusterBlockException checkRequestBlock(ClusterState state, ExplainRequest request) {
   return state.blocks().indexBlockedException(ClusterBlockLevel.READ, request.index());
 }
 @Override
 protected void doExecute(ExplainRequest request, ActionListener<ExplainResponse> listener) {
   request.nowInMillis = System.currentTimeMillis();
   super.doExecute(request, listener);
 }
  @Override
  protected ExplainResponse shardOperation(ExplainRequest request, ShardId shardId)
      throws ElasticsearchException {
    IndexService indexService = indicesService.indexServiceSafe(shardId.getIndex());
    IndexShard indexShard = indexService.shardSafe(shardId.id());
    Term uidTerm =
        new Term(UidFieldMapper.NAME, Uid.createUidAsBytes(request.type(), request.id()));
    Engine.GetResult result = indexShard.get(new Engine.Get(false, uidTerm));
    if (!result.exists()) {
      return new ExplainResponse(shardId.getIndex(), request.type(), request.id(), false);
    }

    SearchContext context =
        new DefaultSearchContext(
            0,
            new ShardSearchRequest(request)
                .types(new String[] {request.type()})
                .filteringAliases(request.filteringAlias())
                .nowInMillis(request.nowInMillis),
            null,
            result.searcher(),
            indexService,
            indexShard,
            scriptService,
            pageCacheRecycler,
            bigArrays,
            threadPool.estimatedTimeInMillisCounter());
    SearchContext.setCurrent(context);

    try {
      context.parsedQuery(indexService.queryParserService().parseQuery(request.source()));
      context.preProcess();
      int topLevelDocId = result.docIdAndVersion().docId + result.docIdAndVersion().context.docBase;
      Explanation explanation = context.searcher().explain(context.query(), topLevelDocId);
      for (RescoreSearchContext ctx : context.rescore()) {
        Rescorer rescorer = ctx.rescorer();
        explanation = rescorer.explain(topLevelDocId, context, ctx, explanation);
      }
      if (request.fields() != null
          || (request.fetchSourceContext() != null && request.fetchSourceContext().fetchSource())) {
        // Advantage is that we're not opening a second searcher to retrieve the _source. Also
        // because we are working in the same searcher in engineGetResult we can be sure that a
        // doc isn't deleted between the initial get and this call.
        GetResult getResult =
            indexShard
                .getService()
                .get(
                    result,
                    request.id(),
                    request.type(),
                    request.fields(),
                    request.fetchSourceContext(),
                    false);
        return new ExplainResponse(
            shardId.getIndex(), request.type(), request.id(), true, explanation, getResult);
      } else {
        return new ExplainResponse(
            shardId.getIndex(), request.type(), request.id(), true, explanation);
      }
    } catch (IOException e) {
      throw new ElasticsearchException("Could not explain", e);
    } finally {
      context.close();
      SearchContext.removeCurrent();
    }
  }