Using Gant to execute a groovy script within the Grails context (updated)

In a previous post I showed a script that I had created to allow the execution of a groovy script within a grails application context (including access to domain objects, controllers, services, etc). A couple of people reported a problem with the script where they were getting lazy initialization exceptions. I finally tracked this issue down to one where many-to-many relationships are being used between two domain classes.

Here is an updated script that fixes this issue by setting up the hibernate session in the Gant script.

scripts/RunScript.groovy:

import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU
import org.springframework.orm.hibernate3.SessionFactoryUtils
import org.springframework.orm.hibernate3.SessionHolder
import org.springframework.transaction.support.TransactionSynchronizationManager

grailsHome = Ant.project.properties."environment.GRAILS_HOME"
includeTargets << new File ( "${grailsHome}/scripts/Package.groovy" )
includeTargets << new File ( "${grailsHome}/scripts/Bootstrap.groovy" )

target('default': "Execute the specified script after starting up the application environment") {
	depends(checkVersion, configureProxy, packageApp, classpath)
	runScript()
}

target(runScript: "Main implementation that executes the specified script after starting up the application environment") {
	parseArguments()
	if (argsMap["params"].size() == 0) {
		event("StatusError", ["Required script name parameter is missing"])
		System.exit 1
	}
	compile()
	classLoader = new URLClassLoader([classesDir.toURL()] as URL[], rootLoader)
	Thread.currentThread().setContextClassLoader(classLoader)
	loadApp()
	configureApp()
	configureHibernateSession()
	argsMap["params"].each { scriptFile ‐>
		executeScript(scriptFile, classLoader)
	}
}

def configureHibernateSession() {
	// without this you’ll get a lazy initialization exception when using a many‐to‐many relationship
	def sessionFactory = appCtx.getBean("sessionFactory")
	def session = SessionFactoryUtils.getSession(sessionFactory, true)
	TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session))
}

def executeScript(scriptFile, classLoader) {
	File script = new File(scriptFile)
	if (script.exists()) {
		def shell = new GroovyShell(classLoader, new Binding(ctx: appCtx, grailsApplication: grailsApp))
		shell.evaluate(script.text)
	} else {
		event("StatusError", ["Designated script doesn't exist: $scriptFile"])
	}
}

// this argument parsing target has actually been submitted as a patch to Init.groovy after some feedback
// on the grails user mailing list and will hopefully be in the next release of grails.
// Vote it up if you like it: http://jira.codehaus.org/browse/GRAILS‐2663
argsMap = [params: []]

target(parseArguments: "Parse the arguments passed on the command line") {
	args?.tokenize().each { token ‐>
		def nameValueSwitch = token =~ "‐‐?(.*)=(.*)"
		if (nameValueSwitch.matches()) { // this token is a name/value pair (ex: ‐‐foo=bar or ‐z=qux)
			argsMap[nameValueSwitch[0][1]] = nameValueSwitch[0][2]
		} else {
			def nameOnlySwitch = token =~ "‐‐?(.*)"
			if (nameOnlySwitch.matches()) {  // this token is just a switch (ex: ‐‐force or ‐‐help)
				argsMap[nameOnlySwitch[0][1]] = true
			} else { // single item tokens, append in order to an array of params
				argsMap["params"] << token
			}
		}
	}
	event("StatusUpdate", ["Done parsing arguments: $argsMap"])
}


If you save that in your application’s scripts directory as RunScript.groovy you can execute it like this:

grails run-script [path-to-script-1] [path-to-script-2]...[path-to-script-n]

The paths are relative to the root of the grails application directory. To see it’s potential use, if you had a directory in your app called userScripts that contained a script createBook.groovy that exercises the many-to-many relationship:

def theTalisman = new Book(title: "The Talisman").save()
def stephenKing = new Author(name:"Stephen King").addToBooks(theTalisman).save()
def peterStraub = new Author(name:"Peter Straub").addToBooks(theTalisman).save()def book = Book.findByTitle("The Talisman")
println "Found ${book.title} with authors = ${book.authors*.name}"

If you had these domain classes:
Author.groovy

class Author {
static hasMany = [books:Book]
String name
}

Book.groovy

class Book {
static belongsTo = Author
static hasMany = [authors:Author]
String title
}

You could then run the script:

grails run-script userScripts/createBook.groovy

And you’d see the output:

Found The Talisman with authors = ["Stephen King", "Peter Straub"]

Since you’re running in the grails context, you also have access to all of your service and controller classes. Anything that you can do inside grails can also be scripted using this target.

Without the new fix that configures the hibernate transaction on the session (configureHibernateSession()), you’d see this error instead:

failed to lazily initialize a collection of role: Book.authors, no session or session was closed

Thanks to Peter Wolf on the GUM mailing list for reporting an issue and testing things out as well as Armin Heinzer for the pointer to the suggested fix on the grails-user list.

4 Comments

  1. Josh Reed
    Posted April 11, 2008 at 12:23 am | Permalink

    Hey Ted,

    Thanks for this work. I ran into a few problems when I was trying to cut and paste this into my Grails install.

    1) On line 4, it looks like WordPress ate a line break. It should end after: TransactionSynchronizationManager

    2) Some of the double quotes got converted to nice HTML double quotes. When you cut and paste, make sure you just do a global search and replace to standard double quotes ”

    3) Finally, the regex in the parseArguments target doesn’t work for me. If I type:
    grails run-script userScripts/populateDatabase.groovy
    This matches the nameOnlySwitch. Just looking at the regex, this makes sense to me because you have “-?(.*)” which I interpret as “zero or one dashes followed by anything”. If I switch it to “-(.*)” or “-+(.*)” (one or more dashes) this falls through and the script name gets picked up. Maybe this is another cut and paste artifact.

    Thanks for the good work.

    Cheers,
    Josh

  2. Josh Reed
    Posted April 11, 2008 at 12:48 am | Permalink

    Just a quick follow up, the reason why things were failing was because the double dash got converted to an mdash. So it really should be two separate dashes and then the question mark, which makes more sense from a regex standpoint.

    Cheers,
    Jsoh

  3. tednaleid
    Posted April 15, 2008 at 3:12 am | Permalink

    Ack…Thanks Josh. I’ve been having some trouble getting wordpress to not screw up my code.

    I believe that I’ve fixed it above, but here’s a pastie link if that causes problem s for anyone.

  4. Armin Heinzer
    Posted April 16, 2008 at 2:58 pm | Permalink

    I tried this new version. I could read and create new objects, but I was not able to update an existing object which was read from database before. Such updates were ignored:

    b = Book.get(1)
    b.name += ‘ updated’
    b.save()

    I added following lines to the script and now updates works as well:


    import org.codehaus.groovy.grails.support.*

    configureHibernateSession()

    // added new
    appCtx.getBeansOfType(PersistenceContextInterceptor).each { k,v ->
    v.init()
    }

    argsMap["params"].each { scriptFile ->
    executeScript(scriptFile, classLoader)
    }

    // added new
    appCtx.getBeansOfType(PersistenceContextInterceptor).each { k,v ->
    v.flush();
    v.destroy()
    }

    May be this is not the most elegant solution (I am not a grails expert yet), but at least it fixed my problem.

    Armin


Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*