0

I've got a Groovy project using Spring framework and its validators doing sanity-checking on my forms' input values. I would like to have Spring populate error messages next to my input form fields via the built-in ${status.errorMessage}; however, I can only get it to populate "errorMessages" in my Model object (from the controller.) So let's take a look at some code.

login.jsp:

<form method="post" action="<c:url value="/login" />">
    <spring:bind path="request.username">
        <label for="username"><fmt:message key="login.username"/>:
            <input type="text" id="username" size="20" maxlength="50" name="username" value="${request.username}"/>
        </label>
        <%-- This part does NOT display the validation errors. --%>
        <c:if test="${status.error}"><span class="error">${status.errorMessage}</span></c:if>
    </spring:bind>

    <spring:bind path="request.password">
        <label for="password"><fmt:message key="login.password"/>:
            <input type="password" id="password" size="20" maxlength="30" name="password" />
        </label>
        <%-- This part does NOT display the validation errors. --%>
        <c:if test="${status.error}"><span class="error">${status.errorMessage}</span></c:if>
    </spring:bind>

    <input id="login" type="submit" value="Login"/>
</form>

<%-- This part does display the validation errors. --%>
<c:if test="${ec > 0}">
    <p>
        <c:forEach items="${errorCodes}" var="error">
            <span class="error"><fmt:message key="${error.defaultMessage}"/></span><br/>
        </c:forEach>
    </p>
</c:if>

LoginController.groovy:

@RequestMapping(method = RequestMethod.GET, value = '/')
ModelAndView defaultView() {
    ModelMap model = new ModelMap()
    model.addAttribute('request', new LoginRequest())
    new ModelAndView('login', model)
}

@RequestMapping(method = RequestMethod.POST, value = '/login')
ModelAndView login(
        LoginRequest loginRequest, HttpServletResponse response,
        HttpSession session, BindingResult br, ModelMap model
) {
    validator.validate(loginRequest, br)

    if (br.hasErrors()) {
        model.addAttribute('request', loginRequest)
        return returnWithError(br, model, 'login')
    }
    ...
}

private ModelAndView returnWithError(BindingResult br, ModelMap model, String redirectTo) {
    br.allErrors.each {error ->
        log.error(error.toString())
    }

    def objectErrors = br.allErrors.findAll {e -> e instanceof ObjectError}

    model.addAttribute('ec', br.errorCount)
    model.addAttribute('errorCodes', objectErrors)
    new ModelAndView(redirectTo, model)
}

LoginRequestValidator.groovy:

@Override
void validate(Object o, Errors errors) {
    ValidationUtils.rejectIfEmpty(errors, 'username', 'username.empty', 'username.empty')
    ValidationUtils.rejectIfEmpty(errors, 'password', 'password.empty', 'password.empty')
}

Which part of the Spring Magic [TM] am I missing?

Mike
  • 7,994
  • 5
  • 35
  • 44

3 Answers3

3

I think your BindingResult object should be the argument immediately following your LoginRequest object. See http://static.springsource.org/spring/docs/3.0.x/reference/mvc.html#mvc-ann-requestmapping and specifically Example 15.1. Invalid ordering of BindingResult and @ModelAttribute

rjsang
  • 1,757
  • 3
  • 16
  • 26
  • So far, that's not making my validation just magically work. :( – Mike Aug 21 '12 at 13:24
  • You still have to do the validation yourself (with the validator.validate). The question is whether you are populating the right BindingResult object when you do it. If the parameters are paired together, your JSP should work. If that's still not working, try also annotating your LoginRequest parameter with @ModelAttribute("request"). If that STILL doesn't work, the next thing I'd try would be to change the name of the attribute from "request" to "loginRequest" as request is a widely used concept and might have some special meaning to either your container or Spring. – rjsang Aug 22 '12 at 02:53
  • I was always calling the `validator.validate()` method myself. That said, it seems that just changing the variable name (in the `model.addAttribute` and `spring:bind`) from `request` to `loginRequest` has made the validation start putting the error messages where I want them to go. Thanks! – Mike Aug 23 '12 at 19:57
0

Review the Javadoc on @RequestMapping as I find it the best on the subject: http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/web/bind/annotation/RequestMapping.html

Try the following:

ModelAndView login(
        @Valid LoginRequest loginRequest, HttpServletResponse response,
        HttpSession session, BindingResult br, ModelMap model
) {}

See What does the @Valid annotation indicate in Spring?

If that doesn't work you should try translating some of your code to plain Java and running the debugger to see whats happening (I know its not an answer but just an idea).

Community
  • 1
  • 1
Adam Gent
  • 47,843
  • 23
  • 153
  • 203
0

Maybe you will find these post from kgiannakakis very usefull. It explains the purpose of these functionnality and how to deal with is explained in Spring 3.1 documentation.

Adding something like that will probably do the trick, isn't it ?

@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(new FooValidator());
    }

    @RequestMapping("/foo", method=RequestMethod.POST)
    public void processFoo(@Valid Foo foo) { ... }

}

Take a look at SpringSource blog and especially these post about new features around data binding. It could add some new look about your validation process.

Community
  • 1
  • 1
BendaThierry.com
  • 2,080
  • 1
  • 15
  • 17