How to validate field level constraint before class level constraint?

人走茶凉 提交于 2019-12-19 04:15:15

问题


I have a class:

@ColumnNameUnique(groups = CreateTableChecks.class)
public class Table {    
    @Valid
    @NotEmpty(groups = CreateTableChecks.class)
    private List<Measure> measures; 
}
  • The class level constraint @ColumnNameUnique(groups = CreateTableChecks.class) always runs first, after that the field level constraint @NotEmpty(groups = CreateTableChecks.class) runs.

  • Is there anyway to force the the field level constraint @NotEmpty(groups = CreateTableChecks.class) runs first?


回答1:


You need to use @GroupSequence and re-define the default group sequence. Without this the validation order within a group is not defined and it can be in any order (that the class level constraint in your case is always executed first is not a must). Something like this should work:

@GroupSequence({FieldChecks.class, ClassChecks.class})
@ColumnNameUnique(groups = ClassChecks.class)
public class Table {    
    @Valid
    @NotEmpty(groups = FieldChecks.class)
    private List<Measure> measures; 
}

Now, if the @Default group gets validated, first the class level constraints and then the field level ones will be validated.




回答2:


Instead of using @Hardy mentioned solution with @GroupSequence you can validate fields manually using reflection before your Validator.validate call.

Method

You can wrap this method

/**
 * Validates all single constrained fields of the given object and returns a
 * set of {@link ConstraintViolation}. If <code>first</code> is
 * <code>true</code> only the ConstraintViolation of the first invalid
 * constraint is returned. <br>
 * This method is useful to validate property constraints before class level
 * constraints.
 *
 * @param validator
 * @param object
 * @param first Set to <code>true</code> if only the exceptions of the first
 *            invalid property shall be thrown
 * @param groups
 */
public static Set<ConstraintViolation<Object>> validateProperties(final Validator validator, final Object object,
    final boolean first, final Class<?>... groups)
{
    if (object == null)
        throw new IllegalArgumentException("object must not be null.");
    if (validator == null)
        throw new IllegalArgumentException("validator must not be null.");

    final Set<ConstraintViolation<Object>> cvs = new HashSet<>();

    forFields: for (final Field field : ReflectionUtils.getAllFields(object.getClass(), null))
    {
        final Annotation[] annotations = field.getDeclaredAnnotations();

        boolean hasValidAnnotation = false;

        for (final Annotation annotation : annotations)
        {
            // single Constraint
            final Constraint constraint = annotation.annotationType().getAnnotation(Constraint.class);
            if (constraint != null)
            {
                cvs.addAll(validator.validateProperty(object, field.getName(), groups));

                if (!cvs.isEmpty() && first)
                    break forFields;
            }

            if (annotation.annotationType().equals(Valid.class))
                hasValidAnnotation = true;
        }

        // nested validation
        if (hasValidAnnotation)
        {
            field.setAccessible(true);
            Object value = null;
            try
            {
                value = field.get(object);
            }
            catch (IllegalArgumentException | IllegalAccessException e)
            {
                // log
            }

            if (value != null)
            {
                cvs.addAll(validateProperties(validator, value, first, groups));

                if (!cvs.isEmpty() && first)
                    break;

            }
        }
    }

    return cvs;
}

/**
 * Validates all single constrained fields of the given object and throws a
 * {@link ConstraintViolationException}. If <code>first</code> is
 * <code>true</code> only the ConstraintViolation of the first invalid
 * constraint is thrown. <br>
 * <br>
 * This method is useful to validate property constraints before class level
 * constraints.
 *
 * https://hibernate.atlassian.net/browse/BVAL-557
 *
 * @see #validateProperty(Validator, Object, String, Class...)
 *
 * @param validator
 * @param object
 * @param first Set to <code>true</code> if only the exceptions of the first
 *            invalid property shall be thrown
 * @param groups
 *
 * @throws ConstraintViolationException
 */
public static void validatePropertiesThrow(final Validator validator, final Object object, final boolean first,
    final Class<?>... groups) throws ConstraintViolationException
{
    if (object == null)
        throw new IllegalArgumentException("object must not be null.");
    if (validator == null)
        throw new IllegalArgumentException("validator must not be null.");

    final Set<ConstraintViolation<Object>> cvs = validateProperties(validator, object, first,
        groups);

    if (!cvs.isEmpty())
        throw new ConstraintViolationException(cvs);
}

I prefer this approach since i do not want to to update all our entities and fields with group sequence annotations.



来源:https://stackoverflow.com/questions/30431293/how-to-validate-field-level-constraint-before-class-level-constraint

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!