英文:
RSQL-Parser and boolean query values failing with IllegalArgumentException
问题
我有一个 REST API,我使用 RSQL 解析器实现了查询功能。它对一切都很有效,但布尔值 "true" 和 "false" 除外。
以下是我 RSQL 规范,根据 Baeldung 的示例稍作修改以处理 API 中的枚举值:
public class GenericRsqlSpecification<T> implements Specification<T> {
  private String property;
  private ComparisonOperator operator;
  private List<String> arguments;
  public GenericRsqlSpecification(String property, ComparisonOperator operator, List<String> arguments) {
    this.property = property;
    this.operator = operator;
    this.arguments = arguments;
  }
  @Override
  public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
    // ... (代码在此省略)
  }
  // ... (代码在此省略)
}
关于 Car 模型的部分如下:
public class CarDO {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(nullable = false)
  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
  private ZonedDateTime dateCreated = ZonedDateTime.now();
  @Column(nullable = false)
  private String licensePlate;
  @Column(nullable = false)
  private int seatCount;
  @Column(nullable = false)
  private boolean convertible;
  @Column(nullable = false)
  private int rating;
  @Column(nullable = false)
  @Enumerated(EnumType.STRING)
  private EngineType engineType;
  @Column(nullable = false)
  private String manufacturer;
  @Column(nullable = false)
  private boolean selected;
  @OneToOne(fetch = FetchType.EAGER)
  @JoinTable(name = "driver_with_car",
    joinColumns = {@JoinColumn(name = "car_id")},
    inverseJoinColumns = {@JoinColumn(name = "driver_id")})
  private DriverDO driver;
  //Constructors, Getters and Setters not included.
}
目前,对这个问题不太确定如何解决。目前,搜索只会引发以下异常:
java.lang.IllegalArgumentException: Parameter value [true] did not match expected type [java.lang.Boolean (n/a)]
有关其余代码的 Github 存储库链接请参见 car-booking-system。
英文:
I have a rest API which I have implemented a query feature using RSQL-Parser. It works great for everything but boolean values "true" & "false".
This is my Rsql Specification following examples from Baeldung with some minor alterations to deal with Enum values have in the API.
public class GenericRsqlSpecification<T> implements Specification<T> {
  private String property;
  private ComparisonOperator operator;
  private List<String> arguments;
  public GenericRsqlSpecification(String property, ComparisonOperator operator, List<String> arguments) {
    this.property = property;
    this.operator = operator;
    this.arguments = arguments;
  }
  @Override
  public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
                               CriteriaBuilder builder) {
    Path<String> propertyExpression = parseProperty(root);
    List<Object> args = castArguments(propertyExpression);
    Object argument = args.get(0);
    switch (RsqlSearchOperation.getSimpleOperator(operator)) {
      case EQUAL:
        if (argument instanceof String)
          return builder.like(propertyExpression,
            argument.toString().replace('*', '%'));
        else if (argument == null)
          return builder.isNull(propertyExpression);
        else return builder.equal(propertyExpression, argument);
      case NOT_EQUAL:
        if (argument instanceof String)
          return builder.notLike(propertyExpression,
            argument.toString().replace('*', '%'));
        else if (argument == null)
          return builder.isNotNull(propertyExpression);
        else return builder.notEqual(propertyExpression, argument);
      case GREATER_THAN:
        return builder.greaterThan(propertyExpression,
          argument.toString());
      case GREATER_THAN_OR_EQUAL:
        return builder.greaterThanOrEqualTo(propertyExpression,
          argument.toString());
      case LESS_THAN:
        return builder.lessThan(propertyExpression,
          argument.toString());
      case LESS_THAN_OR_EQUAL:
        return builder.lessThanOrEqualTo(propertyExpression,
          argument.toString());
      case IN:
        return propertyExpression.in(args);
      case NOT_IN:
        return builder.not(propertyExpression.in(args));
    }
    return null;
  }
  private Path<String> parseProperty(Root<T> root) {
    Path<String> path;
    if (property.contains(".")) {
      // Nested properties
      String[] pathSteps = property.split("\\.");
      String step = pathSteps[0];
      path = root.get(step);
      From lastFrom = root;
      for (int i = 1; i <= pathSteps.length - 1; i++) {
        if (path instanceof PluralAttributePath) {
          PluralAttribute attr = ((PluralAttributePath) path).getAttribute();
          Join join = getJoin(attr, lastFrom);
          path = join.get(pathSteps[i]);
          lastFrom = join;
        } else if (path instanceof SingularAttributePath) {
          SingularAttribute attr = ((SingularAttributePath) path).getAttribute();
          if (attr.getPersistentAttributeType() != Attribute.PersistentAttributeType.BASIC) {
            Join join = lastFrom.join(attr, JoinType.LEFT);
            path = join.get(pathSteps[i]);
            lastFrom = join;
          } else {
            path = path.get(pathSteps[i]);
          }
        } else {
          path = path.get(pathSteps[i]);
        }
      }
    } else {
      path = root.get(property);
    }
    return path;
  }
  private Join getJoin(PluralAttribute attr, From from) {
    switch (attr.getCollectionType()) {
      case COLLECTION:
        return from.join((CollectionAttribute) attr);
      case SET:
        return from.join((SetAttribute) attr);
      case LIST:
        return from.join((ListAttribute) attr);
      case MAP:
        return from.join((MapAttribute) attr);
      default:
        return null;
    }
  }
  private List<Object> castArguments(Path<?> propertyExpression) {
    Class<?> type = propertyExpression.getJavaType();
    return arguments.stream().map(arg -> {
      if (type.equals(Integer.class)) return Integer.parseInt(arg);
      else if (type.equals(Long.class)) return Long.parseLong(arg);
      else if (type.equals(Byte.class)) return Byte.parseByte(arg);
      else if (type.equals(Boolean.class)) return Boolean.parseBoolean(arg);
      else if (type.equals(EngineType.class)) return EngineType.valueOf(arg);
      else if (type.equals(OnlineStatus.class)) return OnlineStatus.valueOf(arg);
      else return arg;
    }).collect(Collectors.toList());
  }
