JSR-303 errors not detected natively by using the #Valid annotation - spring

How come the #Valid annotation does not catch my JSR-303 annotations natively, but do catch them using the following method:
WebConfig.java
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
String[] strBaseNames = {
"resources.messages.layout.LayoutResources",
"resources.messages.layout.MenuResources",
"resources.messages.global.GlobalResources"
};
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
messageSource.setBasenames(strBaseNames);
return messageSource;
}
#Bean
public LocalValidatorFactoryBean jsr303Validator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(this.messageSource());
return localValidatorFactoryBean;
}
UserController.java
#Controller
#RequestMapping(value = "/security/user", method = RequestMethod.GET)
public class UserController {
#Autowired
private SecurityService securityService;
#Autowired
SessionObject sessionObject;
#Autowired
#Qualifier("jsr303Validator") Validator validator;
#RequestMapping(value = "/edit/validate", method = RequestMethod.POST)
public String validateEdit( #ModelAttribute #Valid User user,
BindingResult result,
Model model) {
String strViewName = "common/error";
validator.validate(user, result);
if(result.hasErrors()) {
strViewName = "user/userEdit";
} else {
... update work ...
strViewName = "user/success";
}
return strViewName;
}
Resource:
http://www.wenda.io/questions/2462924/convert-jsr-303-validation-errors-to-springs-bindingresult.html
If I only use the #Valid annotation, result.hasErrors() is always empty, so I end up having an ConstraintViolationException (see below), which is expected. But I would like to have it working using the #Valid annotation only, without having to autowire my jsr303validator bean in each controller I want to implement some JSR-303 validations.
Constraint Violation log trace
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [spring4base.model.security.User] during update time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='error.firstname.notnull', propertyPath=strFirstName, rootBeanClass=class spring4base.model.security.User, messageTemplate='error.firstname.notnull'}
ConstraintViolationImpl{interpolatedMessage='error.userid.notnull', propertyPath=strUserId, rootBeanClass=class spring4base.model.security.User, messageTemplate='error.userid.notnull'}
ConstraintViolationImpl{interpolatedMessage='error.lastname.notnull', propertyPath=strLastName, rootBeanClass=class spring4base.model.security.User, messageTemplate='error.lastname.notnull'}
]
User.java
#NotBlank(message = "error.userid.notnull")
#Size(min = 0, max = 14, message = "error.firstname.length")
private String strUserId = "";
#NotBlank(message = "error.firstname.notnull")
#Size(min = 0, max = 50, message = "error.firstname.length")
private String strFirstName = "";
#NotBlank(message = "error.lastname.notnull")
#Size(min = 0, max = 50, message = "error.lastname.length")
private String strLastName = "";
Thank you

The following was required in order to make it work in my case:
First, define a LocalValidatorFactoryBean in my Java Config class (WebAppContext.java in my case).
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
String[] baseNames = {
"resources.messages.layout.LayoutResources",
"resources.messages.layout.MenuResources",
"resources.messages.option.AppConfigResources",
"resources.messages.global.GlobalResources",
"resources.messages.contact.ContactResources",
"resources.messages.currentsession.CurrentSessionResources",
"resources.messages.welcome.WelcomeResources",
"resources.messages.user.UserResources",
"resources.messages.role.RoleResources",
"resources.messages.profile.ProfileResources"
};
messageSource.setCacheSeconds(60);
messageSource.setFallbackToSystemLocale(false);
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
messageSource.setBasenames(baseNames);
return messageSource;
}
#Bean
public LocalValidatorFactoryBean jsr303Validator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(this.messageSource());
return localValidatorFactoryBean;
}
Then, in my controller, I autowire that validator like so:
#Autowired
private Validator jsr303Validator;
And then, the only way I have found to get my JSR-303 executed is to launch them by hand, like so:
#RequestMapping(value = "/update", method = RequestMethod.POST)
public String processUpdate(#ModelAttribute UserForm userForm,
BindingResult result,
RedirectAttributes redirectAttributes,
SessionStatus status) throws CheckedPersistenceException {
this.userFormValidator.validate(userForm, result);
if (!result.hasErrors()) {
this.jsr303Validator.validate(userForm, result);
}
if(result.hasErrors()) {
return this.UPDATE_VIEW;
}
...
}

Related

spring boot validation i18n message of placeholder

Bean
#Length(min = 1, max = 40, message = "{i18n.reg.validator.firstName.length}")
private String firstName;
Message
i18n.reg.validator.firstName.length = Firstname must be between {1} and {2} characters long.
Here is my config for i18n.
#Bean
public LocaleResolver localeResolver() {
return new FixedLocaleResolver(Locale.ENGLISH);
}
#Bean
public MessageSource messageSource() {
return new DbMessageSource();
}
#Bean
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource);
return bean;
}
When i do valid.
#PostMapping("/customer/reg")
#ResponseBody
void reg(#Valid #RequestBody Customer customer) {
// customerService.add(customer);
}
I get the error message is Firstname must be between 1 and 2 characters long.. It doesn't get the value of min and max from #Length to format message. What did I miss?
Try to change {1} and {2} with {min} and {max}

Not able to get error object in JSON format while using #Valid and MessageSource to get display errors in Spring boot

I am currently learning Spring REST and I am trying to build a demo spring boot app. Incase of DTO object has validation error I want to show it as below:
{
"errors": [
{
"code": "first_error_code",
"message": "1st error message"
"field":"field_name"
}
]
}
Where the code in above JSON should display the validation message that I have given in my entity class i.e
#NotEmpty(message = "{name.not.empty}")
String name;
then code should be name.not.empty and message should be taken from messages.properties file.
Now to achieve this, I used several tutorials. Below are the classes:
Main class: (Included MessageSource and LocalValidatorFactoryBean)
#SpringBootApplication
#EnableSwagger2
public class Demo3PathvariableApplication implements WebMvcConfigurer {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
public static void main(String[] args) {
SpringApplication.run(Demo3PathvariableApplication.class, args);
}
/*
* To enable matrix variables, configurePathMatch() method of WebMvcConfigurer
* needs to overriden. Matrix variables are disabled by default and the
* following configuration
*
* urlPathHelper.setRemoveSemicolonContent(false);
*
* should be present in the overriden method to enable the same. see below
* method.
*/
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST");
}
/* For Swagger Document Generation */
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.basePackage("com.infytel.controller")).paths(PathSelectors.any()).build()
.useDefaultResponseMessages(false);
// To scan for RestControllers from this package and For disabling default
// response messages
}
}
Controller class:
#RestController
#RequestMapping("/customers")
#Api(value = "CustomerController, REST APIs that deal with Customer DTO")
public class CustomerController {
#Autowired
private CustomerService customerService;
#PostMapping(consumes = "application/json")
public ResponseEntity createCustomer(#RequestBody #Valid CustomerDTO customer, Errors errors) {
return ResponseEntity.ok(customerService.createCustomer(customer));
}
}
FieldErrorDTO.java:
public class FieldErrorDTO {
private String errorCode;
private String message;
private String field;
public FieldErrorDTO(String errorCode, String message, String field) {
this.errorCode = errorCode;
this.message = message;
this.field = field;
}
//Getter setter
ValidationErrorDTO.java:
public class ValidationErrorDTO {
private List<FieldErrorDTO> fieldErrors = new ArrayList<>();
public ValidationErrorDTO() {
super();
}
public void addFieldError(String errorCode, String message, String field) {
FieldErrorDTO error = new FieldErrorDTO(errorCode, message, field);
fieldErrors.add(error);
}
public List<FieldErrorDTO> getFieldErrors() {
return fieldErrors;
}
public void setFieldErrors(List<FieldErrorDTO> fieldErrors) {
this.fieldErrors = fieldErrors;
}
}
RestErrorHandler .java
#ControllerAdvice
public class RestErrorHandler {
#Autowired
private MessageSource messageSource;
#ResponseStatus(BAD_REQUEST)
#ResponseBody
#ExceptionHandler(MethodArgumentNotValidException.class)
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getCode(), localizedErrorMessage, fieldError.getField());
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
return localizedErrorMessage;
}
}
messages.properties
name.not.empty=Please provide a name.
email.not.valid=Please provide valid email id.
age.adult.only=Age should be more than 18.
Now with all these config, I am able to see below JSON,
{
"fieldErrors": [
{
"errorCode": "NotEmpty",
"message": "Please provide a name.",
"field": "name"
},
{
"errorCode": "Email",
"message": "Please provide valid email id.",
"field": "email"
}
]
}
How do I acheive this requirement, where instead of "errorCode": "NotEmpty", I want show
"errorCode": "name.not.empty"
From CustomerDTO class?
To do so you need to change you processFieldErrors:
First remove "{}" from your anotations:
#NotEmpty(message = "name.not.empty")
String name;
Second:
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getDefaultMessage(), localizedErrorMessage, fieldError.getField());
}
return dto;
}
And third, change your message.getMessage:
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError.getDefaultMessage(), null, currentLocale);
return localizedErrorMessage;
}
This way you would retrieve the key for the message. In your example it will be :
name.not.empty
Hope this helps

Hibernate validation message from properties

