Here lately I’ve been dabbling in Groovy and have started using Barney Boisvert’s excellent CFGroovy2 custom tag which handles the hard part of running embedded Groovy scripts in ColdFusion. For those that haven’t seen it, here is a small taste of what a groovy script could look like in a ColdFusion function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <cfcomponent> <cffunction name="Transform" returntype="any" access="public" output="false"> <cfargument name="query" type="query" required="true" /> <cfargument name="keyColumn" type="string" required="true" /> <cfimport prefix="g" taglib="../../groovyEngine" /> <cfoutput> <g:script args="#arguments#"> result = [:]; while (arguments.query.next()) { row = [:]; cols = arguments.query.getColumnList(); cols.each { columnName -> value = arguments.query.getString(columnName).toString(); row.put(columnName, (arguments.query.wasNull()) ? "" : value); } result.put(arguments.query.getString(arguments.keyColumn), row); } arguments.result = result; </g:script> </cfoutput> <cfreturn arguments.result /> </cffunction> </cfcomponent> |
The trick in this code, however, is that Barney’s tag does not bind the arguments scope to the Groovy script engine, and as a result your Groovy scripts will not have access to the arguments scope. The reason for this is because the arguments scope isn’t a true “scope” in the sense that it gets passed around with your page context. It doesn’t. I checked.
To circumvent this I pass a custom argument to the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | <cfsilent> <cffunction name="bootstrapGroovyFromJavaLoader" access="private" output="false" returntype="any"> <cfset var cp = listToArray(getDirectoryFromPath(getCurrentTemplatePath()) & "groovy-all-1.6.0.jar") /> <cfset var javaLoader = createObject("component", "JavaLoader").init(cp, true) /> <cfreturn javaLoader.create("org.codehaus.groovy.jsr223.GroovyScriptEngineImpl").init() /> </cffunction> <cffunction name="runScript" access="private" output="false" returntype="string"> <cfargument name="body" type="string" required="true" /> <cfargument name="args" type="struct" required="false" /> <cfset var engine = "" /> <cfset var binding = "" /> <cfset var scope = "" /> <cfset var context = "" /> <cfset var writer = createObject("java", "java.io.StringWriter").init() /> <cfif NOT structKeyExists(server, "cfgroovy")> <cfset server["cfgroovy"] = structNew() /> <cfset server.cfgroovy["scriptCache"] = createObject("java", "java.util.WeakHashMap").init() /> <cfset server.cfgroovy["CompilableClass"] = getPageContext().getClass().forName("javax.script.Compilable") /> </cfif> <cfif NOT structKeyExists(server.cfgroovy, "#attributes.lang#Engine")> <cftry> <cfset server.cfgroovy["#attributes.lang#Engine"] = createObject("java", "javax.script.ScriptEngineManager") .getEngineByName(attributes.lang) /> <cfcatch type="java.lang.ClassNotFoundException" /> <cfcatch type="Railo.commons.lang.classexception" /> <cfcatch type="Object" /> <cfcatch type="Invalid Class" /> </cftry> <cfif NOT structKeyExists(server.cfgroovy, "#attributes.lang#Engine")> <cfif attributes.lang EQ "groovy"> <cfset server.cfgroovy["#attributes.lang#Engine"] = bootstrapGroovyFromJavaLoader() /> <cfelse> <cfthrow type="CFGroovy.UnknownLanguageException" message="The language you specified (#attributes.lang#) is not recognized." detail="You must ensure you're running Java 1.6 or greater, and have the necessary JAR(s) on your classpath." /> </cfif> </cfif> </cfif> <cfset engine = server.cfgroovy["#attributes.lang#Engine"] /> <cfset binding = engine.createBindings() /> <cfset binding.put("variables", attributes.variables) /> <cfset binding.put("pageContext", getPageContext()) /> <cfif structKeyExists(arguments, "args")> <cfset binding.put("arguments", arguments.args) /> </cfif> <cfloop list="url,form,request,cgi,session,application,server,cluster" index="scope"> <cfif isDefined(scope)> <cfset binding.put(scope, evaluate(scope)) /> </cfif> </cfloop> <cfset body = trim(body) /> <cfif len(body) EQ 0> <cfthrow type="CFGroovy.EmptyScriptException" message="You attempted to execute an empty script. This is not allowed" detail="Groovy scripts must have at least one expression in them. Forgetting to use <cfoutput> around your <g:script> tag when <cfsetting enableCfOutputOnly=""true"" /> is a common cause of this problem." /> </cfif> <cfset context = engine.getContext().getClass().newInstance() /> <cfset context.setBindings(binding, context.ENGINE_SCOPE) /> <cfset context.setWriter(writer) /> <cfset context.setErrorWriter(writer) /> <cfif server.cfgroovy["CompilableClass"].isAssignableFrom(engine.getClass())> <cfset script = server.cfgroovy.scriptCache.get(body) /> <cfif structKeyExists(variables, "script")> <cfset script = script.get() /> </cfif> <cfif NOT structKeyExists(variables, "script")> <cfset script = engine.compile(body) /> <cfset server.cfgroovy.scriptCache.put(body, createObject("java", "java.lang.ref.WeakReference").init(script)) /> </cfif> <cfset script.eval(context) /> <cfelse> <cfset engine.eval(body, context) /> </cfif> <cfreturn writer.toString() /> </cffunction> <cfif thisTag.executionMode EQ "start"> <cfparam name="attributes.language" default="groovy" /> <cfparam name="attributes.lang" default="#attributes.language#" /> <cfparam name="attributes.variables" default="#caller#" /> <cfif NOT structKeyExists(attributes, "script") AND NOT thisTag.hasEndTag> <cfthrow type="CFGroovy.NoScriptException" message="No script was supplied <g:script> tag." detail="The <g:script> tag required either a body or a 'script' attribute contining the Groovy script to execute." /> </cfif> <cfelse><!--- executionMode EQ "end" ---> <cfif NOT structKeyExists(attributes, "script")> <cfset attributes.script = thisTag.generatedContent /> </cfif> <cfset thisTag.generatedContent = "" /> </cfif> <!--- forgive the missing linebreak - it's to avoid an unwanted line break in the caller's output ---> </cfsilent><cfif structKeyExists(attributes, "script") AND (NOT thisTag.hasEndTag OR thisTag.executionMode EQ "end")><cfoutput><cfif structKeyExists(attributes, "args")>#runScript(attributes.script, attributes.args)#<cfelse>#runScript(attributes.script)#</cfif></cfoutput></cfif> |
Happy coding you Groovy people!
Related Posts
Related posts:


What advantage is there in using groovy over cfscript? More expressive? More compact? Storing scripts in a database for dynamic execution?
Just curious because I think this is totally cool, and have from time to time wanted something like this in ColdFusion, for things such as dynamic rules.
@Mark DeMoss Biggest benefits are the elegant language constructs, and speed of “components”. In looking around I’m seeing a number of people talk about using Groovy for their model and service layer, and CF in the presentation. In my tests most Groovy code ran faster.
And of course it would RULE for dynamic rules. I suggest you play with that!!
In an Ajax-based application, then you would have JS+HTML in the presentation, and Groovy in the model and service layer. There’s not much need for CF at all, is there?
@Mark Not for much more than a Java application container, not really. Though I suspect that “mixing” it together is a good strategy. Most of the information I’ve found so far indicates that some folks are mixing it, others are using it to create actual JARs, and they tie into Spring for their DI framework.
Awesome stuff. I just wanted to start playing around with Barney’s code so this timing is fantastic.
@Adam,
Thanks for the inspiration. I took your idea and applied it to the script.cfm with a bit of a twist. I am creating an argumentCollection for the runScript() method based on any additional attributes the user provides. Then, I am looping over that and binding each one to the script. The downside is that I have to convert each name to lowercase, but minor issue.
Anyway, thanks again for your insight – tremendously useful.
No problem. Playing around with Groovy has been loads of fun!