Monday, October 11, 2010

Using your application classes inside Grails Gant script

So you have a Grails application, and want to script some cool things on top of it, like calling your RESTful service, using dynamic finders on domain classes, or adding new records using Hibernate. No surprise, there are some caveats that await for you on the way. Let's see what they are:

1. Loading your application context

This is an obvious first step, and this is what Grails expects you to do:

includeTargets << grailsScript("_GrailsBootstrap")   
target(main: "My Gant script") {       
    depends(configureProxy, packageApp, classpath, loadApp, configureApp, compile)       
    ...  
}   
setDefaultTarget("main")   

Yep, all these parts of depends() clause are needed, although "compile" part is needed only if domain objects are directly used, i.e., MyDomainClass.findByAttr().

2. Setting target environment

Two ways to do that. Standard grails way:

grails prod my-script    - for environments pre-defined in Grails, such as 'dev' or 'prod'
grails -Dgrails.env=stage my-script     - for custom environments

or place this at the beginning of your script:

scriptEnv = "envName"
and then simply run
grails my-script

3. Accessing your application classes and calling methods on them

The following example shows how to call a service method in your Gant script.
def serviceClass = grailsApp.getClassForName("com.killerapp.AwesomeService")
def serviceClassMethod = serviceClass.metaClass.getMetaMethod("blazingMethod")
def service = appCtx.getBean("awesomeService")
serviceClassMethod.invoke(service, [arg1, arg2] as Object[])

As seen in the example, there are 2 implicit variables that the script provides: "grailsApp" and "appCtx". First one is an instance of org.codehaus.groovy.grails.commons.GrailsApplication, and the second one is an instance of org.springframework.context.ApplicationContext. To call a service method, both are needed: grailsApp helps retrieve an instance of MetaMethod wrapper of the method you want to call. However, MetaMethod has to be called on a specific instance, which is Spring context singleton that Grails application creates when it initializes. appCtx helps retrieve that singleton. 
However, this is not enough. Your service method has to be defined as an actual method, like this:

void blazingMethod(arg1, arg2) {
      ...
}
, NOT a closure:
def blazingMethod { arg1, arg2 ->
      ...
}

That is because groovy closures are compiled into non-anonymous inner classes, not regular Java methods. Therefore, accessing closures by name will not work.


4. Running queries

To run dynamic finders, nothing extra needs to be done:

MyDomainClass.findByName("someName")

To run updates, an instance of org.springframework.orm.hibernate3.HibernateTemplate is needed. It can be obtained using appCtx:

def sessionFactory = appCtx.getBean("sessionFactory")
def template = new HibernateTemplate(sessionFactory)
def newRecord = new MyDomainClass(name: "someName")
template.saveOrUpdate(newRecord)

HibernateTemplate can also be used for queries, like this:
def domainClass = grailsApp.getClassForName("com.killerapp.MyDomainClass")
def allRecords = template.loadAll(domainClass) 

5. Cleaning script cache

As of Grails 1.3.4, using depends(compile) with accessing domain classes in a Gant script generates the following error simply running a script once, then making a change and re-running it:

java.lang.NoClassDefFoundError: com.killerapp.MyDomainClass

This is because scriptCache needs to be cleaned. Simply delete ~/.grails/1.3.4/projects/killerApp/scriptCache/ directory, and rerun the script.

Thursday, October 7, 2010

passing String array argument to a Groovy method

Let's say we have a Groovy method that takes a String array as an argument:

void myMethod(String[] arr) { ... }

When passing a string array to this method, the most intuitive way to do it is this:

myMethod(["a", "b", "c"])

However, this produces a NoSuchMethodError, because GDK converts ["a", "b", "c"] to an instance of ArrayList instead of String[]. Next thing you may try is this:

myMethod(new String[]{"a", "b", "c"})

, which is standard Java one-line syntax for creating a String array. This, however, also confuses GDK, and creates exception with this message:

No expression for the array constructor call

Groovy notices opening and closing braces and starts compiling it as a closure, which of course it isn't. This can be worked around in 2 ways:

