Search code examples
javajavafxbean-validationhibernate-validator

Hibernate validateValue interaction with value extractor


EDIT: Bug is present in 6.0.1.Final but not 5.4.1.Final. Submitted bug report: https://hibernate.atlassian.net/browse/HV-1471

Hibernate validation is throwing an error when attempting to use the validateValue method

/**
 * Validates all constraints placed on the property named {@code propertyName}
 * of the class {@code beanType} would the property value be {@code value}.
 * <p>
 * {@link ConstraintViolation} objects return {@code null} for
 * {@link ConstraintViolation#getRootBean()} and
 * {@link ConstraintViolation#getLeafBean()}.
 *
 * @param beanType the bean type
 * @param propertyName property to validate
 * @param value property value to validate
 * @param groups the group or list of groups targeted for validation (defaults to
 *        {@link Default}).
 * @param <T> the type of the object to validate
 * @return constraint violations or an empty set if none
 * @throws IllegalArgumentException if {@code beanType} is {@code null},
 *         if {@code propertyName} is {@code null}, empty or not a valid object property
 *         or if {@code null} is passed to the varargs groups
 * @throws ValidationException if a non recoverable error happens
 *         during the validation process
 */
<T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType,
                                              String propertyName,
                                              Object value,
                                              Class<?>... groups);

The issue arises with a bean containing wrapped properties, in this case JavaFX's ObjectProperty<BigInteger>. Validations are placed on the field in the model, and class validations perform correctly with unwrapping the properties.

@NotNull
@Min(value = MINIMUM_ACCEPTABLE_PORT)
@Max(value = MAXIMUM_ACCEPTABLE_PORT)
private ObjectProperty<BigInteger> multicastListenPort = new SimpleObjectProperty<>();

public BigInteger getMulticastListenPort() {
    return multicastListenPort.get();
}

public ObjectProperty<BigInteger> multicastListenPortProperty() {
    return multicastListenPort;
}

public void setMulticastListenPort(BigInteger multicastListenPort) {
    this.multicastListenPort.set(multicastListenPort);
}

Validator Util class:

public static <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value,
        Class<?>... groups) {

    Validator validator = getValidator();

    return validator.validateValue(beanType, propertyName, value);
}

private static Validator getValidator() {
    return Validation.byDefaultProvider()
            .configure()
            .messageInterpolator(
                    new ResourceBundleMessageInterpolator(
                            new PlatformResourceBundleLocator("messages")))
            .buildValidatorFactory()
            .getValidator();
}

When using validateValue, hibernate attempts to cast the provided value to the wrapper class instead of the contained class, causing a class cast exception. java.math.BigInteger cannot be cast to javafx.beans.value.ObservableValue. If the provided value is wrapped in a SimpleObjectProperty, it gets unwrapped before attempting to cast.

Example call

Set<ConstraintViolation<Settings>> violations = ValidationHelper.validateValue(
        Settings.class, "multicastListenPort", updatedValue);

With updatedValue wrapped in a SimpleObjectProperty (same stack trace, gets unwrapped and then attempts to unwrap again)

Set<ConstraintViolation<Settings>> violations = ValidationHelper.validateValue(
        Settings.class, "multicastListenPort", new SimpleObjectProperty<>(updatedValue));

Stack trace

javax.validation.ValidationException: HV000221: An error occurred while extracting values in value extractor org.hibernate.validator.internal.engine.valueextraction.ObservableValueValueExtractor.
at org.hibernate.validator.internal.engine.valueextraction.ValueExtractorHelper.extractValues(ValueExtractorHelper.java:47) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]
at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:104) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:552) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:510) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:479) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:444) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateValueInContext(ValidatorImpl.java:798) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateValue(ValidatorImpl.java:224) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]


Caused by: java.lang.ClassCastException: java.math.BigInteger cannot be cast to javafx.beans.value.ObservableValue
at org.hibernate.validator.internal.engine.valueextraction.ObservableValueValueExtractor.extractValues(ObservableValueValueExtractor.java:20) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]
at org.hibernate.validator.internal.engine.valueextraction.ValueExtractorHelper.extractValues(ValueExtractorHelper.java:41) ~[hibernate-validator-6.0.1.Final.jar:6.0.1.Final]

Solution

  • I confirm this is a bug. William opened https://hibernate.atlassian.net/browse/HV-1471 .

    The issue has been fixed in Hibernate Validator 6.0.2.Final.