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

    Query query = null;
    boolean queryFound = false;
    String parentType = null;

    String filterName = 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 handle `query` element before `type` element...
          String[] origTypes =
              QueryParseContext.setTypesWithPrevious(
                  parentType == null ? null : new String[] {parentType});
          try {
            query = parseContext.parseInnerQuery();
            queryFound = true;
          } finally {
            QueryParseContext.setTypes(origTypes);
          }
        } else if ("filter".equals(currentFieldName)) {
          // TODO handle `filter` element before `type` element...
          String[] origTypes =
              QueryParseContext.setTypesWithPrevious(
                  parentType == null ? null : new String[] {parentType});
          try {
            Filter innerFilter = parseContext.parseInnerFilter();
            query = new XConstantScoreQuery(innerFilter);
            queryFound = true;
          } finally {
            QueryParseContext.setTypes(origTypes);
          }
        } else {
          throw new QueryParsingException(
              parseContext.index(),
              "[has_parent] filter does not support [" + currentFieldName + "]");
        }
      } else if (token.isValue()) {
        if ("type".equals(currentFieldName)
            || "parent_type".equals(currentFieldName)
            || "parentType".equals(currentFieldName)) {
          parentType = parser.text();
        } else if ("_scope".equals(currentFieldName)) {
          throw new QueryParsingException(
              parseContext.index(),
              "the [_scope] support in [has_parent] filter has been removed, use a filter as a facet_filter in the relevant global facet");
        } else if ("_name".equals(currentFieldName)) {
          filterName = parser.text();
        } else {
          throw new QueryParsingException(
              parseContext.index(),
              "[has_parent] filter does not support [" + currentFieldName + "]");
        }
      }
    }
    if (!queryFound) {
      throw new QueryParsingException(
          parseContext.index(), "[parent] filter requires 'query' field");
    }
    if (query == null) {
      return null;
    }

    if (parentType == null) {
      throw new QueryParsingException(
          parseContext.index(), "[parent] filter requires 'parent_type' field");
    }

    DocumentMapper parentDocMapper = parseContext.mapperService().documentMapper(parentType);
    if (parentDocMapper == null) {
      throw new QueryParsingException(
          parseContext.index(),
          "[parent] filter configured 'parent_type' [" + parentType + "] is not a valid type");
    }

    // wrap the query with type query
    query = new XFilteredQuery(query, parseContext.cacheFilter(parentDocMapper.typeFilter(), null));

    SearchContext searchContext = SearchContext.current();

    HasParentFilter parentFilter = HasParentFilter.create(query, parentType, searchContext);
    searchContext.addRewrite(parentFilter);

    if (filterName != null) {
      parseContext.addNamedFilter(filterName, parentFilter);
    }
    return parentFilter;
  }
  @Override
  public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
    XContentParser parser = parseContext.parser();

    String fieldName = null;
    boolean disableCoord = false;
    float boost = 1.0f;
    String minimumShouldMatch = null;
    List<String> values = newArrayList();

    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_ARRAY) {
        fieldName = currentFieldName;
        while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
          String value = parser.text();
          if (value == null) {
            throw new QueryParsingException(
                parseContext.index(), "No value specified for terms query");
          }
          values.add(value);
        }
      } else if (token.isValue()) {
        if ("disable_coord".equals(currentFieldName) || "disableCoord".equals(currentFieldName)) {
          disableCoord = parser.booleanValue();
        } else if ("minimum_match".equals(currentFieldName)
            || "minimumMatch".equals(currentFieldName)) {
          minimumShouldMatch = parser.textOrNull();
        } else if ("minimum_should_match".equals(currentFieldName)
            || "minimumShouldMatch".equals(currentFieldName)) {
          minimumShouldMatch = parser.textOrNull();
        } else if ("boost".equals(currentFieldName)) {
          boost = parser.floatValue();
        }
      } else {
        throw new QueryParsingException(
            parseContext.index(), "[terms] query does not support [" + currentFieldName + "]");
      }
    }

    FieldMapper mapper = null;
    MapperService.SmartNameFieldMappers smartNameFieldMappers =
        parseContext.smartFieldMappers(fieldName);
    String[] previousTypes = null;
    if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) {
      mapper = smartNameFieldMappers.mapper();
      if (smartNameFieldMappers.explicitTypeInNameWithDocMapper()) {
        previousTypes =
            QueryParseContext.setTypesWithPrevious(
                new String[] {smartNameFieldMappers.docMapper().type()});
      }
    }

    try {
      BooleanQuery query = new BooleanQuery(disableCoord);
      for (String value : values) {
        if (mapper != null) {
          query.add(
              new BooleanClause(mapper.termQuery(value, parseContext), BooleanClause.Occur.SHOULD));
        } else {
          query.add(new TermQuery(new Term(fieldName, value)), BooleanClause.Occur.SHOULD);
        }
      }
      query.setBoost(boost);
      Queries.applyMinimumShouldMatch(query, minimumShouldMatch);
      return wrapSmartNameQuery(
          optimizeQuery(fixNegativeQueryIfNeeded(query)), smartNameFieldMappers, parseContext);
    } finally {
      if (smartNameFieldMappers != null
          && smartNameFieldMappers.explicitTypeInNameWithDocMapper()) {
        QueryParseContext.setTypes(previousTypes);
      }
    }
  }