Hibernate validation with custom messages in property file - spring

Hi am using hibernate validator in jersey rest service.
Here how can we pass value to the property file message as follows
empty.check= Please enter {0}
here in {0} i need to pass the value from annotation
#EmptyCheck(message = "{empty.check}") private String userName
here in the {0} i need to pass "user name", similarly i need to reuse message
please help me out to solve this.

You can do this by altering your annotation to provide a field description and then exposing this in the validator.
First, add a description field to your annotation:
#Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EmptyCheckValidator.class)
#Documented
public #interface EmptyCheck {
String description() default "";
String message() default "{empty.check}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Next, change your message so that it uses a named parameter; this is more readable.
empty.check= Please enter ${description}
Since you're using hibernate-validator, you can get the hibernate validator context within your validation class and add a context variable.
public class EmptyCheckValidator
implements ConstraintValidator<EmptyCheck, String> {
String description;
public final void initialize(final EmptyCheck annotation) {
this.description = annotation.description();
}
public final boolean isValid(final String value,
final ConstraintValidatorContext context) {
if(null != value && !value.isEmpty) {
return true;
}
HibernateConstraintValidatorContext ctx =
context.unwrap(HibernateConstraintValidatorContext.class);
ctx.addExpressionVariable("description", this.description);
return false;
}
}
Finally, add the description to the field:
#EmptyCheck(description = "a user name") private String userName
This should produce the following error when userName is null or empty:
Please enter a user name

Related

Spring custom validator with dependencies on other fields

We are using spring custom validator for our request object used in our controller endpoint. We implemented it the same way as how its done in the link below:
https://www.baeldung.com/spring-mvc-custom-validator
The problem we are facing is, it can't work if the particular field has dependencies on other input fields as well. For example, we have the code below as the request object for our controller endpoint:
public class FundTransferRequest {
private String accountTo;
private String accountFrom;
private String amount;
#CustomValidator
private String reason;
private Metadata metadata;
}
public class Metadata {
private String channel; //e.g. mobile, web, etc.
}
Basically #CustomValidator is our custom validator class and the logic we want is, if the supplied channel from Metadata is "WEB". The field "reason" of the request won't be required. Else, it will be required.
Is there a way to do this? I've done additional research and can't see any that handles this type of scenario.
Obviously if you need access to multiple fields in your custom validator, you have to use a class-level annotation.
The same very article you mentioned has an example of that: https://www.baeldung.com/spring-mvc-custom-validator#custom-class-level-validation
In your case it might look something like this:
#Constraint(validatedBy = CustomValidator.class)
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomValidation {
String message() default "Reason required";
String checkedField() default "metadata.channel";
String checkedValue() default "WEB";
String requiredField() default "reason";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package com.example.demo;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.stereotype.Component;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/*
If the supplied channel from Metadata is "WEB". The field "reason" of the request won't be required.
Else, it will be required.
*/
#Component
public class CustomValidator implements ConstraintValidator<CustomValidation, Object> {
private String checkedField;
private String checkedValue;
private String requiredField;
#Override
public void initialize(CustomValidation constraintAnnotation) {
this.checkedField = constraintAnnotation.checkedField();
this.checkedValue = constraintAnnotation.checkedValue();
this.requiredField = constraintAnnotation.requiredField();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object checkedFieldValue = new BeanWrapperImpl(value)
.getPropertyValue(checkedField);
Object requiredFieldValue = new BeanWrapperImpl(value)
.getPropertyValue(requiredField);
return checkedFieldValue != null && checkedFieldValue.equals(checkedValue) || requiredFieldValue != null;
}
}
And the usage will be:
#CustomValidation
public class FundTransferRequest {
...
or with parameters specified:
#CustomValidation(checkedField = "metadata.channel",
checkedValue = "WEB",
requiredField = "reason",
message = "Reason required")
public class FundTransferRequest {
...

Springboot #Valid annotation with String object

So, I have the following controller method:
#RequestMapping(path = "/{application}/users", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public MyObject getUsers(#RequestParam("itemId") String itemId, #PathVariable("application") String application) {
return userService.get(itemId, application);
}
I would like to check if the request parameter itemId exists in the related application (in the path).
My first idea was to create a validator :
#RequestMapping(path = "/{application}/users", method = RequestMethod.GET, produces =
MediaType.APPLICATION_JSON_VALUE)
#CheckItemId
public MyObject getUsers(#RequestParam("itemId") String itemId, #PathVariable("application") String application) {
return userService.get(itemId, application);
}
CheckItemId.java :
#Target({METHOD})
#Retention(RUNTIME)
#Constraint(validatedBy = CheckItemIdValidator.class)
#Documented
public #interface CheckItemId {
String message() default "error";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
CheckItemIdValidator.java :
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class CheckItemIdValidator implements ConstraintValidator<CheckItemId, Object[]>{
#Override
public boolean isValid(Object[] arg0, ConstraintValidatorContext arg1) {
String itemId= (String) arg0[0];
String application = (String) arg0[1];
// Logic business ...
return true;
}
}
This implementation works well, I managed to get the values itemId and application in the validator. I can now do my verification.
I was wondering if there is a better way to do something like that? Since I handle an array of Object, I need to cast it to String and If I change the parameters order, I will not get the same values since I need to use arg0[0] and arg0[1].
Thank you !
You can use spring validation library. Add #Valid on controller level. Then add #NotBlank on method level as below.
getUsers(#RequestParam("itemId") #NotBlank String itemId)

Spring boot entity level custom annotation not working

So I have a type level custom annotation that checks for matching passwords in a user registration form. Even when the validator is returning false, it doesn't throw an error and show the error message. Any help is appreciated!
Entity class. Annotation in question is #ValidPassword
#Entity
#ValidPassword(fields = {"password", "matchingPassword"})
public class User {
private String password;
private String matchingPassword;
Constraintvalidator class
public class PasswordValidator implements ConstraintValidator<ValidPassword, User> {
private String[] fields;
private String message;
#Override
public boolean isValid(User user, ConstraintValidatorContext context) {
if (fields[0]== null || fields[1] == null) {
return false;
}
for ( String temp : fields) {
System.out.println(temp);
}
boolean flag = Pattern.matches("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!#$%^&*-]).{8,}$", fields[0]);
boolean flag1 = fields[0].equals(fields[1]);
if ( !flag1 ) {
message = "Passwords do not match!";
}
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(fields[0]).addConstraintViolation();
return flag && flag1;
}
//Show default message if no special message is set
#Override
public void initialize(ValidPassword validPassword) {
fields = validPassword.fields();
message = validPassword.message();
}
}
validpassword
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = PasswordValidator.class)
#Documented
public #interface ValidPassword {
String message() default "Please enter at least 8 characters, 1 uppercase letter, 1 lowercase letter, and 1 special character";
String[] fields();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

Jackson deserialization errorhandling in spring-framework

I'm looking for a clean way to handle Jackson Deserialization errors for REST web requests.
More precisely: I have an Enum in a incoming DTO object, mapped from JSON. But if the user sends a wrong value, a 400 Bad Request is returned. I would like to return a 422 Unprocessable Entity with a correct message.
One option would be to accept a String, and use bean validation. However, it's not possible to pass all enum values as a list to the annotation (not a constant), so I would need to pass all enum values separately and keep them up to date. This will be very error prone over the whole application. I'm looking for a more structural way to handle this.
I solved this by using a String in the DTO and using a public #interface EnumValueas annotation.
The EnumValue:
#ReportAsSingleViolation
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EnumValueValidator.class)
#Target(ElementType.FIELD)
public #interface EnumValue {
Class<? extends Enum> value();
String message() default "The input contains validation errors.";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
The validator:
public class EnumValueValidator implements ConstraintValidator<EnumValue, String> {
private Class<? extends Enum> enumClass;
private String message;
#Override
public void initialize(final EnumValue constraintAnnotation) {
this.enumClass = constraintAnnotation.value();
this.message = constraintAnnotation.message();
}
#Override
public boolean isValid(final String value, final ConstraintValidatorContext context) {
boolean valid = false;
for (final Enum enumValue : enumClass.getEnumConstants()) {
if (enumValue.name().equals(value)) {
valid = true;
}
}
if (!valid) {
context.buildConstraintViolationWithTemplate(message) //
.addConstraintViolation();
}
return valid;
}
}

