The problem of extracting the error message of Class-level constraints - java

I have created a custom validation annotation to check if the password and confirm password are equal. All the things work fine, but the only issue is I cannot get the field name anymore since it is a class-level constraint instead of field. I only can use the getAllErrors() to get the default message. (Refer to this image)
AddUserDTO
#Data
#NoArgsConstructor
#AllArgsConstructor
#ConfirmPassword
public class AddUsersEntityDTO {
//...blablabla
#NotBlank(message ="Password is required")
#Size(min = 6, message = "Password must be more than 5 characters")
public String password;
#NotBlank(message ="Confirm Password is required")
public String confirmPassword;
}
Validator
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ConfirmPasswordValidation.class)
public #interface ConfirmPassword {
String message() default "Confirm password is not same with the password";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class ConfirmPasswordValidation implements ConstraintValidator<ConfirmPassword, AddUsersEntityDTO> {
#Override
public void initialize(ConfirmPassword constraintAnnotation) {
}
#Override
public boolean isValid(AddUsersEntityDTO addUsersEntityDTO, ConstraintValidatorContext constraintValidatorContext) {
return addUsersEntityDTO.getConfirmPassword().equals(addUsersEntityDTO.getPassword());
}
}

Related

Restrict String field, to not accept true/false as the field value

Any ideas, how should I restrict String field in the Request Body, to not be able to have true or false as it's value. Couldn't find anything similar here. Ideally 400 Bad request would be the best solution. I am hoping that there is something from the javax.validation
#Setter
#Getter
#ToString
public class FeeGroupBody {
#JsonProperty("ext_group_id")
#NotNull
private String groupExtId;
#JsonProperty("ext_fee_id")
#NotNull
private String feeExtId;
}
As already suggested you can do this using a custom annotation and custom validator. You would begin with the custom annotation:
#Documented
#Constraint(validatedBy = NotTrueOrFalseStringValidator.class)
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface NotTrueOrFalseString {
String message() default "String value is one of the following invalid values: true or false";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And then the custom validator:
public class NotTrueOrFalseStringValidator implements ConstraintValidator<NotTrueOrFalseString, String> {
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return !org.apache.commons.lang3.StringUtils.equalsIgnoreCase("true", value) &&
!org.apache.commons.lang3.StringUtils.equalsIgnoreCase("false", value);
}
}
Then you just need to apply it in your model:
#Setter
#Getter
#ToString
public class FeeGroupBody {
#JsonProperty("ext_group_id")
#NotNull
#NotTrueOrFalseString
private String groupExtId;
#JsonProperty("ext_fee_id")
#NotNull
#NotTrueOrFalseString
private String feeExtId;
}
You can read more about custom validators at https://www.baeldung.com/spring-mvc-custom-validator.

Multiple fields (custom + notnull) validation while using spring validation

