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.

I’ve got a similar need for accessing the root bean for validations and placed a question on StackOverflow (Question about complex Java Beans Validation with Spring Boot and Vaadin in the front-end - Stack Overflow), which I reproduce here:

I’ve got an Entity class with lots of fields, including a status to represent the progression of an object thru its life cycle, say “INITIAL”, “READY”, “SUSPENDED” and “CLOSED”. While at the “INITIAL” stage, all fields can be changed, a few can be changed while the object is “SUSPENDED”, very few fields can be changed while in the “READY” status, and once “CLOSED”, nothing more can be changed for that object.

So, setName(@NotBlank String name) is fine, but that is not enough, as as the name can only be changed when status is NOT CLOSED, but setCurrency(@NotBlank String currency) can only be applied when status is INITIAL, or SUSPENDED if other conditions are also valid. Thus, I need varying validations applied as pre-conditions to the set… method execution, not as Class-level validation.

There is more. The status is implemented as an enum, with a Finite State Machine defining the valid transitions from one state to another. And at each state transition, a number of business rules need to be applied to validate the data surrounding the core entity. So instead of using setStatus(Status newStatus), I’m using setStatusReady(), setStatusSuspended()

From all that I’ve found, most tutorials, articles, best practices and solutions cater for the syntactical validations at field or class level and cross-parameter method validations, while I’d need access to whole Bean instance at the time of the method execution.

So far, the “best” solution is from OVAL (see OVal User Guide | oval), but that framework is not longer under development and OVal is not JSR303/JSR380 compliant.

So, what are your suggestions?

Thank you all."

Hey Ismael,

It seems that there are a few concerns mixed in your use case. There’re groups of validation rules, bean modifications and state transitions.

For example:

is more about business logic rather than POJO being valid. I’d suggest separating things:

  • Starting from the entry point (controller/resource) - either have specific POJOs that describe the fields that can be modified in each state or have a generic POJO with all possible fields for any state and apply validation groups to it.
  • At the service level, after an entity is retrieved from DB and its current status is identified, a specific “updater” is selected. By “updater” I mean a set of instructions that modify only the entity properties that can be changed in the current status.
  • At the entity level, you can have a combination of property/class level constraints that check that the overall state of the object is valid.