Category Archives: ant

Ant hooks using Groovy via XML transform

This time the Ant Hook scripts using Groovy is implemented by transforming the target Ant build script. (old post in draft status)

In the post, Ant Hooks using Groovy Script via Scriptdef, we used the Ant BuildListener interface to add a hooks feature that invokes a Groovy script mapped to build events. Then in the last post Ant hooks using Groovy, continued we added the ability to skip a target execution.

The problem with the former implementations is that the target Ant script must be modified to take advantage of hooks. Using XMLTask, we can modify the ant script directly. The InsertHooks.groovy script reads the hooks.inix file and transforms the build.xml to build-hooked.xml. The build-hooked.xml file will have an Ant build listener set to the Hook.groovy script.

The scripts are not general purpose, of course. Just a proof of concept thing.

Approach

hooks.inix:

[>hook/root/compile?when=after,skip=false]
	println " hook: root,{target=${event.target.name},when=post,event=$event}"
[<]

[>hook/demo1/deploy?when=before,skip=true]
	println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
[<]

[>hook/demo1/compile?when=before,skip=false]
	println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
[<]

[>fragment]
	<path id="libs">    
		<fileset dir="lib">
            <include 
                name="groovy-all-2.2.1.jar" />
        </fileset> 
    	
    	<pathelement location="src/main/groovy"/>
	</path>    
     
    <!-- Groovy library -->
    <taskdef name="groovy"
       classname="org.codehaus.groovy.ant.Groovy"
       classpathref="libs"/> 
    
    <!-- sets a BuildListener to the project -->
    <scriptdef name="set-listener" 
        language="Groovy"
        classpathref="libs" 
        src="src/main/groovy/com/octodecillion/util/ant/Hook.groovy"> 
    	<attribute name="path"/>
    </scriptdef>
     
    <!-- install the listener -->
    <set-listener path="hooks.inix"/>   
     
[<fragment]

InsertHooks.groovy

package com.octodecillion.util.ant

import static com.octodecillion.util.inix.Inix.EventType.END

import com.octodecillion.util.inix.Inix

import org.apache.tools.ant.*

import static groovy.io.FileType.FILES

/**
 * Insert the XML into Ant script to enable hooks.
 *   
 * @author josef betancourt
 */
class InsertHooks{

	def ant
	def DEBUG = false
	static final String srcFilePath='build.xml'
	static final String destFilePath="build-hooked.xml"
	static final String INIXFILE = 'hooks.inix'	
	static final String XMLTASK = 'com.oopsconsultancy.xmltask.ant.XmlTask'
	
	static main(args){
		new InsertHooks().execute()
	}
	
	/** An Ant task entry point */
	public void execute() throws BuildException{
		def ant = new AntBuilder()
		
		try {
			
			def fragment = loadFragment(INIXFILE)
			if(!fragment){
				throw new BuildException("'fragment' from $INIXFILE is invalid")
			}		
			
			def engine = new groovy.text.SimpleTemplateEngine()
			def template = engine.createTemplate(fragment)			 
			def xml = template.make([hookFilePath:INIXFILE])
			  
			ant.path(id: "path") {
				fileset(dir: 'lib') {
				   include(name: "**/xml*.jar")
				}
			}
	 
			ant.taskdef(name:'xmltask',classname:
				XMLTASK,
				classpathref: 'path')
			 
			def xpath = '(//target)[1]' 
			ant.xmltask(source:srcFilePath,dest:destFilePath, 
				expandEntityReferences:false,report:false){
				insert(position:"before",path:xpath,xml:xml)				 
			}
				 
			new File(destFilePath).eachLine{
				println it
			}
			
		} catch (Exception e) {
			e.printStackTrace()
			throw new BuildException(e.getMessage(), e)
		}
	}
	
	def loadFragment(String path){
		def text = ''
		def inix = new Inix(path)
		def theEvent = inix.next()
		 
		while(theEvent && theEvent != Inix.EventType.END ){
			Inix.Event event = inix.getEvent()

			if(event.isSection("fragment")){
				text = event.text
				break
			}
			
			theEvent = inix.next()
		}
		
		return text
	}	
	
}

// end Script

Hook.groovy

package com.octodecillion.util.ant

import groovy.transform.TypeChecked;
import groovy.transform.TypeCheckingMode;

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern

import com.octodecillion.util.inix.Inix

import org.apache.tools.ant.BuildEvent
import org.apache.tools.ant.BuildException
import org.apache.tools.ant.Project
import org.apache.tools.ant.SubBuildListener;

import static groovy.io.FileType.FILES

// wire in the listener
def path = binding.attributes.get('path')
if(!path){
	throw new BuildException("'path' to hook inix not set")
}

def listener = new HookListener(project,path)
listener.project = project
project.addBuildListener(listener)

// end wiring

/**
 * Ant build listener that invokes groovy hook scripts.
 *  
 * @author josef betancourt
 *
 */
//@TypeChecked
class HookListener implements SubBuildListener {
	Project project
	boolean DEBUG = false
	
	/**                          */
    def HookListener(Project project, String path){
		this.project = project
		loadInix(path)				
    }    
    
	/** load scripts in inix file */
	def loadInix(String path){
		debugln("load inix")
		def inix = new Inix()
		inix.reader = new BufferedReader(
			new FileReader(new File(path)))
		 
		def theEvent = inix.next()
		def found = false
		 
		while(theEvent && theEvent != Inix.EventType.END ){
			def event = inix.getEvent()

			if(isHook(event)){
				found = true
				def key = [event.path[2],((String)(event.params['when'])).
					toUpperCase()].join('/')
					
				String txt = event.text
				String skString = event.params['skip']
				boolean sk = (skString.compareTo('true')==0 ? true : false)
				debugln "key=$key, ${event.params['skip']}, skip=$sk"
					
				def node = new HookNode(txt, sk)
			
				def prj = event.path[1]				
				if(!hooks[prj]){
					hooks[prj] = [:]
				}	
				
				hooks[prj].put(key,node);			
			}
			
			theEvent = inix.next()
		}
		
		dumpHooks()		
		
	}

    /** invoked by Ant build */
	@Override
    public void targetStarted(BuildEvent event) {
		die("targetStarted invoked with null event", !event)
		invokeTargetHook(event, When.BEFORE)
    }
    
	/** invoked by Ant build */
    @Override
    public void targetFinished(BuildEvent event) {
		die("targetFinished invoked with null event", !event)
		invokeTargetHook(event, When.AFTER)
    }

    /** Invoke the target's hook script */
    def invokeTargetHook(BuildEvent event, When when){
        def b = new Binding()
        b.setProperty("event",event)
		b.setProperty("hook",this)
		
        def shell = new GroovyShell(b)
		
		def hookName = "${event.target.name}/$when"
		def pHook = hooks[event.project.name][hookName]
		def rHook = hooks['root'][hookName]	
		debugln("invokeTargetHook: $hookName\npHook:  $pHook\nrHook:  $rHook")
			
		boolean skipSet = false
		
		if(pHook){
			skipSet = pHook.skip	
			debugln("skipSet=$skipSet")
			shell.evaluate(pHook.text)
			
			if(!override && rHook){
				skipSet = skipSet ? skipSet : rHook.skip
				shell.evaluate(rHook.text)
			}			
			
		}else if(rHook){
			skipSet = rHook.skip			
			shell.evaluate(rHook.text)		
		}
		
		if( skipSet && (pHook || rHook) && (when == When.BEFORE) ){
			createSkipforTarget(event)
		}		
    } 

	/**   */
	private createSkipforTarget(BuildEvent event) {
		debugln "setting skip: ${event.target.name}_skipTarget"
		event.project.setProperty("${event.target.name}_skipTarget", "true")
		event.target.setUnless("${event.target.name}_skipTarget")
	}
	
	/** throw exception if flg is true */
	private die(Object msgObject, boolean flg){
		if(flg){
			throw new IllegalArgumentException(String.valueOf(msgObject))			
		}		
	}	
	
	@TypeChecked(TypeCheckingMode.SKIP)
	private isHook(ev){		
		ev.path && ev.path[0] == 'hook'		
	}
    
	private dumpHooks() {
		if(!DEBUG){
			return			
		}
		
		hooks.each{
			it.each{ node ->
				debugln(node)
			}
		}		
	}
	
	private debugln(Object msg){
		if(DEBUG){
			println(msg)
		}
	}
	
	String TARGETHOOK = "target"
	def override = true;
	
	enum When{
		BEFORE('before'),AFTER('after')
		String name
		
		When(s) {this.name = s}
	}
	
	private class HookNode {
		String text
		boolean skip
		public HookNode(String text, boolean skip){
			this.text = text
			this.skip = skip
		}
		
		def String toString() {return "s:$skip"};
	}
	
	Map<String, Map<String,HookNode>> hooks = [:]
	
    //@formatter:off
    @Override
    public void subBuildFinished(BuildEvent event) {}
    @Override
    public void subBuildStarted(BuildEvent event) {}
    @Override
    public void buildFinished(BuildEvent event) {}
    @Override
    public void buildStarted(BuildEvent event) {}
    @Override
    public void messageLogged(BuildEvent event) {}
    @Override
    public void taskFinished(BuildEvent event) {}
    @Override
    public void taskStarted(BuildEvent event) {}
	//@formatter:on
} // end class HookListener

// end Script

Further reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Ant hooks using Groovy, INIX, and XMLTask

I revisit the Ant Hook scripts using Groovy and change it to use INIX file for hook storage and use XML transform via XMLTask to insert the hook feature into a legacy build script.

In a prior post, Ant hooks using Groovy, continued, I used the Ant BuildListener interface to add a hooks feature that invokes a Groovy script mapped to build events. Then I implemented an “around advise” capability.

Many issues with this approach, but two stand out.

Drawbacks
  1. Existing Ant scripts must be modified to add this in.
  2. Hook scripts are now a bunch of files that must be managed.
Approach
  1. Hook scripts are stored in INIX files
  2. A meta build step is used to insert the hook feature into a build script.

INIX storage
Below, the Groovy hook scripts from the previous blog post are stored in the sections of a INIX file (a form of INI file). The section begin tag is composed of a unique path, optional fragment identifier, and a query string. Just like a URL. The query string indicates when this hook is applied and if the target in the script should be skipped (applicable for ‘before’ hooks). The last section in the file will be discussed later.

hooks.inix
[>hook/root/compile?when=after,skip=false]
    println " hook: root,{target=${event.target.name},when=post,event=$event}"
[<]

[>hook/demo1/deploy?when=before,skip=true]
    println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
[<]

[>hook/demo1/compile?when=before,skip=false]
    println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
[<]

[>fragment]
    <path id="libs">    
        <fileset dir="lib">
            <include 
                name="groovy-all-2.2.1.jar" />
        </fileset> 
        
        <pathelement location="src/main/groovy"/>
    </path>    
     
    <!-- Groovy library -->
    <taskdef name="groovy"
       classname="org.codehaus.groovy.ant.Groovy"
       classpathref="libs"/> 
    
    <!-- sets a BuildListener to the project -->
    <scriptdef name="set-listener" 
        language="Groovy"
        classpathref="libs" 
        src="src/main/groovy/com/octodecillion/util/ant/Hook.groovy"> 
        <attribute name="path"/>
    </scriptdef>
     
    <!-- install the listener -->
    <set-listener path="hooks.inix"/>   
     
[<fragment]

Ant build file transform
In the above inix file, the last section contains the XML fragment of the build script hook support. We read in this section and use Groovy’s Templating support to replace any values. Thus, by running a metabuild or bootstrap process we can take that fragment and insert it into a target Ant script.

Since an Ant script is XML, an XML transform is used by the XMLTask Ant task. This task allows us to indicate with an XPath expression where to insert the fragment. In a Groovy blog post, we of course, will use Groovy to do this via the Groovy AntBuilder. Below, the InsertHooks class implements this.

InsertHooks.groovy
package com.octodecillion.util.ant

import static com.octodecillion.util.inix.Inix.EventType.END

import com.octodecillion.util.inix.Inix

import org.apache.tools.ant.*

import static groovy.io.FileType.FILES

/**
 * Insert the XML into Ant script to enable hooks.
 *   
 * @author josef betancourt
 */
class InsertHooks{

    def ant
    def DEBUG = false
    static final String srcFilePath='build.xml'
    static final String destFilePath="build-hooked.xml"
    static final String INIXFILE = 'hooks.inix'    
    static final String XMLTASK = 'com.oopsconsultancy.xmltask.ant.XmlTask'
    
    static main(args){
        new InsertHooks().execute()
    }
    
    /** An Ant task entry point */
    public void execute() throws BuildException{
        def ant = new AntBuilder()
        
        try {
            
            def fragment = loadFragment(INIXFILE)
            if(!fragment){
                throw new BuildException("'fragment' from $INIXFILE is invalid")
            }        
            
            def engine = new groovy.text.SimpleTemplateEngine()
            def template = engine.createTemplate(fragment)             
            def xml = template.make([hookFilePath:INIXFILE])
              
            ant.path(id: "path") {
                fileset(dir: 'lib') {
                   include(name: "**/xml*.jar")
                }
            }
     
            ant.taskdef(name:'xmltask',classname:
                XMLTASK,
                classpathref: 'path')
             
            def xpath = '(//target)[1]' 
            ant.xmltask(source:srcFilePath,dest:destFilePath, 
                expandEntityReferences:false,report:false){
                insert(position:"before",path:xpath,xml:xml)                 
            }
                 
            new File(destFilePath).eachLine{
                println it
            }
            
        } catch (Exception e) {
            e.printStackTrace()
            throw new BuildException(e.getMessage(), e)
        }
    }
    
    def loadFragment(String path){
        def text = ''
        def inix = new Inix(path)
        def theEvent = inix.next()
         
        while(theEvent && theEvent != Inix.EventType.END ){
            Inix.Event event = inix.getEvent()

            if(event.isSection("fragment")){
                text = event.text
                break
            }
            
            theEvent = inix.next()
        }
        
        return text
    }    
    
}

// end Script

