When Grails converts types for marshalling request parameters to domain objects, behind the scenes, it uses a GrailsDataBinder instance, converting values in the process of loading up the object's properties. And while the GrailsDataBinder implements Spring's TypeConverter and gives us access to the convertIfNecessary() method, it also requires a domain object instance in its constructor so it has a target to bind values to. Since all we were doing is simple type conversion, we wanted a different way.
Enter Spring's SimpleTypeConverter, a class that really only does one thing: convert from a String type to an instance of a target class. SimpleTypeConverter allows us to register custom property editors, the backbone of the type conversion process, so the first task was to implement a PropertyEditor (extending PropertyEditorSupport) that would let us take a series of date formats and parse them to a Date. Thanks to some very detailed information in a response to this StackOverflow Question, implementing our CustomDateEditor was fairly easy:
import java.beans.PropertyEditorSupport
import java.text.SimpleDateFormat
import java.text.ParseException
/**
* A property editor that allows for handling many different
* date formats at once (as opposed to Spring's default CustomDateEditor
* which only handles one date format configured at instantiation)
*
* This class is modified from information found at:
* http://stackoverflow.com/questions/963922/grails-date-unmarshalling
*/
class CustomDateEditor extends PropertyEditorSupport {
private final List< String> formats
CustomDateEditor() {
// in our production code, we get the formats from our Grails config
def formats = [ "yyyyMMdd-HHmmss", "yyyy-mm-dd HH:mm:ss.S" ]
List< String> formatList = new ArrayList< String>(formats.size())
formats.each { format ->
formatList << format.toString() // Force String values (eg. for GStrings)
}
this.formats = Collections.unmodifiableList(formatList)
}
def String getAsText() {
return this.value.toString()
}
def void setAsText(String s) throws IllegalArgumentException {
if (s) {
formats.each { format ->
def df = new SimpleDateFormat(format)
try {
this.value = df.parse(s)
return
} catch (ParseException e) {
// Ignore
}
}
}
}
}
Next, we wanted to make this custom Date editor available across the application, so we added it to our resources.groovy Spring config:
beans = {
customDateEditor(CustomDateEditor) { bean ->
bean.singleton = false // editors store state so are not thread-safe
}
}
Now anywhere we wanted to do type conversion, all our classes needed to do was create a new SimpleTypeConverter, get a CustomDateEditor instance from Spring, and register that editor with the SimpleTypeConverter:
// grailsApplication auto-wired by Grails
def editor = grailsApplication.mainContext.getBean("customDateEditor")
def converter = new SimpleTypeConverter()
converter.registerCustomEditor( Date.class, editor )
With the converter set up and our custom Date editor registered, it was now a simple matter of doing the conversion:
// NOTE: production code determines type dynamically from field being queried
def type = Date.class
def converted = converter.convertIfNecessary( value, type )
If we had wanted to make our custom editor available for marshalling values to a domain object, we could have easily created an PropertyEditorRegistrar object and configured it via Spring so Grails would automatically use our property editor (as described in the StackOverflow Question above). But, again, all we wanted was fairly straightforward type conversion outside of a domain object. The combination of Spring's SimpleTypeConverter and our CustomDateEditor gave us that.