Search

Dark theme | Light theme
Showing posts with label Groovy programming. Show all posts
Showing posts with label Groovy programming. Show all posts

November 15, 2010

Groovy Goodness: Use GroovyWS to Access SOAP Web Services (Part 3)

We learned in the previous posts (Part 1, Part 2) how to create a Grails SOAP web service and how to access it with GroovyWS. In this post we learn how we can apply logging interceptors to our client. The output of these logging interceptors is the XML send and received from the SOAP web service. This can be very useful for tracing the messages or for debugging.

The WSClient class of GroovyWS has a property named client of type org.apache.cxf.endpoint.Client. This is our entry point to the Apache CXF client support. We need a way to get a reference to the client property so we can apply logging interceptors for the input and output. Unfortunately the WSClient API has not a method to get a reference to the property. But we write out client code in Groovy so we can use Groovy's MetaClass support to write out own get method to return the client property.

package com.mrhaki.groovyws.client

import groovyx.net.ws.WSClient
import org.apache.cxf.interceptor.LoggingInInterceptor
import org.apache.cxf.interceptor.LoggingOutInterceptor

class BlogWSClient {

    String wsdlUrl

    def proxy

    def findAuthor(String name) {
        createProxy()
        def searchRequest = createSearchRequest(name)
        proxy.findAuthor searchRequest
    }

    private def createSearchRequest(String name) {
        def searchRequest = proxy.create('com.mrhaki.groovyws.server.SearchRequest')
        searchRequest.authorName = name
        searchRequest
    }

    private void createProxy() {
        if (!proxy) {
            WSClient.metaClass.getCxfClient = { ->
                delegate.client
            }
            proxy = new WSClient(wsdlUrl, this.class.classLoader)
            proxy.initialize()

            def cxfClient = proxy.cxfClient
            cxfClient.inInterceptors.add(new LoggingInInterceptor())
            cxfClient.outInterceptors.add(new LoggingOutInterceptor())
        }
    }

}

After this change we can run our tests again from the Gradle build file with $ gradle -q test. We can open the generated test result HTML file and look for the System.err output to see the input and output messages:

Nov 14, 2010 11:00:15 PM org.apache.cxf.interceptor.LoggingOutInterceptor$LoggingCallback onClose
INFO: Outbound Message
---------------------------
ID: 1
Address: http://localhost:8080/server/services/blog
Encoding: UTF-8
Content-Type: text/xml
Headers: {SOAPAction=[""], Accept=[*/*]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns1:findAuthor xmlns:ns1="http://server.groovyws.mrhaki.com"><in0 xmlns="http://server.groovyws.mrhaki.com"><authorName>mrhaki</authorName></in0></ns1:findAuthor></soap:Body></soap:Envelope>
--------------------------------------
Nov 14, 2010 11:00:15 PM org.apache.cxf.interceptor.LoggingInInterceptor logging
INFO: Inbound Message
----------------------------
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml;charset=UTF-8
Headers: {content-type=[text/xml;charset=UTF-8], Date=[Sun, 14 Nov 2010 22:00:15 GMT], transfer-encoding=[chunked], Server=[Apache-Coyote/1.1]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soap:Body><ns1:findAuthorResponse xmlns:ns1="http://server.groovyws.mrhaki.com"><ns1:out><blogItems xmlns="http://server.groovyws.mrhaki.com"><BlogItem><id>2</id><text>Sample blogitem one.</text><title>Title1</title><version>0</version></BlogItem><BlogItem><id>1</id><text>Sample blogitem two.</text><title>Title2</title><version>0</version></BlogItem></blogItems><id xmlns="http://server.groovyws.mrhaki.com">1</id><name xmlns="http://server.groovyws.mrhaki.com">mrhaki</name><version xmlns="http://server.groovyws.mrhaki.com">0</version></ns1:out></ns1:findAuthorResponse></soap:Body></soap:Envelope>
--------------------------------------

November 14, 2010

Groovy Goodness: Use GroovyWS to Access SOAP Web Services (Part 2)

In a previous post we learned how to create a SOAP web service with the XFire plugin in Grails. The generated WSDL defined that the properties of our objects could be null by default and the minimum occurence is 0. And because of this we must work with JAXBElement objects to get the real object values. In this post we change the property mapping so the properties cannot be null and have a minimum occurence of 1. And then we can change our client code and deal with the object values directly instead of via a JAXBElement object.

Grails SOAP web service

We can change the default mapping of our objects that is generated by the XFire plugin. We must add an Aegis mapping XML file to our classpath and in this file we can define the custom mapping we want to apply. The name of the file is <ClassName>.aegis.xml and must be in the same package as the class we are writing the custom mapping for. If we add the XML files to the src/java directory of our Grails application then the files will be copied to the classpath automatically.

After we have created the XML mapping files we must add an extra runtime dependency. So we change the grails-app/conf/BuildConfig.groovy file and add a dependency to Jaxen, because this is needed to make the custom mapping work.

<!-- File: src/java/com/mrhaki/groovyws/server/Author.aegis.xml -->
<mappings>
    <mapping>
        <property name="name" minOccurs="1" nillable="false"/>
        <property name="blogItems" minOccurs="1" componentType="com.mrhaki.groovyws.server.BlogItem"/>
    </mapping>
</mappings>
<!-- File: src/java/com/mrhaki/groovyws/server/BlogItem.aegis.xml -->
<mappings>
    <mapping>
        <property name="title" minOccurs="1" nillable="false"/>
        <property name="text" minOccurs="1" nillable="false"/>
    </mapping>
</mappings>
<!-- File: src/java/com/mrhaki/groovyws/server/SearchRequest.aegis.xml -->
<mappings>
    <mapping>
        <property name="authorName" minOccurs="1" nillable="false"/>
    </mapping>
</mappings>
// File: grails-app/conf/BuildConfig.groovy
grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
grails.project.dependency.resolution = {
    inherits("global") {
    }
    log "warn"
    repositories {
        grailsPlugins()
        grailsHome()
        grailsCentral()
        mavenCentral()
    }
    dependencies {
        runtime('jaxen:jaxen:1.1.1') {
            transitive = false
        }
    }
}

We can run our Grails application with $ grails run-app and open the URL http://localhost:8080/server/services/blog?wsdl to see the changes in the generated WSDL file.

GroovyWS Client

Because the definition of our SOAP web service is changed we can also change the client code. We now no longer have use JAXBElement objects, so our code is much cleaner. We can access the object types directly. For example for the dynamic SearchRequest object we can set the authorName property directly. In our old client code we had to create a JAXBElement object to set the value.

package com.mrhaki.groovyws.client

import groovyx.net.ws.WSClient

class BlogWSClient {

    String wsdlUrl

    def proxy

    def findAuthor(String name) {
        createProxy()
        def searchRequest = createSearchRequest(name)
        proxy.findAuthor searchRequest
    }

    private def createSearchRequest(String name) {
        def searchRequest = proxy.create('com.mrhaki.groovyws.server.SearchRequest')
        searchRequest.authorName = name
        searchRequest
    }

    private void createProxy() {
        if (!proxy) {
            proxy = new WSClient(wsdlUrl, this.class.classLoader)
            proxy.initialize()
        }
    }

}
package com.mrhaki.groovyws.client

import spock.lang.Specification

class BlogWSClientSpec extends Specification {

    def client = new BlogWSClient(wsdlUrl: 'http://localhost:8080/server/services/blog?wsdl')

    def "get author with name mrhaki and two blog items"() {
        when:
        def author = client.findAuthor('mrhaki')

        then:
        'mrhaki' == author.name
        def arrayOfBlogItems = author.blogItems
        def blogItems = arrayOfBlogItems.blogItem
        2 == blogItems.size()
        'Title1' == blogItems[0].title
        'Title2' == blogItems[1].title
        'Sample blogitem one.' == blogItems[0].text
        'Sample blogitem two.' == blogItems[1].text
    }

}

In the next post we see how we can add logging interceptors to our client so we can see the input and output from the invocation of the SOAP web service.

November 11, 2010

Groovy Goodness: Use GroovyWS to Access SOAP Web Services

With the GroovyWS module we can consume SOAP web services. Under the hood GroovyWS uses CXF to dynamically create all required classes from a WSDL file. These classes are available via the classloader in our code. So we don't need to convert a WSDL file first to source files, but the classes are generated dynamically and can be used directly in our code. This means that if we can access the WSDL for a web service we can invoke the web service without any explicit code generation.

In this blog post we first write a SOAP web service with Grails and the XFire plugin. Next we create a Gradle project with the client code to invoke and use the SOAP web service we just created. We use Spock to write a specification where we really invoke the web service client and check the results. That is a lot of Groovyness in our project!

Grails SOAP webservice

We start by creating a new Grails project and install the XFire plugin. Then we create a new Grails service, BlogService, and use the plugin to expose the service as SOAP webservice. We create one method in our service: Author findAuthor(SearchRequest search). The parameter search is of type SearchRequest and contains the property authorName, which is used to find an Author object. We define this type to show how we can dynamically create an instance with GroovyWS in the client code.

We also create two domain classes: Author and BlogItem. An Author has a one-to-many relation with BlogItem. Finally we write code in the BootStrap to create a single author with two blog items.

$ grails create-app server
$ cd server
$ grails install-plugin xfire
$ grails create-service com.mrhaki.groovyws.server.Blog
$ grails create-domain-class com.mrhaki.groovyws.server.Author
$ grails create-domain-class com.mrhaki.groovyws.server.BlogItem
// File: grails-app/services/com/mrhaki/groovyws/server/BlogService.groovy
package com.mrhaki.groovyws.server

class BlogService {

    // Make this service a SOAP web service.
    static expose = ['xfire']

    static transactional = true

    Author findAuthor(SearchRequest search) {
        Author.findByName(search.authorName)
    }

}
// File: grails-app/domain/com/mrhaki/groovyws/server/Author.groovy
package com.mrhaki.groovyws.server

class Author {

    String name

    static hasMany = [blogItems: BlogItem]

    static mapping = {
        blogItems lazy: false, sort: 'title'
    }

}
// File: grails-app/domain/com/mrhaki/groovyws/server/BlogItem.groovy
package com.mrhaki.groovyws.server

class BlogItem {

    String text
    String title

    static belongsTo = [author: Author]

    // We must use xmlTransients so belongsTo doesn't
    // cause a StackOverflowError by the XFire plugin.
    static xmlTransients = ['author']

}
// File: src/groovy/com/mrhaki/groovyws/server/SearchRequest.groovy
package com.mrhaki.groovyws.server

class SearchRequest {
    String authorName
}
// File: grails-app/conf/BootStrap.groovy
import com.mrhaki.groovyws.server.*

class BootStrap {

    def init = { servletContext ->
        def blogItem1 = new BlogItem(title: 'Title1', text: 'Sample blogitem one.')
        def blogItem2 = new BlogItem(title: 'Title2', text: 'Sample blogitem two.')
        def author = new Author(name: 'mrhaki')
        author.addToBlogItems(blogItem1)
        author.addToBlogItems(blogItem2)
        author.save()
    }

    def destroy = {
    }
}

We are ready to start our Grails application and we open the url http://localhost:8080/server/services/blog?wsdl to see the generated WSDL file. If we see the WSDL contents we know everything works.

GroovyWS client

Okay we are halfway. Now it is time to write the client code to access our Grails webservice. We create a Gradle project build file with the required dependencies. Next we create the file BlogWSClient.groovy. In this file we use GroovyWS to create the client code. And finally we create a Spock specification to invoke BlogWSClient: BlogWSClientSpec.groovy.

// File: build.gradle
apply plugin:'groovy'

repositories {
    mavenCentral()
}

dependencies {
    groovy 'org.codehaus.groovy:groovy-all:1.7.5'
    compile 'org.codehaus.groovy.modules:groovyws:0.5.2'  // GroovyWS dependency.
    testCompile 'org.spockframework:spock-core:0.4-groovy-1.7'
}
// File: src/main/groovy/com/mrhaki/groovyws/client/BlogWSClient.groovy
package com.mrhaki.groovyws.client

import groovyx.net.ws.WSClient
import javax.xml.bind.JAXBElement
import javax.xml.namespace.QName

class BlogWSClient {

    String wsdlUrl

    def proxy

    def findAuthor(String name) {
        createProxy()
        def searchRequest = createSearchRequest(name)
        invokeWebservice searchRequest
    }

    private def invokeWebservice(SearchRequest searchRequest) {
        // Invoke webservice method.
        proxy.findAuthor searchRequest
    }

    private def createSearchRequest(String name) {
        // SearchRequest class is dynamically created by GroovyWS,
        // so we must use the proxy.create() method to create a new
        // instance.
        def searchRequest = proxy.create('com.mrhaki.groovyws.server.SearchRequest')

        // The authorName property of SearchRequest can be null, so
        // we must use JAXBElement to set the value.
        searchRequest.authorName = new JAXBElement(new QName("http://server.groovyws.mrhaki.com", "authorName"), String, name)

        searchRequest
    }

    private void createProxy() {
        if (!proxy) {
            // Use GroovyWS to create a client proxy.
            proxy = new WSClient(wsdlUrl, this.class.classLoader)

            // Make sure all required classes are created and available.
            proxy.initialize()
        }
    }

}
// File: src/test/groovy/com/mrhaki/groovyws/client/BlogWSClientSpec.groovy
package com.mrhaki.groovyws.client

import spock.lang.Specification

class BlogWSClientSpec extends Specification {

    def "get author with name mrhaki"() {
        given:
        def client = new BlogWSClient(wsdlUrl: 'http://localhost:8080/server/services/blog?wsdl')

        when:
        def author = client.findAuthor('mrhaki')

        then:
        // JAXBElement is returned, so we must use 
        // the value property of this element 
        // to get the real type and value.
        'mrhaki' == author.name.value  
        def arrayOfBlogItems = author.blogItems.value
        def blogItems = arrayOfBlogItems.blogItem
        2 == blogItems.size()
        'Title1' == blogItems[0].title.value
        'Title2' == blogItems[1].title.value
        'Sample blogitem one.' == blogItems[0].text.value
        'Sample blogitem two.' == blogItems[1].text.value
   }
}

We have all the files and are ready to run the test:

$ gradle -q test

After the test has succesfully run we can open the test report HTML file. It is a good idea to take a look at the System.err output. The output shows which classes are generated by GroovyWS dynamically:

...
Nov 11, 2010 9:09:33 PM org.apache.cxf.jaxb.JAXBUtils logGeneratedClassNames
INFO: Created classes: com.mrhaki.groovyws.server.ArrayOfBlogItem, com.mrhaki.groovyws.server.Author, com.mrhaki.groovyws.server.BlogItem, com.mrhaki.groovyws.server.FindAuthor, com.mrhaki.groovyws.server.FindAuthorResponse, com.mrhaki.groovyws.server.ObjectFactory, com.mrhaki.groovyws.server.SearchRequest, com.mrhaki.groovyws.server.This$Dist$Get$2, com.mrhaki.groovyws.server.This$Dist$Get$2Response, com.mrhaki.groovyws.server.This$Dist$Invoke$2, com.mrhaki.groovyws.server.This$Dist$Invoke$2Response, com.mrhaki.groovyws.server.This$Dist$Set$2, com.mrhaki.groovyws.server.This$Dist$Set$2Response

This code shows how easy it is to invoke a SOAP web service with GroovyWS. Even if the web service has complex objects as a reponse or as input parameter. In a future post we first see how we can configure the Grails SOAP web service so we don't need to use JAXBElement objects. This makes the client code even more readable and easier to create. And we will take a look at how we can change the logging for the generated client code.

November 10, 2010

Gradle Goodness: Running Tests in Parallel

Once we apply the Java plugin we can run our tests with the test task. Normally each test is run sequentially, but we can also run tests in parallel. This speeds up the test task considerably especially with a lot of tests. We set the property maxParallelForks to a number greater than 1 to enable parallel tests.

We can also set the additional property forkEvery on the test task. With this property we define how many tests should run in a parallel test fork.

In the following build script we first create a lot of tests with the createTests task and then we can run them with the test task. We can pass the properties maxParallelForks and forkEvery to play around and see what happens. Of course we can also use hard coded values in our build script for the test task properties.

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.8.2'
}

test {
    if (project.hasProperty('maxParallelForks')) 
        maxParallelForks = project.maxParallelForks as int
    if (project.hasProperty('forkEvery')) 
        forkEvery = project.forkEvery as int
}

packages = 10
tests = 30

task createTests << {
    (0..packages).each { packageCounter ->
        def packageName = "gradletest${packageCounter}"
        (0..tests).each { classCounter ->
            def testClassName = "Gradle${classCounter}Test"
            copy {
                from 'src/templates'
                into 'src/test/java'
                expand([packageName: packageName, testClassName: testClassName])
                rename '(.*).java', packageName + '/' + testClassName + '.java'
                include 'SampleTest.java'
            }
        }
    }
}
// File: src/templates/SampleTest.java
package ${packageName};

import org.junit.Test;
import static org.junit.Assert.*;

public class ${testClassName} {

    @Test
    public void testString() throws Exception {
        Thread.sleep(200);
        assertEquals("mrhaki", "mr" + "haki");
    }
}

So first we create the tests: $ gradle createTests. And then we can experiment with different options:

$ gradle clean test
...
BUILD SUCCESSFUL

Total time: 1 mins 33.942 secs

$ gradle clean test -PmaxParallelForks=10
...
BUILD SUCCESSFUL

Total time: 36.68 secs

$ gradle clean test -PmaxParallelForks=4 -PforkEvery=25
...
BUILD SUCCESSFUL

Total time: 56.066 secs

Written with Gradle 0.9.

November 9, 2010

Gradle Goodness: Set Java Version Compatibility

We can use the properties sourceCompatibility and targetCompatibility provided by the Java plugin to define the Java version compatibility for compiling sources. The value of these properties is a JavaVersion enum constant, a String value or a Number. If the value is a String or Number we can even leave out the 1. portion for Java 1.5 and 1.6. So we can just use 5 or '6' for example.

We can even use our own custom classes as long as we override the toString() method and return a String value that is valid as a Java version.

apply plugin: 'java'

sourceCompatibility = 1.6 // or '1.6', '6', 6, JavaVersion.VERSION_1_6, new Compatibility('Java 6')

class Compatibility {
    String version

    Compatibility(String versionValue) {
        def matcher = (versionValue =~ /Java (\d)/)
        version = matcher[0][1]
    }

    String toString() { version }
}

Written with Gradle 0.9.

November 8, 2010

Gradle Goodness: Set a Project Description

We can use the description property of a Gradle project to describe the purpose of our project.

version = '1.0-bugfix'

description = """\
Simple Gradle project to show how we can
use the description property of a project.
------------------------------------------
Project version: ${version}
Gradle version: ${gradle.gradleVersion}
------------------------------------------
"""

When we invoke $ gradle projects we get to see our project description in the output.

$ gradle -q projects

Root project 'description' - Simple Gradle project to show how we can
use the description property of a project.
------------------------------------------
Project version: 1.0-bugfix
Gradle version: 0.9-rc-2
------------------------------------------

No sub-projects

To see a list of the tasks of a project, run gradle <project-path>:tasks
For example, try running gradle :tasks

Written with Gradle 0.9.

November 5, 2010

Gradle Goodness: Add Filtering to ProcessResources Tasks

When we apply the Java plugin (or any dependent plugin like the Groovy plugin) we get new tasks in our project to copy resources from the source directory to the classes directory. So if we have a file app.properties in the directory src/main/resources we can run the task $ gradle processResources and the file is copied to build/classes/main/app.properties. But what if we want to apply for example some filtering while the file is copied? Or if we want to rename the file? How we can configure the processResources task?

The task itself is just an implementation of the Copy task. This means we can use all the configuration options from the Copy task. And that includes filtering and renaming the files. So we need to find all tasks in our project that copy resources and then add for example filtering to the configuration. The following build script shows how we can do this:

import org.apache.tools.ant.filters.*

apply plugin: 'java'

version = '1.0-DEVELOPMENT'

afterEvaluate {
    configure(allProcessResourcesTasks()) {
        filter(ReplaceTokens, 
               tokens: [version: project.version, gradleVersion: project.gradle.gradleVersion])
    }
}

def allProcessResourcesTasks() {
    sourceSets.all.processResourcesTaskName.collect {
        tasks[it]
    }
}

Let's create the following two files in our project directory:

# src/main/resources/app.properties
appversion=@version@
# src/test/resources/test.properties
gradleVersion=@gradleVersion@

We can now execute the build and look at the contents of the copied property files:

$ gradle build
:compileJava
:processResources
:classes
:jar
:assemble
:compileTestJava
:processTestResources
:testClasses
:test
:check
:build

BUILD SUCCESSFUL

Total time: 4.905 secs

$ cat build/classes/main/app.properties 
appversion=1.0-DEVELOPMENT
$ cat build/classes/test/test.properties 
gradleVersion=0.9-rc-2

Written with Gradle 0.9.

November 3, 2010

Gradle Goodness: Create JAR Artifact with Test Code for Java Project

Today, during my Gradle session, someone asked how to create a JAR file with the compiled test classes and test resources. I couldn't get the task syntax right at that moment, so when I was at home I had to find out how we can create that JAR file. And it turned out to be very simple:

apply plugin: 'java'

task testJar(type: Jar) {
    classifier = 'tests'
    from sourceSets.test.classes
}

The magic is in the from method where we use sourceSets.test.classes. Because we use sourceSets.test.classes Gradle knows the task testClasses needs to be executed first before the JAR file can be created. And of course the assemble task will pick up this new task of type Jar automatically.

When we run the build we get the following output:

$ gradle assemble
:compileJava
:processResources
:classes
:jar
:compileTestJava
:processTestResources
:testClasses
:testJar
:assemble

Written with Gradle 0.9.

Source Code from Gradle Session at JFall 2010 on GitHub

This morning I had a great early bird session (8 o'clock in the morning) about Gradle at JFall 2010. The source is now available on GitHub.

October 26, 2010

Gradle Goodness: Base Plugin Usage

Gradle has plugins to provide functionality in a modularized way. One of the basic plugins is the base plugin. This plugin is not part of the public API of Gradle, so the functionality can change. In this post we look at what functionality the base plugin provides for Gradle version 0.9-rc1.

When we apply the base plugin to our project we get a couple of tasks we can use:

assemble
task that builds all archives of the project.
clean
task that deletes the build directory.

We get two configurations:

archives
configuration for archives.
default
extends archives configuration.

For each configuration we get a build and upload task:

buildArchives and buildDefault
builds all artifacts for the configuration.
uploadArchives and uploadDefault
upload artifacts for the configuration.

And we get a task rule clean<taskname> that is capable of cleaning the ouput files of any task we create in our project.

Also the following properties are added to the project:

distDirName
directory to store archives created by the project.
libsDirName
directory to store JAR files created by the project.
archivesBaseName
base name for archives.

In the following sample we define a project with the base plugin and use the tasks, configurations and properties that are added by the plugin:

apply plugin: 'base'

version = '1.0'

archivesBaseName = 'sample'
distsDirName = 'dist'
libsDirName = 'projectlibs'

task simpleJar(type: Jar)

task simpleZip(type: Zip)

artifacts {
    archives simpleJar
}

repositories {
    flatDir name: 'localRepo', dirs: "$buildDir/repository"
}

uploadArchives {
    repositories {
        add project.repositories.localRepo
    }
}

Let's run gradle with a couple of the tasks:

$ gradle clean assemble buildDefault uploadArchives cleanSimpleJar
:clean
:simpleJar
:simpleZip
:assemble
:buildDefault
:uploadArchives
:cleanSimpleJar

BUILD SUCCESSFUL

Total time: 3.416 secs

If we look in the build directory we see what is created:

build
  |
  +- dist
  |    |
  |    +- sample-1.0.zip
  |
  +- projectlibs
  |
  +- repository
       |
       +- sample-1.0.jar

Written with Gradle 0.9.

October 25, 2010

Gradle Goodness: Renaming Files while Copying

With the Gradle copy task we can define renaming rules for the files that are copied. We use the rename() method of the copy task to define the naming rules. We can use a closure where the filename is the argument of the closure. The name we return from the closure is the new name of copied file. Or we can define a regular expression and set the replacement value for the corresponding regular expression. We can use groups in the regular expression and use them in the replacement value like $<group>.

task copyFiles(type: Copy) {
    from 'src/files'
    into "$buildDir/files"
    rename '(.*)-(.*).html', '$2/$1.html'
    rename ~/(.*).template.(.*)/, '$1.$2'
    rename { filename ->
        filename.replace 'java', 'groovy'
    }
}

Let's create some source files, so the renaming rules can be applied to them.

src/files/index-en.html:

<html>
    <body>
        <h1>Hello Gradle</h1>
    </body>
</html>

src/files/index-nl_NL.html:

<html>
    <body>
        <h1>Hallo Gradle</h1>
    </body>
</html>

src/files/sample.template.txt:

Sample file.

src/files/Sample.java:

public class Sample {
    private String gradle = "Gradle";
}

We run $ gradle copyFiles and we get the following files in build/files:

nl_NL
 |
 +-- index.html
en
 |
 +-- index.html
Sample.groovy
sample.txt

Written with Gradle 0.9.

October 24, 2010

Gradle Goodness: Copy Files with Filtering

Gradle's copy task is very powerful and includes filtering capabilities. This means we can change the contents of the files that are copied before they reach their new destination. We use the filter() method to define a filter. The good news is we can reuse the Ant filtering classes from the org.apache.tools.ant.filters package. We define the filtering class and can pass parameters for the filter. Or we can pass a closure which is passed each line as an argument. Within the closure we must return the filtered line.

import org.apache.tools.ant.filters.*

task('filterCopy', type: Copy) {
    from 'src/templates'
    into buildDir
    include '**/*.txt'
    filter { line -> line.contains('Gradle') ? line : '' }
    filter(ReplaceTokens, tokens: [author: 'mrhaki', gradleVersion: gradle.gradleVersion])
    filter(ConcatFilter, prepend: file('src/include/header.txt'))
}

Now let's create a sample text file that will get filtered in src/templates/HelloGradle.txt:

This is just a simple text file. This line will not make it.
We show filtering capabilities of Gradle copy.
This file is written by @author@ with Gradle version @gradleVersion@.

And we create the file src/include/header.txt:

Include this header file.
-------------------------

After we run $ gradle filterCopy we get the following contents for the file build/HelloGradle.txt:

Include this header file.
-------------------------
We show filtering capabilities of Gradle copy in combination with the Ant filtering types.
This file is written by mrhaki with Gradle version 0.9-rc-1.

October 22, 2010

Gradle Goodness: Parse Files with SimpleTemplateEngine in Copy Task

With the copy task of Gradle we can copy files that are parsed by Groovy's SimpleTemplateEngine. This means we can expand properties in the source file and add Groovy code that is going to be executed. We must use the expand() method in the copy task where we can pass properties to be used in the source file.

version = 'DEMO'
group = 'com.mrhaki'

task copy(type: Copy) {
    from 'src/templates'
    into "$buildDir"
    include 'projectinfo.html.template'
    rename { file -> 'projectinfo.html' }
    expand(project: project, title: 'ProjectInfo', generated: new Date())
}

We define the following source file in src/templates/projectinfo.html.template:

<html>
    <head>
        <title>${title}</title>
    </head>
    <body>
        <h1>${project.name}</h1>

        <ul>
        <% project.properties.findAll { k,v -> v instanceof String }.each { key, value -> %>
            <li>$key = $value</li>
        <% } %>
        </ul>

        <hr />
        <p>Generated on ${generated.format('dd-MM-yyyy')}</p>
    </body>
</html>

When we run the copy task we get the following output:

Written with Gradle 0.9.

October 20, 2010

Gradle Goodness: Display Available Tasks

To see which tasks are available for our build we can run Gradle with the command-line option -t or --tasks. Gradle outputs the available tasks from our build script. By default only the tasks which are dependencies on other tasks are shown. To see all tasks we must add the command-line option --all.

3.times { counter ->
    task "lib$counter" {
        description = "Build lib$counter"
        if (counter > 0) {
            dependsOn = ["lib${counter - 1}"]
        }
    }
} 

task compile {
    dependsOn { 
        project.tasks.findAll { 
            it.name.startsWith('lib')
        }
    }
    description = "Compile sources"
}
$ gradle -q -t

------------------------------------------------------------
Root Project
------------------------------------------------------------

Tasks
-----
:compile - Compile sources
$ gradle -q --tasks -all

------------------------------------------------------------
Root Project
------------------------------------------------------------

Tasks
-----
:compile - Compile sources
    :lib0 - Build lib0
    :lib1 - Build lib1
    :lib2 - Build lib2

But if we add our tasks to a group, we get even more verbose output. Gradle will group the tasks together and without the --all option we get to see all tasks belonging to the group, even those that are dependency tasks. And with the --all option we see for each task on which tasks it depends on. So by setting the group property on the task we get much better output when we ask Gradle about the available tasks.

3.times { counter ->
    task "lib$counter" {
        description = "Build lib$counter"
        if (counter > 0) {
            dependsOn = ["lib${counter - 1}"]
        }
    }
} 

task compile {
    dependsOn { 
        project.tasks.findAll { 
            it.name.startsWith('lib')
        }
    }
    description = "Compile sources"
}

tasks*.group = 'Compile'
$ gradle -q -t

------------------------------------------------------------
Root Project
------------------------------------------------------------

Compile tasks
-------------
:compile - Compile sources
:lib0 - Build lib0
:lib1 - Build lib1
:lib2 - Build lib2
$ gradle -q --tasks -all

------------------------------------------------------------
Root Project
------------------------------------------------------------

Compile tasks
-------------
:compile - Compile sources [:lib0, :lib1, :lib2]
:lib0 - Build lib0
:lib1 - Build lib1 [:lib0]
:lib2 - Build lib2 [:lib1]

Written with Gradle 0.9.

October 18, 2010

Gradle Goodness: Excluding Tasks for Execution

In Gradle we can create dependencies between tasks. But we can also exclude certain tasks from those dependencies. We use the command-line option -x or --exclude-task and specify the name of task we don't want to execute. Any dependencies of this task are also excluded from execution. Unless another task depends on the same task and is executed. Let's see how this works with an example:

task copySources << {
    println 'Copy sources.'
}

task copyResources(dependsOn: copySources) << {
    println 'Copy resources.'
}

task jar(dependsOn: [copySources, copyResources]) << {
    println 'Create JAR.'
}

task deploy(dependsOn: [copySources, jar]) << {
    println 'Deploy it.'
}

We execute the deploy task:

$ gradle -q deploy 
Copy sources.
Copy resources.
Create JAR.
Deploy it.

Now we exclude the jar task. Notice how the copySources task is still executed because of the dependency in the deploy task:

$ gradle -q deploy -x jar
Copy sources.
Deploy it.

Written with Gradle 0.9.

October 15, 2010

Gradle Goodness: Automatic Clean Tasks

Gradle adds the task rule clean<Taskname> to our projects when we apply the base plugin. This task is able to remove any output files or directories we have defined for our task. For example we can assign an output file or directory to our task with the outputs property. Or we can use the @OutputFile and @OutputDirectories annotations for custom task classes. The clean<Taskname> rule can delete the output files or directories for the task with the name <Taskname> for us. We don't have to write the clean task ourselves we only have define the base plugin in our project. And Gradle will take care of the rest!.

apply plugin: 'base'

outputDir = file("$buildDir/generated-src")
outputFile = file("$buildDir/output.txt")

task generate << {
    outputDir.mkdirs()
    outputFile.write 'Generated by Gradle.'
}
generate.outputs.files outputFile
generate.outputs.dir outputDir

task showBuildDir << {
    def files = buildDir.listFiles()
    files.each {
        print   it.directory ? 'Dir:  ' : 'File: '
        println it.name
    }
    println "${files.size()} files in $buildDir.name"
}

We can first run the generate task and see the output file and directory.

$ gradle -q generate showBuildDir
Dir:  generated-src
File: output.txt
2 files in build

Next we can run the task cleanGenerate, which is added to the project by Gradle, and see the output files are gone.

$ gradle -q cleanGenerate showBuildDir
0 files in build

Written with Gradle 0.9.

Gradle Goodness: Custom Version Object

The project version in a Gradle project is not limited to a String value, but can be any Object. We must implement the toString() method of the object so Gradle can use it for example in naming JAR files.

In the following build script we define a custom Version class. We create an instance of the Version class and assign it to the project version. In the task listJars we print out the project version and the name of the generated JAR file, which should contain our custom version number.

apply plugin: 'java'

version = new Version(major: 2, minor: 3, releaseType: 'beta', bugfix: 2)

task listJars << {
    println "Project version: $project.version"
    configurations.archives.allArtifactFiles.files.each {
        println "Artifact: $it.name"
    }
}
listJars.dependsOn 'assemble'

class Version {
    int major
    int minor
    int bugfix
    String releaseType
 
    String toString() {
        "$major.$minor-$releaseType${bugfix ?: ''}"
    }
}

We can run our script and get the following output:

$ gradle -q listJars
Project version: 2.3-beta2
Artifact: gradle-2.3-beta2.jar

Written with Gradle 0.9.

October 14, 2010

Gradle Goodness: Set Task Values with Project Convention

In a previous post we wrote a custom task to generate a file with version information. When we created the task in our build file we had to provide values for the task properties version and outputFile. Now we want these values to have default values and we want to be able to set values with project properties instead of only task properties.

First we write a plugin where we create a new Plain Old Groovy Object (POGO) which will store the project properties in a convention object. Next we assign the values from the convention object properties to the task properties with a closure. This means the value of the properties are lazy set: only when the task gets executed the task property values are calculated.

Let's take a look at the source files for the plugin, POGO and task before we see what our new build script looks like.

// File: buildSrc/src/main/groovy/com/mrhaki/gradle/generate/GeneratePlugin.groovy
package com.mrhaki.gradle.generate

import org.gradle.api.Project
import org.gradle.api.Plugin

class GeneratePlugin implements Plugin<Project> {
    void apply(Project project) {
        def convention = new GeneratePluginConvention(project)
        project.convention.plugins.generate = convention

        project.tasks.withType(Generate.class).allTasks { Generate task ->
            task.conventionMapping.version = { convention.outputVersion ?: project.version }
            task.conventionMapping.outputFile = { convention.outputFile }
        }
    }
}
// File: buildSrc/src/main/groovy/com/mrhaki/gradle/generate/GeneratePluginConvention.groovy
package com.mrhaki.gradle.generate

import org.gradle.api.Project

class GeneratePluginConvention {
    final Project project
    String outputFilename
    String outputVersion

    public GeneratePluginConvention(Project project) {
        this.project = project
        this.outputFilename = 'version.txt'
    }

    File getOutputFile() {
        project.file("$project.buildDir/$outputFilename")
    }
}
// File: buildSrc/src/main/groovy/com/mrhaki/gradle/generate/Generate.groovy
package com.mrhaki.gradle.generate

import org.gradle.api.internal.ConventionTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction

class Generate extends ConventionTask {
    @Input
    String version

    @OutputFile
    File outputFile

    @TaskAction
    void generate() {
        def file = getOutputFile()
        if (!file.isFile()) {
            file.parentFile.mkdirs()
            file.createNewFile()
        }
        file.write "Version: ${getVersion()}"
    }
}

Our build script has changed to:

// File: build.gradle
import com.mrhaki.gradle.generate.*

apply plugin: GeneratePlugin

version = '3.0'
// Or use explicit values:
// outputVersion = 'OUTPUT' 
// outputFilename = 'another-version.txt'

task generateVersionFile(type: Generate)

task showContents << {
    println generateVersionFile.outputFile.text
}
showContents.dependsOn generateVersionFile

The version property value of the Generate task is now set by either a project property outputVersion or the project version. And the outputFile property is assigned from the default version.txt or the value of the property property outputFilename.

Written with Gradle 0.9.

Gradle Goodness: Add Incremental Build Support to Custom Tasks with Annotations

In a previous post we learned how we can use the inputs and outputs properties to set properties or files that need to be checked to see if a task is up to date. In this post we learn how a custom task class can use annotations to set input properties, file or files and output files or dir.

For input we can use @Input, @InputFile, @InputFiles or @InputDirectory annotations. Gradle uses the properties with annotations for checking if a task is up to date. Output file or directory can be marked with @OutputFile and @OutputDirectory.

task generateVersionFile(type: Generate) {
    version = '2.0'
    outputFile = file("$project.buildDir/version.txt")
}

task showContents << {
    println generateVersionFile.outputFile.text
}
showContents.dependsOn generateVersionFile

class Generate extends DefaultTask {
    @Input
    String version

    @OutputFile
    File outputFile

    @TaskAction
    void generate() {
        def file = getOutputFile()
        if (!file.isFile()) {
            file.parentFile.mkdirs()
            file.createNewFile()
        }
        file.write "Version: ${getVersion()}"
    }
}

We can run our task and get the following output:

$ gradle showContents
:generateVersionFile
:showContents
Version: 2.0

BUILD SUCCESSFUL

And if we run it again we see the task is now up to date:

$ gradle showContents
:generateVersionFile UP-TO-DATE
:showContents
Version: 2.0

BUILD SUCCESSFUL

We can change the version numer in our build script to 2.1 and see the output:

$ gradle showContents
:generateVersionFile
:showContents
Version: 2.1

BUILD SUCCESSFUL

Written with Gradle 0.9.

October 13, 2010

Gradle Goodness: Add Incremental Build Support to Tasks

Gradle has a very powerful incremental build feature. This means Gradle will not execute a task unless it is necessary. We can help Gradle and configure our task so it is ready for an incremental build.

Suppose we have a task that generates a file. The file only needs to be generated if a certain property value has changed since the last task execution. Or the file needs be generated again if a source file is newer than the generated file. These conditions can be configured by us, so Gradle can use this to determine if a task is up to date or not. If the task is up to date Gradle doesn't execute the actions.

A Gradle task has an inputs and outputs property. We can assign a file(s), dir or properties as inputs to be checked. For outputs we can assign a file, dir or custom code in a closure to determine the output of the task. Gradle uses these values to determine if a task needs to be executed.

In the following sample build script we have a task generateVersionFile which create a file version.text in the project build directory. The contents of the file is the value of the version property. The file only needs to be generated if the value of version has changed since the last time the file was generated.

version = '1.0'
outputFile = file("$buildDir/version.txt")

task generateVersionFile << {
    if (!outputFile.isFile()) {
        outputFile.parentFile.mkdirs()
        outputFile.createNewFile()
    }
    outputFile.write "Version: $version"
}

generateVersionFile.inputs.property "version", version
generateVersionFile.outputs.files outputFile

task showContents << {
    println outputFile.text
}
showContents.dependsOn generateVersionFile

Let's run our script for the first time:

$ gradle showContents
:generateVersionFile
:showContents
Version: 1.0

BUILD SUCCESSFUL

Now we run it again and notice how Gradle tells us the task is UP-TO-DATE:

$ gradle showContents
:generateVersionFile UP-TO-DATE
:showContents
Version: 1.0

BUILD SUCCESSFUL

Let's change the build script and set the version to 1.1 and run Gradle:

$ gradle showContents
:generateVersionFile
:showContents
Version: 1.1

BUILD SUCCESSFUL

In a follow-up post we see how can apply this logic to a custom task class via annotations.

Written with Gradle 0.9.