Hooking Up
Now the target Ant script has the ability to invoke the script below via a ScriptDef. The script wires in the Ant BuildListener interface implementation, and then loads the hooks from the inix file. When the Ant script is run, the BuildListener’s invokeTargetHook method is invoked. This in turn executes the correct hook scripts.

Hook.groovy
package com.octodecillion.util.ant

import groovy.transform.TypeChecked;
import groovy.transform.TypeCheckingMode;

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern

import com.octodecillion.util.inix.Inix

import org.apache.tools.ant.BuildEvent
import org.apache.tools.ant.BuildException
import org.apache.tools.ant.Project
import org.apache.tools.ant.SubBuildListener;

import static groovy.io.FileType.FILES

// wire in the listener
def path = binding.attributes.get('path')
if(!path){
    throw new BuildException("'path' to hook inix not set")
}

def listener = new HookListener(project,path)
listener.project = project
project.addBuildListener(listener)

// end wiring

/**
 * Ant build listener that invokes groovy hook scripts.
 *  
 * @author josef betancourt
 *
 */
@TypeChecked
class HookListener implements SubBuildListener {
    Project project
    boolean DEBUG = false
    
    /**                          */
    def HookListener(Project project, String path){
        this.project = project
        loadInix(path)                
    }    
    
    /** load scripts in inix file */
    def loadInix(String path){
        debugln("load inix")
        def inix = new Inix()
        inix.reader = new BufferedReader(
            new FileReader(new File(path)))
         
        def theEvent = inix.next()
        def found = false
         
        while(theEvent && theEvent != Inix.EventType.END ){
            def event = inix.getEvent()

            if(isHook(event)){
                found = true
                def key = [event.path[2],((String)(event.params['when'])).
                    toUpperCase()].join('/')
                    
                String txt = event.text
                String skString = event.params['skip']
                boolean sk = (skString.compareTo('true')==0 ? true : false)
                debugln "key=$key, ${event.params['skip']}, skip=$sk"
                    
                def node = new HookNode(txt, sk)
            
                def prj = event.path[1]                
                if(!hooks[prj]){
                    hooks[prj] = [:]
                }    
                
                hooks[prj].put(key,node);            
            }
            
            theEvent = inix.next()
        }
        
        dumpHooks()        
        
    }

    /** invoked by Ant build */
    @Override
    public void targetStarted(BuildEvent event) {
        die("targetStarted invoked with null event", !event)
        invokeTargetHook(event, When.BEFORE)
    }
    
    /** invoked by Ant build */
    @Override
    public void targetFinished(BuildEvent event) {
        die("targetFinished invoked with null event", !event)
        invokeTargetHook(event, When.AFTER)
    }

    /** Invoke the target's hook script */
    def invokeTargetHook(BuildEvent event, When when){
        def b = new Binding()
        b.setProperty("event",event)
        b.setProperty("hook",this)
        
        def shell = new GroovyShell(b)
        
        def hookName = "${event.target.name}/$when"
        def pHook = hooks[event.project.name][hookName]
        def rHook = hooks['root'][hookName]    
        debugln("invokeTargetHook: $hookName\npHook:  $pHook\nrHook:  $rHook")
            
        boolean skipSet = false
        
        if(pHook){
            skipSet = pHook.skip    
            debugln("skipSet=$skipSet")
            shell.evaluate(pHook.text)
            
            if(!override && rHook){
                skipSet = skipSet ? skipSet : rHook.skip
                shell.evaluate(rHook.text)
            }            
            
        }else if(rHook){
            skipSet = rHook.skip            
            shell.evaluate(rHook.text)        
        }
        
        if( skipSet && (pHook || rHook) && (when == When.BEFORE) ){
            createSkipforTarget(event)
        }        
    } 

    /**   */
    private createSkipforTarget(BuildEvent event) {
        debugln "setting skip: ${event.target.name}_skipTarget"
        event.project.setProperty("${event.target.name}_skipTarget", "true")
        event.target.setUnless("${event.target.name}_skipTarget")
    }
    
    /** throw exception if flg is true */
    private die(Object msgObject, boolean flg){
        if(flg){
            throw new IllegalArgumentException(String.valueOf(msgObject))            
        }        
    }    
    
    @TypeChecked(TypeCheckingMode.SKIP)
    private isHook(ev){        
        ev.path && ev.path[0] == 'hook'        
    }
    
    private dumpHooks() {
        if(!DEBUG){
            return            
        }
        
        hooks.each{
            it.each{ node ->
                debugln(node)
            }
        }        
    }
    
    private debugln(Object msg){
        if(DEBUG){
            println(msg)
        }
    }
    
    String TARGETHOOK = "target"
    def override = true;
    
    enum When{
        BEFORE('before'),AFTER('after')
        String name
        
        When(s) {this.name = s}
    }
    
    private class HookNode {
        String text
        boolean skip
        public HookNode(String text, boolean skip){
            this.text = text
            this.skip = skip
        }
        
        def String toString() {return "s:$skip"};
    }
    
    Map<String, Map<String,HookNode>> hooks = [:]
    
    //@formatter:off
    @Override
    public void subBuildFinished(BuildEvent event) {}
    @Override
    public void subBuildStarted(BuildEvent event) {}
    @Override
    public void buildFinished(BuildEvent event) {}
    @Override
    public void buildStarted(BuildEvent event) {}
    @Override
    public void messageLogged(BuildEvent event) {}
    @Override
    public void taskFinished(BuildEvent event) {}
    @Override
    public void taskStarted(BuildEvent event) {}
    //@formatter:on
} // end class HookListener
// end Script

Demo
Below is a non-hooked Ant script.

build.xml
<project name="demo1" default="build" basedir=".">

<target name="compile">
    <echo>******* 'compile': Hello compile world!</echo>  
</target> 

<target name="deploy">
    <echo>******* 'deploy': Deploying ... ${skipSet}</echo>
</target>

<target name="build" depends="compile,deploy">
    <echo>******* 'build': Building ... </echo>
</target>

</project>

When we run the InsertHooks scripts, a new Ant script is created, build-hooked.xml. The console output (for demonstration) is:

Output

build-hooked.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project basedir="." default="build" name="demo1">

    <path id="libs">
<fileset dir="lib">
<include name="groovy-all-2.2.1.jar"/>
</fileset>

<pathelement location="src/main/groovy"/>
</path>

<!-- Groovy library -->
<taskdef classname="org.codehaus.groovy.ant.Groovy" classpathref="libs" name="groovy"/>

<!-- sets a BuildListener to the project -->
<scriptdef classpathref="libs" language="Groovy" name="set-listener" src="src/main/groovy/com/octodecillion/util/ant/Hook.groovy">
<attribute name="path"/>
</scriptdef>

<!-- install the listener -->
<set-listener path="hooks.inix"/>

<target name="compile">
    <echo>******* 'compile': Hello compile world!</echo>  
    </target> 
    
    <target name="deploy">
        <echo>******* 'deploy': Deploying ... ${skipSet}</echo>
    </target>
    
    <target depends="compile,deploy" name="build">
        <echo>******* 'build': Building ... </echo>
    </target>

</project>

Then we subsequently run the build-hooked.xml script, the console output is shown below. Note that the “deploy” target is skipped, only the output of the hook will show.

