27 February 2011

Replacing properties with a groovy script

I've used groovy in a interesting way recently.

We were writing a Spring/JSF application for a client which managed a set of accounts for the local council. This would send invoices to another system, using a proprietary file format. Part of the file sent contained the text that would be printed out and sent to the client (of the council), including text which could be changed by the council, such as email addresses, phone numbers.

So we had two problems:

1) How do we nicely provide a way for the council to change the names, email addresses and phone numbers on the final invoice? We could have 20 different properties defined, but this is a nightmare.
2) We're talking about formatting invoices, during testing we would have a lot of to-ing a fro-ing with the client, how do we minimize this?

Our solution: use a groovy script which is invoked from java. The script is delivered in the same directory as the other properties (so the client can edit it). We can also modify it and redeploy rapidly when client changes their mind without having to redeploy the entire application.

In the server, we have a java method which invokes the groovy script. To keep this simple, the groovy script we set as input a variable called invoice (an object) and defines a variable called output, a String, which represents the text that will end up on the invoice sent to the user. Here is the java which calls the script:

package uk.co.farwell;

import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;

import org.codehaus.groovy.control.CompilerConfiguration;

public final class RunGroovyTemplate {
public String runTemplate(String root, String script, String encoding, Invoice invoice) throws Exception {
String[] roots = new String[] { root };
GroovyScriptEngine gse = new GroovyScriptEngine(roots);
Binding binding = new Binding();
binding.setVariable("invoice", invoice);

CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
gse.run(script, binding);

return binding.getVariable("output").toString();

Nothing very complex there. And this method is called with something like:

String output = new RunGroovyTemplate().run("C:\\temp", "template.groovy", "UTF-8", invoice);

System.out.println("output=\n\n" + output);

Again, nothing complex. Ok, so what does the groovy look like?

output = "Invoice number: ${invoice.id}\n\n"

for (line in invoice.lines) {
output = output + " ${line.description} ${line.total}\n"

output = output + "-------------------\n";
output = output + "total ${invoice.total}\n";

output = output + """
Please inform us of any changes :
billing address or name(s)

Contact details:

For questions about this invoice:
Fred Bloggs: 021 454 67 78
E-mail: fred.bloggs@foo.com

So you can see that we've not got a lot of Java, but we've built in a lot of flexibility by using groovy. I've carefully separated out the contact details so that they can be changed easily on site.

The output from this looks like:

Invoice number: 66

description 34.00
total 34.00

Please inform us of any changes :
billing address or name(s)

Contact details:

For questions about this invoice:
Fred Bloggs: 021 454 67 78
E-mail: fred.bloggs@foo.com

The disadvantage of this approach are that we are delivering a script to a client, which isn't always a good thing to do, but we thought that the advantages of this approach outweighed the disadvantages.

And don't forget that this can be unit tested just as easily as a pure Java solution.

No comments: