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.

21 February 2011

Backup, backup backup

I was having problems with my Dell Precision M4500. It wasn't booting:

Windows failed to start. A recent hardware or software change might be the cause. To fix the problem:

1. Insert your windows installation disc and restart your computer.
2. Choose your language settings, and then click "Next".
3. Click "Repair your computer"

If you do not have this disc, contact your administrator or computer manufacturer for assistance.

File: \Status: 0xc000000f

Info: An error occured while attempting to read the boot configuration data.

After panicking that my hard disk was broken, I discovered that this was a problem with the windows boot manager.

So, I made a Windows recovery disk from another Windows 7 machine I had, and tried to boot from that. Didn't work. Same problem. Ah, OK, we'll switch off booting from the hard disk, just boot from the CD. F2 to enter setup on the Dell Precision, sorted.

It took an awful long time to boot from the CD, it's always worrying when that happens. But it boots. The CD says that it's a problem with the boot manager, do you want me to fix it. I say yes. And it does work. It reboots successfully.

First thing to do, install Crashplan and do a backup. Just in case.

Then I switched the boot from disk back on, and reboot. It works. Woohoo.

Moral of the story (two actually):

Make a windows recovery disk, before you need it.
Backup, and test your backups. I've started to use Crashplan (http://www.crashplan.com/). It's great.