How can I get the root bean in a custom validator,When using field-level constraints field access strategy

Hi guys,
I’m implement a custom corrrelation validator, just like this:
Correlation.java

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Constraint(validatedBy = CorrelationValidator.class)
public @interface Correlation {
    String message() default "";
    String ref() default "";

    String refValue() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

User.java

public class User {
    @Correlation(ref = "name", refValue = "alan", message = " when name is alan, the age can not be null")
    private Integer age;
    private Integer id;
    private String name;

 //getters and setters...
}

CorrelationValidator.java

public class CorrelationValidator implements ConstraintValidator<Correlation, String> {

    @Override
    public void initialize(Correlation constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        // I want to get the root bean instance User here and get the user.name to do a validation, but I can not obtain it from the constraintValidatorContext ;
        return true;
    }
}

I want to get the root bean instance User in the CorrelationValidator .isValue() method and get the user.name to do a validation, but I can not obtain it from the constraintValidatorContext ;
when I hava a look at your sources code ConstraintTree.java,I found I can obtain the root bean in ValidationContext.getRootBean(), I hope you can put executionContext into the isValue method.

ConstraintTree.java

	private <T, V> Set<ConstraintViolation<T>> validateSingleConstraint(ValidationContext<T> executionContext,
																		ValueContext<?, ?> valueContext,
																		ConstraintValidatorContextImpl constraintValidatorContext,
																		ConstraintValidator<A, V> validator) {
		boolean isValid;
		try {
			@SuppressWarnings("unchecked")
			V validatedValue = (V) valueContext.getCurrentValidatedValue();
			isValid = validator.isValid( validatedValue, constraintValidatorContext );
		}
		catch ( RuntimeException e ) {
			throw log.getExceptionDuringIsValidCallException( e );
		}
		if ( !isValid ) {
			//We do not add these violations yet, since we don't know how they are
			//going to influence the final boolean evaluation
			return executionContext.createConstraintViolations(
					valueContext, constraintValidatorContext
			);
		}
		return Collections.emptySet();
	}

if you hava a better solution please notice me, I will appreciate it.

Hi,

I think for now, the cleanest solution would be to use a class level constraint. That way, you can access the whole bean and check things there.

I don’t think it would be that hard to expose the root bean into HibernateConstraintValidatorContext (the Hibernate Validator-specific version of ConstraintValidatorContext that you can cast to) but I’m not convinced it’s a good idea from a design point of view.

Could you expose why a class level constraint is not a good solution for you?

that case, in order to get root bean I should add a class-leve annotation, I think it is not convenience, like this:
CorrelationOnClass.java

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Constraint(validatedBy = CorrelationValidator.class)
public @interface CorrelationOnClass {
    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

User.java

@CorrelationOnClass
public class User {
    @Correlation(ref = "name", refValue = "alan", message = " when name is alan, the age can not be null")
    private Integer age;
    private Integer id;
    private String name;
   //getters and setters...
}

Correlation.java

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface Correlation {
    String message() default "";
    String ref() default "";

    String refValue() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

CorrelationValidator.java

public class CorrelationValidator implements ConstraintValidator<CorrelationOnClass, Object> {

    @Override
    public void initialize(CorrelationOnClass constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(Object bean, ConstraintValidatorContext constraintValidatorContext) {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            Correlation annotation = field.getAnnotation(Correlation.class);
            //get the feild value and do a validation
        }
        return true;
    }
}

Personally I think I would have made this thing less generic and have specific CorrelationValidators for each class and implement the logic directly with the passed bean (that would be then of the right class directly).

If you want something generic, you will have to use some reflection, yes.

I’m just a bit nervous at the idea of breaking the encapsulation of a field validator. A bit worried that it could open a can of worms.

@mbekhta what do you think?

hmm, I agree that exposing the root object can potentially lead to exposing too much and open access to more “private data”.

In this particular case

If what you are trying to do is to create some conditional validation, I’d suggest taking a look at writing a getter with @AssertTrue on it, something along these lines:

public class User {
	private Integer age;
	private Integer id;
	private String name;
	//getters and setters...
	
	@AssertTrue(message = " when name is alan, the age can not be null")
	public boolean isAgeValidForName(){
		return "alan".equalsIgnoreCase( name ) ? age != null : true;
	}
}

Also, if that User class is supposed to be converted to JSON - you might need to put an additional annotation on this getter, so it is not serialized into JSON (depending on what lib you are using for it).

You can have as many such getters as you need.

It might be that I’ve missed something but if you are planning on using the

in any other classes besides User, exposing the root object would still require you to do some reflection if you’d like to have a single ConstraintValidator implementation.

Another idea to consider - create an interface that exposes the fields that you are interested in (name/age) and implement it in all other classes, including User that way, you’d only have a single ConstraintValidator impl to validate the interface rather than generic Object.

Thank you for your answer, because we are writing a framework for our team, we need to deal with a lot of correlation verification so we need a more general and simple implementation.

I see … well in this case I’d also suggest taking a look at @ScriptAssert - this one allows you to write a simple validation script (let’s say in groovy) where you could place your conditional logic.

I wonder does the declaration of this method have to start with 'is '?

	@AssertTrue(message = " when name is alan, the age can not be null")
	public boolean isAgeValidForName(){
		return "alan".equalsIgnoreCase( name ) ? age != null : true;
	}

Such methods should follow the JavaBean convention. Otherwise, the validation will not be triggered. Please take a look at this section of the documenttation. It provides the details of what will be considered as a getter as well as how you can adjust that.