//GETTERS & SETTERS left out.
}
Car Model
public class CarDO {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(nullable = false)
  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
  private ZonedDateTime dateCreated = ZonedDateTime.now();
  @Column(nullable = false)
  private String licensePlate;
  @Column(nullable = false)
  private int seatCount;
  @Column(nullable = false)
  private boolean convertible;
  @Column(nullable = false)
  private int rating;
  @Column(nullable = false)
  @Enumerated(EnumType.STRING)
  private EngineType engineType;
  @Column(nullable = false)
  private String manufacturer;
  @Column(nullable = false)
  private boolean selected;
  @OneToOne(fetch = FetchType.EAGER)
  @JoinTable(name = "driver_with_car",
    joinColumns = {@JoinColumn(name = "car_id")},
    inverseJoinColumns = {@JoinColumn(name = "driver_id")})
  private DriverDO driver;
//Constructors, Getters and Setters not included.
}
Not really sure on how to work around this issue. Currently, the search just fails with this exception
java.lang.IllegalArgumentException: Parameter value [true] did not match expected type [java.lang.Boolean (n/a)]
Github repo for rest of the code
car-booking-system
答案1
得分: 1
在一个“这不可能是这样,但是算了” 的瞬间,我将可转换和选择的数据类型从boolean更改为Boolean,然后一切正常运作。
public class CarDO {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(nullable = false)
  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
  private ZonedDateTime dateCreated = ZonedDateTime.now();
  @Column(nullable = false)
  private String licensePlate;
  @Column(nullable = false)
  private int seatCount;
  @Column(nullable = false)
  private Boolean convertible;
  @Column(nullable = false)
  private int rating;
  @Column(nullable = false)
  @Enumerated(EnumType.STRING)
  private EngineType engineType;
  @Column(nullable = false)
  private String manufacturer;
  @Column(nullable = false)
  private Boolean selected;
  @OneToOne(fetch = FetchType.EAGER)
  @JoinTable(name = "driver_with_car",
    joinColumns = {@JoinColumn(name = "car_id")},
    inverseJoinColumns = {@JoinColumn(name = "driver_id")})
  private DriverDO driver;
}
英文:
So on a "That can't be it but what the heck" moment I changed the datatype of convertible and selected to Boolean instead of boolean and all worked fine.
public class CarDO {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(nullable = false)
  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
  private ZonedDateTime dateCreated = ZonedDateTime.now();
  @Column(nullable = false)
  private String licensePlate;
  @Column(nullable = false)
  private int seatCount;
  @Column(nullable = false)
  private Boolean convertible;
  @Column(nullable = false)
  private int rating;
  @Column(nullable = false)
  @Enumerated(EnumType.STRING)
  private EngineType engineType;
  @Column(nullable = false)
  private String manufacturer;
  @Column(nullable = false)
  private Boolean selected;
  @OneToOne(fetch = FetchType.EAGER)
  @JoinTable(name = "driver_with_car",
    joinColumns = {@JoinColumn(name = "car_id")},
    inverseJoinColumns = {@JoinColumn(name = "driver_id")})
  private DriverDO driver;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论