I have a class:
@ColumnNameUnique(groups = CreateTableChecks.class)
public class Table {
@Valid
@NotEmpty(groups = CreateTableChecks.class)
p
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.
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.