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.
Related
I've encountered this problem multiple times.
The problem:
I have some class with private Fields (For example User information which is a Dto):
public class RegisterRequest {
private String firstName;
private String lastName;
private String username;
private String email;
private String fieldOfStudy;
private String password;
}
After searching the community on how to read the values of these fields (when for example doing a post request), I saw a lot of answers that said the solution is Reflection.
Lets say I want to check if any field is null (in another class), then my reflection-method would be the following:
for (Field f : registerRequest.getClass().getDeclaredFields()) {
try {
Field field = registerRequest.getClass().getDeclaredField(f.getName());
field.setAccessible(true);
Object value = field.get(registerRequest);
if (value == null) {
throw new AccountException("Vul alle velden in.");
}
}
catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
Now I'm wondering if there is a way to do this without using field.setAccesible(true), Since bypassing the accessibility of fields could lead to run-time errors. I.e. We shouldn't use reflection to change the visibility of a field.
Thank you for your time!
Do the validity check in the class that contains the private Data:
public boolean isValidForm() {
for (Field field : this.getClass().getDeclaredFields()) {
try {
if (field.get(this) == null) {
return false;
}
} catch (IllegalAccessException e) {
throw new AccountException("Something went wrong");
}
}
return true;
}
Use this function in your other class by doing:
if(!registerRequest.isValidForm) {
throw new AccountException("...")
}
Credits to Geocodezip
Maybe there's something I didn't undertsand in your post but I don't think you should use reflection to do validation on your DTO. Here's a solution on how you could handle validation using javax:
#Data
#Value
class RegisterRequest {
#NotNull(message = "First name should not be null")
String firstName;
#NotNull(message = "Last name should not be null")
String lastName;
#NotNull(message = "Username should not be null")
String username;
#NotNull(message = "Email should not be null")
String email;
#NotNull(message = "Field of study should not be null")
String fieldOfStudy;
#NotNull(message = "Password should not be null")
String password;
}
If you use java17+ you can use a record like this (and get rid of lombok):
public record RegisterRequest(
#NotNull(message = "First name should not be null")
String firstName,
#NotNull(message = "Last name should not be null")
String lastName,
#NotNull(message = "Username should not be null")
String username,
#NotNull(message = "Email should not be null")
String email,
#NotNull(message = "Field of study should not be null")
String fieldOfStudy,
#NotNull(message = "Password should not be null")
String password
) {
}
Then you can make a unit test on your validation like this:
class RegisterRequestTest {
private Validator validator;
#BeforeEach
public void setUp() {
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
validator = factory.getValidator();
}
}
#ParameterizedTest
#MethodSource("invalidRegisterRequest")
void validation_should_be_invalid(RegisterRequest registerRequest) {
assertThat(validator.validate(registerRequest)).isNotEmpty();
}
private static List<RegisterRequest> invalidRegisterRequest() {
return List.of(
new RegisterRequest(null, "lasstname", "username", "email", "fieldOfStudy", "password"),
new RegisterRequest("firstName", null, "username", "email", "fieldOfStudy", "password"),
new RegisterRequest("firstName", "lasstname", null, "email", "fieldOfStudy", "password"),
new RegisterRequest("firstName", "lasstname", "username", null, "fieldOfStudy", "password"),
new RegisterRequest("firstName", "lasstname", "username", "email", null, "password"),
new RegisterRequest("firstName", "lasstname", "username", "email", "fieldOfStudy", null)
);
}
}
In my examples I used #NotNull like in your post but I would recommand to use #NotBlank because in addition to checking for nullity, it checks if the property contains at least one non-blank character.
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());
}
}
I'm trying to accomplish the use of a validator into the controller. The two fields origin and destination should be of three capital letters as IATA Code. But it acts without filter, and any request is accepted.
Set of validator interface:
#Documented
#Constraint(validatedBy = IATACodeValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface IATACodeConstraint {
String message() default "Invalid IATA code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Class of Validator:
public class IATACodeValidator implements ConstraintValidator<IATACodeConstraint, String> {
#Override
public void initialize(IATACodeConstraint iataCode) {
}
#Override
public boolean isValid(String codeField, ConstraintValidatorContext context) {
return codeField != null && codeField.matches("^[A-Z]{3}+$")
&& (codeField.length() == 3);
}
Basic class:
public class CrazyAirRequest {
#IATACodeConstraint
private String origin;
#IATACodeConstraint
private String destination;
private String departureDate;
private String returnDate;
private int passengerCount;
// getters & setters
Controller:
#RestController
#RequestMapping("flights")
#Validated
public class BusyFlightsController {
CrazyAirDatabase crazyAirService;
ToughJetDatabase toughJetService;
#Autowired
public BusyFlightsController(CrazyAirDatabase crazyAirService, ToughJetDatabase toughJetService) {
this.crazyAirService = new CrazyAirDatabase();
this.toughJetService = new ToughJetDatabase();
}
#RequestMapping(value = "/crazy-air-response", method = RequestMethod.GET)
public List<CrazyAirResponse> getCrazyAirResponse(
#RequestParam("origin") String origin,
#RequestParam("destination") String destination,
#RequestParam("departureDate") String departureDate,
#RequestParam("returnDate") String returnDate,
#RequestParam("passengerCount") int passengerCount
) {
CrazyAirRequest crazyAirRequest = new CrazyAirRequest(origin, destination, departureDate, returnDate,
passengerCount);
return crazyAirService.getCrazyAirResponse(crazyAirRequest);
}
You can directly use #Pattern annotation without creating a custom validator for two fields as shown below :
#Pattern(regexp ="^[A-Z]{3}" message ="Invalid IATA code")
private String origin;
#Pattern(regexp ="^[A-Z]{3}" message ="Invalid IATA code")
private String destination;
#virendra chauhan: thank you for your inspiration.
I solved so:
#RequestMapping(value = "/crazy-air-response", method = RequestMethod.GET)
public List<CrazyAirResponse> getCrazyAirResponse(
#RequestParam("origin") #Pattern(regexp = "^[A-Z]{3}", message = "Invalid IATA code")
String origin,
#RequestParam("destination") #Pattern(regexp = "^[A-Z]{3}", message = "Invalid IATA code")
String destination,
#RequestParam("departureDate") String departureDate,
#RequestParam("returnDate") String returnDate,
#RequestParam("passengerCount") int passengerCount
) {
CrazyAirRequest crazyAirRequest = new CrazyAirRequest(origin, destination, departureDate, returnDate,
passengerCount);
return crazyAirService.getCrazyAirResponse(crazyAirRequest);
}
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;
// ...
}
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()) {