Tuesday, February 9, 2010

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>

2 comments:

James Strachan said...

BTW have you looked at Scalate yet? Scalate == Scala Template Engine.

http://scalate.fusesource.org/

Its kinda like Velocity in some ways - but using Scala syntax for expressions & letting you use Scala methods to create a kind of custom tag.


It also code generates template classes on the fly so there's no reflection & you can verify your statically typed templates don't have any errors in them at build time.

There are currently 2 flavours of templates, SSP which are like JSP/ASP/Erb syntax and Scaml which is like HAML but using Scala

For more info see the documentation

Scot McSweeney-Roberts said...

I've had a look at Scalate and I'm currently try to figure out how to get it to work with Spring.

Assuming I can get it to work, it'll be my next S+S+T post.