private SearchRequestBuilder criteria2builder(ElasticSearchCriteria criteria) {
   String[] indices = criteria.getIndices();
   if (indices == null || indices.length == 0)
     indices = new String[] {indexManager.getIndexName()};
   SearchRequestBuilder srb = client.prepareSearch(indices);
   srb.setTimeout(new TimeValue(10, TimeUnit.SECONDS));
   String[] types = criteria.getTypes();
   if (types != null && types.length > 0) srb.setTypes(types);
   QueryBuilder qb = criteria.getQueryBuilder();
   String query = criteria.getQuery();
   if (qb == null && StringUtils.isBlank(query))
     throw new NullPointerException("queryBuilder is null and queryString is blank");
   if (qb == null && StringUtils.isNotBlank(query)) {
     if (wildcardQueryPattern.matcher(query).matches()) {
       String[] arr = query.split(":", 2);
       qb = QueryBuilders.wildcardQuery(arr[0], arr[1]);
     } else {
       QueryStringQueryBuilder qsqb = new QueryStringQueryBuilder(query);
       qsqb.defaultOperator(Operator.AND);
       qb = qsqb;
     }
   }
   srb.setQuery(qb);
   Map<String, Boolean> sorts = criteria.getSorts();
   for (Map.Entry<String, Boolean> entry : sorts.entrySet())
     srb.addSort(entry.getKey(), entry.getValue() ? SortOrder.DESC : SortOrder.ASC);
   return srb;
 }
  @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);
            }
          }
        });
  }