I would like to put the value of a spring boot environment variable into a validation annotation (#Min, #Max), but i don't know how to do it. Here is my code :
public class MessageDTO {
#Value("${validationMinMax.min}")
private Integer min;
#JsonProperty("Message_ID")
#NotBlank(message = "messageId cannot be blank.")
#Pattern(regexp = "\\w+", message = "messageId don't suits the pattern")
private String messageId;
#JsonProperty("Message_Type")
#NotBlank(message = "messageType cannot be blank")
private String messageType;
#JsonProperty("EO_ID")
#NotBlank(message = "eoId cannot be blank")
private String eoId;
#JsonProperty("UI_Type")
#NotNull(message = "uiType cannot be null")
#Min(1)
#Max(3)
private Integer uiType;
And here is my application.yml :
server:
port: 8080
spring:
data:
cassandra:
keyspace-name: message_keyspace
port: 9042
contact-points:
- localhost
validationMinMax:
min: 1
max: 3
I would like to put the field "min" and "max" of my yml into the annotation field #Min() and #Max() of my attribute uiType. Does anyone knows how to do it ? Thanks in advance for your help !
You can write your own validation annotation with a custom validator. In this validator you can autowire spring beans and inject configuration properties:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { MyValidator.class })
#Documented
public #interface MyValidationAnnotation {
String message() default "";
Class<?>[] groups() default {};
Class<? extends javax.validation.Payload>[] payload() default {};
}
The validator class:
public class MyValidator implements ConstraintValidator<MyValidationAnnotation, Integer> {
#Autowired
private MyService service;
public void initialize(MyValidationAnnotation constraintAnnotation) {
// ...
}
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if(service.validate(value)) {
return true;
} else {
return false;
}
}
}
And then use it:
#MyValidationAnnotation
Integer foo;
Related
I am working on javax validation API in Spring Boot Application. I have a User bean and i have to validate that username given in request is unique and doesn't exist into Database.
I have created custom annotation (UniqueUser.java) and custom Validator(UniqueUserValidator.java) for this requirement.
public class User {
#NotNull
#UniqueUser
private String username;
#NotNull
private String password;
#NotNull
#Email
private String email;
#NotNull
private String phone;
}
UniqueUser.java
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = UniqueUserValidator.class)
#Documented
public #interface NameMatch
{
String message() default "User id already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
UniqueUserValidator.java
public class UniqueUserValidator implements ConstraintValidator<NameMatch, Object>
{
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String userName, final ConstraintValidatorContext context)
{
boolean isValidUser = false;
if(userName!=null && !userName.isEmpty()) {
Optional<User> user= userRepository.findByUserId(userName);
isValidUser = !user.isPresent();
}
return isValidUser;
}
}
In above code, the unique user validation get called for username field even if the field is null and shows Username already exists error message. I want the custom validator to get called only when username has some value. Is it possible to avoid this call.
I can fix the error by modifying the below method and returning true if username is null, but i want't to avoid this unnecessary call.
public class UniqueUserValidator implements ConstraintValidator<NameMatch, Object>
{
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String userName, final ConstraintValidatorContext context)
{
boolean isValidUser = false;
if(userName!=null && !userName.isEmpty()) {
Optional<User> user= userRepository.findByUserId(userName);
isValidUser = !user.isPresent();
} else {
isValidUser = true;
}
return isValidUser;
}
}
I think what you are looking for is Grouping constraints;
Groups allow you to restrict the set of constraints applied during
validation. One use case for validation groups are UI wizards where in
each step only a specified subset of constraints should get validated.
The groups targeted are passed as var-arg parameters to the
appropriate validate method.
In your case it should look like this:
#GroupSequence({ FirstConstaint.class, SecondConstaint.class })
public class User {
#NotNull(groups = FirstConstaint.class)
#UniqueUser(groups = SecondConstaint.class)
private String username;
// the rest of your fields
}
interface FirstConstaint {
}
interface SecondConstaint {
}
This way, it will check #UniqueUser only if the field is not null, otherwise, it will return the message of #NotNull validation.
Otherwise like #User said you can use this checks in service layer ^^
I created an API and added an custom-annotation to validate the Request body object, but this was never getting called. Below is the Object. Please go through the code and help me out where the code need to be corrected?
#NotNull, #Size is also not working
Request Body Object
#Getter
#AllArgsConstructor
#Sample
public class SaleRequest {
#NotNull
private Integer sale;
#NotNull
private Date dateTime;
#NotNull
#Size(min = 10, max = 10)
private String customerId;
}
Annotation
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {SalesRequestValidator.class})
#Documented
public #interface Sample {
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
String message() default "Invalid Sale Request";
}
Validator
public class SalesRequestValidator implements ConstraintValidator<Sample, SaleRequest> {
private String message;
#Override
public void initialize(Sample constraintAnnotation) {
this.message = constraintAnnotation.message();
}
#Override
public boolean isValid(SaleRequest sale, ConstraintValidatorContext context) {
System.out.println("Tested!");
return sale.getSale() > 0;
}
}
Api Implementation
public interface SalesApi {
#RequestMapping(
value = {"/sales"},
produces = {"application/json"},
consumes = {"application/json"},
method = {RequestMethod.POST}
)
ResponseEntity<Integer> submitSale(#RequestBody #Valid SaleRequest saleRequest);
}
Could not figure where I went wrong
Implementation looks ok.
Make sure you have set the #Valid annotation in your controller method where you expect to receive the request body of SaleRequest.
It should look something like this:
addNewSaleRequest(#RequestBody #Valid SaleRequest saleRequest)
Try to extend #Target({ElementType.TYPE}) with ElementType.PARAMETER as you want to validate a method parameter with it.
A #Validated annotation is needed on the related Controller as well.
I have implemented a ConstraintValidator in order to valide a DTO that contains an enumeration. I followed this Spring documentation for that.
This is the custom annotation to be applied to the enum field:
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR,
ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EnumValidator.class)
public #interface ValidEnum {
String message() default "{com.test.validation.constraints.ValidEnum}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> target();
}
The EnumValidator looks like this:
public class EnumValidator implements ConstraintValidator<ValidEnum,String> {
private Set<String> enumValues;
#Override
public void initialize(ValidEnum targetEnum) {
Class<? extends Enum> enumSelected = targetEnum.targetClassType();
enumValues = (Set<String>) EnumSet.allOf(enumSelected).stream()
.map(e -> ((Enum<? extends Enum<?>>) e).name()).collect(Collectors
.toSet());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return enumValues.contains(value);
}
}
This is the enum:
public enum Gender {
MALE,FEMALE;
}
This is the DTO to validate:
public final class UserDTO{
#ValidEnum(target = Gender.class)
private Gender gender;
#NotEmpty
#Max(100)
private String fullName;
}
And the controller that is validating the field:
#Controller
public class RegistrationController {
private static final String REGISTER_USER = "/register";
private final RegistrationService registrationService;
#PostMapping(value = REGISTER_USER)
#Consumes(APPLICATION_JSON)
#Produces(APPLICATION_JSON)
public UserRegistrationResponse register(#Valid UserDTO userRegistrationRequest) {
return registrationService.register(userRegistrationRequest);
}
}
It seems that Spring is not detecting the validator, because it throws this exception:
org.springframework.web.util.NestedServletException: Request processing failed;
nested exception is javax.validation.UnexpectedTypeException: HV000030:
No validator could be found for constraint 'com.test.ws.web.validation.ValidEnumType' validating type 'com.test.ws.domain.model.Gender'. Check configuration for 'gender'
I am using Spring boot 2.0.4 which include the required dependencies for validation.
Any idea why it fails?
You have three mistakes in your code.
#Max(100) this annotation shouldn't be applied to String field! only for numeric type. If you need to specify String size range restrictions you may use #Size(min = 2, max = 250) annotation.
You forgot RequestBody annotation in the controller method signature:
register(#Valid #RequestBody UserDTO userRegistrationRequest)
Enum validation consume String value from Rest API, not Enum itself. That's why you've got an error here.
I've created test project for you. Please check it here https://github.com/alex-petrov81/stackoverflow-answers/tree/master/enum-validator
with respect to javax.validation
#NotNull(message = "From can't be null")
#Min(value = 1, message = "From must be greater than zero")
private Long from;
#NotNull(message = "To can't be null")
#Min(value = 1, message = "To must be greater than zero")
private Long to;
I want to also validate that FROM should be less than TO and TO should be greater than FROM ? how we can do this using javax validation's annotation ?
You need a custom cross field validation annotation.
One way is to annotate your custom class with #YourCustomAnnotation.
In YourCustomAnnotationValidator you have access to your value, hence you can implement your logic there:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Constraint(validatedBy = DateValidator.class)
public #interface RangeCheck {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class RangeCheckValidtor implements ConstraintValidator<RangeCheck, YourDto> {
#Override
public void initialize(RangeCheck date) {
// Nothing here
}
#Override
public boolean isValid(YourDto dto, ConstraintValidatorContext constraintValidatorContext) {
if (dto.getFrom() == null || dto.getTo() == null) {
return true;
}
return from < to;
}
}
Then mark your YourDto class with #RangeCheck:
#RangeCheck(message = "your messgae")
public class YourDto {
// from
// to
}
Or simply manually validate the relation of two fields.
I'm using javax validation annotations with Spring Boot and internationalization. So I have the following field:
#Size(min = 3, max = 3, message = "{javax.validation.constraints.Size.message}")
private String currencyCode;
The US resource bundle has:
javax.validation.constraints.Size.message = size must be between {min} and {max}
So when this field fails on validation, I see the message "size must be between 3 and 3".
But what I want is to have a message like:
javax.validation.constraints.Size.message = {fieldName}'s size must be between {min} and {max}
Which would result in the message "currencyCode's size must be between 3 and 3".
Is this possible? Do I need to override a bean to make it work? Is there a pre-defined property for the field name?
You can create handler for MethodArgumentNotValidException
#ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) { ...
inside them you can invoke
e.getBindingResult().getFieldErrors();
This is collection with all informations about invalid field error eg. field name, rejected value.
At next, use message like in your's example and use StrSubstitutor from org.apache.commons.lang3. Just create map of parameters
key --> fieldName , value --> currencyCode
Of course you have to write some code, but here is the solution :)
By default #Size annotation does not provide option to put label in the message, instead you can create your own implementation like below so that it can be used across your application:
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = MySizeValidator.class)
public #interface MySize {
String message() default "{javax.validation.constraints.Size.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int min() default 0;
int max() default Integer.MAX_VALUE;
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#interface List {
Size[] value();
}
public abstract String label();
}
then create your own validator like MySizeValidator to validate MySize annotation like below
public class MySizeValidator implements ConstraintValidator<MySize, String> {
private static final Log log = LoggerFactory.make();
private int min;
private int max;
#Override
public void initialize(MySize parameters) {
min = parameters.min();
max = parameters.max();
validateParameters();
}
#Override
public boolean isValid(String field, ConstraintValidatorContext constraintValidatorContext) {
if (field == null) {
return true;
}
int length = field.length();
return length >= min && length <= max;
}
private void validateParameters() {
if (min < 0) {
throw log.getMinCannotBeNegativeException();
}
if (max < 0) {
throw log.getMaxCannotBeNegativeException();
}
if (max < min) {
throw log.getLengthCannotBeNegativeException();
}
}
}
Create ValidationMessages.properties with below key value
javax.validation.constraints.mySize.message={label}'s size must be between {min} and {max}
then in your POJO, you can put this new annotation,
#MySize(min = 3, max = 3, message = "{javax.validation.constraints.mySize.message}", label = "Currency Code")
private String currencyCode;