Spring - How to ignore/reset some error(s) in BindingResult? - spring-mvc

Having this handler method:
#RequestMapping(method = RequestMethod.POST, value = "/myform1")
public String formPost(#ModelAttribute("myModel") #Valid MyModel myModel, BindingResult binder)
how can I ignore errors on certain (or all) fields?
Even if I omit the #Valid annotation, it still detects errors like "abc entered into a Number field" (ie binder.hasErrors() returns true). And the the error message (from the catalog) is displayed on the final web page, which I do not want.
If I omit the BindingResult binder, then the code never reaches this method but throws an exception/error before it.
Is there a #DontValidateAtAll annotation or some other method to achieve this?
Related problem: I can not override the bad value in the method, it keeps showing the old rejected value. For example, user enters "abc" into myModel.someNumber and submits the form, then even if I do myModel.setSomeNumber(22) in the method formPost(), after returning from it, the web page (JSP) will show "abc" (and the error text) instead of 22 in that field.

For the specific String-to-Number conversion exception you were referring to, you can use the below manipulation. This is a type conversion exception that occurs even before Spring MVC's form validation, ie, even before the validate() method.
If your only purpose is to NOT see the errors on your final web page, then you can write extra code in your Controller method.
#RequestMapping(method = RequestMethod.POST, value = "/myform1")
public ModelAndView formPost(#ModelAttribute("myModel") #Valid MyModel myModel, BindingResult binder){
List<ObjectError> errors = bindingResult.getAllErrors();
boolean hasIgnorableErrorsOnly = true;
if(bindingResult.hasErrors()){
for(ObjectError error : errors){
if(error.getCode().equals("myModel.someNumber.NotANumber")){ //or whatever your error code is
}else{
hasIgnorableErrorsOnly = false;
}
}
}
if(hasIgnorableErrorsOnly){
//You have not shown where your final view is. I am assuming your myModel form view is myform.jsp and final web page is myModel.jsp
// Notice that I have also changed this method signature to return ModelAndView instead of only String view.
if(myModel.getSomeNumber() == null)
myModel.setSomeNumber(22);
return new ModelAndView("myModel.jsp", myModel); //Take care of view Resolvers here
}else{
return new ModelAndView("myform.jsp", myModel); //Take care of view Resolvers here
}
}
Now, if your BindingResult has more than ignorable errors, then it would go to myModel.jsp and I believe you already have code in place for display of errors. But if, because of above code, you are forwarded to myModel.jsp, you would have to iterate over the ${errors} key on your jsp and write the someNumber element so that it does not show errors. For example,
<spring:hasBindErrors name="myModel">
<c:forEach items="${errors.allErrors}" var="error">
<c:if test="${error.code eq 'myModel.someNumber.NotANumber'}">
//skip your display of global errors
</c:if>
</c:forEach>
</spring:hasBindErrors>

This works:
add a (Model)Map parameter to the handler method (it is usually used anyway, I omitted it in the question for brevity)
overwrite the model attribute with a fresh copy
Code:
#RequestMapping(method = RequestMethod.POST, value = "/myform1")
public String formPost(#ModelAttribute("myModel") #Valid MyModel myModel, BindingResult binder, Map<String, Object> modmap) {
if(ignore_errors) {
modmap.put("myModel", new MyModel());
return "myForm.jsp";
} // else ... other things
}
Apparently this procedure makes the framework to "forget" about the validation errors.
Note: I use Spring 3.0.x, other versions might behave differently.

Related

Spring MVC 3: CustomDateEditor working but there is a validation error (BindingResult)

I have a problem with a validation error displayed when i submit my form with an empty date like that in the resulting web page:
Failed to convert property value of type java.lang.String to required type
java.util.Date for property dateFin; nested exception is
java.lang.IllegalArgumentException: Could not parse date: Unparseable date: ""
My controller looks like this:
#Controller
#SessionAttributes
#Lazy
public class MyController extends AbstractMVPAction {
#RequestMapping(value = "/secured/cp/saveProgram")
public String enregistrerProgramme(#ModelAttribute Program program,
BindingResult bindingResult, ModelMap model){
if(bindingResult.hasErrors()){
model.put("program", program);
return "/secured/cp/showProgram"
}else{
// ... saves the programme
model.put("program", null);
return "/secured/cp/backToOtherPage"
}
}
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new CustomDateEditor(
new SimpleDateFormat("dd/MM/yyyy"), false));
}
}
When I debug my method, I can see my object is fine, the modif I did are well reported, the date is null, but the bindingResult.hasErrors() returns true and according to me it shouldn't.
I used to have some validation annotations in the Program object and a #Valid annotation but I removed them all and still have the problem.
I have read lot's of similar issues and every time the solution is the #InitBinder/CustomDateEditor.
So it is there and I guess it's working, the dates are displayed the way I want (this was not the case before I add it) and I can submit them provided it's not empty.
Thank's in advance, I'm starting to go crazy...
You've constructed a CustomDateEditor that explicitly disallows the empty string. Check the Javadoc for the constructor you're using, when the boolean argument is false, passing an empty string to that editor causes the IllegalArgumentException you're seeing.
Try this:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new CustomDateEditor(
new SimpleDateFormat("dd/MM/yyyy"), false));
}

