@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);
            }
          }
        });
  }