I have a dto like this
#FieldMatch(first = "email", second = "emailConfirm", message = "E-posta adresleri eslesmeli")
public class EmailDTO {
private String email;
private String emailConfirm;
this is validator
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
private String message;
#Override
public void initialize(final FieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
message = constraintAnnotation.message();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
boolean valid = true;
try
{
final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
valid = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
}
catch (final Exception ignore)
{
// ignore
}
if (!valid){
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(firstFieldName)
.addConstraintViolation()
.disableDefaultConstraintViolation();
}
return valid;
}
}
this is interface
arget({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = FieldMatchValidator.class)
#Documented
public #interface FieldMatch {
String message() default "The fields must match";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String first();
String second();
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List
{
FieldMatch[] value();
}
}
but i also want to change the class to this
#FieldMatch(first = "email", second = "emailConfirm", message = "E-posta adresleri eslesmeli")
public class EmailDTO {
#Max(value = 150, message = "E-posta karakter sayisi fazla!")
#Email(message = "Email should be valid")
#NotNull(message = "Name cannot be null")
#NotEmpty(message = "Name cannot be null")
private String email;
#Max(value = 150, message = "E-posta karakter sayisi fazla!")
#Email(message = "Email should be valid")
#NotNull(message = "Name cannot be null")
#NotEmpty(message = "Name cannot be null")
private String emailConfirm;
Should I use another generic constraint or a lot of annotations like shown above? I also have other entities like password, etc and those entities will also have same validations.
Some fields can be nullable, so not every field has to be checked by null constraint. I want to do a very generic validation. I will send to front end (thymeleaf), so I need to see which constraint is violated.
Email has #email annotation that wont be in the password validation. Others are common. like
notnull, notempty, matching, notblank
I was not able to find good examples. I found below posts on the topic, but I could not find an example of custom + for example #email validation together.
spring-custom-annotation-validation-with-multiple-field
how-can-i-validate-two-or-more-fields-in-combination
spring-mvc-custom-validator post shows validation for different fields.

Spring Boot validation - one from two not null

I'm trying to validate if one of two fields are not null in Spring Boot?
I have set that in the method class for the main object:
#NotNull(message = "Username field is required")
private String username;
#NotNull(message = "Email field is required")
private String email;
but that will require to have both fields not null. Then I went with custom validation described here https://lmonkiewicz.com/programming/get-noticed-2017/spring-boot-rest-request-validation/ but I wasn't able to get that example to work. I have to stuck on
User class declaration:
#CombinedNotNull(fields = {"username","email"})
public class User implements {
private long id = 0L;
#NotNull(message = "First name field is required")
private String firstName;
#NotNull(message = "Last name field is required")
private String lastName;
private String username;
private String email;
#NotNull(message = "Status field is required")
private String status;
...all methods here...
...setters and getters...
}
CombibnedNotNull class:
#Documented
#Retention(RUNTIME)
#Target({ TYPE, ANNOTATION_TYPE })
#Constraint(validatedBy = userValidator.class)
public #interface CombinedNotNull {
String message() default "username or email is required";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
userValidator class:
#Component
public class userValidator implements ConstraintValidator<CombinedNotNull, User> {
#Override
public void initialize(final CombinedNotNull combinedNotNull) {
fields = combinedNotNull.fields();
}
#Override
public boolean isValid(final User user, final ConstraintValidatorContext context) {
final BeanWrapperImpl beanWrapper = new BeanWrapperImpl(user);
for (final String f : fields) {
final Object fieldValue = beanWrapper.getPropertyValue(f);
if (fieldValue == null) {
return false;
}
}
return true;
}
}
Is there any other way to get this done or should I go with the "complex" example from that page?
I'm assuming username OR email must not be null. Not XOR.
Add this getter in User class:
#AssertTrue(message = "username or email is required")
private boolean isUsernameOrEmailExists() {
return username != null || email != null;
}
In my experience, the method name must follow the getter name convention otherwise this won't work. For examples, getFoo or isBar.
This has a small problem: the field name from this validation error would be usernameOrEmailExists, only 1 error field. If that is not a concern, this might help.
But If you want to have username and email fields when errors occur, you can use this workaround:
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
#AssertTrue(message = "username or email is required")
private boolean isUsername() {
return isUsernameOrEmailExists();
}
#AssertTrue(message = "username or email is required")
private boolean isEmail() {
return isUsernameOrEmailExists();
}
private boolean isUsernameOrEmailExists() {
return username != null || email != null;
}
get... methods are just simple getters for general use, and is... are for validation. This will emit 2 validation errors with username and email fields.
I'll try to implement it for you (even if I'm without an IDE).
Inside ConstraintValidator#initialize you can get a hold of the configured fields' names which cannot be null.
#Override
public void initialize(final CombinedNotNull combinedNotNull) {
fields = combinedNotNull.fields();
}
Inside ConstraintValidator#isValid you can use those fields' names to check the Object fields.
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
final BeanWrapperImpl beanWrapper = new BeanWrapperImpl(value);
for (final String f : fields) {
final Object fieldValue = beanWrapper.getPropertyValue(f);
if (fieldValue == null) {
return false;
}
}
return true;
}
Annotation:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Retention(RUNTIME)
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Constraint(validatedBy = CombinedNotNullValidator.class)
public #interface CombinedNotNull {
String message() default "username or email is required";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Fields to validate against null.
*/
String[] fields() default {};
}
The annotation could be applied as
#CombinedNotNull(fields = {
"fieldName1",
"fieldName2"
})
public class MyClassToValidate { ... }
To learn how to create a Class-level constraint annotation, refer always to the official documentation. Docs
If you want to validate that exactly one is set and the others are null:
Annotation
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Retention(RUNTIME)
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Constraint(validatedBy = OneNotNullValidator.class)
public #interface OneNotNull {
String message() default "Exactly one of the fields must be set and the other must be null";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Fields to validate against null.
*/
String[] fields() default {};
}
Validator
import java.util.Arrays;
import java.util.Objects;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.stereotype.Component;
#Component
public class OneNotNullValidator implements ConstraintValidator<OneNotNull, Object> {
private String[] fields;
#Override
public void initialize(final OneNotNull combinedNotNull) {
fields = combinedNotNull.fields();
}
#Override
public boolean isValid(final Object obj, final ConstraintValidatorContext context) {
final BeanWrapperImpl beanWrapper = new BeanWrapperImpl(obj);
return Arrays.stream(fields)
.map(beanWrapper::getPropertyValue)
.filter(Objects::isNull)
.count()
== 1;
}
}
Usage
#OneNotNull(
fields = {"username","email"},
message="Either username or email must be set"
)
public class User {
private String username;
private String email;
// ...
}

Is it possible to make my custom validation annotation to be ignored on persist?

