Grails run-script with parameters

Grails, programming

Scripts vs. parameters

The “grails run-script” task allows running a bunch of scripts, and therein lies the problem: the parameters passed to the command are interpreted by the command to be names of scripts to run:

grails run-script ~/script1.groovy ~/script2.groovy ~/script3.groovy

This works fine if the scripts are self-contained and did not expect any parameters on their own.  What if ~/script1.groovy expects some parameters?

The problem is that the RunScript.groovy script assumes each parameter to be script files to run, so there is no support for parameters to any of the script files run.

So I would like to trade:

  • the ability to run multiple scripts with one call to run-script

with

  • multiple calls to something like run-script-with-params

I figured that, if I were to be calling scripts in a batch, making multiple calls shouldn’t be a big deal. Not being able to pass parameters to individual scripts, on the other hand, IS a big deal.

A tweak of the script RunScript.groovy (below) can help with that:

UPDATE: I just found that a similar script (even the name is remarkably similar) has already been suggested here.  (Scroll down the comment section to see the suggestion.)  Oh well.  Wish it was added to Grails so I didn’t have to spend so much time on this.

import org.springframework.orm.hibernate3.SessionFactoryUtils
import org.springframework.orm.hibernate3.SessionHolder
import org.springframework.transaction.support.TransactionSynchronizationManager

/**
* GANT script to run an arbitrary Groovy script with GORM and Spring context and also with parameters
*
* A sample invocation of a script:
* grails run-script-with-params ~/script1.groovy -enableLog -name=van
*
* Arguments are processed as follows:
* --xx=yy or -xx=yy are parsed into key/value pairs added into argsMap and passed to the script
* --xx or -xx are interpreted as xx:true and treated as a key/value pair and added into argsMap
* xxxxx are treated as a parameter and stored into the array argsMap.params
*
* The FIRST parameter is assumed to be the path to the script file to run.
* The rest of the parameters will be parsed into a map passed onto the script as argsMap
*
* This script is based on and inspired by
* {@link RunScript}
*/
includeTargets << grailsScript("_GrailsBootstrap")

target(main: "GANT script to run an arbitrary Groovy script with GORM and Spring context") {
depends checkVersion, configureProxy, bootstrap, runScript

}

target(runScript: 'Runs a Groovy script file with the parameters propagated to it') {
configureHibernateSession()

if (!argsMap.params) {
event('StatusError', ['ERROR: Required script name parameter is missing'])
System.exit 1
}
executeScriptWithArgsMap argsMap.params[0], classLoader, argsMap
}

def executeScriptWithArgsMap(scriptFile, classLoader, argsMap) {
File script = new File(scriptFile)
if (!script.exists()) {
event('StatusError', ["ERROR: Script $scriptFile does not exist"])
System.exit 1
}
event('StatusUpdate', ["Running script $scriptFile ..."])
def persistenceInterceptor = appCtx.containsBean('persistenceInterceptor') ? appCtx.persistenceInterceptor : null
persistenceInterceptor?.init()
try {
def binding = new Binding(ctx:appCtx, grailsApplication:grailsApp, argsMap: argsMap)
def shell = new GroovyShell(classLoader, binding)
shell.evaluate script.text
event('StatusUpdate', ["Script $scriptFile complete!"])
} finally {
persistenceInterceptor?.flush()
persistenceInterceptor?.destroy()
}
}

def configureHibernateSession() {
// bind a Hibernate Session to avoid lazy initialization exceptions
TransactionSynchronizationManager.bindResource(appCtx.sessionFactory,
new SessionHolder(SessionFactoryUtils.getSession(appCtx.sessionFactory, true)))
}

setDefaultTarget(main)

The trick is how we start a Groovy shell, passing it a Binding object:

def binding = new Binding(ctx:appCtx, grailsApplication:grailsApp, argsMap: argsMap)
def shell = new GroovyShell(classLoader, binding)

We added argsMap to the Binding object passed to the shell. And this allows the target script to now have access to argsMap, in addition to the already available ctx (the Spring application context) and grailsApplication (the GrailsApplication object).

Put the script above into the Grails project’s script subdir as /scripts/RunScriptWithParams.groovy and we can now run this from the Grails subdir:

grails run-script-with-params ~/script1.groovy --enableThis --param1=val1

The target script (in this case ~/script1.groovy) will be able to get the parameters via the argsMap global variable injected into the shell:

println "enableThis = ${argsMap.enableThis}, param1 = ${argsMap.param1}"

Windows caveat

The only caveat I found is that, when running in the Windows command prompt, parameters with a format --key=value need to be in quotes:

grails run-script-with-params ~/script1.groovy --enableThis "--param1=val1"

Running under Cygwin’s terminal works fine without the quotes, however.  So it’s something special about the Windows command.exe or cmd.exe.

Grails class names

Grails, programming

