Reputation: 3249
I worked out a concept to conditionally validate using JSR 303 groups. "Conditionally" means that I have some fields which are only relevant if another field has a specific value.
Example: There is an option to select whether to register as a person or as a company. When selecting company, the user has to fill a field containing the name of the company.
Now I thought I use groups for that:
class RegisterForm
{
public interface BasicCheck {}
public interface UserCheck {}
public interface CompanyCheck {}
@NotNull(groups = BasicCheck.class)
private Boolean isCompany
@NotNull(groups = UserCheck.class)
private String firstName;
@NotNull(groups = UserCheck.class)
private String lastName;
@NotNull(groups = CompanyCheck.class)
private String companyName;
// getters / setters ...
}
In my controller, I validate step by step depending on the respective selection:
@Autowired
SmartValidator validator;
public void onRequest(@ModelAttribute("registerForm") RegisterForm registerForm, BindingResult result)
{
validator.validate(registerForm, result, RegisterForm.BasicCheck.class);
if (result.hasErrors()
return;
// basic check successful => we can process fields which are covered by this check
if (registerForm.getIsCompany())
{
validator.validate(registerForm, result, RegisterForm.CompanyCheck.class)
}
else
{
validator.validate(registerForm, result, RegisterForm.UserCheck.class);
}
if (!result.hasErrors())
{
// process registration
}
}
I only want to validate what must be validated. If the user selects "company" fills a field with invalid content and then switches back to "user", the invalid company related content must be ignored by the validator. A solution would be to clear those fields using Javascript, but I also want my forms to work with javascript disabled. This is why I totally like the approach shown above.
But Spring breaks this idea due to data binding. Before validation starts, Spring binds the data to registerForm. It adds error to result
if, for instance, types are incompatible (expected int-value, but user filled the form with letters). This is a problem as these errors are shown in the JSP-view by <form:errors />
tags
Now I found a way to prevent Spring from adding those errors to the binding result by implementing a custom BindingErrorProcessor
. If a field contains null
I know that there was a validation error. In my concept null
is not allowed - every field gets annotated with @NotNull
plus the respective validation group.
As I am new to Spring and JSR-303 I wonder, whether I am totally on the wrong path. The fact that I have to implement a couple of things on my own makes me uncertain. Is this a clean solution? Is there a better solution for the same problem, as I think this is a common problem?
EDIT
Please see my answer here if you are interested in my solution in detail: https://stackoverflow.com/a/30500985/395879
Upvotes: 1
Views: 2918
Reputation: 21482
I know this question is old, but I came upon it looking for an answer for a different situation.
I think for your situation you could use inheritance for the forms and then use two controller methods:
The forms would look like this:
public class RegistrationForm
{
// Common fields go here.
}
public class UserRegistrationForm
extends RegistrationForm
{
@NotNull
private String firstName;
@NotNull
private String lastName;
// getters / setters ...
}
public class CompanyRegistrationForm
extends RegistrationForm
{
@NotNull
private String companyName;
// getters / setters ...
}
The controller methods would look like this:
@RequestMapping(method = RequestMethod.POST, params = "isCompany=false")
public void onRequest(
@ModelAttribute("registerForm") @Valid UserRegistrationForm form,
BindingResult result)
{
if (!result.hasErrors())
{
// process registration
}
}
@RequestMapping(method = RequestMethod.POST, params = "isCompany=true")
public void onRequest(
@ModelAttribute("registerForm") @Valid CompanyRegistrationForm form,
BindingResult result)
{
if (!result.hasErrors())
{
// process registration
}
}
Notice that the @RequestMapping
annotations include a params
attribute so the value of the isCompany
parameter determines which method is called.
Also notice that the @Valid
annotation is place on the form
parameter.
Finally, no groups are needed in this case.
Upvotes: 0
Reputation: 1705
You are correct that Spring MVC is a bit picky in this regard,and it is a common problem. But there are work-arounds:
Good luck!
Upvotes: 0