Java Bean validation: Combine two contstraints with OR on one field - java

I want to validate a field 'foo' against either of two constraints, i.e. something like this
#ConstraintA OR #ConstraintB
private String foo;
Is this possible?

This is possible with Hibernate Validator, but only using a Hibernate Validator specific extension. Using it is not standard conform to Bean Validation.
You will have to use boolean composition of constraints as described here - http://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-boolean-constraint-composition
You will need a "wrapper" constraint. Something like this:
#ConstraintComposition(OR)
#PConstraintA
#ConstraintB
#ReportAsSingleViolation
#Target({ METHOD, FIELD })
#Retention(RUNTIME)
#Constraint(validatedBy = { })
public #interface ConstraintAOrB {
String message() default "{com.acme.ConstraintAOrB.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}

Related

Calling the real method on a Java annotation using Mockito

I have a custom annotation called Matches which has a default value for message. This is essentially a class-level custom constraint validator but that's not part of the problem.
#Constraint(validatedBy = MatchesValidator.class)
#Documented
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Repeatable(Matches.List.class)
public #interface Matches {
String field();
String otherField();
String message() default "{com.example.api.validation.constraints.Matches.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Documented
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface List {
Matches[] value();
}
}
This is a simple constraint that will be applied on a class to validate if two fields have the same value:
#Matches(field = "password", otherField = "confirmPassword")
class UserRegistration {
private String password;
private String confirmPassword;
// Getters
}
I'm writing a test using JUnit and Mockito and and I'm trying to mock the Matches annotation:
#Test
void isValid_whenFieldsDoNotMatch_thenReturnsFalse() {
Matches invalidPropertyMatches = Mockito.mock(Matches.class);
when(invalidPropertyMatches.field()).thenReturn("password");
when(invalidPropertyMatches.field()).thenReturn("confirmPassword");
when(invalidPropertyMatches.message()).thenCallRealMethod(); // This throws
}
I want that when the Matches.message() value is called, I get back the default defined value "{com.example.api.validation.constraints.Matches.message}", so I added:
when(invalidPropertyMatches.message()).thenCallRealMethod();
However, this throws the following exception:
org.mockito.exceptions.base.MockitoException:
Cannot call abstract real method on java object!
Calling real methods is only possible when mocking non abstract method.
//correct example:
when(mockOfConcreteClass.nonAbstractMethod()).thenCallRealMethod();
It essentially says that Matches.message() is abstract and cannot be called. Are annotation properties in Java abstract? Is there a way to fix this?

Custom validation with javax.validation

I'm trying to make a custom java validation annotation and returns me
Request processing failed; nested exception is javax.validation.ConstraintDeclarationException: HV000144: Cross parameter constraint com.my.company.CustomConstraint is illegally placed on field 'private java.util.List com.my.company.ElementOfTheList'."
the code is really naive
#Documented
#Retention(RUNTIME)
#Target({ FIELD, METHOD})
#Constraint(validatedBy = ConstraintValidation.class)
public #interface CustomConstraint {
String message() default "this is the default message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConstraintValidationimplements ConstraintValidator<CustomConstraint , List<ElementOfTheList>> {
public boolean isValid(List<ElementOfTheList> value, ConstraintValidatorContext context) {
System.out.println("only a sysout to test");
return true;
}
}
And in the rest object model
#JsonProperty("ElementOfTheList")
#Valid
#NotNull(message ="not null message")
#NotEmpty(message = "not empty message")
#CustomConstraint
private List<ElementOfTheList> list = null;
change
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
to
#SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
since you want to validate and element (here is List list)
and not the parameters of a method or a constructor

Java validator annotation for value from properties file

I am trying to write a Validator that should validate the value of a property in application.properties
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = BaseUrlStartsWithHttpsValidator.class)
public #interface CheckBaseUrlStartsWithHttps {
String message() default "Base url does not start with https:// check your configuration, "
+ "Found: ${validatedValue}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value() default "";
}
It's a simple validation I am just checking if the String starts with https://.
and the way I am trying to use it by annotating the field with it so:
#CheckBaseUrlStartsWithHttps
#Value("${my.base.url}")
private String baseUrl;
But it seems not to do the trick I have tried changing the #Target type is it even possible to validate properties this way, I am using Spring Framework.
So figured it out my self upon reading, reading, reading and trying different things. Turns out that in the class where I am reading in the property I had to annotate the class itself with #Validated and #ConfigurationProperties(prefix = "my") then my check was working as supposed to. So the end product is:
public class BaseUrlValidator implements ConstraintValidator<CheckBaseUrl, String> {
#Override
public boolean isValid(#Nullable String value, #Nullable ConstraintValidatorContext context) {
if (value == null) {
return false;
}
return value.startsWith("https://");
}
}
and
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = BaseUrlValidator.class)
public #interface CheckBaseUrl {
String message() default "Base URL should start with https://. Found: ${validatedValue}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value() default "";
}
and
#Service
#Validated
#ConfigurationProperties(prefix = "my")
public class MyService {
#CheckBaseUrl
#Value("${my.base.url}")
private String baseUrl;
...
}
Only thing that might be a bit annoying though is that this will make the application fail on startup if the urls is not configured correctly, which is in its own probably a good thing such that it can be fixed right away, but I would rather want it to fail on runtime when it is accessed and throw a RumetimeException instead. Anyway this seems to do the trick.

