How to decouple validators from controllers in Spring MVC - java

I've been building Spring MVC (3.2/4.0) controllers with validators for my web application along the lines of what I found in the Spring Petclinic sample application. However, in the example application, validators are created within the relevant controlllers using the new keyword, creating a tight coupling. Now that I'm writing tests to cover this code it is proving difficult to isolate these classes due to this coupling.
Is there a recommended way to decouple validators from controllers? Is there some other solution to this problem?
Here's an example from the Petclinic application of the tight coupling I mean:
#RequestMapping(value = "/owners/{ownerId}/pets/new", method = RequestMethod.POST)
public String processCreationForm(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/createOrUpdatePetForm";
} else {
this.clinicService.savePet(pet);
status.setComplete();
return "redirect:/owners/{ownerId}";
}
}

Define Petvalidator as a bean in your application-context and make the following changes to your controller
#RequestMapping(value = "/owners/{ownerId}/pets/new", method = RequestMethod.POST)
public String processCreationForm(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
PetValidator petValidator; //change this line
petValidator.validate(pet,result); //change this line
if (result.hasErrors()) {
return "pets/createOrUpdatePetForm";
} else {
this.clinicService.savePet(pet);
status.setComplete();
return "redirect:/owners/{ownerId}";
}
}
And you can use property injection to inject the appropriate Petvalidator to your controller. If ur using component-scanning add the following to autowire the registered bean into your controller.
#Autowired
PetValidator petValidator;
Inside your controller istead of PetValidator petvalidator;