spring auto populate user details for every request

I have a spring MVC based web application. Currently in my web page i am showing the user first name and last name after user logs in. The way i am doing this is, for every HttpServletRequest that comes into #Controller#RequestMapping, i get the Principal object and get the user details from it, then populate the ModelMap with firstname and lastname attribute. For example here is the sample code
#Autowired
private SecurityDetails securityDetails;
#RequestMapping(method = RequestMethod.GET)
public String showWelcomePage(HttpServletRequest request,
HttpServletResponse response, ModelMap model, Principal principal)
{
securityDetails.populateUserName(model, principal);
... lot of code here;
return "home";
}
public boolean populateUserName(ModelMap model, Principal principal) {
if (principal != null) {
Object ob = ((Authentication)principal).getPrincipal();
if(ob instanceof MyUserDetails)
{
MyUserDetails ud = (MyUserDetails)ob;
model.addAttribute("username", ud.getFirstName() + " " + ud.getLastName());
}
return true;
}
else
{
logger.debug("principal is null");
return false;
}
}
My problem is i am having to call the populateUserName method for every RequestMapping. Is there a elegant way, like populating this in Interceptor method, which will result in this method being called just in one place for entire application?
Its good that you want to prevent duplication of code. Here is how you can do it.
Create a custom HandlerInterceptor http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/web/servlet/HandlerInterceptor.html
Post handle is the only method of interest for us, for the others return defaults.
In the post handle method, you have access to the model and view returned from your controller, go ahead and add whatever you want.
The Principal will not be available directly here, you will have to look it up using some code like SecurityContextHolder.getContext().getAuthentication().getPrincipal()
Wire the handler interceptor to intercept all or some of your controllers.
Hope this helps.
You can use either Servlet Filters or Spring Interceptors.
BTW, where do you populate the Principal from?
In any case, thats where you should do this populating stuff.

How does ASP.NET MVC pass model to the view without explicitly passing it

Here is one of the examples that I've seen on how to do validation on Controller:
[HttpPost]
public ViewResult Create(MyModel response)
{
if (ModelState.IsValid)
{
return View("Thanks");
}
else
{
return View();
}
}
If there are validation errors, than return View() method is called without any parameters. Obviously you have #Html.ValidationSummary() in your View and Model has all required property attributes.
The data that was entered into the form was preserved and displayed again when the view was rendered with the validation summary.
My question: how is the data preserved? Since it was not passed to the View like
return View(response);
Thanks a lot.
Sincerely,
Vlad
It is because the values have been bound to the model state which is passed back along to the view. This is along the same issue/question on why you cannot change a model value and return the view again. Meaning, let's assume I have the following property on my Viewmodel
public string Name {get;set;}
Using the controller below, I cannot change the ViewModel property without also either clearing the ModelState or updating the value in the model state. Try it!
[HttpPost]
public ViewResult Create(MyModel response)
{
response.Name = response.Name + "Some Random String"
return View();
}
The name property will remain unchanged. Essentially, once the ModelBinding occurs, the values from your form (ViewModel) are bound to the model state, which is why you do not have to pass the model back to the view.
As a side note, I always pass the model back in my call to return View();, it just seems more correct and a little easier to read

