Extensions via metaclass in Grails

Even after several releases and wrapper utilities, representation of time in Java seems to be an unconquerable mess that it originally was. At the same time, surprisingly, web services have been there for a long time now, still there is no magic wand to create webservices and clients.

Webservices is something Groovy/Grails should have made it ridiculously easy to work with, by default. When xml, json, http and so on have been outrageously simplified, the natural expectation is that the webservice call would be like:

WebServiceObject ws = new WebServiceSlurper().parse('http://someweathersite.com/weather.wsdl')
WeatherObject w = ws.getWeatherObject()
Temperature[] temperatures = w.findTemperature(zipcode: '11505', days: 5, unit: 'farenheit')

There are so many wrapper api-s, every webservice client generation is lacking in someway, making a new comer to Grails to try and find what works. axis, axis2, groovyws, xfire, cxf, jws, jaxb – there are so many acronyms and some of them are just wrappers over others, the choice may be overwhelming. Forums do provide lot of answers, but most times what works for one goose, does not work for even for its goose twin.

So after my friend pointed out the pot-holes of webservices in Grails, I started using the jws. All good until I hit the xs:date object in our wsdl, which translates to XMLGregorianCalendar. Like we all wanted a new class to represent date and time. I seriously hope Java/Groovy will deprecate and delete all its Date and Calendar classes and introduce just one class with a philosophical outlook:

/**
* Dead yesterday, unborn tomorrow, why fret about them, if today be sweet?
*/
class StarDate{
public double day;
public double increment(double unit) { day += unit }
public String toString() { return "Captain's log, stardate $day"  }
}

Conversion between a date in String to XMLGregorianCalendar goes like this:

SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy")
Date d = sdf.parse(dateString)
GregorianCalendar g = new GregorianCalendar()
g.setTime(d)
def date = DatatypeFactory.newInstance().newXMLGregorianCalendar(g)

So much for a date? I tried to generate the client classes with a custom binding to Date instead of XMLGregorianCalendar. No matter what I tried, the value inside the webservice always ended up as long format “Sun Dec 4 …”. Fed up, I changed it back to XMLGregorianCalendar and now I saw the date as 1995-03-28T06:00:00. So where did the timezone come from and how do I avoid it? Stackoverflow had an answer:

dob.setTimezone(DatatypeConstants.FIELD_UNDEFINED)

Seriously, DatatypeConstants? Minimum integer value to specify undefined? Anyway, with that undefining of the timezone, the webservice was correctly seeing the date as just 1995-03-28. But I still dont like the fact that I had to convert this in the service class or that I had to put this in an utility class.

And all this while I was developing a Grails plugin. I wanted to make the conversion as an extension to String, so that my call is like:

dateString.toXmlGregorianCalendar()

Recall that Grails plugin will load the *BootStrap.groovy when it starts up. We need to inject a metaclass method into java.lang.String so Grails runtime knows about it. (Only when run standalone, not after installing within an application).

public class MyPluginBootStrap {
def init = { servletContext ->
println "booting the plugin"
new GroovyShell(this.getClass().getClassLoader()).evaluate(new File("grails-app/conf/MetaFunctions.groovy"))
}
}

And define the MetaFunctions.groovy:

String.metaClass.toXmlGregorianCalendar = {
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy")
Date d = sdf.parse(delegate)
GregorianCalendar gcal = new GregorianCalendar()
gcal.setTime(d)
def dob = DatatypeFactory.newInstance().newXMLGregorianCalendar(gcal)
dob.setTimezone(DatatypeConstants.FIELD_UNDEFINED)
return dob
}

In .Net doing this may be simpler – just define StringExtensions. But they are static methods and cannot override existing methods. But in Groovy that is entirely possible, which makes a powerful feature, because if you don’t like the behaviour of a plugin class, just override it via metaclass using the above technique.

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: