In my Spring 3 MVC application a users need to save a password and it would be a nice feature if they also were able to confirm the password upon saving.
In the bean I'm using annotation based validation. Is there an annotation validator available for performing this checking?
After some googleing I found this blog: http://gochev.blogspot.com/2010/06/spring-mvc-spring-bean-validation.html . But I guess I'm missing a jar-lib here as Eclipse is unable to find/suggest any jars. Anyone know what jar I need for this to work?
Thanks in advance :)
I wrote the following in order to validate passwords:
Constraint implementation:
package com.test.web.validation.user;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = PasswordsEqualConstraintValidator.class)
public #interface PasswordsEqualConstraint {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package com.test.web.validation.user;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import com.test.logic.dto.UserDto;
public class PasswordsEqualConstraintValidator implements
ConstraintValidator<PasswordsEqualConstraint, Object> {
#Override
public void initialize(PasswordsEqualConstraint arg0) {
}
#Override
public boolean isValid(Object candidate, ConstraintValidatorContext arg1) {
UserDto user = (UserDto) candidate;
return user.getPassword().equals(user.getPasswordRepeat());
}
}
My DTO Object:
package com.test.logic.dto;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.esldic.web.validation.user.EmailExistsConstraint;
import com.esldic.web.validation.user.PasswordsEqualConstraint;
#PasswordsEqualConstraint(message = "passwords are not equal")
public final class UserDto extends AbstractDto implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
#NotNull
#Size(min = 3, max = 30)
#EmailExistsConstraint(message = "email is not available")
private String email;
private String username;
#NotNull
#Size(min = 2, max = 30)
private String password;
#NotNull
#Size(min = 2, max = 30)
private String passwordRepeat;
...
}
Finally, my controller
package com.test.web.controllers;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.test.logic.dto.UserDto;
#Controller
public final class SignupController {
#Autowired
private Validator validator;
#RequestMapping(value = "/signup.html", method = RequestMethod.POST)
public #ResponseBody
ModelAndView handleSignupForm(#ModelAttribute UserDto candidate,
HttpServletResponse response) throws ServiceException {
Set<ConstraintViolation<UserDto>> failures = validator
.validate(candidate);
if (!failures.isEmpty()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return ValidationHelper.validationMessages(failures);
} else {
return userService.create(candidate);
}
}
Also, in google you will find a lot of samples with JSR-303 bean validation.
You need the Hibernate Validation and JSR 303 Api jar.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.1.0.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
http://mvnrepository.com/artifact/org.hibernate/hibernate-validator
http://mvnrepository.com/artifact/javax.validation/validation-api/1.0.0.GA
See this question: Cross field validation with Hibernate Validator (JSR 303)
there are several ways to deal with that problem.
Accepted solution given by Cyril Deba worked for me too. But then I had to make another annotation for ResetPassword and ChangePassword Page, as they have different DTO. To overcome that I changed isValid to below code. Although it could be acheived by implementing an interface too, but I think this one is more realistic. Hope it will help.
#Override
public boolean isValid(Object candidate, ConstraintValidatorContext context) {
try {
Method methodGetPassword = candidate.getClass().getMethod("getPassword");
Method methodGetConfirmpassword = candidate.getClass().getMethod("getConfirmpassword");
if(methodGetPassword.invoke(candidate) == null && methodGetConfirmpassword.invoke(candidate)==null)
return true;
else if(methodGetPassword.invoke(candidate) == null )
return false;
return methodGetPassword.invoke(candidate).equals(methodGetConfirmpassword.invoke(candidate));
} catch (NoSuchMethodException ex) {
ex.printStackTrace();
return false;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
Related
I am trying to validate the json-resquest using hibernate-validator, it is working as expected but response is not there in postman.
Customer.java
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "cin", "firstName"})
public class Customer {
#JsonProperty("cin")
private String cin;
#JsonProperty("firstName")
#NotEmpty(message = "First Name must have some values")
#Size(min = 2, message = "First Name must greater or equal to 2 characters")
private String firstName;
//getters and setters
}
and Errors class - to wrap error in one object.
public class Errors {
private Integer status;
private String message;
private List<String> details;
public Errors(Integer status, String message, List<String> details) {
super();
this.status = status;
this.message = message;
this.details = details;
}
// Getters and Setters
}
ControllerAdvice class
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.ecommerce.ms.customer.model.Errors;
#ControllerAdvice
#ResponseBody
public class CustomerExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value=ConstraintViolationException.class)
public final ResponseEntity<Errors> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
List<String> details = ex.getConstraintViolations().parallelStream().map(e -> e.getMessage())
.collect(Collectors.toList());
Errors error = new Errors(HttpStatus.BAD_REQUEST.value(), "Request Validation Error", details);
return ResponseEntity.badRequest().body(error);
}
}
CustomerController.java
*
*/
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
import javax.ws.rs.core.MediaType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ecommerce.ms.customer.api.service.CustomerService;
import com.ecommerce.ms.customer.model.Customer;
#RestController
#RequestMapping("/api/customers")
public class CustomerController {
#Autowired
private CustomerService customerService;
#GetMapping("/status")
public String getStatus() {
return "ok";
}
#PostMapping(consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public ResponseEntity<Customer> addCustomer(#Valid #RequestBody Customer customer) {
return ResponseEntity.accepted().body(customerService.addCustomer(customer));
}
}
Hibernate-validator is already added pom.xml and I am expecting the below reason.
{
"status":400,
"message": "Request Validation Error",
"details":["First Name must greater or equal to 2 characters"]
}
I am trying to to get proper response body but I couldn't find it in postman.
Looking at the ResponseEntityExceptionHandler there is no such method that handles ConstraintValidationExceptions, and therefore the custom method you have created is not being called.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.html
As well:
You cannot catch ConstraintViolationException.class because it's not
propagated to that layer of your code, it's caught by the lower
layers, wrapped and rethrown under another type. So that the exception
that hits your web layer is not a ConstraintViolationException.
Ref: SpringBoot doesn't handle org.hibernate.exception.ConstraintViolationException
An example of proper usage is to use the method handleMethodArgumentNotValid and return the Errors as body:
#RestControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler{
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, Object> responseBody = new LinkedHashMap<>();
List<String> allErrors = new ArrayList<>();
ex.getBindingResult().getAllErrors().forEach(error -> allErrors.add(error.getDefaultMessage()));
responseBody.put("Errors:", allErrors);
return new ResponseEntity<>(responseBody, headers, status);
}
}
I would like to create a custom annotation in my Spring Boot application which always adds a prefix to my class level RequestMapping path.
My Controller:
import com.sagemcom.smartvillage.smartvision.common.MyApi;
import org.springframework.web.bind.annotation.GetMapping;
#MyApi("/users")
public class UserController {
#GetMapping("/stackoverflow")
public String get() {
return "Best users";
}
}
My custom annotation
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RestController
#RequestMapping(path = "/api")
public #interface MyApi {
#AliasFor(annotation = RequestMapping.class)
String value();
}
GOAL: a mapping like this in the end: /api/users/stackoverflow
Notes:
server.servlet.context-path is not an option because I want to create
several of these
I'm using Spring Boot version 2.0.4
I was not able to find an elegant solution for the issue. However, this worked:
Slightly modified annotation, because altering behavior of value turned out to be more difficult.
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RestController
#RequestMapping
public #interface MyApi {
#AliasFor(annotation = RequestMapping.class, attribute = "path")
String apiPath();
}
Bean Annotation Processor
import com.sagemcom.smartvillage.smartvision.common.MyApi;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
#Component
public class MyApiProcessor implements BeanPostProcessor {
private static final String ANNOTATIONS = "annotations";
private static final String ANNOTATION_DATA = "annotationData";
public Object postProcessBeforeInitialization(#NonNull final Object bean, String beanName) throws BeansException {
MyApi myApi = bean.getClass().getAnnotation(MyApi.class);
if (myApi != null) {
MyApi alteredMyApi = new MyApi() {
#Override
public Class<? extends Annotation> annotationType() {
return MyApi.class;
}
#Override
public String apiPath() {
return "/api" + myApi.apiPath();
}
};
alterAnnotationOn(bean.getClass(), MyApi.class, alteredMyApi);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(#NonNull Object bean, String beanName) throws BeansException {
return bean;
}
#SuppressWarnings("unchecked")
private static void alterAnnotationOn(Class clazzToLookFor, Class<? extends Annotation> annotationToAlter, Annotation annotationValue) {
try {
// In JDK8 Class has a private method called annotationData().
// We first need to invoke it to obtain a reference to AnnotationData class which is a private class
Method method = Class.class.getDeclaredMethod(ANNOTATION_DATA, null);
method.setAccessible(true);
// Since AnnotationData is a private class we cannot create a direct reference to it. We will have to manage with just Object
Object annotationData = method.invoke(clazzToLookFor);
// We now look for the map called "annotations" within AnnotationData object.
Field annotations = annotationData.getClass().getDeclaredField(ANNOTATIONS);
annotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> map = (Map<Class<? extends Annotation>, Annotation>) annotations.get(annotationData);
map.put(annotationToAlter, annotationValue);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Controller:
import com.sagemcom.smartvillage.smartvision.common.MyApi;
import org.springframework.web.bind.annotation.GetMapping;
#MyApi(apiPath = "/users")
public class UserController {
#GetMapping("/stackoverflow")
public String get() {
return "Best users";
}
}
I am trying to implement a custom password match validation in spring boot. But I am getting an error as follows:-
PasswordMatch contains Constraint annotation, but does not contain a message parameter
I am following this link https://www.baeldung.com/registration-with-spring-mvc-and-spring-security for custom validation. The problem is I am getting error as this.
javax.validation.ConstraintDefinitionException: HV000074: com.bikram.booking.validation.PasswordMatch contains Constraint annotation, but does not contain a message parameter.
at org.hibernate.validator.internal.metadata.core.ConstraintHelper.assertMessageParameterExists(ConstraintHelper.java:915)
I have searched solutions on web but couldn't find the decent soultion.
My Modal is
package com.bikram.booking.dto;
import com.bikram.booking.validation.PasswordMatch;
import com.bikram.booking.validation.ValidEmail;
import javax.validation.constraints.*;
#PasswordMatch
public class UserDto {
#NotNull
#Size(min = 6, message = "Password should be more than 6 characters")
#NotEmpty(message = "Please provide a password")
private String password;
#NotNull
#Size(min = 6, message = "Password should be more than 6 characters")
private String confirmPassword;
}
My Interface is
package com.bikram.booking.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({ TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = PasswordMatchValidator.class)
#Documented
public #interface PasswordMatch {
String messages() default "Sorry, passwords does not match";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And Implementation is
package com.bikram.booking.validation;
import com.bikram.booking.dto.UserDto;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, Object> {
#Override
public void initialize(PasswordMatch constraintAnnotation) {
}
#Override
public boolean isValid(Object obj, ConstraintValidatorContext constraintValidatorContext) {
UserDto userDto = (UserDto) obj;
return true;
}
}
Any hints will be higly appreciable.
Rename messages() to message() in PasswordMatch :
public #interface PasswordMatch {
String message() default "Sorry, passwords does not match";
....
}
Using Spring Boot, I've created an example application.
package hello;
import org.springframework.web.bind.annotation.RestController;
import constraint.CheckHelloId;
import dto.HelloDto;
import javax.validation.Valid;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#RestController
public class HelloController {
#RequestMapping(value = "/", method = RequestMethod.POST)
public String index(#RequestBody #Valid final HelloDto hello) {
hello.setId(null);
validateFromMethodHeader(hello);
return "Greetings from Spring Boot!";
}
private void validateFromMethodHeader(#CheckHelloId final HelloDto helloDto) {
System.out.println("Validating DTO...");
}
}
I'm trying to add a custom constraint to the DTO HelloDto in the private method that checks if the id field is null or not.
The interface:
package constraint;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({ FIELD, ANNOTATION_TYPE, TYPE, METHOD, PARAMETER })
#Retention(RUNTIME)
#Constraint(validatedBy = CheckHelloIdValidator.class)
#Documented
public #interface CheckHelloId {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The validator:
package constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
import org.apache.commons.lang3.StringUtils;
import dto.HelloDto;
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class CheckHelloIdValidator implements ConstraintValidator<CheckHelloId, HelloDto> {
private static final String ID_VALIDATION_ERROR = "Null or blank ID.";
#Override
public void initialize(CheckHelloId arg0) {
}
#Override
public boolean isValid(HelloDto helloDto, ConstraintValidatorContext context) {
if (StringUtils.isBlank(helloDto.getId())){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(ID_VALIDATION_ERROR).addConstraintViolation();
return false;
}
return true;
}
}
The DTO:
package dto;
import java.io.Serializable;
public class HelloDto implements Serializable {
private static final long serialVersionUID = 8792903048191496378L;
private String id;
private String message;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
As seen in the private method at the controller, I'm trying to apply the validation of the DTO at a parameter level (I know I can just do it at the index via entity validation, but I want to test this concrete case, that's why I set the id field as null at the controller).
When I run the application and perform the call, the constraint does not apply in the private method, even when the id field is null. Could anybody shed some light on this? Thank you in advance.
To validate consuming json on rest controller beside annotated method parameters #RequestBody #Valid final HelloDto hello you have to annotate filed of your json data class with special constraints. For you case it enough to use hibernate validation constraints #NotNull or #NotEmpty:
package dto;
import java.io.Serializable;
public class HelloDto implements Serializable {
private static final long serialVersionUID = 8792903048191496378L;
#NotEmpty
private String id;
Is there any possibility to validate StandardMultipartHttpServletRequest using standard #Valid annotation and custom Validator?
I've implemented such validator, annotated method param in controller the validator is not invoked.
I've figured it out myself. To make it work you need a DTO:
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
#Getter
#Setter
public class NewOrderFilesDTO {
List<MultipartFile> files;
}
Then, a validator:
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import static org.springframework.util.CollectionUtils.isEmpty;
#Component
public class NewOrderFilesValidator implements Validator {
private static final String MIME_TYPE_PDF = "application/pdf";
private static final long ALLOWED_SIZE = 3 * 1024 * 1024;
#Override
public void validate(Object target, Errors errors) {
if (target == null) {
return;
}
NewOrderFilesDTO newOrderFilesDTO = (NewOrderFilesDTO) target;
List<MultipartFile> newOrderFiles = newOrderFilesDTO.getFiles();
if (isEmpty(newOrderFiles)) {
return;
}
for (MultipartFile file : newOrderFiles) {
if (!MIME_TYPE_PDF.equals(file.getContentType())) {
errors.rejectValue(file.getName(), file.getName(), "'application/pdf' files allowed only!");
}
if (file.getSize() > ALLOWED_SIZE) {
errors.rejectValue(file.getName(), file.getName(), "File size allowed up to 3MB!");
}
}
}
#Override
public boolean supports(Class<?> cls) {
return NewOrderFilesDTO.class.equals(cls);
}
}
And finally a controller:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.Valid;
import static org.springframework.http.HttpStatus.NO_CONTENT;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
#Controller
class OrderController {
private final NewOrderFilesValidator newOrderFilesValidator;
#Autowired
OrderController(NewOrderFilesValidator newOrderFilesValidator) {
this.newOrderFilesValidator = newOrderFilesValidator;
}
#InitBinder("newOrderFiles")
void initOrderFilesBinder(WebDataBinder binder) {
binder.addValidators(newOrderFilesValidator);
}
#ResponseStatus(NO_CONTENT)
#RequestMapping(value = ORDERS_PATH, method = POST, consumes = MULTIPART_FORM_DATA_VALUE)
void createOrder(
#Valid #ModelAttribute NewOrderFilesDTO newOrderFiles
) {
}
}
With the configuration above the DTO will be validated automatically by spring.