August 4, 2004
Part One
Rod Cope's recent talk on Groovy at our last Boulder JUG meeting inspired me
to do something Groovy.
For the uninitiated Groovy is a new scripting language for the JVM. There is
even a movement afoot to make it the 'standard' scripting language for Java
(see JSR # 241 here).
Groovy is the most semantically dense scripting language I have ever seen;
there are shortcuts for everything. I've talked to a few active Groovy users
who say that Groovy = 50% java code and 50% the development time for simple
tasks (your mileage may vary).
There is one particular aspect of Groovy that really caught my attention at
Rod's talk : the Java language is (99%) a sub-set of Groovy. Rod explained that
there are still a few Java language features that aren't part of Groovy yet,
but the Groovy evangelists don't seem to miss them. But basically 99% of the
time you can take a .java, rename it .groovy, run it through the groovy interpreter
and get expected results.
This feature got me to thinking about writing Webwork actions in Groovy. Webwork
actions seem like a perfect candidate for scripting because a) they are pretty
simple b) I want to change them a lot while I develop and not have to recompile
and redeploy my web application. For this to work well there has to be a smooth
mechanism for interacting with groovy scripts from Java. Turns out there is.
You can invoke a method on an object which you build from a .groovy script
by doing the following:
1) create a plain old .java interface for the class you want to write in groovy.
2) write the implementation for the class just as you would in java and save
it to a .groovy file. 3) use something like the following java code to instantiate
an object from the .groovy script and invoke a method:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
GroovyClassLoader groovyCl = new GroovyClassLoader(cl);
Class groovyClass = groovyCl.parseClass(
cl.getResourceAsStream("FooImplementation.groovy"));
FooInterface foo = (FooInterface) groovyClass.newInstance();
foo.callSomeMethod();
So, to write Webwork actions in Groovy, a little surgery has to be done on
the mechanism that loads action classes in xwork.
In xwork (the guts of Webwork) there is a simple ObjectFactory with a getClassInstance(String
classname) method. This method simply loads the specified class by name and
returns it. This only works for .java classes. A simple addition allows this
to work with .groovy scripts too using the classloading method above. The final
com.opensymphony.xwork.ObjectFactory.getClassInstance(String classname) method
looks like this :
public Class getClassInstance(String className) throws ClassNotFoundException {
Class clazz;
// check className
if (className.matches(".*\\.groovy")) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
GroovyClassLoader groovyCl = new GroovyClassLoader(cl);
try {
clazz = groovyCl.parseClass(cl.getResourceAsStream(className));
} catch (CompilationFailedException e) {
throw new ClassNotFoundException("Error parsing " + className, e);
} catch (IOException e) {
throw new ClassNotFoundException("Error reading " + className, e);
}
} else {
clazz = (Class) classes.get(className);
if (clazz == null) {
clazz = ClassLoaderUtil.loadClass(className, this.getClass());
classes.put(className, clazz);
}
}
return clazz;
}
A groovy action configured in the xwork.xml file looks like this :
<action name="TestGroovyAction" class="TestGroovyAction.groovy">
<param name="foo">bar</param>
<result name="success">/index.jsp</result>
<result name="error">/hello.jsp</result>
</action>
The TestGroovyAction.groovy looks like this :
import com.opensymphony.xwork.Action;
import org.apache.log4j.Logger;
public class TestGroovyAction implements Action {
Logger logger = Logger.getLogger("com.vitagroup.TestGroovyAction");
String foo;
public TestGroovyAction() {
}
public String execute () {
logger.debug("TestGroovyAction is executing foo is " + foo);
return "error";
}
public void setFoo (String foo) {
this.foo = foo;
}
}
Notice there is no throws Exception clause on the execute method. This is one
of the Java language features that hasn't been added to Groovy yet.
The only piece left to work out is reloading the groovy classes automatically
when the .groovy script is changed. As it stands the web application has to
be reloaded in order for the action class to be re built. Hopefully that won't
be to hard to solve.
This is working well for me so far and I'm enjoying writing webwork actions
in groovy. Check back soon to see the solution to the reloading problem. ;)
Part Two
Scripting webwork actions is down right groovy.
Earlier I posted about modifying webwork
to allow actions to be written in Groovy. The modification is actually to xwork
which is used by webwork.
After the first pass the basic functionality was working fine except that modifications
to the .groovy scripts while the application was deployed in the servlet container
had no effect. I suspected xwork was caching the actions somewhere. Actually
Tomcat was the culprit. Tomcat uses it's own classloader implementation called
WebappClassloader.
This classloader caches calls to getResourceAsStream() and the cache doesn't
get invalidated until the webapp is reloaded. However calls to getResoruce which
return URLs are not cached. So changing to using getResource solved the caching
problem.
Now it's possible to load a page, edit the .groovy behind it, reload the page,
and see it change. Pretty slick for speedy developing.
But it's not necessary to re-compile the .groovy each time the webpage is reloaded.
I took a tip from Brian McCallister
and looked at Nanocontainer
at Codehaus. Nanocontainer has a feature called nanoweb which is an ultra light
WW like action framework that allows actions to be implemented in groovy.
Instead of recompiling the .groovy action each time instead it's easy to add
the compiled groovy class to a cache using the file's timestamp as a key. With
each page reload the timestamp of the .groovy is compared against the cache
timestamp and the .groovy is recompiled only if it's newer than the cached version.
With this in place there is no difference performance-wise between .groovy actions
and .java actions.
Now it's on to sorting out how to implement a base action in groovy.
CP
About the author
Christian Parker
christian@adigio.com
Blog: http://adigio.com/blogs/christian/
Christian Parker recently left his Senior Software Engineering position in the interactive TV world to co found Adigio Inc. (http://adigio.com). Christian has 10 years of software development experience and is currently active in the Java community. He is enjoying hacking away on Groovy, Spring, WebWork2, and other technology.
|