Creating a Spring @StrictDateTimeFormat Annotation

Published:

Spring Formatters and Converters make it easy to annotate fields for conversion from Objects to Strings, and are especially useful in web apps. But there is no easy or straightforward way to strictly validate the String before parsing into an object, without creating a custom Formatter. Here is a reusable solution that uses a RegexParserDecorator to decorate any Spring Formatter to apply a regex pattern, in turn creating a @StrictDateTimeFormat annotation as an example implementation.

A little background: Spring 3.0 brought the Converter and Formatter framework with a concise @DateTimeFormat annotation, simplifying the Date to Object conversion that previously took custom binders or other wiring code. With @DateTimeFormat you can easily supply a String pattern used to parse and print a Date (or joda DateTime) object. However, the annotation does not strictly validate the String before converting to a Date. For instance, supplying a MM/dd/yyyy pattern does NOT enforce a 4 digit year. Instead a 2 digit year will be accepted and parsed using the SimpleDateFormat rules. Similar loose checking goes for 1 digit days and months, and also the slash character used as a separator. It would be easy to just throw the @Pattern tag onto your Date field except that @Pattern is only allowed on String fields. Combining @Pattern and @DateTimeFormat is what drove the creation of @StrictDateTimeFormat:


//sample usage using defaults for regex and pattern
@StrictDateTimeFormat
private DateTime birthday;

Read more below for a discussion and snippets of code, and the entire codeset with comments is available on github.

The RegexParserDecorator: The first step is to create a Regex Parser class that will apply a regex pattern to validate a String for us. Creating this as a Decorator gives the added benefit that you can easily wrap any Spring Formatter to apply Regex patterns. The constructor takes a Parser to wrap and a regex to apply; and the parse method first validates against the regex before passing onto the decorated Parser:


public RegexParserDecorator(Parser parser, String regex) {
this.parser = parser;
this.regexPattern = Pattern.compile(regex);
}
public T parse(String text, Locale locale) throws ParseException {
if (!regexPattern.matcher(text).matches()) {
throw new IllegalArgumentException(“Text does not match regex: " + text);
}
return parser.parse(text, locale);
}

The @StrictDateTimeFormat Annotation: Next step is to setup the annotation interface class. It is very similar to DateTimeFormat but adds the field to hold a regex. The default regex allows 1 or 2 digit days and months, requires a forward slash as the separator, and enforces a 4 digit year. This can be easily overriden when applying the annotation to a field by supplying your own (regex=”“, pattern=”") extension. A pattern is still required so make sure your pattern and regex are paired appropriately.


@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface StrictDateTimeFormat {
public static final String REGEX_DEFAULT =
“^(0?[1-9]|1[012])/(0?[1-9]|[12][0-9]|3[01])/dddd$”;
public static final String PATTERN_DEFAULT = “MM/dd/yyyy”;
String regex() default REGEX_DEFAULT;
String pattern() default PATTERN_DEFAULT;
}

Spring then requires a StrictDateTimeFormatAnnotationFormatterFactory to wire the annotation with the parser. Nothing fancy here as it borrows heavily upon Spring’s own JodaDateTimeFormatAnnotationFormatterFactory. The getParser method applies our regex to the DateTimeFormat functionality:


public class StrictDateTimeFormatAnnotationFormatterFactory implements
AnnotationFormatterFactory {
…
public Parser getParser(StrictDateTimeFormat annotation, Class fieldType) {
DateTimeParser parser = new DateTimeParser(forPattern(annotation.pattern()));
return new RegexParserDecorator(parser, annotation.regex());
}
private DateTimeFormatter forPattern(String pattern) {
return org.joda.time.format.DateTimeFormat.forPattern(pattern);
}
…

Hooking it all together: Here is the snippet from my applicationConfig.xml showing how the annotation is registered into Spring:


<mvc:annotation-driven conversion-service=“myConversionService” />
<bean id=“myConversionService”
class=“org.springframework.format.support.FormattingConversionServiceFactoryBean”>
<property name=“formatters”>
<list>
<bean class=“jeffsheets.util.format.StrictDateTimeFormatAnnotationFormatterFactory”
/>
</list>
</property>
</bean>

Hopefully this information is helpful in creating a reusable regex validating DateTime formatter for use in your own web application.

 Originally published by me on the Object Partners blog: https://objectpartners.com/2012/01/19/creating-a-spring-strictdatetimeformat-annotation/