Custom JSR 303 validation is not invoked - spring

My custom JSR 303 validation is not getting invoked. Here is my code
my spring config has
<mvc:annotation-driven />
My controller's handler method:
#RequestMapping(value="update", method = RequestMethod.POST ,
consumes="application/json" ,
produces="application/json"))
#ResponseBody
public String update(#Valid #RequestBody MyBean myBean){
return process(myBean);
}
MyBean (annotated with ValidMyBeanRequest):
#ValidMyBeanRequest
public class MyBean {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
ValidMyBeanRequest annotaion:
#Target({ TYPE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = {MyBeanValidator.class})
public #interface ValidMyBeanRequest {
String message() default "{validMyBeanRequest.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
MyBeanValidator class:
public class MyBeanValidator implements
ConstraintValidator<ValidMyBeanRequest, MyBean> {
#Override
public void initialize(ValidMyBeanRequest constraintAnnotation) {
// TODO Auto-generated method stub
}
#Override
public boolean isValid(MyBean myBean, ConstraintValidatorContext context) {
boolean isValid = true;
int id = myBean.getId();
if(id == 0){
isValid = false;
}
return isValid;
}
}
My http POST request has below JSON data:
{id:100}
The problem is MyBeanValidator's isValid is not getting invoked. I am using Spring 3.1.0 and HibernateValidator is in classpath.
Please see what I am missing??
Update: Updated handler method to include POST request type and consumes, produces values. Also included my http request with JSON data.

Assuming that you do get model correctly, in this case you are doing everything right, except one thing: you need to handle your validation's result manually.
For achieving this you need to add BindingResult object into list of your handler parameters, and then process validation constraints in the way you would like:
#RequestMapping(value="update")
#ResponseBody
public String update(#Valid #ModelAttribute #RequestBody MyBean myBean, BindingResult result) {
if (result.hasErrors()){
return processErrors(myBean);
}
return process(myBean);
}

Related

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 class level validation and Thymeleaf

I am learning Spring Framework and Thymeleaf. I have known how to display field error by using something like ${#fields.errors("xx")}. However, I get stuck about how to display object error message in Thymeleaf.
Here is my UserForm class:
#PasswordMatches
public class UserForm {
#NotNull
#NotEmpty
private String username;
#NotNull
#NotEmpty
private String password;
#NotNull
#NotEmpty
private String matchingPassword;
#NotNull
#NotEmpty
#ValidEmail
private String email;
/* setter and getter methods */
Here is my PasswordMatches annotation:
#Target({ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = PasswordMatchesValidator.class)
#Documented
public #interface PasswordMatches {
String message() default "Passwords don't match";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, Object> {
#Override
public void initialize(PasswordMatches constraintAnnotation) {
}
#Override
public boolean isValid(Object obj, ConstraintValidatorContext context){
UserDto user = (UserDto) obj;
return user.getPassword().equals(user.getMatchingPassword());
}
}
Here is my Controller method:
#RequestMapping(value="/registration", method=RequestMethod.POST)
public ModelAndView registerUserAccount(#ModelAttribute("user") #Valid UserForm userForm,
BindingResult result, WebRequest request, Errors errors) {
if (!result.hasErrors()) {
return new ModelAndView("registerSuccess");
}
else {
return new ModelAndView("registration", "user", userForm);
}
}
Now here is my problem: If the password field and confirmPass field doesn't match, how can I get the default error message returned by the class level annotation in Thymeleaf?
I know this is old post but I also encountered this problem and here is the soulution (maybe it will also help someone else):
Modify PasswordMatchesValidator to this:
class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, Object> {
#Override
public void initialize(PasswordMatches constraintAnnotation) {
}
#Override
public boolean isValid(Object obj, ConstraintValidatorContext context){
UserDto user = (UserDto) obj;
boolean isValid = user.getPassword().equals(user.getMatchingPassword());
if(!isValid){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode( "matchingPassword" ).addConstraintViolation();
}
return isValid;
}
it will bind the validation result to your 'matchingPassword' attribute. So in your thymeleaf template us it like this:
${#fields.errors("matchingPassword")}
Add this inside the form tag:
<p data-th-each="err : ${#fields.allErrors()}" data-th-text="${err}" class="error">
Invalid input.
</p>
<p th:if="${#fields.hasErrors('${yourObject}')}" th:errors="${yourObject}"></p>

How do I validate a #QueryParam?

I've got a simple REST resource which accepts a couple of query parameters. I'd like to validate one of these parameters, and came across ConstraintValidator for this purpose. The REST resource expects the query param territoryId to be a UUID, so I'd like to validate that it indeed is a valid UUID.
I've created an #IsValidUUID annotation, and a corresponding IsValidUUIDValidator (which is a ConstraintValidator). With what I have now, nothing gets validated and getSuggestions accepts anything I throw at it. So clearly I'm doing something wrong.
What am I doing wrong?
The REST resource now looks like this :
#Component
#Path("/search")
public class SearchResource extends AbstractResource {
#GET
#Path("/suggestions")
#Produces(MediaType.APPLICATION_XML)
public Response getSuggestions(
#QueryParam("phrase") List<String> phrases,
#IsValidUUID #QueryParam("territoryId") String territoryId) {
[...]
}
}
IsValidUUID
#Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = {IsValidUUIDValidator.class})
public #interface IsValidUUID {
String message() default "Invalid UUID";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
IsValidUUIDValidator
public class IsValidUUIDValidator implements ConstraintValidator<IsValidUUID, String> {
#Override
public void initialize(IsValidUUID constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
try {
UUID.fromString(value);
return true;
} catch (Exception e) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("The provided UUID is not valid")
.addConstraintViolation();
return false;
}
}
}
You need to set the supported targets on IsValidUUID, using the following annotation.
#SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
or
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
Edit:
Sorry, I wasn't able to make it work either on a RequestParam directly. However, if you can, try creating a POJO that you can bind your request parameters to and annotate the binding field with your constraint instead. This worked for me.
public class MyModel {
#IsValidUUID
private String territoryId;
public String getTerritoryId() {
return territoryId;
}
public void setTerritoryId(String territoryId) {
this.territoryId = territoryId;
}
}
#GET
#Path("/suggestions")
#Produces(MediaType.APPLICATION_XML)
public Response getSuggestions(
#QueryParam("phrase") List<String> phrases,
#Valid #ModelAttribute MyModel myModel) {
[...]
}

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 validate Spring MVC #PathVariable values?

For a simple RESTful JSON api implemented in Spring MVC, can I use Bean Validation (JSR-303) to validate the path variables passed into the handler method?
For example:
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") String customerNumber) {
...
}
Here, I need to validate the customerNumber variables's length using Bean validation. Is this possible with Spring MVC v3.x.x? If not, what's the best approach for this type of validations?
Thanks.
Spring does not support #javax.validation.Valid on #PathVariable annotated parameters in handler methods. There was an Improvement request, but it is still unresolved.
Your best bet is to just do your custom validation in the handler method body or consider using org.springframework.validation.annotation.Validated as suggested in other answers.
You can use like this:
use org.springframework.validation.annotation.Validated to valid RequestParam or PathVariable.
*
* Variant of JSR-303's {#link javax.validation.Valid}, supporting the
* specification of validation groups. Designed for convenient use with
* Spring's JSR-303 support but not JSR-303 specific.
*
step.1 init ValidationConfig
#Configuration
public class ValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
return processor;
}
}
step.2 Add #Validated to your controller handler class, Like:
#RequestMapping(value = "poo/foo")
#Validated
public class FooController {
...
}
step.3 Add validators to your handler method:
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public ResponseEntity<Foo> delete(
#PathVariable("id") #Size(min = 1) #CustomerValidator int id) throws RestException {
// do something
return new ResponseEntity(HttpStatus.OK);
}
final step. Add exception resolver to your context:
#Component
public class BindExceptionResolver implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex.getClass().equals(BindException.class)) {
BindException exception = (BindException) ex;
List<FieldError> fieldErrors = exception.getFieldErrors();
return new ModelAndView(new MappingJackson2JsonView(), buildErrorModel(request, response, fieldErrors));
}
}
}
The solution is simple:
#GetMapping(value = {"/", "/{hash:[a-fA-F0-9]{40}}"})
public String request(#PathVariable(value = "hash", required = false) String historyHash)
{
// Accepted requests: either "/" or "/{40 character long hash}"
}
And yes, PathVariables are ment to be validated, like any user input.
Instead of using #PathVariable, you can take advantage of Spring MVC ability to map path variables into a bean:
#RestController
#RequestMapping("/user")
public class UserController {
#GetMapping("/{id}")
public void get(#Valid GetDto dto) {
// dto.getId() is the path variable
}
}
And the bean contains the actual validation rules:
#Data
public class GetDto {
#Min(1) #Max(99)
private long id;
}
Make sure that your path variables ({id}) correspond to the bean fields (id);
#PathVariable is not meant to be validated in order to send back a readable message to the user. As principle a pathVariable should never be invalid. If a pathVariable is invalid the reason can be:
a bug generated a bad url (an href in jsp for example). No #Valid is
needed and no message is needed, just fix the code;
"the user" is manipulating the url.
Again, no #Valid is needed, no meaningful message to the user should
be given.
In both cases just leave an exception bubble up until it is catched by
the usual Spring ExceptionHandlers in order to generate a nice
error page or a meaningful json response indicating the error. In
order to get this result you can do some validation using custom editors.
Create a CustomerNumber class, possibly as immutable (implementing a CharSequence is not needed but allows you to use it basically as if it were a String)
public class CustomerNumber implements CharSequence {
private String customerNumber;
public CustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
#Override
public String toString() {
return customerNumber == null ? null : customerNumber.toString();
}
#Override
public int length() {
return customerNumber.length();
}
#Override
public char charAt(int index) {
return customerNumber.charAt(index);
}
#Override
public CharSequence subSequence(int start, int end) {
return customerNumber.subSequence(start, end);
}
#Override
public boolean equals(Object obj) {
return customerNumber.equals(obj);
}
#Override
public int hashCode() {
return customerNumber.hashCode();
}
}
Create an editor implementing your validation logic (in this case no whitespaces and fixed length, just as an example)
public class CustomerNumberEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text) && !StringUtils.containsWhitespace(text) && text.length() == YOUR_LENGTH) {
setValue(new CustomerNumber(text));
} else {
throw new IllegalArgumentException();
// you could also subclass and throw IllegalArgumentException
// in order to manage a more detailed error message
}
}
#Override
public String getAsText() {
return ((CustomerNumber) this.getValue()).toString();
}
}
Register the editor in the Controller
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(CustomerNumber.class, new CustomerNumberEditor());
// ... other editors
}
Change the signature of your controller method accepting CustomerNumber instead of String (whatever your ResponseObject is ...)
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") CustomerNumber customerNumber) {
...
}
You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
You need to added an #Validated annotation to Controller class and any validation annotation before path variable field
Path variable may not be linked with any bean in your system. What do you want to annotate with JSR-303 annotations?
To validate path variable you should use this approach Problem validating #PathVariable url on spring 3 mvc
Actually there is a very simple solution to this. Add or override the same controller method with its request mapping not having the placeholder for the path variable and throw ResponseStatusException from it. Code given below
#RequestMapping(value = "/number")
#ResponseBody
public ResponseObject searchByNumber() {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"customer number missing")
}

Resources