myMethod(["a", "b", "c"] as String[])

or

myMethod((String[]) ["a", "b", "c"])

Monday, September 27, 2010

Replacing a persistent collection in Grails domain object

If I have a domain model set up the following way:

class Site {
    static hasMany = [instances: Instance]
}

class Instance {
    static belongsTo = [Site]
}

and have to perform a task of wiping out a collection of Instances in a Site, including the deletion of all records of Instance, this would be the right way to do it:

site.instances*.delete()
site.instances.clear()
site.save(flush: true)

This may be a bit non-intuitive for such a trivial task, however, if you omit the first delete() call, you'll end up with orphan Instance records. If you omit the clear() call, you'll get this error:

deleted object would be re-saved by cascade (remove deleted object from associations): [Instance]

And of course, site.removeFromInstances() does not take a Collection, and if you loop through it and delete each individual Instance, you'll get a ConcurrentModificationException. 

Thursday, August 19, 2010

Grails UrlMappings - mutually exclusive patterns

Let's say, I have URL patterns that are mutually exclusive and have to map to 2 different controllers. For instance, if I have an enum Vals with the following values: ROCK, PAPER, SCISSORS, and I want URLs like

/root/group/rock (or ROCK)
/root/group/paper (or PAPER) 
/root/group/scissors (or SCISSORS) to map to YouWinController

and

/root/group/(everything-else-but-ROCK|rock|PAPER|paper|SCISSORS|scissors) to map to YouLoseController


Both URL patterns are very similar, because each one consists of 3 strings separated by '/'. We can start by defining some constraints, but at the same time we have to define the exclusive pattern first. In this case, exclusive pattern is the one mapping to YouLoseController.

class UrlMappings {
    static mappings = {
        "/root/group/$loseParam"(controller: "youLose", action: "/loseAction")
        "/root/group/$winParam"(controller: "youWin", action: "/winAction") {
            constraints {
                  // get all string values from the enum
                  def list = stringList(Vals) 
                  // make sure lowercase values are also included
                  list.addAll(list*.toLowerCase()) 
                  $winParam(inList: list)
            }
        }
    }
}

Wednesday, August 18, 2010

Criteria queries don't work properly with Grails Enums

Looks like Grails (as of version 1.3.1) still didn't fix all the issues with Enums.

I'm using a criteria query like this:

c = MyDomainClass.createCriteria()
def list = c.list {
    and {
        classAttr {
            eq ("attr1", param1)
        }
        eq ("enumAttr", param2)
    }
} 

In this case, MyDomainClass.enumAttr represents an enum. It looks something like this:

enum CountryEnum {
    AUSTRIA("austria")
    BRAZIL("brazil")
    CANADA("canada")

    String name

    CountryEnum(String name) {
        this.name = name
    }
}

Grails, quite wisely, stores 'AUSTRIA', 'BRAZIL', and 'CANADA' in the DB.
However, the above criteria query generates the following error:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Enum

There a little section about Enums in Grails 1.1 release notes, which mentions that the value attribute should now be named 'id'. However switching 'name' to 'id' in my CountryEnum simply got rid of the exception, but the 'list' was still empty after the query completed. Extracting SQL from Hibernate debug output produced valid results, which means the problem lies somewhere in the Hibernate layer. Also DB values became 'austria', 'brazil', and 'canada', which are the values of 'id'.

Dynamic finders seem to handle Enums pretty well, but evidently the problem still exists when using Criteria or HQL.

Tuesday, August 17, 2010

Grails: using Groovy MarkupBuilder to render properly indented XML

When using Grails, the easiest way to render XML is to use 
 render obj as XML
command. However, that returns the entire XML on one line.

If you want XML to look pretty, you can use groovy.xml.MarkupBuilder

def showXml = {
    def writer = new StringWriter() 
    new MarkupBuilder(writer).root {
        child1 {
            child2('someValue')
        }       
    }

    render(text: writer.toString(), contentType: "text/xml")
}

This will produce a neat output where each tag is on a new line and properly indented.