I use Spring 4 and Hibernate 5
I have User class with password field with custom validator.
I need to validate it while form binding to be 8 characters long and include lowercase and uppercase letters, and numbers.
When user enters password it is valid, but it is not valid when i encode it.
So is there a way to make my custom validation annotation to be ignored on persist?
I know I can make different field for unencrypted password, or make data transfer object, validate it and then pasrse it's data to User.
But I am interested in possibility of annotation parametrization.
#Entity
#Table(name = "user")
public class User {
//other fields
#NotNull
#NotEmpty
#ValidPassword
#Column(name = "password", nullable = false, length = 60)
private String password;
//getters and setters
}
My validator
#Target({ TYPE, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = PasswordValidator.class)
#Documented
public #interface ValidPassword {
String message() default "Password is too short! Must be 8 digits and include lowercase, uppercase letters and numbers.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
private Pattern pattern;
private Matcher matcher;
private static final String PATTERN = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,})";
#Override
public void initialize(ValidPassword constraintAnnotation) {
}
#Override
public boolean isValid(String password, ConstraintValidatorContext context) {
return (validate(password));
}
private boolean validate(String password) {
pattern = Pattern.compile(PATTERN);
matcher = pattern.matcher(password);
return matcher.matches();
}
}
Controller method
#RequestMapping(value = "/registeruser", method = RequestMethod.POST)
public String registerUser(#ModelAttribute("user") #Valid User user, BindingResult result, Model model) {
if (result.hasErrors()) {
model.addAttribute("errorSummary", result.getFieldErrors().stream()
.map(e -> e.getField() + " error - " + e.getDefaultMessage() + " ").collect(Collectors.toList()));
model.addAttribute("user", user);
} else {
User registered = null;
registered = createUserAccount(user, result);
if (registered == null) {
model.addAttribute("errorSummary", "User with this email already registered!");
model.addAttribute("user", user);
return "registration";
}
model.addAttribute("flashMessage", "User registered successfully!");
}
return "registration";
}
UserService implementation method(where I encode my password)
#Transactional
#Override
public User registerNewUserAccount(User user) throws EmailExistsException {
if (emailExist(user.getEmail())) {
throw new EmailExistsException("There is an account with that email address:" + user.getEmail());
}
if (user.getPassword() == null) {
user.setPassword(new BigInteger(130, new SecureRandom()).toString(32));
System.out.println("+++++++++++++++" + user.getPassword());
}
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setUserRole(new HashSet<UserRole>(1));
user.getUserRole().add(new UserRole(user, Constants.RoleType.USER.name()));
save(user);
return user;
}
By default validation will happen for all constraints. Or you can specify Grouping Constraints
You can create a group by creating an interface:
interface FormValidationGroup{}
And annotate the password field like this:
#ValidPassword(groups = FormValidationGroup.class)
private String password;
Documentation for Custom Constraints annotations where groups parameter is mentioned.
Hibernate Validator should now ignore the password field unless you specify the group for validation. In order to specify a group for validation of a parameter of a Spring MVC handler method, use the Validated annotation instead of Valid. E.g.:
String registerUser(#ModelAttribute #Validated(FormValidationGroup.class) User user,
BindingResult result, Model model) {
if (result.hasErrors()) {

spring combine two validation annotations in one

I'm using Spring+Hibernate+Spring-MVC.
I want to define a custom constraint combining two other predefined validation annotations: #NotNull #Size like this:
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
#NotNull
#Size(min=4)
public #interface JPasswordConstraint {
} // this is not correct. It's just a suggestion.
and I want to use this annotation in my form models.
public class ChangePasswordForm {
#NotNull
private String currentPass;
#JPasswordConstraint
private String newPass;
#JPasswordConstraint
private String newPassConfirm;
}
UserController.java
#RequestMapping(value = "/pass", method = RequestMethod.POST)
public String pass2(Model model, #Valid #ModelAttribute("changePasswordForm") ChangePasswordForm form, BindingResult result) {
model.addAttribute("changePasswordForm", form);
try {
userService.changePassword(form);
} catch (Exception ex) {
result.rejectValue(null, "error.objec", ex.getMessage());
System.out.println(result);
}
if (!result.hasErrors()) {
model.addAttribute("successMessage", "password changed successfully!");
}
return "user/pass";
}
But it does not work. It accepts the less than 4 character passwords.
How can I solve this problem?
This is a bit late, but technique of combining validation annotations described in
https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/?v=5.4#section-constraint-composition
Maybe it was not available, at the time of writing, but solution is following
#NotNull
#Size(min=4)
#Target({ METHOD, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { })
#Documented
public #interface JPasswordConstraint {
String message() default "Password is invalid";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
You can add
#ConstraintComposition
Who is by default at AND
and if one day you want #NotNull or #Size
You add
#ConstraintComposition(OR)
Edit : this is an exemple who combine #Pattern(regexp = "[a-z]") and
#Size(min = 2, max = 3)
#ConstraintComposition(OR)
#Pattern(regexp = "[a-z]")
#Size(min = 2, max = 3)
#ReportAsSingleViolation
#Target({ METHOD, FIELD })
#Retention(RUNTIME)
#Constraint(validatedBy = { })
public #interface PatternOrSize {
String message() default "";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}

Categories

Resources