ConstraintValidation not getting applied to the list

I have List of Objects that I need to run some validation on
#KeyValid
#Valid
protected List<KeyValue> keyValues;
and I have a the following annotation created for it:
#Target({ElementType.TYPE, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = KeyValidator.class)
public #interface KeyValid{
String message() default "invalid_parameter_default_message";
String[] checks() default {};
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And this is my validator:
public class KeyValidator implements ConstraintValidator<KeyValid, KeyValue> {
#Override
public void initialize(KeyValid keyValid) {
}
#Override
public boolean isValid(KeyValue keyValue, ConstraintValidatorContext constraintValidatorContext) {
return true;
}
}
I had read somewhere that collections can be validated in bulk if the list or map or set is annotated by custom constraint then all of the elements of the collection call the validator but the above code throws the following error
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'com.util.Validators.KeyValid' validating type 'java.util.List<com.model.KeyValue>'. Check configuration for 'keyValue'
Your constraint would get the actual List passed not its elements. If you are using Java 8 and the latest version of Hibernate Validator, you can use type argument constraints. You just have to make sure to also add ElementType.TYPE_USE to #Target in your constraint. Type argument constraints are not yet official part of Bean Validation, but will be in the next version of it (BV 2.0).
You would have something like this:
protected List<#KeyValid KeyValue> keyValues;
Alternatively, could you not put the #KeyValid constraint as class level constraint on KeyValue?

How to set default group in bean validation context

I'm working with bean validations and I'm searching for a possibility to set a default group of my own bean validation annotation.
I have something (working) like this:
Application.class (calling validate on MyBean)
public class Application {
public static void main(String[] args) {
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<MyBean>> violations =
validator.validate(new MyBean(), SecondStep.class);
}
}
MyBean.class (the bean itself; here is what I want to prevent)
public class MyBean {
// I don't want to write this "groups" attribute every time, because it's very clear,
// that this should only be validated for the second step, isn't it?
#RequiredBySecondStep(groups=SecondStep.class)
private Object myField;
}
RequiredBySecondStep.class (the bean validation annotation)
#Documented
#Target(FIELD)
#Retention(RUNTIME)
#Constraint(validatedBy = RequiredBySecondStepValidator.class)
public #interface RequiredBySecondStep {
String message() default "may not be null on the second step";
Class<?>[] groups() default {}; // <-- here I want to set SecondStep.class
Class<? extends Payload>[] payload() default {};
}
RequiredBySecondStepValidator.class (an implemented constraint validator)
public class RequiredBySecondStepValidator implements ConstraintValidator<RequiredBySecondStep, Object> {
public void initialize(RequiredBySecondStep constraintAnnotation) {
}
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
return object != null;
}
}
SecondStep.class (the bean validation group)
public interface SecondStep {
}
Unfortunately, it's not possible by specification, to set the default group in the RequiredBySecondStep annotation like this:
Class<?>[] groups() default SecondStep.class;
// and using just the following in the bean:
#RequiredBySecondStep
private Object myField;
This will result in a RuntimeException:
javax.validation.ConstraintDefinitionException: Default value for
groups() must be an empty array
Furthermore, there is not only a SecondStep. There are probably 5 different groups which I want to annotate directly with a #RequiredByFirstStep or #RequiredByFifthStep.
Is there a good way to implement this?
I think you got it all a bit wrong. There is indeed to way to do what you want and that's because the aspect of constraints and their validation via a ConstraintValidator is orthogonal to groups and groups sequences. Per design a constraint (annotation and its validator) should be independent of the group getting validated. Even if you would get this to work, it would not be portable constraints. Personally, I would re-think what you want to achieve. #RequiredByFirstStep does not tell you what the requirement is. You should develop constraints which imply what they are valiating (a string length, not null, etc), when or better in which condition they are executed is a then controlled by group interfaces.

Categories