Tuesday, February 9, 2010

Using Scala To Configure Spring's DispatcherServlet

After reading Francois Armand's post about "Scala and Spring 3 JavaConfig", I was inspired to try and use Scala to replace some servletname-servlet.xml Spring config files.

I have a small Scala/Spring MVC project that at the moment uses two DispatcherServlets, one for each controller. There was a historical reason for this (I needed two different Velocity configurations) and while I can now probably get away with a single DispatcherServlet, what I'm going to try to do is move one of the servlets over to a Scala based configuration and leave one using xml based configuration.

Of the two configurations, I'm going to move the simplest one over. It does nothing more than define a root controller bean. So the Scala config should be as simple as


@Configuration
class CalendarConfig {
@Bean def rootController = new CalendarController()
}


And a quick change to my web.xml

...
<servlet>
<servlet-name>calendars</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<!-- Configure DispatcherServlet to use JavaConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.maethorechannen.calendars.CalendarConfig</param-value>
</init-param>
</servlet>
...



I started my app up in my local development Google App Engine server and it failed to start up, thanks to "java.lang.IllegalStateException: CGLIB is required to process @Configuration classes. Either add CGLIB to the classpath or remove the following @Configuration bean definitions: [calendarConfig]". I added cglib-nodep-2.2 (available from here if you're not using something like maven or ivy) to my library of jars and it started to work.

In case anyone's interested - I pushed it to App Engine and it still worked (occasionally, what works locally doesn't work when on the Big G's servers). One thing I'm really interested in seeing is what (if any) sort of impact on startup times programmatic configuration has compared to XML configuration. When using GAE every millisecond of startup time counts and I would think Scala based configuration should be quicker.


What's interesting to note (at least to me) is that I didn't need to create a global WebApplicationContext or move everything over to Java(Scala) based configuration, which is something I wasn't sure about after reading the documentation from SpringSource.

Scala + Spring + Templates - Velocity

Next up on my list of template engines to try with Scala & Spring - Velocity. Unlike FreeMarker, I had actual success in getting it to work. Unsurprisingly, "Native" Scala object support needed to be added in - fortunately the way Velocity was designed makes that easy.

Velocity uses introspection/reflection to access properties and methods on an object and this is where the Scala customizations fit in. The first thing to create is a subclass of UberspectImpl which does two things - return a java.util.Iterator for iterable objects and return a VelPropertyGet object for getting access to an object's properties. There is a third thing that could also be done - returning a VelPropertySet object to support setting properties from Velocity but I haven't implemented this as I don't need that functionality at the moment.

Getting a Java Iterator is fairly straightforward (with Scala 2.7.7 - I'm not sure what, if any, changes will be needed with 2.8)

override def getIterator(obj: java.lang.Object, i: Info): JavaIterator[_] = {
def makeJavaIterator(iter: Iterator[_]) = new JavaIterator[AnyRef] {
override def hasNext() = iter.hasNext
override def next() = iter.next().asInstanceOf[AnyRef]
override def remove() = throw new java.lang.UnsupportedOperationException("Remove not supported")
}

obj match {
case i: Iterable[_] => makeJavaIterator(i.elements)
case i: Iterator[_] => makeJavaIterator(i)
case _ => super.getIterator(obj, i)
}
}


The method to return a VelPropertyGet object is almost as simple - but it also shows that we need two more classes - one for accessing Scala properties and one for accessing Scala Map members like they're properties.


override def getPropertyGet(obj: java.lang.Object, identifier: String, i: Info): VelPropertyGet = {
if (obj != null) {
val claz = obj.getClass()

val executor = obj match {
case m: Map[_, _] => new ScalaMapGetExecutor(log, claz, identifier)
case _ => new ScalaPropertyExecutor(log, introspector, claz, identifier)

}

if (executor.isAlive) {
new VelGetterImpl(executor)
} else {
super.getPropertyGet(obj, identifier, i)
}
} else {
null
}
}

(You can get the full source code here)

To access Scala style properties I'll need a Scala specific subclass of PropertyExecutor. The code to access properties from Scala objects is once again trivial. In fact, because of the way Scala generates it's getter methods the code to access Scala properties is simpler than the code to access JavaBean style properties. (source)


override def discover(clazz: java.lang.Class[_], property: String) = {
val params = Array[java.lang.Object]()
setMethod(introspector.getMethod(clazz, property, params))
if(!isAlive()) {
super.discover(clazz, property)
}
}



Compare that to the code in the parent class


protected void discover(final Class clazz, final String property)
{
/*
* this is gross and linear, but it keeps it straightforward.
*/

try
{
Object [] params = {};

StringBuffer sb = new StringBuffer("get");
sb.append(property);

setMethod(introspector.getMethod(clazz, sb.toString(), params));

if (!isAlive())
{
/*
* now the convenience, flip the 1st character
*/

char c = sb.charAt(3);

if (Character.isLowerCase(c))
{
sb.setCharAt(3, Character.toUpperCase(c));
}
else
{
sb.setCharAt(3, Character.toLowerCase(c));
}

setMethod(introspector.getMethod(clazz, sb.toString(), params));
}
}
/**
* pass through application level runtime exceptions
*/
catch( RuntimeException e )
{
throw e;
}
catch(Exception e)
{
String msg = "Exception while looking for property getter for '" + property;
log.error(msg, e);
throw new VelocityException(msg, e);
}
}



It took me a little while to get Maps working (even though there's not much code to show for it).


class ScalaMapGetExecutor(val llog: Log, val clazz: java.lang.Class[_], val property: String) extends MapGetExecutor(llog, clazz, property) {
override def isAlive = true

override def execute(o: AnyRef) = o.asInstanceOf[Map[String, AnyRef]]
.getOrElse[AnyRef](property, null).asInstanceOf[java.lang.Object]

}


All that's left to do now is configure Velocity from Spring so that it uses the Scala specific Uberspector instead of the default. So, in my servletname-servlet.xml file I needed to add the following


<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/views/"/>
<property name="velocityProperties">
<props>
<prop key="runtime.introspector.uberspect">info.threebelow.verla.mvc.view.velocity.ScalaUberspect</prop>
</props>
</property>
</bean>