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.
Related
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?
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.
I am trying to validate my rest request according to some fields existance. For example, if transactionDate field is null or didnt exist in my request object, I want to throw an error to client.
I couldn't do it despite the source of this guide and still my requests can pass in controller.
How can I validate two or more fields in combination?
DTO
#FraudRestRequestValidator
public class FraudActionsRestRequest {
private BigDecimal amount;
private String receiverTransactionDate;
private String receiverNameSurname;
private BigDecimal exchangeRate;
private String transactionReferenceNumber;
#NotNull
private String transactionDate;
#NotNull
private String transactionTime;
private String transactionTimeMilliseconds;
private BigDecimal tlAmount;
private String channel;
}
ANNOTATION
#Constraint(validatedBy = FraudActionsRestValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface FraudRestRequestValidator {
String message() default "Invalid Limit of Code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
VALIDATOR
public class FraudActionsRestValidator implements ConstraintValidator<FraudRestRequestValidator, FraudActionsRestRequest> {
#Override
public void initialize(FraudRestRequestValidator constraintAnnotation) {
}
#Override
public boolean isValid(FraudActionsRestRequest fraudActionsRestRequest, ConstraintValidatorContext constraintValidatorContext) {
//I will implement my logic in future
return false;
}
}
REST CONTROLLER
#PostMapping("/getFraudActions")
public ResponseEntity<?> getFraudActions(#Valid #RequestBody FraudActionsRestRequest fraudActionsRestRequest, Errors errors) throws Exception
Thanks.
In your custom validator just implement logic you want to have. You did everything correct except some minor thing:
#Constraint(validatedBy = FraudActionsRestValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidFraudRestRequest {
String message() default "Invalid Limit of Code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class FraudActionsRestValidator implements ConstraintValidator<ValidFraudRestRequest, FraudActionsRestRequest> {
#Override
public void initialize(ValidFraudRestRequest constraintAnnotation) {
}
#Override
public boolean isValid(FraudActionsRestRequest fraudActionsRestRequest, ConstraintValidatorContext constraintValidatorContext) {
return fraudActionsRestRequest.getTransactionDate() != null && fraudActionsRestRequest.getTransactionTime() != null && additional check you need;
}
}
Looks all okaish.
You might be missing the #Validated annotation on the rest controller class,
See https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-validation.html for more info
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
I am trying to use spring to check user online input to ensure that the two characters they enter is an actual US state, is there any way of doing this, hopefully using a preset pattern? like, #State or something (if that was a legit annotation). Also, is there a good annotation commonly used for a String street, and String city field? That is other than #NotNull and #NotEmpty
Any help would be greatly appreciated!!
Unfortunately there is no out of the box however you can create your own #State annotation , all you need is to define your annotation and class implementing ConstraintValidator(which handles the validation logic) E.g.
#Constraint(validatedBy = StateConstraintValidator.class)
#Target( { ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface State {
String message() default "{State}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class StateConstraintValidator implements ConstraintValidator<String, String> {
private static final Set<String> CODE_MAP = new HashSet<>(){
{add("AR");}
{add("AK");} //add more codes ...
};
#Override
public void initialize(String state) { }
#Override
public boolean isValid(String value, ConstraintValidatorContext cxt) {
if(value == null) {
return false;
}
return CODE_MAP.contains(value);
}
}
In the similar manner you can create other annotations.