Spring-MVC form validation highlight input field after DAO validation - java

I'm converting Struts 1.3 project to Spring. Instead of struts form fields, I'm using spring form.
I have used ActionErrors in struts to highlight the field using errorStyleClass attribute.
Similarly, in spring cssErrorClass is available. But, How to use it after the dao validation?
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(#ModelAttribute("login") #Validated Login login, BindingResult result, Model model) {
if (result.hasErrors()) {
//THIS VALIDATION DONE BY ANNOTATION AND HIGHLIGHTING THE FIELD
//USING "cssErrorClass"
return HOMEPAGE;
}
boolean checkAuthentication = authService.checkAuthentication(login);
if(!checkAuthentication){
// HOW TO SET THE ERROR HERE?
// Is there any way to set the error like
// error.setMessage("userId","invalid.data");
// so that, is it possible to display error message by
// highlighting the fields using "cssErrorClass"?
}
return HOMEPAGE;
}

You need to annotate your entities using Java Bean Validation framework JSR 303, like this
public class Model{
#NotEmpty
String filed1;
#Range(min = 1, max = 150)
int filed2;
....
}
And add #Valid to your controller, like this
public class MyController {
public String controllerMethod(#Valid Customer customer, BindingResult result) {
if (result.hasErrors()) {
// process error
} else {
// process without errors
}
}
You can find more examples for it here and here
EDIT:
If you want to register more errors based on custom validation steps in code, you can use rejectValue() method in the BindingResult instance, like this:
bindingResult.rejectValue("usernameField", "error code", "Not Found username message");

Related

Spring MVC validation form attribute name problem

Just got a strange behavior with validation of a very simple HTML form with Spring MVC and Thymleaf.
I have this methods for edit page rendering and submitted form handle.
#GetMapping("/{id}")
public String editPage(#PathVariable("id") Long id, Model model) {
model.addAttribute("user", userService.findById(id)
.orElseThrow(NotFoundException::new));
return "user_form";
}
#PostMapping("/update")
public String update(#Valid UserRepr user, BindingResult result, Model model) {
logger.info("Update endpoint requested");
if (result.hasErrors()) {
return "user_form";
}
userService.save(user);
return "redirect:/user";
}
The wired thing in the update() method is that in case of a validation error the attribute of the model with form content has the name "userRepr" but not "user" as I expect and the thymleaf form view failed because of that.
It's easy to fix the problem by renaming the attribute but Is there some contract about such attribute naming? Is it changeable?
You can do this using #ModelAttribute.
#ModelAttribute is used to map/bind a a method parameter or method return type to a named model attribute. See #ModelAttributes JavaDoc. This is a Spring annotation.
#PostMapping("/update")
public String update(#Valid #ModelAttribute("user") UserRepr user, BindingResult result, Model model) {
logger.info("Update endpoint requested");
if (result.hasErrors()) {
return "user_form";
}
userService.save(user);
return "redirect:/user";
}
Why does it look like this?
Model object auto generates attribute names and then forward the above method calls to addAttribute(Object attributeValue).
Following are the rules of name generation strategy:
For an object which is not a collection, short class name is generated. For example for java.lang.String, 'string' will be
generated.
For collections/arrays, 'List' is appended after the type of elements in it e.g. 'stringList'. The collection/array should not be
empty because the logic uses the first element to find it's type.
You can check this link for more details. https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-model-attribute-generated-names.html

Spring form submission with minum boilerplate

I've been trying to figure out what the best practice is for form submission with spring and what the minimum boilerplate is to achieve that.
I think of the following as best practise traits
Validation enabled and form values preserved on validation failure
Disable form re-submission F5 (i.e. use redirects)
Prevent the model values to appear in the URL between redirects (model.clear())
So far I've come up with this.
#Controller
#RequestMapping("/")
public class MyModelController {
#ModelAttribute("myModel")
public MyModel myModel() {
return new MyModel();
}
#GetMapping
public String showPage() {
return "thepage";
}
#PostMapping
public String doAction(
#Valid #ModelAttribute("myModel") MyModel myModel,
BindingResult bindingResult,
Map<String, Object> model,
RedirectAttributes redirectAttrs) throws Exception {
model.clear();
if (bindingResult.hasErrors()) {
redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.myModel", bindingResult);
redirectAttrs.addFlashAttribute("myModel", myModel);
} else {
// service logic
}
return "redirect:/thepage";
}
}
Is there a way to do this with less boilerplate code or is this the least amount of code required to achieve this?
First, I wouldn't violate the Post/Redirect/Get (PRG) pattern, meaning I would only redirect if the form is posted successfully.
Second, I would get rid of the BindingResult style altogether. It is fine for simple cases, but once you need more complex notifications to reach the user from service/domain/business logic, things get hairy. Also, your services are not much reusable.
What I would do is pass the bound DTO directly to the service, which would validate the DTO and put a notification in case of errors/warning. This way you can combine business logic validation with JSR 303: Bean Validation.
For that, you can use the Notification Pattern in the service.
Following the Notification Pattern, you would need a generic notification wrapper:
public class Notification<T> {
private List<String> errors = new ArrayList<>();
private T model; // model for which the notifications apply
public Notification<T> pushError(String message) {
this.errors.add(message);
return this;
}
public boolean hasErrors() {
return !this.errors.isEmpty();
}
public void clearErrors() {
this.errors.clear();
}
public String getFirstError() {
if (!hasErrors()) {
return "";
}
return errors.get(0);
}
public List<String> getAllErrors() {
return this.errors;
}
public T getModel() {
return model;
}
public void setModel(T model) {
this.model = model;
}
}
Your service would be something like:
public Notification<MyModel> addMyModel(MyModelDTO myModelDTO){
Notification<MyModel> notification = new Notification();
//if(JSR 303 bean validation errors) -> notification.pushError(...); return notification;
//if(business logic violations) -> notification.pushError(...); return notification;
return notification;
}
And then your controller would be something like:
Notification<MyModel> addAction = service.addMyModel(myModelDTO);
if (addAction.hasErrors()) {
model.addAttribute("myModel", addAction.getModel());
model.addAttribute("notifications", addAction.getAllErrors());
return "myModelView"; // no redirect if errors
}
redirectAttrs.addFlashAttribute("success", "My Model was added successfully");
return "redirect:/thepage";
Although the hasErrors() check is still there, this solution is more extensible as your service can continue evolving with new business rules notifications.
Another approach which I will keep very short, is to throw a custom RuntimeException from your services, this custom RuntimeException can contain the necessary messages/models, and use #ControllerAdvice to catch this generic exception, extract the models and messages from the exception and put them in the model. This way, your controller does nothing but forward the bound DTO to service.
Based on the answer by #isah, if redirect happens only after successful validation the code can be simplified to this:
#Controller
#RequestMapping("/")
public class MyModelController {
#ModelAttribute("myModel")
public MyModel myModel() {
return new MyModel();
}
#GetMapping
public String showPage() {
return "thepage";
}
#PostMapping
public String doAction(
#Valid #ModelAttribute("myModel") MyModel myModel,
BindingResult bindingResult,
RedirectAttributes redirectAttrs) throws Exception {
if (bindingResult.hasErrors()) {
return "thepage";
}
// service logic
redirectAttrs.addFlashAttribute("success", "My Model was added successfully");
return "redirect:/thepage";
}
}
One possible way is to use Archetype for Web forms, Instead of creating simple project, you can choose to create project from existing archetype of web forms. It will provide you with sufficient broiler plate code. You can also make your own archetype.
Have a look at this link to get deeper insight into archetypes.
Link To Archetypes in Java Spring

Convert JSR-303 validation errors to Spring's BindingResult

I have the following code in a Spring controller:
#Autowired
private javax.validation.Validator validator;
#RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form) {
Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);
...
}
Is it possible to map errors to Spring's BindingResult object without manually going through all the errors and adding them to the BindingResult? Something like this:
// NOTE: this is imaginary code
BindingResult bindingResult = BindingResult.fromConstraintViolations(errors);
I know it is possible to annotate the CustomForm parameter with #Valid and let Spring inject BindingResult as another method's parameter, but it's not an option in my case.
// I know this is possible, but doesn't work for me
public String submitForm(#Valid CustomForm form, BindingResult bindingResult) {
...
}
A simpler approach could be to use Spring's abstraction org.springframework.validation.Validator instead, you can get hold of a validator by having this bean in the context:
<bean id="jsr303Validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
#Autowired #Qualifier("jsr303Validator") Validator validator;
With this abstraction in place, you can use the validator this way, passing in your bindingResult:
validator.validate(obj, bindingResult);
Spring uses a SpringValidatorAdapter to convert javax.validation.ConstraintViolation objects to ObjectError or FieldError objects, as found in the binding result.
The BindStatus then uses a message source (like the web application context itself) to translate the errors.
In short, you could do:
SpringValidatorAdapter springValidator = new SpringValidatorAdapter(validator);
BindingResult bindingResult= new BeanPropertyBindingResult(myBeanToValidate, "myBeanName");
springValidator.validate(myBeanToValidate, bindingResult);
This is easier when writing a unit test, because you don't even need to create a Spring context.
Expanding on Kristiaan's answer, for testing purposes it is not necessary to create a spring context to validate using Spring's bindingResult. The following is an example:
public class ValidatorTest {
javax.validation.Validator javaxValidator = Validation.buildDefaultValidatorFactory().getValidator();
org.springframework.validation.Validator springValidator = new SpringValidatorAdapter(javaxValidator);
#Test
public void anExampleTest() {
JSR303AnnotatedClassToTest ctt = new JSR303AnnotatedClassToTest( ..init vars..)
... test setup...
WebDataBinder dataBinder = new WebDataBinder(ctt);
dataBinder.setValidator(springValidator);
dataBinder.validate();
BindingResult bindingResult = dataBinder.getBindingResult();
... test analysis ...
}
}
This approach doesn't require creating a binding result ahead of time, the dataBinder builds the right one for you.
#RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form) {
Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);
BindingResult bindingResult = toBindingResult(errors, form, "form");
...
}
private BindingResult toBindingResult(ConstraintViolationException e, Object object, String objectName) {
BindingResult bindingResult = new BeanPropertyBindingResult(object, objectName);
new AddConstraintViolationsToErrors().addConstraintViolations(e.getConstraintViolations(), bindingResult);
return bindingResult;
}
private static class AddConstraintViolationsToErrors extends SpringValidatorAdapter {
public AddConstraintViolationsToErrors() {
super(Validation.buildDefaultValidatorFactory().getValidator()); // Validator is not actually used
}
#SuppressWarnings({"rawtypes", "unchecked"})
public void addConstraintViolations(Set<? super ConstraintViolation<?>> violations, Errors errors) {
// Using raw type since processConstraintViolations specifically expects ConstraintViolation<Object>
super.processConstraintViolations((Set) violations, errors);
}
}
Unlike the other answers to this question, this solution handles the case where there already exists a Set<ConstraintViolation<?>> which needs to be converted to to a BindingResult.
Explanation
Spring provides the SpringValidatorAdapter class to perform bean validations, storing the results in an Errors instance (note that BindingResult extends Errors). The normal manual use of this class would be to use it to perform the validations via the validate method:
Validator beanValidator = Validation.buildDefaultValidatorFactory().getValidator();
SpringValidatorAdapter validatorAdapter = new SpringValidatorAdapter(beanValidator);
BindException bindException = new BindException(form, "form");
validatorAdapter.validate(form, bindException);
However, this doesn't help in the case where there already exists a Set<ConstraintViolation<?>> which needs to be converted to a BindingResult.
It is still possible to achieve this goal, though it does require jumping through a couple extra hoops. SpringValidatorAdapter contains a processConstraintViolations method which converts the ConstraintViolation objects into the appropriate Spring ObjectError subtypes, and stores them on an Errors object. However, this method is protected, limiting its accesibility to subclasses.
This limitation can be worked around by creating a custom subclass of SpringValidatorAdapter which delegates to or exposes the protected method. It is not a typical usage, but it works.
public class AddConstraintViolationsToErrors extends SpringValidatorAdapter {
public AddConstraintViolationsToErrors() {
super(Validation.buildDefaultValidatorFactory().getValidator()); // Validator is not actually used
}
#SuppressWarnings({"rawtypes", "unchecked"})
public void addConstraintViolations(Set<? super ConstraintViolation<?>> violations, Errors errors) {
// Using raw type since processConstraintViolations specifically expects ConstraintViolation<Object>
super.processConstraintViolations((Set) violations, errors);
}
}
This custom class can be used to populate a newly created BindingResult, achieving the goal of creating a BindingResult from a Set<ConstraintViolation<?>>.
private BindingResult toBindException(ConstraintViolationException e, Object object, String objectName) {
BindingResult bindingResult = new BeanPropertyBindingResult(object, objectName);
new AddConstraintViolationsToErrors().addConstraintViolations(e.getConstraintViolations(), bindingResult);
return bindingResult;
}
I've encountered a similar issue and this is how I resolved it.
Given your example, this is how I implemented it
First, I used a smart validator, and in the method I let spring inject the BindingResult
#Autowired
private org.springframework.validation.SmartValidator validator;
#RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form, BindingResult bindingResult) {
Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);
...
}
And then using that binding result i pass it in the SmartValidator so that any errors will be bounded to BindingResult.
validator.validate(form, bindingResult);
if(bindingResult.hasErrors()) {
throw new BindException(bindingResult);
}