What does it mean when Spring MVC #Controller returns null view name?

I downloaded the code for the Spring MVC 3 Showcase. One thing puzzles me (well, more than one), why does this (edited for concision) sample return null?
#Controller
#RequestMapping("/form")
public class FormController {
#RequestMapping(method=RequestMethod.POST)
public String processSubmit(#Valid FormBean form,
BindingResult result,
WebRequest webRequest,
HttpSession session, Model model) {
if (result.hasErrors()) {
return null;
} else {
session.setAttribute("form", form);
return "redirect:/form";
}
}
}
If a controller returns a null view name, or declares a void return type, Spring will attempt to infer the view name from the request URL.
In your case, it will assume the view name is form, and proceed on that assumption.
It does this using an implementation of RequestToViewNameTranslator, the default implementation of which is DefaultRequestToViewNameTranslator, the javadoc for which explains the exact rules it applies.
AnnotationMethodHandlerAdapter.invokeHandlerMethod() takes care of invoking handler methods. Here, a ModelAndView will be retrieved via ServletHandlerMethodInvoker.getModelAndView().
In your case, getModelAndView() gets provided the handler method's null return value. The getModelAndView() method checks for the return value's type, but as in Java null is never an instanceof any class, that method's logic will create a new ModelAndView. A new ModelAndView has initially its view property set to null.
Then later back up the call stack, in DispatcherServlet.doDispatch(), there is a test if the ModelAndView object has a View associated with it ( mv.hasView() ). Because view == null, doDispatch()'s logic calls mv.setViewName(getDefaultViewName(request)). It delegates to the registered RequestToViewNameTranslator, whose default implementation is DefaultRequestToViewNameTranslator. This subclass translates the request URI into a view name, in your case form.
Later in doDispatch(), via render() -> resolveViewName(), this sample's ViewResolvers are provided with the view name form. Only one ViewResolver, InternalResourceViewResolver is used in this sample. Also, this InternalResourceViewResolver was configured in src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml to add the prefix /WEB-INF/views/ and the suffix .jsp to the view name. So in total, it will create a View using the JSP file /WEB-INF/views/form.jsp. Luckily, a JSP file exists at exactly this location.

Why Spring MVC does not allow to expose Model or BindingResult to an #ExceptionHandler?

