Shlrm.org Blog

Linux, Java, Ruby, and Politics

Maven Resource Filtering With Groovy

| Comments

On the my project at work, there are configuration files that are used to configure the artifact for the various environments it gets deployed to. We’ve got about 6 different environments some with remarkably similar configurations, and that means lots of duplicate property values. With the current setup using maven resource filtering, if one environment is different from the other 5, the property has to be specified in all of the filtering config files. Worst case scenario, that means 5 files have duplicate data, and the 6th has something different.

Well, that was driving me crazy and so I implemented a groovy based solution using a YAML file instead.

I replaced all the maven resource filtering plugin stuff (with a few exceptions) with an execution from the GMaven Plugin. We’re already using the GMaven plugin for compiling our groovy test code, and a few groovy main classes.

The following groovy source file is generic enough to be used anywhere, with a small amount of tweaking. It’s got some project specific logic for our project, regarding a directory for output of the configured files. I think we’re putting out location specific files, even though they’re all for one location. So that’s just been hard-coded into this file. It will likely evolve if/when we add another location specific config. There’s a small bit of voodoo magic in there to use a SimpleTemplateEngine to perform simple filtering on the yaml file itself allowing you to do things like: ${project.basedir}, which can be quite handy when referencing simple databases for testing.

Groovy Script for Filtering
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import java.util.regex.Pattern
import org.yaml.snakeyaml.Yaml
import groovy.text.SimpleTemplateEngine

class NoSuchResourceException extends Exception {
    NoSuchResourceException(String reason) {
        super(reason)
    }
}

class GroovyMavenFilter {
    def data
    final def PATTERN = Pattern.compile(/.*(\$\{(.*)\}).*/)

    GroovyMavenFilter(String configYaml, def variables) {
        Yaml yaml = new Yaml();
        File f = new File(configYaml)
        def engine = new SimpleTemplateEngine()
        //This gives it access to the simple maven properties that exist
        // project.basedir, etc.
        data = yaml.load(engine.createTemplate(f.getText()).make(variables).toString())
    }

    def environments() {
        data.keySet().collect {
            if (it != "common")
                it
        } - null
    }

    /**
     * This actually filters the file
     * Follows the maven resource convention of ${thingy.thinger}* @param sourceFile
     * @param targetEnv
     * @param destinationFile
     * @return
     */
    def filterFile(def sourceFile, def targetEnv, def destinationFile) {
        File source = new File(sourceFile)
        File destination = new File(destinationFile)
        //Make sure we've got a directory to barf the files into
        destination.getParentFile().mkdirs()
        destination.withPrintWriter { writer ->
            source.eachLine { line ->
                def matcher = PATTERN.matcher(line)
                if (matcher.matches()) {
                    //println "matched ${matcher.groupCount()} groups"
                    //println("matched group: ${matcher.group(1)}")
                    //println "group 2: ${matcher.group(2)}"
                    def g2 = matcher.group(2)
                    def g1 = matcher.group(1)

                    String replacement = null
                    if (data[targetEnv].containsKey(g2)) {
                        replacement = data[targetEnv][g2]
                    } else if (data["common"].containsKey(g2)) {
                        replacement = data["common"][g2]
                    } else {
                        throw new NoSuchResourceException("No such resource defined: ${g2}")
                    }

                    if (replacement == null) {
                        replacement = ""
                    }
                    writer.println(line.replace(g1, replacement))
                } else {
                    writer.println(line)
                }
            }
        }
    }
}

def fs = File.separator

def sourcePath = "${project.basedir}${fs}src${fs}main${fs}config"
def filterConfig = "${project.basedir}${fs}src${fs}main${fs}filters${fs}filter.yaml"

//Pass in the simple variables that make it all work
GroovyMavenFilter gmf = new GroovyMavenFilter(filterConfig, this.binding.variables)

File configs = new File(sourcePath)
gmf.environments().each { env ->
    configs.eachFile { file ->
        //target/properties/test/ord
        println "Filtering file: ${file.getAbsolutePath()} in environment ${env} to some path"
        gmf.filterFile(file.getAbsolutePath(), env, "${project.basedir}${fs}target${fs}properties${fs}${env}${fs}ord${fs}${file.getName()}")
    }
}

A sample YAML file would look like this:

Filter.yaml
1
2
3
4
5
6
7
8
9
common:
    my.authority: localhost:8185

local:
    my.scheme: http

dev:
    my.authority: www.example.com
    my.scheme: https

The settings in the more specific environment would override the common settings. So for the “dev” environment, my.authority would be set to www.example.com not  localhost:8185, but it would be localhost:8185 for the local environment.

If you specified a property that didn’t exist, the groovy script would through a nice exception for you, so that you know you’re referencing a property that doesn’t exist.

Comments