Validation in Spring MVC

We are using Spring MVC 3.0 in our web application. We are also using the validation framework of Spring MVC.
While doing validation we need to create our validators for each entity we need to validate. For example if I have a Person entity, I will validate it using following PersonValidator.
public class PersonValidator implements Validator {
/**
* This Validator validates just Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "age", "field.required");
}
}
My question is, is it possible to have different validations for same entities for different methods.
#RequestMapping(method = RequestMethod.POST)
public String add(#Valid Person person, BindingResult result) {
if (result.hasErrors()) {
return "person/new";
}
personService.addPerson(person);
return "redirect:/persons";
}
#RequestMapping(method = RequestMethod.POST)
public String update(#Valid Person person, BindingResult result) {
if (result.hasErrors()) {
return "person/edit";
}
personService.updatePerson(person);
return "redirect:/persons";
}
I want to validate first name, last name and age while adding a new person but while updating I don't want age to be mandatory.
This is just a random situation, it can be any entity and any property.
How do we handle such situations?
Thanks.
You could drop the #Valid annotation and instead inside the method set a flag on your model object for insert vs update and then call the Validator directly (you can inject them into the controller).
Then inside the validator you can choose which validations are required for your current scenario.

Spring MVC and JSR-303 hibernate conditional validation

I've a form I want to validate. It contains 2 Address variables. address1 has always to be validated, address2 has to be validated based on some conditions
public class MyForm {
String name;
#Valid Address address1;
Address address2;
}
public class Address {
#NotEmpty
private String street;
}
my controller automatically validates and binds my form obj
#RequestMapping(...)
public ModelAndView edit(
#ModelAttribute("form")
#Valid
MyForm form,
BindingResult bindingResult,
...)
if(someCondition) {
VALIDATE form.address2 USING JSR 303
the problem is that if I use the LocalValidatorFactoryBean validator i can't reuse the BinidingResult object provided by Spring. The bind won't work as the target object of 'result' is 'MyForm' and not 'Address'
validate(form.getAddress2(), bindingResult) //won't work
I'm wondering what's the standard/clean approach to do conditional validation.
I was thinking in programmatically create a new BindingResult in my controller.
final BindingResult bindingResultAddress2 = new BeanPropertyBindingResult(address2, "form");
validate(form.getAddress2(), bindingResultAddress2);
but then the List of errors I obtain from bindingResultAddress2 can't be added to the general 'bindingResult' as the field names are not correct ('street' instead of 'address2.street') and the binding won't work.
Some dirty approach would be to extend BeanPropertyBindingResult to accept some string to append to the fields name.. do you have a better approach?
The standard approach for validating hierarchical structures is to use pushNestedPath()/popNestedPath(), though I'm not sure how it plays with JSR-303:
bindingResult.pushNestedPath("address2");
validate(form.getAddress2(), bindingResult);
bindingResult.popNestedPath();
I've never tried myself, but I think the correct approach is using validator groups.
First of all, let's see #javax.validation.Valid API
Mark an association as cascaded. The associated object will be validated by cascade.
When Spring framework uses #Valid as a marker to validate its command objects, it corrupts its purpose. Spring should instead create your own specific annotation which specifies the groups which should be validated.
Unfortunately, you should use Spring native Validator API if you need to validate some groups
public void doSomething(Command command, Errors errors) {
new BeanValidationValidator(SomeUserCase.class, OtherUserCase.class)
.validate(command, errors);
if(errors.hasErrors()) {
} else {
}
}
BeanValidationValidator can be implemented as
public class BeanValidationValidator implements Validator {
javax.validation.Validator validator = ValidatorUtil.getValidator();
private Class [] groups;
public BeanValidationValidator(Class... groups) {
this.groups = groups;
}
public void validate(Object command, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(command, groups);
for(ConstraintViolation<Object> constraintViolation: constraintViolationSet) {
errors.rejectValue(constraintViolation.getPropertyPath().toString(), null, constraintViolation.getMessage());
}
}
}

Categories