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.
Related
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 have a class Wrapper that contains two arraylist and another properties :
public class Wrapper<T, E> {
private List<T> toInsert = new ArrayList<>();
private List<T> toUpdate = new ArrayList<>();
private Set<E> toDelete = new HashSet<>();
// getter setter ....
}
I have a Class Dto :
public class UserDto {
private long id;
private LocalDateTime createdDate;
#NotBlank
private String registrationNumber;
#NotBlank
private String firstName;
#NotBlank
private String lastName;
#NotBlank
private String email;
#NotBlank
}
I create a validator :
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {CustomerLocationValidator.class})
#Documented
public #interface ValidCustomerLocation {
String message() default "Invalid List userDto";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The implementation of validator annotation :
public class CustomerLocationValidator implements ConstraintValidator<ValidCustomerLocation, Wrapper<UserDto, String>> {
#Autowired
Validator validator;
#Override
public boolean isValid(Wrapper<UserDto, String> value, ConstraintValidatorContext constraintValidatorContext) {
boolean isValid = true;
final Set<ConstraintViolation<UserDto>> constraintViolations = new HashSet();
value.getToInsert().forEach(userDto -> constraintViolations.addAll( validator.validate(userDto)) );
if (!CollectionUtils.isEmpty(constraintViolations)) {
constraintValidatorContext.disableDefaultConstraintViolation();
for (ConstraintViolation<UserDto> violation : constraintViolations) {
constraintValidatorContext.buildConstraintViolationWithTemplate(violation.getMessageTemplate()).addConstraintViolation();
}
isValid = false;
}
return isValid;
}
}
The method in controller :
#PostMapping
public ResponseEntity<Void> save(
#RequestBody #ValidCustomerLocation Wrapper<UserDto, String> itemsUsers) {}
In this controlller I try to send invalid data but the annotation ValidCustomerLocation didn't catch the error , I try to use #Valid but i have the same problem
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;
// ...
}
How can I tell spring-web to validate my dto without having to use getter/setter?
#PostMapping(path = "/test")
public void test(#Valid #RequestBody WebDTO dto) {
}
public class WebDTO {
#Valid //triggers nested validation
private List<Person> persons;
//getter+setter for person
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public static class Person {
#NotBlank
public String name;
public int age;
}
}
Result:
"java.lang.IllegalStateException","message":"JSR-303 validated property
'persons[0].name' does not have a corresponding accessor for Spring data
binding - check your DataBinder's configuration (bean property versus direct field access)"}
Special requirement: I still want to add #AssertTrue on boolean getters to provide crossfield validation, like:
#AssertTrue
#XmlTransient
#JsonIgnore
public boolean isNameValid() {
//...
}
you have to configure Spring DataBinder to use direct field access.
#ControllerAdvice
public class ControllerAdviceConfiguration {
#InitBinder
private void initDirectFieldAccess(DataBinder dataBinder) {
dataBinder.initDirectFieldAccess();
}
}
Try something like this:
#Access(AccessType.FIELD)
public String name;
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 { };
}