Buildfile: C:\Users\jbetancourt\workspace-4.3\AntAroundAdvice2\build-hooked.xml
compile:
  hook: {project=demo1,target=compile,when=pre,event=org.apache.tools.ant.BuildEvent}
     [echo] ******* 'compile': Hello compile world!
 hook: root,{target=compile,when=post,event=org.apache.tools.ant.BuildEvent}
deploy:
  hook: {project=demo1,target=deploy,when=pre,event=org.apache.tools.ant.BuildEvent}
build:
     [echo] ******* 'build': Building ... 
BUILD SUCCESSFUL
Total time: 3 seconds

Summary
Shown was an improvement of the Groovy Ant Hooks idea. If nothing else, shown was some Groovy code that used AntBuilder, XMLTask, and Inix file reading.

The actual use case for the need of a Ant hook is probably very narrow, such as a build server that reuses legacy Ant build scripts, or the targeting of legacy build to new requirements.

Further reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Ant hooks using Groovy, continued

The Ant Hook scripts using Groovy implementation is extended with the ability to also skip target execution. Thus, an “around” advice AOP like or filter.

In the last post, Ant Hooks using Groovy Script via Scriptdef, we used the Ant BuildListener interface to add a hooks feature that invokes a Groovy script mapped to build events. Now we implement a true “around advice”. Admittingly kludgy, but ….

Approach
Since the hook script has the Ant Event object, we have access to the Target object currently about to be executed. Target has a convenient method to allow target skip:

public void setUnless(java.lang.String property):  
 Sets the "unless" condition to test on execution. 
 if the property is set, the task will not execute. 

Demo
The demo Ant build script is modified with two new targets, ‘deploy’ and ‘build’. Also, the default target is changed to ‘build’.

<project name="demo1" default="build" basedir=".">
    ............ see original code .....
     
    <target name="compile">
        <echo>Hello compile world!</echo>  
    </target> 

    <target name="deploy">
        <echo>Deploying ... ${skipSet}</echo>
    </target>
    
    <target name="build" depends="compile,deploy">
        <echo>Building ... </echo>
    </target>

</project>

A new hook script to filter the ‘deploy’ target and skip it is added.
demo1/deploy_targetStarted.groovy

println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
hook.skipTarget(event)

Listener implementation
The actual listener is slightly modified to test for the skip property. If found the ‘unLess’ method is used on the target and the target is skipped.

/**
 * Ant build listener that invokes groovy hook scripts.
 *  
 * @author josef betancourt
 *
 */
class HookListener implements SubBuildListener {
    
    ...... see prior post ......

    /** Invoke the 'started' or 'finished' root or target hook script */
    def evokeTargetHook(BuildEvent event, When when){
        def b = new Binding()
        b.event=event
        b.hook=this

        def shell = new GroovyShell(b)
        
        def hookName = "${event.target.name}_${TARGETHOOK}${when.name}"
        
        def hooked = false
        
        def stored = projectHooks[hookName]        
        if(stored){
            hooked = true
            shell.evaluate(stored)
        }else{ 
            def hook = rootHooks[hookName]            
            if(hook){
                hooked = true
                shell.evaluate(hook)
            }
        }        
        
        if( hooked && (when == When.STARTED) ){
            def skipSet = event.project.getProperty(
                 "${event.target.name}_skipTarget")

            if(skipSet){
                event.project.setProperty(skipSet, "true")
                event.target.setUnless(skipSet)
            }            
        }
    }    

    /** invoked from a hook script */
    static void skipTarget(BuildEvent event) {
        def name = event.target.name
        def project = event.project
        project.setProperty(name + "_skipTarget", 'set')
    }

    ...... see prior post ......
}

    ...... see prior post ......

Output

Buildfile: C:\Users\jbetancourt\workspace-4.3\AntAroundAdvice\build.xml
compile:
  hook: {project=demo1,target=compile,when=pre,event=org.apache.tools.ant.BuildEvent}
     [echo] Hello compile world!
 hook: root,{target=compile,when=post,event=org.apache.tools.ant.BuildEvent}
deploy:
  hook: {project=demo1,target=deploy,when=pre,event=org.apache.tools.ant.BuildEvent}
build:
     [echo] Building ... 
BUILD SUCCESSFUL

Further reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Ant hooks using Groovy scripts via Scriptdef

Ant BuildListener interface is used to add a hooks feature that invokes a Groovy script mapped to build events.

One way of managing or customizing a complex Ant build is to use hook scripts that are executed at defined points in the Ant build life cycle. A use case for this is the reuse of build scripts in multiple build environments, like a build server vs a local workstation build, another is to add extra auditing.

Approach
We create a listener that take the project name and the current running target’s name to locate a matching hook file. This file contains a Groovy script to be invoked. Further, we allow a global hook script folder that contains hook scripts that would be applied if there are no matching hook scripts in a project level folder, named after the Ant build script project name.

Of course, this is Groovy specific, but it would be very easy to make this into a more generic hook invocation facility.

Similar ideas
Version control systems use the concept of hook scripts. Before a commit, for example, a hook script can ensure that the commit log contains the correct metadata. In AOP we can advise at ‘before’ and ‘after’ pointcuts.

Ant listeners
An Ant listener will be alerted to:

  • build started
  • build finished
  • target started
  • target finished
  • task started
  • task finished
  • message logged

In this demo we use only the target events.

Implementation
In the Ant script below, we install a listener before any ant targets are invoked. A scriptdef defines the listener as a script file.

Demo build script

<project name="demo1" default="compile" basedir=".">

    <path id="libs">    
        <fileset dir="lib">
            <include 
                name="groovy-all-1.8.6.jar" />
        </fileset>         
    </path>    
     
    <!-- Groovy library -->
    <taskdef name="groovy"
       classname="org.codehaus.groovy.ant.Groovy"
       classpathref="libs"/> 
    
    <!-- sets a BuildListener to the project -->
    <scriptdef name="set-listener" 
        language="Groovy"
        classpathref="libs" 
        src="src/main/groovy/com/octodecillion/ant/Hook.groovy"> 
    </scriptdef>
     
    <!-- install the listener -->
    <set-listener/>   
     
    <target name="compile">
        <echo>Hello compile world!</echo>  
    </target> 

</project>

Now we want to use the following actual Groovy hook scripts. The start target hook is defined at the project level, and the finished target hook is defined at the root level.

demo1/compile_targetStarted.groovy

println "hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"

root/compile_targetFinished.groovy

println "hook: root,{target=${event.target.name},when=post,event=$event}"

Listener implementation
The actual listener implementation is shown below. It uses the build event object to find the project name and target name. Then it searches for the matching hook script, _listenerMethod[Started | Finished].groovy, in the folder that matches the project name “demo1” here. If a hook script is not found, it searches for a matching hook script in the root folder. The found script is then evaluated.

In the example, the hooks/demo1 folder contains file: compile_targetStarted.groovy. Thus, when the Ant runtime invokes the targetStarted listener method on the ‘compile’ target, the listener will invoke the targetStarted hook script.

package com.octodecillion.ant