Seems like there are many flavors of the name for a Grails class:

  • fullName — Returns the full name of the class in the application with the the trailing convention part and with the package name
  • name — Returns the logical name of the class in the application without the trailing convention part if applicable and without the package name.
  • naturalName — Returns the name of the property in natural terms (eg. ‘lastName’ becomes ‘Last Name’)
  • shortName — Returns the short name of the class without package prefix
  • propertyName — Returns the name of the class as a property name
  • logicalPropertyName — Returns the logical name of the class as a property name

These are from http://grails.org/doc/2.2.x/api/org/codehaus/groovy/grails/commons/GrailsClass.html.  It looks like somebody just typed in something just to have something show up in Javadoc.

A pause in the code to show an example is probably the best way to document these names.  For a class like com.blah.UserManagementController:

  • fullName: com.blah.UserManagementController
  • name: UserManagement
  • naturalName: User Management Controller
  • shortName: UserManagementController
  • propertyName: userManagementController
  • logicalPropertyName: userManagement

Now that that’s out of the way, notes:

Spring’s application context (e.g. grailsApplication.mainContext) holds on to its beans using names that may not be obvious. The easiest way to figure out is to fire up

grails console

and evaluate

grailsApplication.mainContext.getBeanDefinitionNames()

and find the bean’s name.

Grails scripting

Grails, programming

Within any Web application, there is the Web site, which is pretty well served by Grails.  Very soon, there will inevitably be some background processing that needs to be done for the holistic system.  These background tasks are typically:

  • background in nature such that typical users won’t know of their existence
  • process-intensive with little or no interactive flows
  • console stdin/stdout for scripting
  • independent of the Web app’s life cycle / process space

With respect to Grails, the Web app has GORM and all its convenient properties abstracted in very concise domain classes.  However, these are only available to the Grails run-time.  There are posts (this and the like) that talks about having multiple Grails apps sharing the domain objects, but that doesn’t fit the profiles listed above since I’d have two interactive Web apps, albeit one for public and one for admin/internal controllers and services.

An alternative is to look at Grails scripting, of which there are two flavors that I know of:

  • Grails scripts
  • Custom Groovy scripts

Both of these seem to be able to get at the Spring ApplicationContext and transitively all its beans.

Grails scripts (create-script)

Create these using:

grails create-script MyScript

A script template will be added under the /scripts subdir (e.g. /scripts/MyScript.groovy):

includeTargets << grailsScript("_GrailsBootstrap")

target(main: "The description of the script goes here!") {
    // ...

}

setDefaultTarget(main)

Within the body of target(...) { }, we can add a list of dependencies and then use “appCtx” to refer to the Spring ApplicationContext:

target(...) {
    depends(configureProxy, packageApp, classpath, loadApp, configureApp, compile)

    def myService = appCtx.myService
    println(myService.doSomething())
}

Note the use of appCtx to reference the Spring ApplicationContext.

The script can be moved to these places also:

  • $GRAILS_HOME/scripts
  • $USER_HOME/.grails/scripts
  • $PROJECT_HOME/plugins/*/scripts

To run it:

cd $PROJECT_HOME
grails MyScript

Note the conciseness of the command.

Custom Groovy scripts (run-scripts)

This approach allows writing less coupled Groovy scripts that can be placed anywhere on the file system (not confined under the project’s /scripts subdir, for instance).

The price to pay for that flexibility are:

  • The working directory when I run the script still must be where the Grails project is
  • Getting the reference to the ApplicationContext is quite convoluted a process

So I can create a script at ~/scripts/grails/myapp/MyService.groovy:

def appCtx = new User().domainClass.grailsApplication.mainContext
def myService = appCtx.myService

println(myService.doSomething())

Note how I got the appCtx instance: creating a bean (must be properly imported or qualified) class, then walk the reference to the domainClass and then grailsApplication and then mainContext.

To run it:

cd $PROJECT_HOME
grails run-script ~/scripts/grails/myapp/MyScript.groovy

Note the use of the run-script target and the full path to the script file. Since the script file can be anywhere, there can certainly be many versions of the script file.

Similarities and Differences

Both types of scripts have full access to GORM and Spring.

Scripts created by “grails create-script”:

can only reside in one of four locations

  • $GRAILS_HOME/scripts
  • $USER_HOME/.grails/scripts
  • $PROJECT_HOME/scripts
  • $PROJECT_HOME/plugins/*/scripts

can be invoked simply via “grails <script name>” (the .groovy suffix is not required)

can reuse some of the _Grails* scripts that came with Grails (e.g. _GrailsBootstrap)

does NOT have access to $PROJECT_HOME/src/groovy or $PROJECT_HOME/src/java during run-time

Scripts run by “grails run-script”:

can reside outside $PROJECT_HOME.  However, when running, the working subdirectory needs to be $PROJECT_HOME

requires full path to *.groovy file when run using “grails run-script <full path to script file>.groovy”

HAS access to $PROJECT_HOME/src/groovy or $PROJECT_HOME/src/java during run-time

Reference

Page on create-script from Grails

Page on run-script from Grails