Problem
I have an archive compiled with properties pre-defined within, but they need to change as I promote my build through the different stages of the SDLC. One example could be with a connection.xml that has URLs pointing to different databases.Gradle already offers some great solutions such as keyword expansion and ant token replace, but both methods require the token to be in a predefined format. The expand() solution looks very similar to groovy string interpolation and looks for ${..}, whereas ant replace tokens looks for @..@.
Solution
The solution here was to pass a closure to filter which is called for every line of the filtered file. The closure performs some simple processing using the MapFilter() class and its MapReplace() method. An example is shown below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
buildscript { | |
apply plugin: 'groovy' | |
dependencies { | |
gradleApi() | |
} | |
} | |
ext{ | |
earDir = "${projectDir}/src/dist" | |
explodedDir = "${buildDir}/exploded" | |
} | |
/** | |
* SINGLETON VIA METAPROGRAMMING | |
* http://groovy.codehaus.org/Singleton+Pattern | |
*/ | |
class MapFilter { | |
String MapReplace( def input, Map map ){ | |
try { | |
map.each { key, value -> | |
input = input.replace( key, value ) | |
} | |
input | |
} catch ( e ) { | |
logger.error e.message | |
} | |
} | |
} | |
class MapFilterMetaClass extends MetaClassImpl { | |
private final static INSTANCE = new MapFilter() | |
MapFilterMetaClass() { super( MapFilter ) } | |
def invokeConstructor( Object[] arguments ) { return INSTANCE } | |
} | |
def registry = GroovySystem.metaClassRegistry | |
registry.setMetaClass( MapFilter, new MapFilterMetaClass() ) | |
def myMap = ["devURL" : "productionURL", | |
"devProp1" : "productionProp1", | |
"devProp2" : "productionProp2", | |
"devProp3" : "productionProp3", | |
"devProp4" : "productionProp4"] | |
task unzipFilterWithMap(type: Copy){ | |
def zipFile = file( "${earDir}/myZip.zip") | |
from zipTree(zipFile) | |
into explodedDir | |
filter { line -> | |
new MapFilter().MapReplace(line, myMap) | |
} | |
} |
There's more ...
You may have noticed that this example also includes a lot of meta-programming. This was just my first dip into the world of meta-programming. In this example the metaclass simply intercepts every new call to map filter and returns the original map filter instance.