Custom validation annotation fails - java

There is a custom validation annotation created to check if two spring form fields are equal or not.
PasswordVerification:
#Constraint(validatedBy = PasswordVerificationValidator.class)
#Target({ElementType.TYPE, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface PasswordVerification {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
PasswordVerificationValidator:
public class PasswordVerificationValidator implements ConstraintValidator<PasswordVerification, UserFormRegistration> {
#Override
public void initialize(PasswordVerification constraintAnnotation) {}
#Override
public boolean isValid(UserFormRegistration userFormRegistration, ConstraintValidatorContext context) {
return userFormRegistration.getPassword().equals(userFormRegistration.getVerifyPassword());
}
}
UserFormRegistration:
#PasswordVerification(message = "Password and password confirmation fields don't match")
public class UserFormRegistration {
private String password;
...
So, if the annotation is applied to the class UserFormRegistration, it works fine. But if I want to apply it to the field (see below), it fails.
public class UserFormRegistration {
#PasswordVerification(message = "Password and password confirmation fields don't match")
private String password;
...
Exception:
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'ua.com.vertex.validators.interfaces.PasswordVerification' validating type 'java.lang.String'. Check configuration for 'password'
How to fix?

I guess you want to apply the annotation at method level also so you need to have ElementType.METHOD
so change #Target({ElementType.TYPE, ElementType.FIELD}) to
#Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD})
so now #PasswordVerification will be applicable to methods, classes,interfaces,enums and fields

Related

Is there any way I can enable/disable nested object's annotation based on parent object's property?

Say I have a request payload class like the following:
#Data
class Payload {
Type type;
#Valid
Details details;
}
And,
#Data
class Dtails {
#OnlyAlphabet
String name;
String email;
}
I have the enum defined as follows:
public enum Type {
HUMAN,
ALIEN
}
I have defined the #OnlyAlphabet constraint like this:
#Target({TYPE, FIELD, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = NameValidator.class)
public #interface OnlyAlphabet {
String message() default "Invalid Name";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And,
public class NameValidator implements ConstraintValidator<OnlyAlphabet, String> {
#Override
public void initialize(OnlyAlphabet constraintAnnotation) {
}
#Override
public boolean isValid(String name, ConstraintValidatorContext constraintValidatorContext) {
return isContainOnlyAlphabet(name);
}
}
If the type is ALIEN, I want the #OnlyAlphabet validation constraint disabled. Or even, based on the type property, I want to use another annotation. Is there any way I can achieve it?

Hibernate validate object with a list

I have User class with list of roles:
#Data
#Table(name = "users")
#Entity
public class User {
String username;
String password;
List<Role> roles;
}
And the role enum:
public enum Role {
Admin,User,Manager
}
I need to validate the bean before insert it to DB. The validation expression should look like this:
long count = user.getRoles().stream().filter(r -> r.equals(Role.Manager)).count();
!(count > 1);
User cant have more than one manager role, but for other roles its ok to have duplicates;
For this i created a custom constraint and a validator for him:
Constraint:
#Target({ ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = RoleValidator.class)
public #interface RoleConstraint {
String message() default "error";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Validator:
public class RoleValidator implements ConstraintValidator<RoleConstraint, List<Role>> {
#Override
public boolean isValid(List<Role> roles, ConstraintValidatorContext context) {
long count = roles.stream().filter(r -> r.equals(Role.Manager)).count();
return !(count > 1);
}
}
but it doesnt work. I also found a solution here but i cant wrap the list in beanList because a lot of other classes depends on this class. Is there other way to solve this problem. Any suggestion is acceptable
Since you are validating the User, you can make you annotation work with a user. Have the annotation work on classes:
#Target({ ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = RoleValidator.class)
public #interface RoleConstraint {
String message() default "error";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Change the validator to work with user:
public class RoleValidator implements ConstraintValidator<RoleConstraint, User> {
#Override
public boolean isValid(User user, ConstraintValidatorContext context) {
long count = user.roles.stream().filter(r -> r.equals(Role.Manager)).count();
//that's simpler, but is the same as '!(count > 1)'
return count <= 1;
}
}
Then apply #RoleConstraint on User class.

Is it possible to pass validatedBy attribute to the annotation as a parameter?

I hard coded the validatedBy value as following.
#Constraint(validatedBy = ReminderValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyValidator{
String message() default "{error.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Instead of hard coding the validatedBy value into my annotation interface, I want to pass it as a parameter something similar to the following.
#MyValidator(validatedBy = "ReminderValidator.class")
public class Reminder {
...
}
This will enable me to create just 1 annotation for all class validations. I will just provide different validator classes to the annotation to validate different classes. Is it possible to do something like this?
You can add multiple validators in the #Constraint annotation and it will pick the appropriate based on the object type.
#Constraint(validatedBy = { ReminderValidator.class, PendingValidator.class } )
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyValidator{
String message() default "{error.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And
#MyValidator()
public class Reminder {
...
}
#MyValidator()
public class Pending {
...
}
Validator Examples
public class ReminderValidator implements ConstraintValidator<MyValidator, Reminder> {
...
public class PendingValidator implements ConstraintValidator<MyValidator, Pending> {
...
Let me know if this is an acceptable solution for you -
#Constraint(validatedBy = CommonValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyValidator{
// this decides what actual validator we will use
String validator();
String message() default "{error.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Now in the CommonValidator we can decide the actual validator based on the value of validator.
Example of a static validator:
public class SomeValidator {
public static boolean isValid(Object field, ConstraintValidatorContext context) {
// your actual validation code
}
}
Similarly you can define your other validators as well.
This is how you can use the static validator in actual validator :
public class CommonValidator implements ConstraintValidator<MyValidator, Object> {
private String validator;
#Override
public void initialize(MyValidator myValidator) {
this.validator = myValidator.validator();
// do other inits as per your requirement
}
#Override
public boolean isValid(Object field, ConstraintValidatorContext context) {
ValidatorFactory.getValidator("someValidator").isValid(field, context)
// return other validators like above
return false;
}
}
finally you can use you annotation like this -
#MyValidator(validator = "someValidator")
private Object object;
Object can be any other class as well, you have to handle this in
individual static validator.
FYI - this is just an idea, Actual implementation might differ as per your use case.

How to enable javax annotations when value not null and disabled when value is null?

I need to disable validation if the value of email is null and check on it if the value is not null.
#Email(message = "{invalidMail}")
private String email;
I found the answer and it's that almost all java validation annotations accepts null so if my value is null it's going to accept it otherwise it will check.
You can't achieve this result with the predefined set of validation annotations only.
You have to create a custom validation annotation which performs the validation based on the specifications. You can get inspired on the Baeldung's article Spring MVC Custom Validation.
Here is the annotation.
#Documented
#Constraint(validatedBy = MyEmailValidator.class) // Class which performsthe validation
#Target({ ElementType.FIELD }) // Applicable to a field
#Retention(RetentionPolicy.RUNTIME)
public #interface MyEmail {
String message() default "The email is invalid"; // The default message
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And a class which actually performs the validation itself against the email Regex if the input is not null. Returns true otherwise as null is accepted. Note this class has to implement ConstraintValidator<A extends Annotation, T>.
public class MyEmailValidatorimplements ConstraintValidator<MyEmail, String> {
// Email Regex
private final String emailPattern= "[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}";
#Override
public void initialize(MyEmail myEmail) { }
#Override
public boolean isValid(String input, ConstraintValidatorContext cxt) {
if (input == null) {
return true;
} else return Pattern.matches(emailPattern, input);
}
}

Problems extending a Hibernate Validator constraint

I am trying to extend the behavior of the #NotBlank constraint to apply to URIs by making a custom constraint called #NotBlankUri.
Here's my constraint annotation:
#Target({ METHOD, FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotBlankUriValidator.class)
public #interface NotBlankUri {
String message() default "{project.model.NotBlankUri.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and here is the ConstraintValidator:
public class NotBlankUriValidator implements ConstraintValidator<NotBlankUri, URI> {
public void initialize(NotBlankUri annotation) {
}
public boolean isValid(URI uri, ConstraintValidatorContext context) {
NotBlankValidator nbv = new NotBlankValidator();
return nbv.isValid(uri.toString(), context);
}
}
Problem is that the isValid() method on the ConstraintValidator is getting null values for the URI argument. I thought this wasn't supposed to happen given the fact that #NotBlank itself is annotated #NotNull. That not being the case, I tried adding #NotNull as a meta-annotation to my #NotBlankUri, but that didn't have the desired effect either. How can I make my annotation constraint behave like #NotBlank, which seems to be stacking on top of the behavior of #NotNull?
As per the documentation, you can't use the #NotBlank annotation on a datatype that is not a String.
public #interface NotBlank
Validate that the annotated string is not null or empty. The difference to NotEmpty is that trailing whitespaces are getting ignored.
So if you declared your validator to validate a String, everything would be fine and you could write your annotation like this:
#Target({ METHOD, FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotBlankUriValidator.class)
#NotBlank
public #interface NotBlankUri {
String message() default "{project.model.NotBlankUri.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
If you are deadset on using the URI class 1 you need to perform custom validation logic yourself like this:
Annotation:
#NotNull(message="URI must not be null")
#Target({ METHOD, FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotBlankUriValidator.class)
public #interface NotBlankUri {
String message() default "URI must not be blank";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator:
public class NotBlankUriValidator implements ConstraintValidator<NotBlankUri, URI> {
public void initialize(NotBlankUri annotation) {
}
public boolean isValid(URI uri, ConstraintValidatorContext context) {
boolean isValid = true;
System.out.println("URI: " + uri);
//Leave null checks to your #NotNull constraint.
//This is only here to prevent a NullPointerException on the next check.
if(uri == null){
return true;
}
if(uri.toString().isEmpty()){
isValid = false;
}
return isValid;
}
}
I ran the above with a test harness:
public class UriContainer {
public UriContainer(URI uri){
this.uri = uri;
}
#NotBlankUri
private URI uri;
public URI getUri() {
return uri;
}
}
public static void main(String[] args) throws URISyntaxException{
UriContainer filledContainer = new UriContainer(new URI("Stuff"));
UriContainer emptyContainer = new UriContainer(new URI(""));
UriContainer nullContainer = new UriContainer(null);
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<UriContainer>> filledViolations = validator
.validate(filledContainer);
Set<ConstraintViolation<UriContainer>> emptyViolations = validator
.validate(emptyContainer);
Set<ConstraintViolation<UriContainer>> nullViolations = validator
.validate(nullContainer);
System.out.println("Filled: ");
filledViolations.stream().forEach(System.out::println);
System.out.println("Empty: ");
emptyViolations.stream().forEach(System.out::println);
System.out.println("Null: ");
nullViolations.stream().forEach(System.out::println);
}
which output the following violations:
URI: Stuff
URI:
URI: null
Filled:
Empty:
ConstraintViolationImpl{interpolatedMessage='URI must not be blank', propertyPath=uri, rootBeanClass=class sandbox.UriContainer, messageTemplate='URI must not be blank'}
Null:
ConstraintViolationImpl{interpolatedMessage='URI must not be null', propertyPath=uri, rootBeanClass=class sandbox.UriContainer, messageTemplate='URI must not be null'}
As you can see, this allows you to output different error messages based on if the URI is blank or null. Just make sure if you are using a javax.validation annotation you check which datatype you operate on.
1: which by the way, performs validation when you construct the object, and will throw a URISyntaxException if the String passed to the constructor violates RFC 2396

Categories

Resources