I am trying to get a custom message from property file, but I can't.
My configs:
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:/messages/ValidationMessages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setFallbackToSystemLocale(true);
return messageSource;
}
Class which must be valid:
public class NewUserDto {
#NotEmpty(message = "{NotEmpty.newUserDto.email}")
private String email;
}
Value in my property file: NotEmpty.newUserDto.email = Some value.
But instead Some value I get {NotEmpty.newUserDto.email}, why it is so?
If you are using java configuration. You can refer to the below example:
Delcare a bean like this
public class Employee implements Serializable {
#Email #NotEmpty
private String email;
}
A custom message is located in a file messages.properties
Email.employee.email=Please provide a valid Email address
and this file is added in WebMvcConfigurerAdapter
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
return messageSource;
}
I found this example from the post Spring MVC Form Validation Annotation Example
If I understood correctly, you are trying to inject a String message from properties file, which in it, you have an entry of:
NotEmpty.newUserDto.email
And it got a value that should act as your message error in case of bean validation failure.
In this case:
First, use expression language to inject the property. like this:
#Component
public class NewUserDto {
#Value("${NotEmpty.newUserDto.email}")
private String msg;
#NotEmpty(message = msg)
private String email;
}
Don't forget to annotate your class with #Component annotation and turn it to bean in order to do this #Value injection. of course, you'll have to add to your configuration file the next:
<context:component-scan base-package="package.to.NewUserDto" />
You can try to do it directly, like this:
#Component
public class NewUserDto {
#NotEmpty(message = #Value("${NotEmpty.newUserDto.email}"))
private String email;
}

Spring 4 & Hibernate 5 validator messageSource does not work

I can't get use to work hibernate validation 5.1.0.Final with Spring MVC 4.2.5.RELEASE.
My WebConfig:
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource());
return validator;
}
#Bean
#Autowired
public MethodValidationPostProcessor getValidationPostProcessor(LocalValidatorFactoryBean validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(validator);
return processor;
}
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("i18n/messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}
#Override
public Validator getValidator() {
return validator();
}
#Bean
public LocaleChangeInterceptor localeChangeInterceptor(){
LocaleChangeInterceptor l = new LocaleChangeInterceptor();
l.setParamName("lang");
return l;
}
#Bean
public SessionLocaleResolver localeResolver(){
SessionLocaleResolver s = new SessionLocaleResolver();
s.setDefaultLocale(Locale.ENGLISH);
return s;
}
I have an ExceptionHanlder which gets validation messages and push it back as a json:
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ExceptionHandler(ConstraintViolationException.class)
#ResponseBody
public ValidationError handleConstraintViolation(final ConstraintViolationException exception) {
ValidationError v = new ValidationError();
exception.getConstraintViolations().forEach(violation -> {
v.addError(violation.getPropertyPath().toString(), violation.getMessage());
});
logger.warn(exception, exception);
return v;
}
I have 3 files in src/main/resources/i18n/: messages_en_EN.properties, messages_pl_PL.properties, messages.properties.
My model class with validation has one validated parameter:
#Column(name = "VALUE_", nullable = false)
#Email(message = "{Email.contractorContactEmail.value}")
#NotNull(message = "{NotNull.contractorContactEmail.value}")
private String value;
What I see is that hibernate validator look into classpath:ValidationMessages properties not into my spring message source. It may be ok for me but Hibernate does not want to translate those messages - locale is always server default. What am I doing wrong?? How can I fix it?
PS. In controller I use #org.springframework.validation.annotation.Validated.
PS2. I am sure that my messageSource is working correctly because if I add this code into ExceptionHandler it translates perfectly but I know that it is bad practice.
exception.getConstraintViolations().forEach(violation -> {
String messageTemplate = violation.getMessageTemplate();
if (messageTemplate.startsWith("{") && messageTemplate.endsWith("}")) {
String key = StringUtils.substring(messageTemplate, 1, messageTemplate.length() - 1);
v.addError(violation.getPropertyPath().toString(), messageSource.getMessage(key, null, LocaleContextHolder.getLocale()));
}
});
What I can tell you right away, judging from your configuration, your messages are in the wrong place - you've configured the message source to look at i18/messages but your messages are in the classpath root. So, your message source definition should look like:
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}
Aside from that, the rest of the configuration looks pretty much okay. I'm guessing that you're using the WebMvcConfigurerAdapter class?
EDIT:
For future reader's reference, the issue was not configuration-related, JPA entity was missing a #Valid annotation on a #OneToMany field so controller-level validation failed to pick it up and Hibernate's JPA validator used default messages.

Spring MockMvc Passing Nested Form Parameters

I have the following form
public class MyForm {
private Account account;
}
public class Account {
private String firstName;
}
How do I pass firstName parameter?
(The following approach does not work)
mockMvc.perform(post("/xyz")
.param("account.firstName", "John"))
.andExpect(model().hasErrors())
.andExpect(view().name("/xyz"))
.andExpect(status().isOk())
Finally I resolved this issue. Since I am using standalone setup I had to define validator and messagesource.
void setupTest() {
MockitoAnnotations.initMocks(this)
this.mockMvc = MockMvcBuilders.standaloneSetup(getController())
.setValidator(getValidator())
.alwaysDo(MockMvcResultHandlers.print())
.build()
}
private MessageSource getMessageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}
private LocalValidatorFactoryBean getValidator() {
def validator = new LocalValidatorFactoryBean()
validator.setValidationMessageSource(getMessageSource());
return validator;
}

Resources