@Override
  public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
    XContentParser parser = parseContext.parser();

    Query query = Queries.newMatchAllQuery();
    Filter filter = null;
    boolean filterFound = false;
    float boost = 1.0f;
    FilterCachingPolicy cache = parseContext.autoFilterCachePolicy();
    HashedBytesRef cacheKey = null;
    String queryName = null;

    String currentFieldName = null;
    XContentParser.Token token;
    FilteredQuery.FilterStrategy filterStrategy = CUSTOM_FILTER_STRATEGY;

    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token == XContentParser.Token.START_OBJECT) {
        if ("query".equals(currentFieldName)) {
          query = parseContext.parseInnerQuery();
        } else if ("filter".equals(currentFieldName)) {
          filterFound = true;
          filter = parseContext.parseInnerFilter();
        } else {
          throw new QueryParsingException(
              parseContext.index(), "[filtered] query does not support [" + currentFieldName + "]");
        }
      } else if (token.isValue()) {
        if ("strategy".equals(currentFieldName)) {
          String value = parser.text();
          if ("query_first".equals(value) || "queryFirst".equals(value)) {
            filterStrategy = FilteredQuery.QUERY_FIRST_FILTER_STRATEGY;
          } else if ("random_access_always".equals(value) || "randomAccessAlways".equals(value)) {
            filterStrategy = ALWAYS_RANDOM_ACCESS_FILTER_STRATEGY;
          } else if ("leap_frog".equals(value) || "leapFrog".equals(value)) {
            filterStrategy = FilteredQuery.LEAP_FROG_QUERY_FIRST_STRATEGY;
          } else if (value.startsWith("random_access_")) {
            int threshold = Integer.parseInt(value.substring("random_access_".length()));
            filterStrategy = new CustomRandomAccessFilterStrategy(threshold);
          } else if (value.startsWith("randomAccess")) {
            int threshold = Integer.parseInt(value.substring("randomAccess".length()));
            filterStrategy = new CustomRandomAccessFilterStrategy(threshold);
          } else if ("leap_frog_query_first".equals(value) || "leapFrogQueryFirst".equals(value)) {
            filterStrategy = FilteredQuery.LEAP_FROG_QUERY_FIRST_STRATEGY;
          } else if ("leap_frog_filter_first".equals(value)
              || "leapFrogFilterFirst".equals(value)) {
            filterStrategy = FilteredQuery.LEAP_FROG_FILTER_FIRST_STRATEGY;
          } else {
            throw new QueryParsingException(
                parseContext.index(), "[filtered] strategy value not supported [" + value + "]");
          }
        } else if ("_name".equals(currentFieldName)) {
          queryName = parser.text();
        } else if ("boost".equals(currentFieldName)) {
          boost = parser.floatValue();
        } else if ("_cache".equals(currentFieldName)) {
          cache = parseContext.parseFilterCachePolicy();
        } else if ("_cache_key".equals(currentFieldName) || "_cacheKey".equals(currentFieldName)) {
          cacheKey = new HashedBytesRef(parser.text());
        } else {
          throw new QueryParsingException(
              parseContext.index(), "[filtered] query does not support [" + currentFieldName + "]");
        }
      }
    }

    // parsed internally, but returned null during parsing...
    if (query == null) {
      return null;
    }

    if (filter == null) {
      if (!filterFound) {
        // we allow for null filter, so it makes compositions on the client side to be simpler
        return query;
      } else {
        // even if the filter is not found, and its null, we should simply ignore it, and go
        // by the query
        return query;
      }
    }
    if (filter == Queries.MATCH_ALL_FILTER) {
      // this is an instance of match all filter, just execute the query
      return query;
    }

    // cache if required
    if (cache != null) {
      filter = parseContext.cacheFilter(filter, cacheKey, cache);
    }

    // if its a match_all query, use constant_score
    if (Queries.isConstantMatchAllQuery(query)) {
      Query q = new ConstantScoreQuery(filter);
      q.setBoost(boost);
      return q;
    }

    FilteredQuery filteredQuery = new FilteredQuery(query, filter, filterStrategy);
    filteredQuery.setBoost(boost);
    if (queryName != null) {
      parseContext.addNamedQuery(queryName, filteredQuery);
    }
    return filteredQuery;
  }
  @Override
  public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
    XContentParser parser = parseContext.parser();

    Query query = null;
    Query noMatchQuery = Queries.newMatchAllQuery();
    boolean queryFound = false;
    boolean indicesFound = false;
    boolean currentIndexMatchesIndices = false;
    String queryName = null;

    String currentFieldName = null;
    XContentParser.Token token;
    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token == XContentParser.Token.START_OBJECT) {
        if ("query".equals(currentFieldName)) {
          // TODO We are able to decide whether to parse the query or not only if indices in the
          // query appears first
          queryFound = true;
          if (indicesFound && !currentIndexMatchesIndices) {
            parseContext.parser().skipChildren(); // skip the query object without parsing it
          } else {
            query = parseContext.parseInnerQuery();
          }
        } else if ("no_match_query".equals(currentFieldName)) {
          if (indicesFound && currentIndexMatchesIndices) {
            parseContext.parser().skipChildren(); // skip the query object without parsing it
          } else {
            noMatchQuery = parseContext.parseInnerQuery();
          }
        } else {
          throw new QueryParsingException(
              parseContext.index(), "[indices] query does not support [" + currentFieldName + "]");
        }
      } else if (token == XContentParser.Token.START_ARRAY) {
        if ("indices".equals(currentFieldName)) {
          if (indicesFound) {
            throw new QueryParsingException(
                parseContext.index(), "[indices] indices or index already specified");
          }
          indicesFound = true;
          Collection<String> indices = new ArrayList<>();
          while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
            String value = parser.textOrNull();
            if (value == null) {
              throw new QueryParsingException(
                  parseContext.index(), "[indices] no value specified for 'indices' entry");
            }
            indices.add(value);
          }
          currentIndexMatchesIndices =
              matchesIndices(
                  parseContext.index().name(), indices.toArray(new String[indices.size()]));
        } else {
          throw new QueryParsingException(
              parseContext.index(), "[indices] query does not support [" + currentFieldName + "]");
        }
      } else if (token.isValue()) {
        if ("index".equals(currentFieldName)) {
          if (indicesFound) {
            throw new QueryParsingException(
                parseContext.index(), "[indices] indices or index already specified");
          }
          indicesFound = true;
          currentIndexMatchesIndices = matchesIndices(parseContext.index().name(), parser.text());
        } else if ("no_match_query".equals(currentFieldName)) {
          String type = parser.text();
          if ("all".equals(type)) {
            noMatchQuery = Queries.newMatchAllQuery();
          } else if ("none".equals(type)) {
            noMatchQuery = Queries.newMatchNoDocsQuery();
          }
        } else if ("_name".equals(currentFieldName)) {
          queryName = parser.text();
        } else {
          throw new QueryParsingException(
              parseContext.index(), "[indices] query does not support [" + currentFieldName + "]");
        }
      }
    }
    if (!queryFound) {
      throw new QueryParsingException(parseContext.index(), "[indices] requires 'query' element");
    }
    if (!indicesFound) {
      throw new QueryParsingException(
          parseContext.index(), "[indices] requires 'indices' or 'index' element");
    }

    Query chosenQuery;
    if (currentIndexMatchesIndices) {
      chosenQuery = query;
    } else {
      chosenQuery = noMatchQuery;
    }
    if (queryName != null) {
      parseContext.addNamedQuery(queryName, chosenQuery);
    }
    return chosenQuery;
  }
  @Override
  public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
    XContentParser parser = parseContext.parser();

    MoreLikeThisQuery mltQuery = new MoreLikeThisQuery();
    mltQuery.setSimilarity(parseContext.searchSimilarity());
    Analyzer analyzer = null;
    List<String> moreLikeFields = null;
    boolean failOnUnsupportedField = true;
    String queryName = null;
    boolean include = false;

    XContentParser.Token token;
    String currentFieldName = null;

    List<String> likeTexts = new ArrayList<>();
    MultiTermVectorsRequest likeItems = new MultiTermVectorsRequest();

    List<String> unlikeTexts = new ArrayList<>();
    MultiTermVectorsRequest unlikeItems = new MultiTermVectorsRequest();

    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token.isValue()) {
        if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.LIKE_TEXT)) {
          likeTexts.add(parser.text());
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.LIKE)) {
          parseLikeField(parser, likeTexts, likeItems);
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.UNLIKE)) {
          parseLikeField(parser, unlikeTexts, unlikeItems);
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.MIN_TERM_FREQ)) {
          mltQuery.setMinTermFrequency(parser.intValue());
        } else if (parseContext
            .parseFieldMatcher()
            .match(currentFieldName, Fields.MAX_QUERY_TERMS)) {
          mltQuery.setMaxQueryTerms(parser.intValue());
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.MIN_DOC_FREQ)) {
          mltQuery.setMinDocFreq(parser.intValue());
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.MAX_DOC_FREQ)) {
          mltQuery.setMaxDocFreq(parser.intValue());
        } else if (parseContext
            .parseFieldMatcher()
            .match(currentFieldName, Fields.MIN_WORD_LENGTH)) {
          mltQuery.setMinWordLen(parser.intValue());
        } else if (parseContext
            .parseFieldMatcher()
            .match(currentFieldName, Fields.MAX_WORD_LENGTH)) {
          mltQuery.setMaxWordLen(parser.intValue());
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.BOOST_TERMS)) {
          float boostFactor = parser.floatValue();
          if (boostFactor != 0) {
            mltQuery.setBoostTerms(true);
            mltQuery.setBoostTermsFactor(boostFactor);
          }
        } else if (parseContext
            .parseFieldMatcher()
            .match(currentFieldName, Fields.MINIMUM_SHOULD_MATCH)) {
          mltQuery.setMinimumShouldMatch(parser.text());
        } else if ("analyzer".equals(currentFieldName)) {
          analyzer = parseContext.analysisService().analyzer(parser.text());
        } else if ("boost".equals(currentFieldName)) {
          mltQuery.setBoost(parser.floatValue());
        } else if (parseContext
            .parseFieldMatcher()
            .match(currentFieldName, Fields.FAIL_ON_UNSUPPORTED_FIELD)) {
          failOnUnsupportedField = parser.booleanValue();
        } else if ("_name".equals(currentFieldName)) {
          queryName = parser.text();
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.INCLUDE)) {
          include = parser.booleanValue();
        } else {
          throw new QueryParsingException(
              parseContext, "[mlt] query does not support [" + currentFieldName + "]");
        }
      } else if (token == XContentParser.Token.START_ARRAY) {
        if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.STOP_WORDS)) {
          Set<String> stopWords = Sets.newHashSet();
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            stopWords.add(parser.text());
          }
          mltQuery.setStopWords(stopWords);
        } else if ("fields".equals(currentFieldName)) {
          moreLikeFields = new LinkedList<>();
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            String field = parser.text();
            MappedFieldType fieldType = parseContext.fieldMapper(field);
            moreLikeFields.add(fieldType == null ? field : fieldType.names().indexName());
          }
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.DOCUMENT_IDS)) {
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            if (!token.isValue()) {
              throw new IllegalArgumentException("ids array element should only contain ids");
            }
            likeItems.add(newTermVectorsRequest().id(parser.text()));
          }
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.DOCUMENTS)) {
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            if (token != XContentParser.Token.START_OBJECT) {
              throw new IllegalArgumentException("docs array element should include an object");
            }
            likeItems.add(parseDocument(parser));
          }
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.LIKE)) {
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            parseLikeField(parser, likeTexts, likeItems);
          }
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.UNLIKE)) {
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            parseLikeField(parser, unlikeTexts, unlikeItems);
          }
        } else {
          throw new QueryParsingException(
              parseContext, "[mlt] query does not support [" + currentFieldName + "]");
        }
      } else if (token == XContentParser.Token.START_OBJECT) {
        if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.LIKE)) {
          parseLikeField(parser, likeTexts, likeItems);
        } else if (parseContext.parseFieldMatcher().match(currentFieldName, Fields.UNLIKE)) {
          parseLikeField(parser, unlikeTexts, unlikeItems);
        } else {
          throw new QueryParsingException(
              parseContext, "[mlt] query does not support [" + currentFieldName + "]");
        }
      }
    }

    if (likeTexts.isEmpty() && likeItems.isEmpty()) {
      throw new QueryParsingException(
          parseContext, "more_like_this requires 'like' to be specified");
    }
    if (moreLikeFields != null && moreLikeFields.isEmpty()) {
      throw new QueryParsingException(
          parseContext, "more_like_this requires 'fields' to be non-empty");
    }

    // set analyzer
    if (analyzer == null) {
      analyzer = parseContext.mapperService().searchAnalyzer();
    }
    mltQuery.setAnalyzer(analyzer);

    // set like text fields
    boolean useDefaultField = (moreLikeFields == null);
    if (useDefaultField) {
      moreLikeFields = Collections.singletonList(parseContext.defaultField());
    }
    // possibly remove unsupported fields
    removeUnsupportedFields(moreLikeFields, analyzer, failOnUnsupportedField);
    if (moreLikeFields.isEmpty()) {
      return null;
    }
    mltQuery.setMoreLikeFields(moreLikeFields.toArray(Strings.EMPTY_ARRAY));

    // support for named query
    if (queryName != null) {
      parseContext.addNamedQuery(queryName, mltQuery);
    }

    // handle like texts
    if (!likeTexts.isEmpty()) {
      mltQuery.setLikeText(likeTexts);
    }
    if (!unlikeTexts.isEmpty()) {
      mltQuery.setIgnoreText(unlikeTexts);
    }

    // handle items
    if (!likeItems.isEmpty()) {
      // set default index, type and fields if not specified
      MultiTermVectorsRequest items = likeItems;
      for (TermVectorsRequest item : unlikeItems) {
        items.add(item);
      }

      for (TermVectorsRequest item : items) {
        if (item.index() == null) {
          item.index(parseContext.index().name());
        }
        if (item.type() == null) {
          if (parseContext.queryTypes().size() > 1) {
            throw new QueryParsingException(
                parseContext,
                "ambiguous type for item with id: " + item.id() + " and index: " + item.index());
          } else {
            item.type(parseContext.queryTypes().iterator().next());
          }
        }
        // default fields if not present but don't override for artificial docs
        if (item.selectedFields() == null && item.doc() == null) {
          if (useDefaultField) {
            item.selectedFields("*");
          } else {
            item.selectedFields(moreLikeFields.toArray(new String[moreLikeFields.size()]));
          }
        }
      }
      // fetching the items with multi-termvectors API
      items.copyContextAndHeadersFrom(SearchContext.current());
      MultiTermVectorsResponse responses = fetchService.fetchResponse(items);

      // getting the Fields for liked items
      mltQuery.setLikeText(MoreLikeThisFetchService.getFields(responses, likeItems));

      // getting the Fields for ignored items
      if (!unlikeItems.isEmpty()) {
        org.apache.lucene.index.Fields[] ignoreFields =
            MoreLikeThisFetchService.getFields(responses, unlikeItems);
        if (ignoreFields.length > 0) {
          mltQuery.setUnlikeText(ignoreFields);
        }
      }

      BooleanQuery.Builder boolQuery = new BooleanQuery.Builder();
      boolQuery.add(mltQuery, BooleanClause.Occur.SHOULD);

      // exclude the items from the search
      if (!include) {
        handleExclude(boolQuery, likeItems);
      }
      return boolQuery.build();
    }

    return mltQuery;
  }
  @Override
  public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
    XContentParser parser = parseContext.parser();

    ArrayList<Query> queries = newArrayList();
    boolean queriesFound = false;

    String queryName = null;
    String currentFieldName = null;
    XContentParser.Token token = parser.currentToken();
    if (token == XContentParser.Token.START_ARRAY) {
      while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
        queriesFound = true;
        Query filter = parseContext.parseInnerFilter();
        if (filter != null) {
          queries.add(filter);
        }
      }
    } else {
      while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
        if (token == XContentParser.Token.FIELD_NAME) {
          currentFieldName = parser.currentName();
        } else if (parseContext.isDeprecatedSetting(currentFieldName)) {
          // skip
        } else if (token == XContentParser.Token.START_ARRAY) {
          if ("filters".equals(currentFieldName)) {
            queriesFound = true;
            while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
              Query filter = parseContext.parseInnerFilter();
              if (filter != null) {
                queries.add(filter);
              }
            }
          } else {
            queriesFound = true;
            while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
              Query filter = parseContext.parseInnerFilter();
              if (filter != null) {
                queries.add(filter);
              }
            }
          }
        } else if (token.isValue()) {
          if ("_name".equals(currentFieldName)) {
            queryName = parser.text();
          } else {
            throw new QueryParsingException(
                parseContext, "[and] query does not support [" + currentFieldName + "]");
          }
        }
      }
    }

    if (!queriesFound) {
      throw new QueryParsingException(
          parseContext, "[and] query requires 'filters' to be set on it'");
    }

    if (queries.isEmpty()) {
      // no filters provided, this should be ignored upstream
      return null;
    }

    BooleanQuery query = new BooleanQuery();
    for (Query f : queries) {
      query.add(f, Occur.MUST);
    }
    if (queryName != null) {
      parseContext.addNamedQuery(queryName, query);
    }
    return query;
  }