How to get validate a #PathVariable before authorization in Spring MVC

I have a REST handler with an endpoint for the GET verb. Where from an identifier (ObjectID of MongoDB) I get the information of that entity.
To validate that the ObjectID is valid and avoid errors when using Spring Data Mongo. I have developed a simple validator following the guidelines of the JPA bean validation standard.
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#Constraint(validatedBy = ValidObjectIdValidator.class)
#NotNull
#Documented
public #interface ValidObjectId {
String message() default "{constraints.valid.objectid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class ValidObjectIdValidator implements ConstraintValidator<ValidObjectId, String> {
#Override
public void initialize(ValidObjectId constraintAnnotation) {}
#Override
public boolean isValid(String id, ConstraintValidatorContext context) {
return ObjectId.isValid(id);
}
}
Then I apply the variable-level validation of the controller using the following configuration:
#Api
#RestController("RestUserController")
#Validated
#RequestMapping("/api/v1/children/")
public class ChildrenController implements ISonHAL, ICommentHAL, ISocialMediaHAL
Using #Validated annotation at controller level.
#GetMapping(path = "/{id}")
#ApiOperation(value = "GET_SON_BY_ID", nickname = "GET_SON_BY_ID", notes = "Get Son By Id",
response = SonDTO.class)
#PreAuthorize("#authorizationService.hasParentRole() && #authorizationService.isYourSon(#id)")
public ResponseEntity<APIResponse<SonDTO>> getSonById(
#Valid #ValidObjectId(message = "{son.id.notvalid}")
#ApiParam(value = "id", required = true) #PathVariable String id) throws Throwable {
logger.debug("Get User with id: " + id);
return Optional.ofNullable(sonService.getSonById(id))
.map(sonResource -> addLinksToSon(sonResource))
.map(sonResource -> ApiHelper.<SonDTO>createAndSendResponse(ChildrenResponseCode.SINGLE_USER, HttpStatus.OK, sonResource))
.orElseThrow(() -> { throw new SonNotFoundException(); });
}
Using the #Valid annotation on the #PathVariable.
The problem is that I must verify that the user is currently authenticated is the parent of the child for whom he wants to see his information. This is verified by the execution of:
#PreAuthorize("#authorizationService.hasParentRole() && #authorizationService.isYourSon(#id)")
And here the error occurs. Because I must convert the received id to an ObjectID mediate new ObjectId (id). And this may not be valid.
Is there any way to configure validation to occur before authorization?.
This is my configuration to enable security at the method level:
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
Thanks in advance.

Resources