import org.apache.tools.ant.BuildEvent
import org.apache.tools.ant.Project
import org.apache.tools.ant.SubBuildListener;
import static groovy.io.FileType.FILES

/**
 * Ant build listener that invokes groovy hook scripts.
 *  
 * @author josef betancourt
 *
 */
class HookListener implements SubBuildListener {
    def project
    Map rootHooks = [:]
    Map projectHooks = [:] 
    
    String TARGETHOOK = "target"
    
    enum When{
        STARTED('Started'),FINISHED('Finished')
        String name
        
        When(s) {this.name = s}
    }
    
    /**                          */
    def HookListener(project){
        this.project = project
        
        // cache the root hooks
        new File("hooks/root").eachFileMatch FILES, ~/.*\.groovy/, 
            { file ->
                rootHooks.put(getBaseName(file.name), file.text)
            }
            
        // cache the project hooks
        new File("hooks/$project.name").eachFileMatch FILES, ~/.*\.groovy/,
            { file ->
                projectHooks.put(getBaseName(file.name), file.text)
            }
            
    }    
    
    @Override
    public void targetFinished(BuildEvent event) {
        evokeTargetHook(event, When.FINISHED)        
    }

    @Override
    public void targetStarted(BuildEvent event) {
        evokeTargetHook(event, When.STARTED)    
    }
    
    /** Invoke the 'started' or 'finished' root or target hook script */
    def evokeTargetHook(BuildEvent event, When when){
        def b = new Binding()
        b.event=event
        def shell = new GroovyShell(b)
        
        def hookName = "${event.target.name}_${TARGETHOOK}${when.name}"        

        def stored = projectHooks[hookName]        
        if(stored){
            shell.evaluate(stored)
        }else{ 
            // use the cached root hooks if found       
            def hook = rootHooks[hookName]            
            if(hook){
                shell.evaluate(hook)
            }
        }    
    }    

    /**                          */
    private String getBaseName(fileName){
        fileName.replaceFirst(~/\.[^\.]+$/, '')        
    }
    
    /**                          */
    private String getSuffix(fileName){
        def parts = fileName.split("\\.")
        parts.size() > 0 ? parts[parts.size()-1] : ''        
    }    
   
    //@formatter:off
    @Override
    public void subBuildFinished(BuildEvent event) {}
    @Override
    public void subBuildStarted(BuildEvent event) {}
    @Override
    public void buildFinished(BuildEvent event) {}
    @Override
    public void buildStarted(BuildEvent event) {}
    @Override
    public void messageLogged(BuildEvent event) {}
    @Override
    public void taskFinished(BuildEvent event) {}
    @Override
    public void taskStarted(BuildEvent event) {}
    //@formatter:on
}

// wire in the listener
def listener = new HookListener(project)
listener.project = project
project.addBuildListener(listener)

// end Script

Output

C:\Users\jbetancourt\workspace-4.3\AntAroundAdvice>ant
Buildfile: C:\Users\jbetancourt\workspace-4.3\AntAroundAdvice\build.xml

compile:
hook: {project=demo1,target=compile,when=pre,event=org.apache.tools.ant.BuildEvent}
     [echo] Hello compile world!
hook: root,{target=compile,when=post,event=org.apache.tools.ant.BuildEvent}

BUILD SUCCESSFUL
Total time: 1 second

Around Advice
While possibly useful, a further powerful potential is to allow the bypass of an invocation of a target altogether. One way of doing this is to use a library like XMLTask to modify the build script. This would have to be done before the script is parsed by Ant of course.

Alternative
One issue with described approach is that the target Ant build scripts must be modified (very minor) to hook in the hooks feature. Using the Ant API itself to add targets and change the target dependency chains might be possible. So is changing Ant itself using AspectJ is also possible.

Of course if very complex scenarios are necessary to manage legacy builds, it may be time to replace Ant with something like Gradle.

Project Layout
The project layout used to the test this hook approach is shown below:

|   .classpath
|   .gitignore
|   .project
|   build.xml
|   tree.txt
|   
+---.settings
|       org.eclipse.jdt.core.prefs
|       org.eclipse.jdt.groovy.core.prefs
|       
+---docs
|       .gitignore
|       Backup of docs.wbk
|       docs.docx
|       
+---hooks
|   +---demo1
|   |       compile_targetStarted.groovy
|   |       
|   \---root
|           compile_targetFinished.groovy
|           
+---lib
|       ant-antlr.jar
|       ant.jar
|       groovy-all-1.8.6.jar
|       groovy-all-2.2.0-rc-1.jar
|       
+---src
|   \---main
|       \---groovy
|           \---com
|               \---octodecillion
|                   \---ant
|                           Hook.groovy
|                           

Further reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Notes on using Groovy in Ant Scriptdef

Ant build scripts can define new tasks in a script language using ‘scriptdef‘. The following are a few notes I’m collecting on using the Groovy language with scriptdef.

Since I don’t do this often I have to re-read the info on how to use it. Thus, I’d thought I create a post where I collect this info. for future use.

Groovy Ant task
Groovy already supplies a Groovy Ant script task: org.codehaus.groovy.ant.Groovy.
To use, you define declare it in the Ant script:

<path id="libs">
    <fileset dir="lib">
        <include name="*.jar" />
    </fileset>
</path>

<taskdef name="groovy" 
      classname="org.codehaus.groovy.ant.Groovy" 
      classpathref="libs"/>

Then use it:

<groovy>
   println "Hello World"
</groovy>

The bindings supplied with the Groovy Ant task are:

Name Description
ant an instance of AntBuilder that knows about the current ant project
project the current ant project
properties a Map of ant properties
target the owning target that invoked this groovy script
task the wrapping task, can access anything needed in org.apache.tools.ant.Task
args command line arguments, if any

ScriptDef task
The ScriptDef task in Ant just allows the creation of custom tasks using a scripting language, unlike the Taskdef task. I’m sure there are other approaches, perhaps for many situations, a taskdef that uses the groovy task within it is doable.

Example

Listing 1, example ant script
<?xml version="1.0" encoding="UTF-8"?>
<project name="project" default="demo1" basedir=".">
	<path id="libs">
	    <fileset dir="lib">
	    	<include name="*.jar" />
	    </fileset>
	</path>
	 
	<!-- Groovy library -->
	<taskdef name="groovy" 
		classname="org.codehaus.groovy.ant.Groovy" 
		classpathref="libs"/>
	 
    <scriptdef name="groovyInfo" language="groovy" 
    	classpathref="libs"
    	src="src/ScriptInfo.groovy">
    	
    	<attribute name="attr1"/>
    	<element name="fileset" type="fileset"/>
    	
 	</scriptdef>
	
    <!-- target: default -->
    <target name="demo1" description="description">
    	
    	<groovyInfo attr1="anAttrib">
    		<fileset dir="src"/>
    		<fileset dir="lib"/>    		
    	</groovyInfo>
        
    </target>

</project>

Hmmm. I’m thinking of perhaps creating a Groovy utility that one can just use in a Scriptdef to give the same bindings as the Groovy Ant task.

