Wednesday, February 20, 2008

Date Validation in Spring MVC

I am using Spring MVC (Spring 2.5.1) and needed date validation in my form, with a custom error message. Reflecting my inability to R(or find)TFM, I failed miserably to find a resource that clearly outlined how to set up validation for a date. So here is my public record of a small saga.

First, I set up a date field in my JSP.

<form:errors cssClass="error"
    path="document.publishDate"/>
<form:input id="publishDate"
    path="document.publishDate"
    cssStyle="width:100px;"  />

There is a matching java.util.Date instance variable called publishDate in my Document class.

Next step is to define an object that can validate my dates. In servletName-servlet.xml, I have defined a custom date editor.

<bean id="customDateEditor"
    class="org.springframework.beans.propertyeditors.CustomDateEditor">
    <constructor-arg >
      <bean class="java.text.SimpleDateFormat">
        <constructor-arg><value>dd/MM/yyyy</value></constructor-arg>
      </bean>
    </constructor-arg>
    <constructor-arg ><value>true</value></constructor-arg>
  </bean>

The same config file defines my controller, and I inject the custom data editor into the controller through my Spring config.

<!-- Controller for ... -->
  <bean name="doJob.htm" id="jobController" class="..."
    ...
    <property name="customDateEditor" ref="customDateEditor"/>
    ...
  </bean>

This means that my controller will have an instance of CustomDateEditor ready to use, injected by Spring. I could have had my controller create its own CustomDateEditor, however. Either way, the next step is to register the data editor in the initBinder(HttpServletRequest, ServletRequestDataBinder) method of my controller.

  /**
   * {@inheritDoc}
   * @see org.springframework.web.servlet.mvc.
   *    BaseCommandController#initBinder(javax.servlet.http.HttpServletRequest, 
   *    org.springframework.web.bind.ServletRequestDataBinder)
   */
  protected void initBinder(HttpServletRequest request, 
      ServletRequestDataBinder binder) throws Exception {
    binder.registerCustomEditor(Date.class, getCustomDateEditor());
  }

The controller needs to be a subclass of org.springframework.web.servlet.mvc.BaseCommandController to have an initBinder() method. In this instance, my controller is a org.springframework.web.servlet.mvc.SimpleFormController.

This means that I have automatically defined the date format required for all dates that get validated by this controller.

Next step is to set up the error message I want. Personally, I found this the hardest part. I had to look at my logging closely to work out what was going on. In my controller's showForm(HttpServletRequest, HttpServletResponse, BindException, Map) method, I was outputting the result of a toString() call on the BindException object and I saw the following. The key things are in red.

Field error in object 'viewDocCommand' on field 'document.publishDate':
  rejected value [noWayIsThisAGoodDateValue!];
  codes [typeMismatch.viewDocCommand.document.publishDate,
  typeMismatch.document.publishDate,typeMismatch.publishDate,
  typeMismatch.java.util.Date,typeMismatch];
  arguments [org.springframework.context.support.DefaultMessageSourceResolvable:
  codes [viewDocCommand.document.publishDate,document.publishDate];
  arguments []; default message [document.publishDate]];
  default message [Failed to convert property value of type
  [java.lang.String] to required type [java.util.Date]
  for property 'document.publishDate';
  nested exception is java.lang.IllegalArgumentException:
  Could not parse date: Unparseable date: "noWayIsThisAGoodDateValue!"]

After a lot of searching in forums and javadocs I finally accumulated enough hints to get a hunch! Those dot separated values are message keys. Spring looks in the messages.properties file to find values to replace them with. I put entries into messages.properties to cover the most generic error type (typeMismatch.java.util.Date) and the field name.

typeMismatch.java.util.Date={0} is an invalid date. Use format DD/MM/YYYY.
document.publishDate=Publish Date

Now when I have a bad date value, I see the error message: Publish Date is an invalid date. Use format DD/MM/YYYY.

Links that helped me with this.

10 comments:

Anonymous said...

Thanks Rob, was going round the bend looking for a way to implement Date formating :)

Paul said...

Hi and thanks for this, it's very useful. One thing I noticed was that adding a SimpleDateFormat validator, I needed to set a property of lenient=false in the bean, or it will accept weird dates like 20/20/2008... Thanks again. Paul

Rebeccah said...

I've been looking all over for something like this, being new to Spring. However, I'm obviously overlooking something important, because when I override initBinder() as written, I get a compilation error that the compiler can't find the method getCustomDateEditor(). It's not a method of SimpleFormController.

If I add the method manually, how do I get the CustomDateEditor injected? If I have to manually construct the CustomDateEditor in the controller, then why put it in the app-servlet.xml?

Any help much appreciated.

Rebeccah

Rebeccah said...

OK, now I feel stupid. Just create the most simple-minded of getter and setter and declare the variable. The framework uses them to do the injection. You'd think I had never read a single Spring document. Sigh.

Feel free to not post my previous question.

Rebeccah

RobertMarkBram said...

Glad you fixed it Rebeccah! I wish I could say "I didn't see your second post until after I published the first", but truth be told I was so happy to get a response from someone who read my post and (hopefully) found value in it. :)

Rebeccah said...

BTW, while having some difficulty getting a CustomNumberEditor configured, I ran across a thread on the Spring forum, and posted to it when I figured out how to do it.

http://forum.springframework.org/showthread.php?t=18335

My post got some responses, and it turns out that it's not a good idea to put PropertyEditors in application-servlet.xml and inject them, because they are not thread safe (they maintain state information). However, someone else has followed up asking what about if you set the bean scope to "request" scope. I'll be interested to see how that discussion ends up.

Rebeccah

RobertMarkBram said...

I never realised that - thank you very much Rebeccah. The classes in org.springframework.beans.propertyeditors that extend java.beans.PropertyEditorSupport don't convert their values through a single method call - instead they use a method to set the base value and another method to get the converted value. Thus they store state. I should amend the code above..

be said...

I am having problem with errors cache
I've noticed that it catches the previous error only if the current value from the form is TypeMismatchError (in this case is Date)
Steps to produce errors:
1. First – user enters future date 12/12/2009 – this is violate the rule which is developer defined validation.
System displays error message “Cannot enter future Date”

2. Second - user enters wrong date format 04/31/2008
System display 2 errors messages:
one for TypeMisMatchError (spring binding validation )
and developer defined error in this case “Cannot enter future Date” from previous submit.

3. Third – user blanks out the date field
System displays only one error message “Date is required field – Enter Date”

How can I clear out the previous errors?
thanks
Anh

Cyril said...

Thank for Great post :0) You really help me!

Anonymous said...

Thanks so much! I spent hours looking at the javadoc before doing a google search on customizing the bind error messages & finding your post. Greatly appreciated!