That's what #Validis for:
public String processCreationForm(#ModelAttribute("pet") #Valid Pet pet, BindingResult result, SessionStatus status) {
if (result.hasErrors()) {
There's no need to do the validation yourself. Let Spring handle it automatically.

If your PetValidator is of type org.springframework.validation.Validator you could bind it using WebDataBinder.setValidator().
In your controller or #ControllerAdvice add a method annotated with #InitBinder.
#InitBinder
public void initBinder(WebDataBinder binder) {
// Add the validator. Could be an auto wired instance as well.
binder.setValidator(new PetValidator());
}
Now all objects of a type supported by your validator will be validated automatically.

Related

How to set validation groups dynamically

I have a Spring Boot application where I want to validate a request body object with the #valid annotation.
#PostMapping("update")
public void upateSnippet(#Valid #RequestBody MyDto dto)
This works as expected. But I need to do the validation based on session specific informations. So I created validation groups which should be set dynamically. I don´t want to call the validator manually in the controller. The validation logic should be completely outside of the controller.
So what I´m trying to do is to register a custom MethodValidationInterceptor where I can specify the groups in determineValidationGroups().
public class SessionAwareValidationInterceptor extends MethodValidationInterceptor
{
#Autowired
public SessionAwareValidationInterceptor(Validator validator)
{
super(validator);
}
#Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation)
{
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpSession session = requestAttributes.getRequest().getSession();
//return groups from session
}
}
In my configuration I register the interceptor as any other bean:
#Bean
public MethodValidationInterceptor methodValidationInterceptor(Validator validator)
{
return new SessionAwareValidationInterceptor(validator);
}
But I have serveral problems with this:
The #Valid annotation works on its own, but the MethodValidationInterceptor is only called when I add a #Validated annotation on the controller class.
But even when I use both annotations combined, Spring does not use my custom interceptor.
How do I register my custom MethodValidationInterceptor? Or is there maybe an other way to register the validation groups dynamically? Plan B would be some custom constraint validators which get the necessary informations out of the session, but I would prefer the group approach.
I figured it out how to do it. I had to create a custom MethodValidationPostProcessor that returns my SessionAwareValidationInterceptor.
public class CustomMethodValidationPostProcessor extends MethodValidationPostProcessor {
#Override
#NotNull
protected Advice createMethodValidationAdvice(Validator validator) {
return validator != null ? new SessionAwareValidationInterceptor(validator) : new SessionAwareValidationInterceptor();
}
}
Then I simply registered it as bean. This way, I was able to use my solution from above. To make it work, I have to use the #Validated annotation on class level and the #Valid annotation on the method parameter.

How to set a BindingErrorProcessor globally for all my Spring MVC controllers?

I have a Spring MVC application where most of the controllers receive the HTTP parameters encapsulated in DTOs, nothing special:
#RequestMapping(...)
public String handleUpdate(#Valid MyDto myDto, BindingResult bindingResult){...}
So the input Http parameters must have values that can be converted to the fields of my DTOs, when this doesn't happen and an binding exception is thrown Spring adds an Error with the exception message to the binding result. For example if myDto has an enum field but the Https param for that field has an invalid value.
The problem is these error messages get back to the UI and they can reveal information about the technologies, frameworks, libraries we use to a possible attacker. I need to hide these technical details behind a generic error message, say "invalid input".
I can add a BindingErrorProcessors to each controller's data binder like:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setBindingErrorProcessor(new MyBindingErrorProcessor());
}
but of course I don't what to do this for each controller class.
Question How can I set the BindingErrorProcessor for all my controllers? I think #ControllerAdvice only works for unhandled exceptions, maybe there's something similar?
Just for others looking this up, #ControllerAdvice accepts #InitBinder methods:
#ControllerAdvice
public class MyAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setBindingErrorProcessor(new BindingErrorProcessor() {
#Override
public void processMissingFieldError(String missingField, BindingResult bindingResult) {
log.info("missing field " + missingField);
}
#Override
public void processPropertyAccessException(PropertyAccessException ex, BindingResult bindingResult) {
bindingResult.rejectValue(ex.getPropertyName(), "label.input.error", "label.general.error");
log.info("property access exc. " + ex);
}
}
}
Method processPropertyAccessException will be called for conversion exceptions without adding any new Error to the bindingResult. You will have to add the error yourself.

certain parameter should be attached on every return by controller using model in spring MVC

I am using spring MVC with Hibernate
My Query is that when a Controller return some data to a view using Model, as defined below
#RequestMapping(value = "/finGeneralJournalAdd", method = RequestMethod.POST)
public String finGeneralJournalAdd(Model model) {
model.addAttribute("srcDocumentList", pt.getAll(FinSourceDocumentModel.class));
model.addAttribute("currencyList", pt.getAll(GenCurrencyModel.class));
model.addAttribute("batchList", pt.getAll(FinBatchModel.class));
return "fin/finGeneralJournalAdd";
}
Certain parameters should be attached to Model by default on every return.i.e CurrencyId
If I understand your question, that's exactly what #ModelAttribute is for (docs). You add a method to the controller that is called everytime before a handler method is called. The return value is added to the model.
#ModelAttribute("currencyId")
public Integer currencyId(...) {
...
return currencyId;
}
If that should happen for every controller you can define it in an #ControllerAdvice-annotated class.
You can use the postHandle method of a Spring HandlerInterceptor.
See this blog http://www.mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example/ for an example of how to implement a HandlerInterceptor

In Spring, how do you specify the BindingResult implementation

I would like to specify the implementing type of BindingResult within a controller. Is there a way to do that? At the moment, it appears that Spring must determine the implementing type itself (which happens to be a BeanPropertyBindingResult). I suspect there is either a configuration that I'm missing somewhere, or I just need to specify the actual type in the Controller's method signature.
Example:
/**
* {#inheritDoc}
*/
#Override
public ModelAndView continue(#ModelAttribute("model") #Valid final T model, final BindingResult results) { ... }
You don't need a custom BindingResult for what you want to do. You need to implement a BindingErrorProcessor and use it in the WebDataBinder. It is responsible for adding the errors to the BindingResult and will allow you to use your custom Errors implementation. Here is how you would use it in your controller...
#Controller
public class MyFormController {
...
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setBindingErrorProcessor(new MyCustomBindingErrorProcessor());
}
}
Mixing binding and validation is bad practice, classes should have a single responsibility. You should not be doing validation in the BindingResult itself.
You have to add ModelAndView in front of #ModelAttribute like:
public ModelAndView continue(ModelAndView model, #ModelAttribute("model") ObjectType objectName){
}

Spring MVC Bean Validation

I have to implement validations for a web app that uses Spring MVC 3. The problem is that the bean class has methods like getProperty("name") and setProperty("name",valueObj). The validations have to be done on the data that is returned by passing different values to getProperty("name") , for eg: getProperty("age") should be greater than 16 and getProperty("state") should be required.
I would like to know if there is any support for validation this kind of Bean and if not, what can be the work around.
Thanks,
Atif
I don't think so. Bean validation is performed on javabeans, i.e. class fields with getters and setters. Even if you can register a custom validator, and make validation work, binding won't work. You would need to also register a custom binder that populates your object. It becomes rather complicated. So stick to the javabeans convention.
It sounds like you want to a custom validation class which implements org.springframework.validation.Validator.
#Component
public class MyValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyBean.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyBean myBean = (MyBean) target;
if (StringUtils.isBlank(myBean.getProperty("state"))) {
errors.rejectValue("state", "blank");
}
}
}
In your controller you would do manual validaton like follows:
#Autowired
private MyValidator myValidator;
#RequestMapping(value = "save", method = RequestMethod.POST)
public String save(#ModelAttribute("myBean") MyBean myBean, BindingResult result) {
myValidator.validate(myBean, result);
if (result.hasErrors()) {
...
}
...
}

Categories

Resources