Listing 1, ScriptInfo.groovy source
println "project: ${project ?: ''}"
println "properties: ${properties ?: ''}"
println "Object:\n ${dump()}"
println "The binding:\n ${properties['binding'].dump()}"

int indent = 1
println "\n\n\nProperties"
binding.getProperties().each{ p ->
	if(p.value instanceof Map){
		println ' ' * indent + p.key + ":"
		indent ++
		p.value.each{ entry ->
			println '  ' * indent + "${entry}"
		}
		indent--
	}else{
		println "${' ' * indent}prop: $p"
	}
}

println "\nDirect access of attribute"
println  "\tattribute attr1=" + binding.attributes.get('attr1')

println "Direct access to elements"
elements.fileset.each{ fs ->
  fs.each{
    println "\t$it"
  }
}

The result of running the above Ant script is shown below:

demo1:
 project: org.apache.tools.ant.Project@5213fce3
 properties: [class:class Script1, binding:org.codehaus.groovy.jsr223.GroovyScriptEngineImpl$1@11a7a000]
 Object:
  <Script1@4d129d08 binding=org.codehaus.groovy.jsr223.GroovyScriptEngineImpl$1@11a7a000>
 The binding:
  <org.codehaus.groovy.jsr223.GroovyScriptEngineImpl$1@11a7a000 val$ctx=javax.script.SimpleScriptContext@668ce272 this$0=org.codehaus.groovy.jsr223.GroovyScriptEngineImpl@2b66def7 variables=[project:org.apache.tools.ant.Project@5213fce3, context:javax.script.SimpleScriptContext@668ce272, self:org.apache.tools.ant.taskdefs.optional.script.ScriptDefBase@6eb84063, attributes:[attr1:anAttrib], elements:[fileset:[BindTest.class;BindTest.groovy;ScriptInfo.groovy, groovy-all-2.2.1.jar]], out:java.io.PrintWriter@28b5c5f]>
 
 
 
 Properties
  prop: class=class org.codehaus.groovy.jsr223.GroovyScriptEngineImpl$1
  variables:
     project=org.apache.tools.ant.Project@5213fce3
     context=javax.script.SimpleScriptContext@668ce272
     self=org.apache.tools.ant.taskdefs.optional.script.ScriptDefBase@6eb84063
     attributes={attr1=anAttrib}
     elements={fileset=[BindTest.class;BindTest.groovy;ScriptInfo.groovy, groovy-all-2.2.1.jar]}
     out=java.io.PrintWriter@28b5c5f
 
 Direct access of attribute
 	attribute attr1=anAttrib
 Direct access to elements
 	C:\Users\jbetancourt\workspace-4.3\ScriptdefInfo\src\BindTest.class
 	C:\Users\jbetancourt\workspace-4.3\ScriptdefInfo\src\BindTest.groovy
 	C:\Users\jbetancourt\workspace-4.3\ScriptdefInfo\src\ScriptInfo.groovy
 	C:\Users\jbetancourt\workspace-4.3\ScriptdefInfo\lib\groovy-all-2.2.1.jar
BUILD SUCCESSFUL
Total time: 1 second

About Ant
Ant is the breakthrough build system for the Java platform. Since there are now many other build systems, such as Maven and Gradle, Ant could be seen as a legacy build system, in some sense. JSON and the resurgence of scripting languages is changing the view of an build language in XML.
From the Ant homepage:

“Apache Ant is a Java library and command-line tool whose mission is to drive processes described in build files as targets and extension points dependent upon each other. The main known usage of Ant is the build of Java applications. Ant supplies a number of built-in tasks allowing to compile, assemble, test and run Java applications. Ant can also be used effectively to build non Java applications, for instance C or C++ applications. More generally, Ant can be used to pilot any type of process which can be described in terms of targets and tasks. ”

About Groovy
From the Groovy home page we have:

  • is an agile and dynamic language for the Java Virtual Machine
  • builds upon the strengths of Java but has additional power features inspired by languages like Python, Ruby and Smalltalk
  • makes modern programming features available to Java developers with almost-zero learning curve
  • provides the ability to statically type check and statically compile your code for robustness and performance
  • supports Domain-Specific Languages and other compact syntax so your code becomes easy to read and maintain
  • makes writing shell and build scripts easy with its powerful processing primitives, OO abilities and an Ant DSL
  • increases developer productivity by reducing scaffolding code when developing web, GUI, database or console applications
  • simplifies testing by supporting unit testing and mocking out-of-the-box
  • seamlessly integrates with all existing Java classes and libraries
  • compiles straight to Java bytecode so you can use it anywhere you can use Java

No, they didn’t pay me to advertise Groovy. 🙂

Further Reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Ant transforms using Groovy template scriptdef

Ant provides powerful transformation capability. For example, the copy task can include filters that replace tokens in the text. There is also the XSLT task to perform more complex XML based transforms.

Sometimes you may need more dynamic or procedural transforms for non-xml based templates. While this can be done in Ant, they should not be; Ant should really only be used in a declarative manner. One can do this outside of Ant using custom solutions or use the various templating frameworks, such as Velocity and Freemarker, which provide Ant plugins.

Another alternative is the Groovy language which has always provided text processing in the form of GStrings and templates. These are used in various solutions such as Groovlets and Groovy Server Pages. Below I show how Groovy templates can be easily used in Ant.

listing 1 shows a meeting agenda text template with a list of topics. Note that it has a GString interpolated string using “$” character. It also, analogous to Java Server Pages, uses a scriptlet with <% and %> to execute code. Yes I know, scriptlets are evil.

List 1, template1.txt, a template:

Hello ${name}!
Agenda is:
<% 
   subject.split(',').each{
%>- $it 
<% }	
%>

In listing 2 below, an Ant script invokes the “template” task to transform the template in listing 1.

Listing 2, build.xml, use template task:

<project default="init">
	
<import file="tasks.xml"/>
	
<target name="init" 
  xmlns:jb="http://octodecillion.com">
  <jb:template 
	src="template1.txt" 
	dest="temp.txt" 
	property="result" 
	failOnError="true">
		
	<property name="name"    
		value="world"/>
	<property name="subject" 
		value="beans,GStrings,templates"/>
		
  </jb:template>
	
  <echo>${result}</echo>
	
</target>	
</project>

Listing 3 shows an example run.

Listing 3, example use:

init:
Saving result to temp.txt
     [echo] Hello world!
     [echo]     Agenda is:
     [echo]     - beans
     [echo]     - GStrings
     [echo]     - templates
     [echo]

BUILD SUCCESSFUL

Listing 4 is the implementation. It is a simple scriptdef using Groovy as the script language. The Groovy code is inline but could have been in external file using the “src” attribute of the scriptdef declaration.

Listing 4, tasks.xml, the scriptdef implementation:

<project>
<!-- config stuff not shown -->

<!--
Render a Groovy template using properties to create the binding 
 -->	
<scriptdef name="template" 
  language="Groovy" 
  classpathref="groovy.lib" 
  uri="http://octodecillion.com">
	
  <attribute name="src"/>   <!--  required -->
  <attribute name="dest"/>  <!--  optional -->
  <attribute name="property"/> <!--  optional -->
  <attribute name="failOnError"/> <!--  optional, true -->
  <element name="property" type="property"/> <!--  required -->
	
