I've been trying to add custom messages for validation errors for a REST Service managed by Spring MVC within a #Controller class.
The Employee class:
public class Employee {
#NotNull
#NotEmpty
private String company;
...
}
My REST Service:
#ResponseStatus(value = HttpStatus.CREATED)
#RequestMapping(method = RequestMethod.POST)
public void add(#RequestBody #Valid Employee employee) {
employees.add(employee);
}
And the validation errors parses
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public #ResponseBody
List<String> validationExceptions(MethodArgumentNotValidException e) {
List<String> errors = new ArrayList<String>();
for (FieldError error : e.getBindingResult().getFieldErrors()) {
errors.add(error.getDefaultMessage());
}
return errors;
}
So I've put a ValidationMessages.properties on the root of my classpath, and I'm not able to get my custom messages with the following key NotEmpty.employee.company.
I know there are many ways to do this with a ResourceBundle and error.getCode(), or even with the key org.hibernate.validator.constraints.NotEmpty.message, but I'd like have specific messages to specific field of specific objects.
I also don't want to do this with #NotEmpty(message = "NotEmpty.employee.company}"). I want it the simplest.
What should I do?
Have you tried to implement your own
org.springframework.validation.MessageCodesResolver
and then declaring your implementation in the config file:
<mvc:annotation-driven message-codes-resolver="org.example.YourMessageCodesResolverImpl"/>
I'd give it a try, it seems this one is able to build custom error codes like the ones you want:
String[] resolveMessageCodes(String errorCode, String objectName, String field, Class<?> fieldType)
The only and important thing I'm not sure is whether it'll override the error codes generated by the hibernate validators...
I hope it helps (and works).
Cheers,
Chico.
Related
I have a class:
public class user{
private String id;
private MultiPartFile file;
**Getters And Setters**
}
And in the Controller:
#PostMapping(value="/upload)
public void upload(User user){
}
In the front end I post data with form-data.I can get the user object.
But when I add #RequestBody and #RequestParam,it can't works.
in my opinion,#RequestParam is used to binding parameter to simple class . when I use #RequestBody ,spring will find HttpMessageConverter to convert http request body to class.But I'm not sure about that.Does anyone can explain to me?
So, I believe we are talking about org.springframework.web.multipart.MultipartFile, which is to be used together with #RequestParam variable. The mechanism is somewhat special in this case.
I had a similar problem, and what I ended up using was org.springframework.web.multipart.commons.CommonsMultipartResolver. From frontend I've constructed multipart request with two parts, in your scenario it could be user (containing just JSON data) and file (containing the file itself), e.g.:
#PostMapping(value="/upload")
public void upload(#RequestParam("user") User user, #RequestParam("file") MultipartFile file){
...
}
But then, you need to configure custom serialization of the User part, which can be done using org.springframework.web.multipart.commons.CommonsMultipartResolver. You can configure it using bean config like this:
#Configuration
public class MappingConfig {
#Order(Integer.MIN_VALUE)
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
#Bean
public Converter<String, User> stringToUser() {
return new Converter<String, User>() {
#Override
public User convert(String jsonString) {
return new Gson().fromJson(jsonString, User.class);
}
};
}
...
}
Also, as you can see I am using Gson manually, I couldn't find a better way how to do it. Also, it doesn't play with Java 8 lambdas, so it cannot be shortened (because of explicit types are needed for it to work).
I hope that this will at least points you to a right path.
I'm having this error when I try persist a Task class with a join column
This is my join column on Task model
#NotEmpty
#ManyToOne
#JoinColumn(name="project_id")
private Project project;
And this is the controller request.
#RequestMapping(value = "/new", method=RequestMethod.POST, consumes="application/json", headers = "content-type=application/x-www-form-urlencoded")
public String addTask(#Valid Task task, BindingResult result, ModelMap model, HttpServletResponse response) {
//TODO
task.setProject(service.findById(1).getProject());
List<String> errors = service.save(task);
if(errors != null) {
model.addAttribute("errors", errors);
response.setStatus(500);
return "alertErrors";
}
model.addAttribute("tasks", service.findGrid(null));
return "tasks/listTable";
}
But when I call the controller they return me a error No validator could be found for type: ...model.Project..
I'm already put the error on messages and add headers = "content-type=application/x-www-form-urlencoded" to method but still returning error.
What I'm nees to do to fix it?
Per Hibernate Validator documentation you cannot add #NotEmpty on your custom property - it should be used with Strings, Arrays, Collections etc...
You probably should use: javax.validation.constraints.NotNull.
i have problem with Thymeleaf when validating form. I'm trying to create simple user register form to learn Spring and i'm unfortunately stuck.
Here is my UserForm class
public class UserForm {
#NotEmpty
private String username;
#NotEmpty
private String password;
#NotEmpty
private String passwordConfirm;
\\ Getters and Setters
}
First problem is when I add my custom validator class in initBinder
#Autowired
private UserFormValidator formValidator;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(formValidator);
}
"Default" annotated by #NotEmpty validation stops working. This is exptected behavior?
Second problem is how can I show global reject messages in thymeleaf?
My validator class is like below
public class UserFormValidator implements Validator {
#Autowired
UserService userService;
#Override
public boolean supports(Class<?> clazz) {
return UserForm.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
UserForm userForm = (UserForm) target;
if(!userForm.getPassword().equals(userForm.getPasswordConfirm())) {
errors.reject("passwords.no.match", "Passwords not match");
}
if(userService.findOneByUsername(userForm.getUsername()).isPresent()) {
errors.reject("user.exist", "User already exists (default)");
}
}
}
and post mapping from controller
#PostMapping("/create")
public String registerUser(#ModelAttribute("form") #Valid final UserForm form, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "newuser";
}
userService.saveUser(form);
return "redirect:/";
}
As "default" validation errors i can show by using exth:if="${#fields.hasErrors('passwordConfirm')}" i have no idea how can i show message for error passwords.no.match or check if this error occured?
By default spring boot uses bean validation to validated form object annotated with #Valid. If you want to use your custom validator and register it through #InitBinder, then bean validation will not take place, this is expected behavior. If you want to bean validation also works with your custom validation you need to do it manually inside your validator class or even in controller.
Here comes your second problem to show password not match error message. Inside your custom validator UserFormValidator.class while rejecting any value you need to use rejectValue() method like below:
#Override
public void validate(Object target, Errors errors) {
UserForm userForm = (UserForm) target;
if(!userForm.getPassword().equals(userForm.getPasswordConfirm())) {
errors.rejectValue("passwordConfirm", "passwords.no.match", "Passwords not match");
}
if(userService.findOneByUsername(userForm.getUsername()).isPresent()) {
errors.rejectValue("username", "user.exist", "User already exists (default)");
}
}
The rejectValue() method is used to add a validation error to the Errors object.
The first parameter identifies which field the error is associated with. The second parameter is an error code which acts a message key for the messages.properties file (or messages_en.properties or messages_fr.properties etc, if these are being used). The third parameter of rejectValue() represents the fallback default message, which is displayed if no matching error code is found in the resource bundle.
Now you can show error messages using th:if="${#fields.hasErrors('passwordConfirm')} inside your form.
I have a Roo generated application and use validation, spring security with a custom PermissionEvaluator and the generated web controllers. My entity has a field like this:
#NotNull
private Date creationDate;
This field is set automatically inside the controller's create method when saving the entity and is not included in the form (render="false").
#RequestMapping(method = RequestMethod.POST, produces = "text/html")
#PreAuthorize("hasPermission(#myEntity, 'create')")
public String create(#Valid MyEntity myEntity,
BindingResult bindingResult, Model uiModel,
HttpServletRequest httpServletRequest) {
// ... check binding result
myEntity.setCreationDate(new Date());
myEntity.persist();
// ...
}
The problem is, that validation always fails because it runs before the field is set. The PermissionEvaluator (called by #PreAuthorize) is also missing the value. How can I place my code somewhere between data binding and validation so that the entity is complete right from the beginning?
To solve the problem of #PreAutorize move the persistence logic to a #Service bean and call it from the controller. This way security check will be after validation. Roo can help you on it with service command.
Second, you can use validation groups to make different validation on for the same entity. This and this are two howto post.
An example:
#RequestMapping("/myEntity")
public MyEntityController {
#Autowired
MyEntityService myEntityService;
#RequestMapping(method = RequestMethod.POST, produces = "text/html")
public String create(#Validated({Account.ValidationStepOne.class}) MyEntity myEntity,
BindingResult bindingResult, Model uiModel,
HttpServletRequest httpServletRequest) {
// ... check binding result
myEntityService.save(myEntity);
//...
}
}
#Service
public MyEntityService {
#PreAuthorize("hasPermission(#myEntity, 'create')")
public save(MyEntity myEntity) {
//...
myEntity.setCreationDate(new Date());
myEntity.persist();
}
}
Good luck!
I want to use validation not with web controllers. Suppose I have a class Person
public class Person {
private String name;
private String surname;
//getters and setters
...
}
Also I have a validator class:
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname",
"surname.empty");
}
}
How I can use this validator for example in console application? Or validation is only for web application in spring?
You can use the validation tools in a console application. You simply need to call ValidationUtils.invokeValidator(validator, object, errors). Your main concern would be having a suitable Errors instance. You would probably end up using BeanPropertyBindingResult, or subclassing AbstractErrors.
You probably know, but you should consult the Spring reference and javadoc.
Rough guess at untested code:
Person person = new Person();
Errors errors = new BeanPropertyBindingResult(person, "person");
ValidationUtuls.invokeValidator(new PersonValidator(), person, errors);
if (errors.hasErrors()) { ... }
Out of interest, why are you using Spring validation in preference to javax.validation? I've found that it's generally easier to use the javax.validaton/JSR-303 API. Hibernate Validator is the reference implementation and Spring integrates with JSR-303.