Situation
I'm trying to group the code that logs the exceptions and render a nice view in a few methods. At the moment the logic is sometime in the #RequestHandler itself (in the a catch block), othertimes is delegated to an utility class (that works but moves the logic away from the place where the exception is thrown).
Spring's #ExceptionHandler seemed the way to group everything in one place (the controller itself or a parent) and get rid of some code (no need to put logic in the try-catch and no need for an utility class)... until I realized that an #ExceptionHandler methods won't have the ModelMap or BindingResult parameters autowired. Currently those objects are used to render the view with a sensible error message and we want to log some information contained in these objects as well.
Question
Why Spring doesn't not support method arguments such ModelMap or BindingResult for the #ExceptionHandler? What is the rationale behind it?
Possible solution
In the Spring source code (3.0.5) the arguments for the method are resolved in the HandlerMethodInvoker.invokeHandlerMethod. A an exception thrown by the request handler is caught there and re-thrown. The #ExceptionHandler and it's parameters are resolved elsewhere. As a workaround I thought to check if the Exception implements an hypothetical "ModelAware" or "BindingResultAware" interface, and in that case set Model and BindingResult attributes before re-throwhing it.
How does it sound?
As stated before you can raise an exception wrapping a binding result object in some method of your controller:
if (bindingResult.hasErrors()) {
logBindingErrors(bindingResult);
//return "users/create";
// Exception handling happens later in this controller
throw new BindingErrorsException("MVC binding errors", userForm, bindingResult);
}
With your exception defined as illustrated here:
public class BindingErrorsException extends RuntimeException {
private static final Logger log = LoggerFactory.getLogger(BindingErrorsException.class);
private static final long serialVersionUID = -7882202987868263849L;
private final UserForm userForm;
private final BindingResult bindingResult;
public BindingErrorsException(
final String message,
final UserForm userForm,
final BindingResult bindingResult
) {
super(message);
this.userForm = userForm;
this.bindingResult = bindingResult;
log.error(getLocalizedMessage());
}
public UserForm getUserForm() {
return userForm;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
Next you just have to extract the required information from the raised then caught exception. Here assuming you have a suitable exception handler defined on your controller. It might be in a controller advice instead or even elewhere. See the Spring documentation for suitable and appropriate locations.
#ExceptionHandler(BindingErrorsException.class)
public ModelAndView bindingErrors(
final HttpServletResponse resp,
final Exception ex
) {
if(ex instanceof BindingErrorsException) {
final BindingErrorsException bex = (BindingErrorsException) ex;
final ModelAndView mav = new ModelAndView("users/create", bex.getBindingResult().getModel());
mav.addObject("user", bex.getUserForm());
return mav;
} else {
final ModelAndView mav = new ModelAndView("users/create");
return mav;
}
}
I ran into to same problem a while ago. The ModelMap or BindingResult are explicitly not listed as supported argument types in the JavaDocs of #ExceptionHandler, so this must have been intentional.
I reckon the reason behind it being that throwing exceptions in general could leave your ModelMap in an inconsistent state. So depending on your situation you might consider
Explicitly catch the exception to tell Spring MVC that you know what you're doing (you could use the Template pattern to refactor exception handling logic into one single place)
If you're in control of the exception hierarchy you could hand over the BindingResult to the exception and extract it from the exception later for rendering purposes
Not throw an exception in the first place, but use some result code (just like BeanValidation does for example)
HTH
To improve the first answer:
#ExceptionHandler(value = {MethodArgumentNotValidException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public VndErrors methodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {
List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
List<ObjectError> globalErrors = ex.getBindingResult().getGlobalErrors();
List<VndError> errors = new ArrayList<>(fieldErrors.size() + globalErrors.size());
VndError error;
for (FieldError fieldError : fieldErrors) {
error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(), fieldError.getField() + ", "
+ fieldError.getDefaultMessage());
errors.add(error);
}
for (ObjectError objectError : globalErrors) {
error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(), objectError.getDefaultMessage());
errors.add(error);
}
return new VndErrors(errors);
}
There is already MethodArgumentNotValidException has already a BindingResult object, and you can use it, if you don't need to create an specific exception for this purpose.
I had the same problem to "add" FunctinalException to ourthe BindingResult
To resolve it, we use aop, if the controller method throws a runtime exception (or the one you want),
the aop catch it and update the bindingresult or model (if they are args of the method).
The method has to be annoted with a specific annotation containing the error path (configurable for specific exception if necessary).
It is not the best way because developer must not forget to add args that he don't use in its method but Spring does not provide a simple system to do this need.
I've wondered this too.
In order to handle bean validation in a way that allows for a non-global error view to display any ConstraintViolationExceptions that may be thrown, I opted for a solution along the lines of what #Stefan Haberl proposed:
Explicitly catch the exception to tell Spring MVC that you know what you're doing (you could use the Template pattern to refactor exception handling logic into one single place)
I created a simple Action interface:
public interface Action {
String run();
}
And an ActionRunner class which does the work of ensuring ConstraintViolationExceptions are handled nicely (basically the messages from each ConstraintViolationException is simply added to a Set and added to the model):
public class ActionRunner {
public String handleExceptions(Model model, String input, Action action) {
try {
return action.run();
}
catch (RuntimeException rEx) {
Set<String> errors = BeanValidationUtils.getErrorMessagesIfPresent(rEx);
if (!errors.isEmpty()) {
model.addAttribute("errors", errors);
return input;
}
throw rEx;
}
}
}
Java 8 makes this pretty nice to run within the controller action method:
#RequestMapping(value = "/event/save", method = RequestMethod.POST)
public String saveEvent(Event event, Model model, RedirectAttributes redirectAttributes) {
return new ActionRunner().handleExceptions(model, "event/form", () -> {
eventRepository.save(event);
redirectAttributes.addFlashAttribute("messages", "Event saved.");
return "redirect:/events";
});
}
This is to wrap up those action methods for which I'd like to explicitly handle exceptions that could be thrown due to Bean Validation. I still have a global #ExceptionHandler but this deals with only "oh crap" exceptions.
Actually it does, just create an #ExceptionHandler method for MethodArgumentNotValidException.
That class gives you access to a BindingResult object.

Resources