protected void applyQueryFilters(
      final MultivaluedMap<String, String> p, final CriteriaBuilder builder) {
    final MultivaluedMap<String, String> params = new MultivaluedMapImpl();
    params.putAll(p);

    builder.distinct();
    builder.limit(DEFAULT_LIMIT);

    // not sure why we remove this, but that's what the old query filter code did, I presume there's
    // a reason  :)
    params.remove("_dc");

    if (params.containsKey("limit")) {
      builder.limit(Integer.valueOf(params.getFirst("limit")));
      params.remove("limit");
    }
    if (params.containsKey("offset")) {
      builder.offset(Integer.valueOf(params.getFirst("offset")));
      params.remove("offset");
    }
    // Is this necessary anymore? setLimitOffset() comments implies it's for Ext-JS.
    if (params.containsKey("start")) {
      builder.offset(Integer.valueOf(params.getFirst("start")));
      params.remove("start");
    }

    if (params.containsKey("orderBy")) {
      builder.orderBy(params.getFirst("orderBy"));
      params.remove("orderBy");

      if (params.containsKey("order")) {
        if ("desc".equalsIgnoreCase(params.getFirst("order"))) {
          builder.desc();
        } else {
          builder.asc();
        }
        params.remove("order");
      }
    }

    final String query = removeParameter(params, "query");
    if (query != null) builder.sql(query);

    final String matchType;
    final String match = removeParameter(params, "match");
    if (match == null) {
      matchType = "all";
    } else {
      matchType = match;
    }
    builder.match(matchType);

    final Class<?> criteriaClass = builder.toCriteria().getCriteriaClass();
    final BeanWrapper wrapper = getBeanWrapperForClass(criteriaClass);

    final String comparatorParam = removeParameter(params, "comparator", "eq").toLowerCase();
    final Criteria currentCriteria = builder.toCriteria();

    for (final String key : params.keySet()) {
      for (final String paramValue : params.get(key)) { // NOSONAR
        // NOSONAR the interface of MultivaluedMap.class declares List<String> as return value,
        // the actual implementation com.sun.jersey.core.util.MultivaluedMapImpl returns a String,
        // so this is fine in some way ...
        if ("null".equalsIgnoreCase(paramValue)) {
          builder.isNull(key);
        } else if ("notnull".equalsIgnoreCase(paramValue)) {
          builder.isNotNull(key);
        } else {
          Object value;
          Class<?> type = Object.class;
          try {
            type = currentCriteria.getType(key);
          } catch (final IntrospectionException e) {
            LOG.debug("Unable to determine type for key {}", key);
          }
          if (type == null) {
            type = Object.class;
          }
          LOG.warn("comparator = {}, key = {}, propertyType = {}", comparatorParam, key, type);

          if (comparatorParam.equals("contains")
              || comparatorParam.equals("iplike")
              || comparatorParam.equals("ilike")
              || comparatorParam.equals("like")) {
            value = paramValue;
          } else {
            LOG.debug("convertIfNecessary({}, {})", key, paramValue);
            try {
              value = wrapper.convertIfNecessary(paramValue, type);
            } catch (final Throwable t) {
              LOG.debug("failed to introspect (key = {}, value = {})", key, paramValue, t);
              value = paramValue;
            }
          }

          try {
            final Method m =
                builder.getClass().getMethod(comparatorParam, String.class, Object.class);
            m.invoke(builder, new Object[] {key, value});
          } catch (final Throwable t) {
            LOG.warn(
                "Unable to find method for comparator: {}, key: {}, value: {}",
                comparatorParam,
                key,
                value,
                t);
          }
        }
      }
    }
  }