Wednesday, August 8, 2012

Maven is groovy!

Recently, I had another one of those cases where Maven almost does the right thing, but not quite. Let me explain the use case:
I've got a software component that can initialize the database from an SQL script. Such an SQL script (in what follows: The DDL, or data definition language script) is ideally generated by the Hibernate Schema Exporter, aka "hbm2ddl", which in turn is available in Maven by running the Hibernate3 Maven Plugin. But, if just creating the database is not sufficient and you need to run a second SQL script (in what follows: The data script) to populate the DB with some initial entries? Well, I came up with the following solution:
  1. At build time, have Maven create the DDL script (below target/classes, so that it is available at run time)
  2. At development time, manually create the data script (in src/main/db)
  3. At build time, have Maven concatenate these scripts into a third SQL script (in what follows: The concatenated script, also below target/classes, as it must also be available at runtime)
Question: How do we do that last step? The most obvious solution was the Maven Antrun Plugin, Ant even's got a "concat" task, which should do exactly what I want (Including uptodate checks). However, I wasn't really happy with that solution, because Ant, or the "concat" task behaved too unpredictable (For example, no error was produced, if either of the source files didn't exist. An, error checking is, where Ant scripts become really nasty.) In the end, I had to admit: It didn't work.
So I came up with another idea: Why not have a small Groovy Script in the Maven POM. And, as is usually the case, someone else already had that idea and there is a Maven Plugin, which already provides just that:
I can embed a Groovy snippet into my Maven POM and have it executed at a suitable point of my build script. Here's the snippet I came up with:
def concat(s1, s2, t) {
def f1 = new
def f2 = new
def ft = new
def long l1 = f1.lastModified()
def long l2 = f2.lastModified()
def long lt = ft.lastModified()
if (l1 == 0) {
throw new IllegalStateException("Source file must exist:" + f1);
} else if (l2 == 0) {
throw new IllegalStateException("Source file must exist:" + f2);
} else if (lt == 0 || l1 > lt || l2 > lt) { pd = ft.getParentFile()
if (pd != null && !pd.isDirectory() && !pd.mkdirs()) {
throw new IOException("Unable to create parent directory: " + pd)
println("Creating target file: " + ft)
println("Source1 = " + f1)
println("Source2 = " + f2) fi1 = new fi2 = new
} else {
println("Target file is uptodate: " + ft)
println("Source1 = " + f1)
println("Source2 = " + f2)

perhaps in combination with a byte array, for performance reasons, but in Groovy a file has got a method append(InputStream), which does exactly that. And, although I am declaring the variable ft above as an instance of, it is nevertheless a Groovy file, with all the added sugar of Groovy! Which is, why embedding Groovy into the POM is much nicer than embedding Java!

In the future. I will most likely never ever write Maven plugins and use Groovy scripts instead.

 Second: We are inside a Maven POM, or, to put it different: Inside an XML file. As a consequence, I've got to be careful with characters like '&', or '!'. Which is why I am using the strings ">" and "&" instead. I might as well use a CDATA section, or, even better: An external script (in src/main/groovy) However, I believed to make this postings point better with an internal (albeit somewhat lengthy) snippet. Hope, you agree, so let's be groovy!

1 comment:

SteveL said...

Why not just jump to Gradle and skip the XML altogether?