@Override
  protected QueryNode postProcessNode(QueryNode node) throws QueryNodeException {

    if (node instanceof AqpWhiteSpacedQueryNode) {

      QueryConfigHandler config = getQueryConfigHandler();

      if (!config.has(
          AqpAdsabsQueryConfigHandler.ConfigurationKeys.FUNCTION_QUERY_BUILDER_CONFIG)) {
        throw new QueryNodeException(
            new MessageImpl("Invalid configuration", "Missing FunctionQueryBuilder provider"));
      }

      String funcName = getFuncName();
      String subQuery = ((FieldQueryNode) node).getTextAsString();
      String field = ((FieldQueryNode) node).getFieldAsString();
      if (field.equals(
          config.get(AqpAdsabsQueryConfigHandler.ConfigurationKeys.UNFIELDED_SEARCH_FIELD))) {
        field = null;
      }

      if (field != null) {
        subQuery = field + ":" + subQuery;
      }

      if (node.getParent() instanceof SlopQueryNode) {
        subQuery = "(" + subQuery + ")";
        subQuery = subQuery + "~" + ((SlopQueryNode) node.getParent()).getValue();
        if (node.getParent().getParent() instanceof BoostQueryNode) {
          subQuery = subQuery + "^" + ((BoostQueryNode) node.getParent().getParent()).getValue();
        }
      } else if (node.getParent() instanceof BoostQueryNode) {
        subQuery = "(" + subQuery + ")";
        subQuery = subQuery + "^" + ((BoostQueryNode) node.getParent()).getValue();
      }

      node.setTag("subQuery", subQuery);

      AqpFunctionQueryBuilder builder =
          config
              .get(AqpAdsabsQueryConfigHandler.ConfigurationKeys.FUNCTION_QUERY_BUILDER_CONFIG)
              .getBuilder(funcName, (QueryNode) node, config);

      if (builder == null) {
        throw new QueryNodeException(
            new MessageImpl(
                QueryParserMessages.INVALID_SYNTAX, "Unknown function: \"" + funcName + "\""));
      }

      List<OriginalInput> fValues = new ArrayList<OriginalInput>();
      fValues.add(
          new OriginalInput(
              subQuery,
              ((AqpWhiteSpacedQueryNode) node).getBegin(),
              ((AqpWhiteSpacedQueryNode) node).getEnd()));
      return new AqpFunctionQueryNode(funcName, builder, fValues);
    }
    return node;
  }
  @Override
  protected QueryNode preProcessNode(QueryNode node) throws QueryNodeException {
    final QueryConfigHandler conf = this.getQueryConfigHandler();

    // If the current node is a datatype query node, validate the datatype and assign it to its
    // child
    if (node instanceof DatatypeQueryNode) {
      final Map<String, Analyzer> dtAnalyzers =
          conf.get(KeywordConfigurationKeys.DATATYPES_ANALYZERS);
      final DatatypeQueryNode dt = (DatatypeQueryNode) node;
      String datatype = dt.getDatatype();

      // check if the datatype is correctly registered
      if (dtAnalyzers == null) {
        throw new IllegalArgumentException(
            "KeywordConfigurationKeys.DATAYPES_ANALYZERS "
                + "should be set on the ExtendedKeywordQueryConfigHandler");
      }
      if (!dtAnalyzers.containsKey(datatype)) {
        throw new IllegalArgumentException("Unknown datatype: [" + datatype + "]");
      }
      if (dtAnalyzers.get(datatype) == null) {
        throw new IllegalArgumentException(
            "Analyzer of datatype [" + datatype + "] cannot be null.");
      }

      // transfer the datatype to its child
      dt.getChild().setTag(DatatypeQueryNode.DATATYPE_TAGID, datatype);
    }
    // If the current node is a twig query node, assign the json:field datatype to the root, and
    // assign the default
    // or the tagged datatype to the child
    else if (node instanceof TwigQueryNode) {
      final TwigQueryNode twig = (TwigQueryNode) node;
      if (twig.getTag(DatatypeQueryNode.DATATYPE_TAGID) == null) {
        twig.getChild().setTag(DatatypeQueryNode.DATATYPE_TAGID, this.getDefaultDatatype(conf));
      } else {
        twig.getChild().setTag(DatatypeQueryNode.DATATYPE_TAGID, this.getDatatype(conf, node));
      }
      twig.getRoot().setTag(DatatypeQueryNode.DATATYPE_TAGID, JSONDatatype.JSON_FIELD);
    }
    // in any other cases, if the node is not a leaf node, transfer the datatype to its children
    else if (!node.isLeaf()) {
      for (final QueryNode child : node.getChildren()) {
        child.setTag(DatatypeQueryNode.DATATYPE_TAGID, this.getDatatype(conf, node));
      }
    }

    return node;
  }