<![CDATA[
def SRC= 'src';
def PROPERTY = 'property'
def DEST = 'dest'
def FAILONERROR = 'failonerror'
		
try{
	// convert element properties to map
	def map = [:]
	elements.get(PROPERTY).each{
		map.put(it.name,it.value)
	}
			
	// render the template
	String result = new groovy.text.GStringTemplateEngine()
	  .createTemplate(new File(attributes.get(SRC))
	  .getText())
	  .make(map).toString()
	
	def prop = attributes.get(PROPERTY)
	if(prop){ // save to property?
		project.setNewProperty(prop, result)
	}
		
	def dest = attributes.get(DEST)
	if(dest){ // write to file?
		project.log("Saving to $dest",project.MSG_INFO)
		new File(dest).write(result)
	}
}catch(Exception ex){
	def f = attributes.get(FAILONERROR)
	def failOnError = f ? f : true;

	if(failOnError == 'true'){
		throw ex;
	}		
}			
]]>
</scriptdef>  <!-- end name="template" -->

</project>

Summary
Shown was an example of using Groovy templates in an Ant task. A simple scriptdef was created to allow reuse in Ant scripts.

Extensions
Kind of begs the question, since scriptlets have a bad rep, should there instead be a way of using taglibs in the templates. I think Grails implemented Groovy taglibs.

References

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Jenkins CI Server is great

Finally got a Jenkins server installed. Had a host of system issues, like communicating to our source code repo.

Jenkins is a joy to use. Well, it is not perfect, what is? Like, I need to pass the user’s name that invoked a build via Jenkins to the target DOS script (yea, Windows) that eventually invokes the legacy Ant scripts. A quick Google search shows that this is asked in various ways, but no answers. For example, here or here. Hmmmm.

Anyway, now comes a trial use, to see if it is what we really need and can we manage it to do what we will want. With 400 plugins, I don’t see how it could lack. Plus, I’m sure I can use the Groovy plugin to cobble something up. Jenkins even includes a Groovy Console. Finally, there is a road map for possible migration of legacy Ant scripts to Gradle using the Gradle Plugin.

I take back my past snarky comment. Jenkins is not just a pretty face on Cron.

Off Topic
Was watching the Easyb intro video. BDD is interesting. Definitely “should” is a better then “test”. With so many great tools why are products still bug ridden?

Enterprise Level CI Server?
I was told that QuickBuild is better for corporate use.

Wikis and the home link
BTW, is there some Wiki law that says a wiki shall never ever have a link to the parent project? If you get tossed into a wiki by following a link, invariably you will click in agony at links that should go to the real home. Instead, you have to edit the URL in the address bar. Since I never curse, I can’t write “wtf”.

More stuff

  1. Jenkins home page
  2. QuickBuild

  3. Continuous integration Not a very good Wikipedia article
  4. Continuous Integration Much better
  5. Continuous Integration in Agile Software Development
  6. Hooking into the Jenkins(Hudson) API
  7. Five Cool Things You Can Do With Groovy Scripts
  8. Parameterized Builds in Jenkins – choosing subversion folders
  9. Groovy Console
  10. Groovy plugin
  11. Switching to Jenkins–Download and Install Artifact Script for Tester
  12. Gradle Plugin
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

How to fail an Ant Groovy Scriptdef task

While you can certainly just throw an exception in a scriptdef to stop an Ant script that is using a scriptdef, perhaps it is best to use Ant’s Exit task. This allows Ant to do whatever it has to do as it handles the resulting BuildException.

The exit task is used in an Ant script as “fail”. To use ‘fail’ in a scriptdef you just use the predefined ‘self’ variable that references the scriptdef instance. This is illustrated in listing 1 below.

An alternative to the scriptdef ant task is to use the ‘groovy’ task that comes with the Groovy environment. This is perhaps the simplest approach when you don’t need the full structure that the scriptdef task makes available to a script. The scriptdef task allows one to pass in Ant XML elements and attributes.

Listing 1

<project name="example" default="demo">

  <!-- reference the groovy libs -->
  <path id= "libs" >
    <fileset dir= "../lib">
      <include name="groovy/groovy-all-1.8.6.jar" />
    </fileset>
  </path>

  <!-- An inline scriptdef that throws an exception -->
  <scriptdef name="solveProblem" 
        language="Groovy" classpathref="libs">

     self.fail("intentionally failed")	 

  </scriptdef>

  <!-- a call of the scriptdef -->
  <target name="demo">
	<solveProblem/>
  </target>
</project>

A run …

>ant -f fail.xml
Buildfile: fail.xml

demo:

BUILD FAILED
fail.xml:15: intentionally failed

Total time: 1 second

Further reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Archive files using Ant from Groovy

In Ant The zip task takes a fileset but only creates one archive. How do you zip files into individual archives?

Use case
One use case for this is log files. Sooner rather then later, every system must deal with the growth of log files. One of the best utilities for handling this is logrotate. This is available on *nix systems. There are probably native versions that do similar tasks in Windows, but I did not locate any ‘free as in beer’ ones.

Approach
Since I’m working with Ant, on Windoze, and can’t install cygwin or other system, will use Groovy to get around Ant limitations. This will not be an attempt to recreate logrotate, of course.

As in a previous post, we can use the Groovy AntBuilder and iterate a fileset to repeatably call an Ant task. In this case we’ll be invoking the zip task. Note that the same approach could be used to create a scriptdef to do this directly in an Ant build script. This would be more usable since we could declaratively define the fileset, etc.

Example

First the configuration. In listing 1, we use a JSON configuration file. No reason, I’m just tired of Property files.

Listing 1, JSON configuration file

{ "about":
    {
    	"project":"archivelogs",
    	"author":"jbetancourt",
    	"date":"20110909T2156"    	
    },
  "config":
	{
		"basedir":"test/logs",
		"archivedir":"test/logs/archive",
		"includes":"*.log",
		"propertyName":"found.files",
		"when":"before",
		"update":"true",
		"days":"1",
		"level":"9"
	}
}

In listing 2, the main entry point calls the config loader and then the ‘archiveLogs’ method.

The fileset created with the AntBuilder uses an Ant ‘date’ selector to find the files that are x amount of days old. Then the resulting fileset is scanned in a loop, invoking the zip task as we go and then deleting the just zipped file. It is assumed here that the target file name has descriptive information, like a date. And, we are not adding a “rotate” counter to the name for future logrotate like behavior.

Listing 2, Groovy archive source

package ant

import org.apache.tools.ant.*
import java.text.*
import groovy.json.JsonSlurper

/**
 * @author jbetancourt
 */
class ArchiveLogs {
	def format = "yyMMdd'T'kkmm"
	def df = new SimpleDateFormat(format)

	/** entry point */
	static main(args) {
		def als = new ArchiveLogs()	
		
		def p = (
		  (args) ?
		  args[0] : "test/conf/archivelogs.json")
				
		def map = als.loadConfiguration(p)
		als.archiveLogs(map)
	}

	/**  */
	def archiveLogs(conf) {
		def base = conf.base
		def targetDateString = df.format(
			(new Date() - Integer.parseInt(conf.days))
		)
		
		def ant = new AntBuilder()		
		ant.fileset( 
			dir:conf.basedir, 
			includes:conf.includes, 
			id:conf.propertyName){			
				date( 
					datetime:targetDateString,
					when:conf.when,
					pattern:format)
		}	
		
		def ref = ant.project.getReference(
			 conf.propertyName)
		
		ref.each{ fileResource ->
			File file = fileResource.getFile()
			def name = file.getName()
			
			ant.zip(
				basedir:conf.basedir,
				destfile:
				 "${conf.archivedir}/${name}.zip",
				update:conf.update,
				level:conf.level,
				includes:name
			)
			
			ant.delete(verbose:"true",file:file.getPath())			
		}		
		
	}	
	
	/** load json file */
	def loadConfiguration(String path){
		def slurper = new JsonSlurper()
		def obj = slurper.parse(
			 new File(path).newReader())
		
		if(obj.config.debug == 'true'){
			obj.config.each{
				println it
			}		
		}
		
		return obj.config
		
	}
}

Caveats
This is, of course, a simplistic approach. For example, what if the file is being used when we attempt to delete it? Or we fail to zip the file, but continue to delete it? And, this source, being an example, does not have any error handling.

Alternatives
The ant-contribs project has looping support, foreach. Directly using the Java util packages for archiving, such as java.util.zip. Or, the libraries found in, for example, the Apache commons project.

Further Reading


“cafe”, egberto gismonti

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Groovy Scriptdef to detect properties with trailing spaces

One potential source of errors in a project is the presence of undesired terminating spaces on properties loaded by java.util.Properties. Since property files are also used to deploy or configure applications, it is possible for these sources of errors to disrupt deployed services.

Alternatives
There are of course many options to prevent this from happening, such as:
1. Property file or general editors that can detect or show “spaces”.
2. Another is to create property file loaders that can ‘trim’ spaces.
3. And, yet another is that configuration properties are part of the resources that a management layer monitors as part of an autonomic processing.
4. Perhaps an optimal and most immediate approach is to just have actual tests triggered on any changes, and, where it matters, properties are validated according to specified rules.

The simple approach
An Ant Scriptdef definition implemented in the Groovy language. This can be easily used in the build and deployment process. It can also be used at the deployment location or remotely to check deployed property files.

In listing 1, an Ant script shows the definition of the scriptdef and its use.

Listing 1, Snippet of Ant script

<scriptdef name="blankpropertycheck" 
      language="Groovy" 
      classpathref="libs" 
      src="TrailingSpacePropertiesCheck.groovy">		
		
             <attribute name="failonerror"/>
             <attribute name="property"/>
             <attribute name="verbose"/>
		
             <element name="fileset" type="fileset"/>		
</scriptdef>

<target name="init"  description="---initialize build">
    <!-- use the scriptdef -->	
    <blankpropertycheck
           failonerror="true"
           property="anyFound">		

        <fileset dir=".">
             <include name="**/*.properties"/>
        </fileset>		
			
    </blankpropertycheck>
		
    <echo>Properties with trailing spaces:</echo>
    <echo>${anyFound}</echo>
		
</target>

The new task definition specification:

Attributes
failonerror optional
verbose optional, one of verbose or property
property optional, one of verbose or property
Elements
fileset required

Now we create two example property files in the directory (not shown here) and run the Ant script, with results shown in listing 2. This output was produced with a debug variable set true, to show the data binding in the scriptdef.

Listing 2, sample run

C:tempantblanksCheck&gt;ant
Buildfile: C:tempantblanksCheckbuild.xml

init:
[blankpropertycheck] ****************************************************************
[blankpropertycheck] failOnError='true'
[blankpropertycheck] propToSave='anyFound'
[blankpropertycheck] verbose='null'
[blankpropertycheck] fileset='abc.properties;liblog4j.properties;libxyz.properties'
[blankpropertycheck] ****************************************************************
     [echo] Properties with trailing spaces:
     [echo] C:tempantblanksCheckabc.properties
     [echo]     four=[4    ]
     [echo]     twoWithSpaces=[2  ]
     [echo] C:tempantblanksChecklibxyz.properties
     [echo]     badwiththreespace=[m   ]

BUILD SUCCESSFUL
Total time: 1 second

The Groovy script is shown in listing 3 below. As implemented, the failonerror is for script or variable binding errors. Since properties could by design have trailing spaces, this is not considered an error. It is easy to change the script to behave differently, of course.

The scripts also serves as an example (to myself) of:

  • regular expression use
  • dynamic object to boolean conditionals
  • accessing object properties via a map
  • reusing closures as actions

Listing 3

/*
    File: TrailingSpacePropertiesCheck.groovy
    Author: josef betancourt
*/

import org.apache.tools.ant.*
import org.apache.tools.ant.types.*

boolean debug = true

failOnError = attributes.get("failonerror")
propToSave = attributes.get("property")
verbose = attributes.get('verbose')
fileset = elements.get('fileset')[0] as FileSet

if(debug){
    band = '*' * 72
    println(band)
    ['failOnError','propToSave','verbose','fileset'].each{
        println "$it='${this[it]}'"
    }
    println(band)
}

// Do we have all essential attributes and elements?
def msg =''
msg =  !fileset ? "'fileset' not found;" : ''
msg += !(verbose || propToSave) ?
         "both 'verbose' and 'propToSave' not found" : '';

if(msg){
    if(failOnError){
        throw new BuildException(msg)
    }
    
    println msg
    return    
}


pattern =  ~/(s+)$/

/** Count any trailing spaces in string  */
int countTrailingSpaces(aString){
    def matcher = pattern.matcher(aString)
    def result = matcher.find()
    
    if(result){
        def blanks = matcher[0][1]
        return blanks.size()
    }
    
    return 0
}


def props = new Properties()
def count
// map of  file:[list of potential properties in error]
def results = [:]

// analyize each file in fileset
fileset.each{
    File file = it.getFile()    
    props.load(file.newReader());
    def list = []
    
    props.each{ entry ->
        count = countTrailingSpaces(entry.value)
        if(count){
            list.push(entry)
        }
    }
    
    if(list){
        results[file.getPath()] = list
    }
    
    props.clear()

} // end each file

def action // a closure
def resString = ""

if(!results){
    return
}

if(verbose){
    action = { x -> println x}
}else{
    // converts to StringBuilder
    resString <<= ""
    action = {x -> resString << (x + "n") }
}
    
results.each{
    action("${it.key}")
    it.value.each{
        action("t${it.key}=[${it.value}]")
    }
}
    
if(!verbose){
    project.setNewProperty(propToSave,resString.toString())
}

// end of file TrailingBlankPropertiesCheck.groovy

Properties
Should any properties in configuration or data files have trailing spaces? Seems like having one of those “kick me” signs on your back. If one could use visible terminators like a quote or brace, but usually a Java based properties file will not have terminators, afaik.

Summary
Shown was a simple method of checking property files for entries with trailing spaces on values.

Further Reading

  • The Groovy Truth” Groovy has special rules for coercing non-boolean objects to a boolean value. Using this language feature makes coding much more productive.
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.