Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Dennis Field
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Dennis Field ( @dennis_field )

Calling ColdFusion Components And Methods From Groovy

By on
Tags:

As you may or may not know, when ColdFusion compiles your code, it doesn't compile down to Java objects that directly mimic your ColdFusion objects; rather, ColdFusion compiles your code down into sets of nested Java classes that make ColdFusion's extreme flexibility possible. In the ColdFusion context, this is irrelevant as it happens seamlessly behind the scenes. However, when you need to use ColdFusion components and methods outside of the ColdFusion context (such as in Groovy), suddenly things get much more complicated.

To help me learn more about Groovy and about how ColdFusion works under the hood, I wanted to see if could create Groovy wrapper classes for ColdFusion components and ColdFusion methods (UDFs - not built-in method) that would facilitate the communication between the two layers. It took me around 5-6 hours, but I think I finally got it working! The Groovy wrappers use undocumented Java methods on the ColdFusion objects, so there was a lot of guess work here; as such, take this with a grain of salt.

Before we look at the Groovy aspect, let's take a look at the ColdFusion component that I was testing with:

Greet.cfc

<cfcomponent
	output="false"
	hint="I am simple CFC for Groovy testing.">

	<!---
		Define public values that will act as the default for
		the following method call arguments.
	--->
	<cfset this.firstName = "Joanna" />
	<cfset this.lastName = "Smith" />


	<cffunction
		name="hello"
		access="public"
		returntype="string"
		output="false"
		hint="I say hello to the name stored in this CFC context.">

		<!--- Define arguments. --->
		<cfargument
			name="firstName"
			type="string"
			required="false"
			default="#this.firstName#"
			hint="I am the first name. NOTE: I default to the public property within this CFC context."
			/>

		<cfargument
			name="lastName"
			type="string"
			required="false"
			default="#this.lastName#"
			hint="I am the last name. NOTE: I default to the public property within this CFC context."
			/>

		<!--- Return the hello message. --->
		<cfreturn "Hello #arguments.firstName# #arguments.lastName#" />
	</cffunction>

</cfcomponent>

The Greet.cfc ColdFusion component has one method, Hello(), which returns a simple greeting message. As you can see in the code, the Hello() method can take a first name and last name argument which, if not passed-in, will default to the THIS-scoped firstName and lastName properties of the component. The defaulting of the arguments becomes important when we invoke the Hello() method outside of the CFC-binding.

Now that we see the CFC, let's take a look at the Groovy code (powered by CFGroovy). Before the page actually entered the Groovy code, there are two important things to notice: One, we are defining a firstName and lastName variable in the calling page; and Two, we are getting a reference to the current Page Context, which is required when invoking the ColdFusion methods. The beginning bulk of the Groovy code is the CF wrapper class definition - you may want to scroll down to the bottom to see how they are used before you look at the wrappers.

<!--- Import the CFGroovy tag library. --->
<cfimport prefix="g" taglib="../cfgroovy/" />



<!---
	Create an instance of our greet CFC. Note that the default
	values of the Greet component are:

	firstName: Joanna
	lastName: Smith
--->
<cfset greet = createObject( "component", "Greet" ) />


<!---
	Let's create two variables here (firstName, lastName) that
	mimic the THIS-scope properties of our Greet CFC. This will
	be used farther down when we invoke the CFC method outside
	of the context of the containing ColdFusion component.
--->
<cfset firstName = "Tricia" />
<cfset lastName = "Badonkastein" />



<!---
	Get the page context. This is required when we create the
	ColdFusion to Groovy bridge objects (it is used in the
	method invokation calls).
--->
<cfset pageContext = getPageContext() />

<g:script>

	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->


	class CFBridge {

		def public variablesScope;
		def public pageContext;

		<!---
			The constructor needs to store the variables scope
			and the page context so that it can create the
			appropriate ColdFusion to Groovy bridges.
		--->
		def public CFBridge( variablesScope, pageContext ){
			this.variablesScope = variablesScope;
			this.pageContext = pageContext;
		}


		<!---
			The call method allows this object to be invoked
			as a method (basically, it override the parenthesis
			operator). This will take a single ColdFusion object
			and wrap it in the appropriate ColdFusion to Groovy
			bridge.
		--->
		def public call( target ){
			<!---
				Check to see what kind of object we have so
				that we can create the appropriate bridge.
			--->
			if (this.isCFC( target )){

				<!--- Target is a CFC object. --->
				return( new CFC( this, target ) );

			} else if (this.isCFMethod( target )){

				<!--- Target is a CF Method reference. --->
				return( new CFMethod( this, target ) );

			}

			<!--- This bridge is not supported yet. --->
			throw new Exception( "CF Brdige not supported." );
		}


		<!---
			Determines if the given object is a ColdFusion
			component.
		--->
		def static public isCFC( target ){
			return(
				target.getClass().getName() == "coldfusion.runtime.TemplateProxy"
			);
		}


		<!---
			Determines if the given object is a ColdFusion
			method reference.
		--->
		def static public isCFMethod( target ){
			return(
				target.getClass().getName().indexOf( "\$func" ) > 0
			);
		}

	}


	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->


	class CFC {

		def private bridgeFactory;
		def private target;

		<!--- Consturctor. --->
		def public CFC( bridgeFactory, target ){
			this.bridgeFactory = bridgeFactory;
			this.target = target;
		}


		<!---
			This method is similar to ColdFusion's
			onMissingMethod() event handler and will route the
			method calls from Groovy to the underlying ColdFusion
			component (CFC).
		--->
		public methodMissing( String name, args ){
			<!---
				Check to see if there is only one argument and if
				it is a linked hash map. ColdFusion methods can be
				invoked either using ordered arguments or named
				arguments, so this will try to guess between the
				two types.
			--->
			if (
				(args.size() == 1) &&
				(args[ 0 ] instanceof java.util.LinkedHashMap)
				){

				<!---
					Invoke ColdFusion method on Component using
					hash map approach (the first argument).
				--->
				return(
					this.target.invoke(
						name.toString(),
						args[ 0 ],
						this.bridgeFactory.pageContext
					)
				);

			} else {

				<!---
					Invoke ColdFusion method on Component using
					ordered arguments approach.
				--->
				return(
					this.target.invoke(
						name.toString(),
						args,
						this.bridgeFactory.pageContext
					)
				);

			}

		}

	}


	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->


	class CFMethod {

		def private bridgeFactory;
		def private target;

		<!--- Consturctor. --->
		def public CFMethod( bridgeFactory, target ){
			this.bridgeFactory = bridgeFactory;
			this.target = target;
		}


		<!---
			The call method allows this object to be invoked
			as a method (basically, it override the parenthesis
			operator). This method will pass use the given
			arguments to invoke the underlying ColdFusion method.
		--->
		def public call( Object... args ){
			<!---
				Check to see if there is only one argument and if
				it is a linked hash map. ColdFusion methods can be
				invoked either using ordered arguments or named
				arguments, so this will try to guess between the
				two types.
			--->
			if (
				(args.size() == 1) &&
				(args[ 0 ] instanceof java.util.LinkedHashMap)
				){

				<!---
					Invoke ColdFusion method using hash map
					approach (the first argument).
				--->
				return(
					this.target.invoke(
						this.bridgeFactory.variablesScope,
						"",
						this.bridgeFactory.pageContext.getPage(),
						args[ 0 ]
					)
				);

			} else {

				<!---
					Invoke ColdFusion method using ordered
					arguments approach.
				--->
				return(
					this.target.invoke(
						this.bridgeFactory.variablesScope,
						"",
						this.bridgeFactory.pageContext.getPage(),
						args
					)
				);

			}
		}

	}


	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->


	<!---
		Create an instance of the ColdFusion to Groovy bridge
		class. This will provide the factory for the rest of
		the bridges.
	--->
	def CF = new CFBridge( variables, variables.pageContext );


	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->


	<!--- This is a utility method for testing. --->
	def output( value = "" ){
		println(
			value.toString() + ("<br />" * 2)
		);
	}



	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->


	<!---
		Call the hello method on the current CFC. Notice that we
		are feeding the greet CFC instance through our bridge
		factory before we call the hello() method.
	--->
	output(
		CF( variables.greet ).hello()
	);


	<!---
		Now, rather than let the hello() method use the default
		values, let's override the arguments using an ordered
		arguments approach.
	--->
	output(
		CF( variables.greet ).hello( "Kim", "McSinful" )
	);


	<!---
		Now, let's override the arguments again, but this time,
		lets use a named-arguments approach.

		NOTE: The arguments are being passed-in in reverse order
		to demonstrate that order is not being considered.
	--->
	output(
		CF( variables.greet ).hello([
			"lastName": "O'Gazmic",
			"firstName": "Libby"
		])
	);


	<!---
		Now, let's call the hello() method OUTSIDE of the context
		of the ColdFusion component in which it was defined.
		Notice that this time, we pass the hello() reference to
		the bridge factory before we invoke it.
	--->
	output(
		CF( variables.greet.hello )()
	);

</g:script>

Once you get to the bottom of the Groovy code, you can see that we are feeding the ColdFusion objects and ColdFusion methods to our ColdFusion-to-Groovy bridge builder before we invoke any methods:

CF( component ).method( ... );
CF( component.method )( ... );

When we pass in a ColdFusion component, any methods we call on that bridge will be executed in the context of that ColdFusion component. When we pass in a ColdFusion method (even one that is a property of a component), its execution will be performed in the context of the calling page. This latter point is why we defined firstName and lastName variables at the top of the calling page.

You will also notice that the methods can be invoked using ordered arguments or named-arguments (just as they can be in ColdFusion). This took some fenagling since I basically decided that if the first and only argument passed into the method was a linked hash map, it was being invoked using named arguments - a somewhat arbitrary decision.

Anyway, when we run the above code, we get the following output:

Hello Joanna Smith

Hello Kim McSinful

Hello Libby O'Gazmic

Hello Tricia Badonkastein

As you can see, all of the ColdFusion methods were invoked successfully from the Groovy context. Furthermore, you'll notice that in the last example we invoked the method, Hello(), outside of the CFC-binding; in that case, the firstName and lastName values were pulled out of the calling page context rather than the Greet CFC.

Being able to call ColdFusion methods from within Java is something that's baffled me for about three years. Now, trying it once again in Groovy, I was finally able to make it work! Of course, I am not 100% sure how the underlying methods work and, it has to be in the greater context of ColdFusion (to get the page context), but they work well enough for this experiment. Groovy has some really awesome stuff in it.

Want to use code from this post? Check out the license.

Reader Comments

25 Comments

Nice Ben. There is already a 'pageContext' binding provided for you by CFGroovy, so you don't need an explicit reference the variables scope. Though now that I think about it, it's the PageContext for the g:script tag, not the calling template, so maybe it does matter.

15,640 Comments

@Barney,

Yeah, I think you're right (re: custom tag scope vs. calling page scope). I'm not really sure... I'm basically just a blind man hacking my way through a jungle of unfamiliar functionality :) This whole things took me hours to figure out, a and lot of that time was just trying to debug the Groovy aspect. Heck, an hour was spent trying to figure out why this caused an infinite loop:

getProperty( name ){
return( this[ name ] );
}

... I assumed you could use "this" internally to an object without it re-invoking the getProperty() method. Who knew :D

There's some really awesome stuff in Groovy. In particular, I love the operator overloading and just all the awesome operators in general!

26 Comments

Any reason you aren't using my ColdFusion Component Dynamic Proxy, in JavaLoader 1.0?

It works with Java objects, and since Groovy works with Java objects, it should work as well, and would be a much more seamless experience as Groovy won't know the CFC isn't actually just a plain ol' POJO.

Just a thought ;o)

Side idea - since Groovy is untyped, and has it's own version of onMissingMethod, why not just build your own Groovy Proxy class to wrap around a CFC, and then call methods on it. Would save all the CF(obj) calls), and you could just have:

proxy = new CFCProxy( variables.greet )
proxy.hello()

That is if you didn't want to use the JavaLoader's one.

15,640 Comments

@Mark,

To be honest, I actually looked at your JavaProxy.cfc when I was trying to figure all of this out. Is that what you are referring to? I had a little bit of trouble understanding it, but from what I gathered, I thought it was going the other way (CF to Java).

As far as creating a proxy class, under the hood of the CF() method call, that's what is happening. The reason that I delegate to the call() method on the CF class is that the CF class instance has the reference to the *variables* scope and to the *pageContext*; if I created the proxy classes directly, I would have to pass in the above with every call:

new CFCProxy( variables.greet, variables, variables.pageContext );

... at least as far as I gather. This stuff is really new to me and like I said to Barney, it's like blindly hacking through a jungle :)

26 Comments

@Ben,

You are right, the JavaProxy.cfc is CF->Java.

What I'm actually referring to is the CFC Dynamic Proxy documented here:
http://www.compoundtheory.com/javaloader/docs/#ColdFusion_Component_Dynamic_P_1999409235798828

And also talked about here:
http://www.compoundtheory.com/?action=displayPost&ID=422

Which enables much more seamless interaction between Java and ColdFusion.

If you were to write your own proxy, you don't need to pass that all in with every call. For one, you can always get at the current PageContext through: FusionContext.getCurrent().pageContext

Also not sure why you are passing through the variables scope.... to invoke a cfc from Java it's just:

getCFC().invoke("methodName", argArray, pageContext);

You can check out my Dynamic Proxy code to see how I am doing it:
http://svn.riaforge.org/javaloader/trunk/cfcdynamicproxy/src/com/compoundtheory/coldfusion/cfc/CFCDynamicProxy.java

15,640 Comments

@Mark,

Hmmm, I think I *may* have seen that before, but I think it was over my head at the time. I think I may have also thought it was only in regards to Spring integration (your blog post looks familiar).

Where are you getting the FusionContext from? Is that internal to your proxy?

As far as the Variables scope, yeah, I don't need that for the CFC-method invocation; however, when I detatch the method from the CFC as in:

CF( greeting.hello )( .. )

... I use the variables scope as the binding context (as a normal UDF would use)??

I'll read up more on your stuff. Thanks for putting in the links. I really want to wrap my head around this better.

15,640 Comments

@Mark,

I finally downloaded and poked around in your CFC Dynamic Proxy code. Looks very cool. One thing that I was curious about, and I think this might be a pure limitation of Java, is that you have to uphold *some* interface, right? Like this wouldn't work on a CFC that is powered by onMissingMethod() would it?

I wish I knew more Java. Just reading through your code is making me sweat ;)

26 Comments

@Ben,

Re: 'is that you have to uphold *some* interface, right'.

Right - because Java is typed, you have to tell it is a type of 'something', otherwise it doesn't know what to do with it.

In theory, with Groovy you could probably work around this.

The interesting question would be - could you pass your Groovy->CFC Proxy back to ColdFusion, and would it run? (I have a feeling it would not, due to the way that CF attempts to resove a method to one on a class/interface), whereas with the Java->CFC Dynamic Proxy, you can, as ColdFusion can look up an actual method (via reflection) on the interface the Proxy implements.

15,640 Comments

@Mark,

Yeah, Groovy can be dynamic because it has a methodMissing() handler, which can act as the pipe into ColdFusion. But, no, you could not pass the Groovy wrapper back into ColdFusion. Or, you could, but it would not work the way you expect. I am pretty sure you would need to manually invoke the "call" method on Groovy object in a ColdFusion context. So yeah, it sounds like the Dynamic Proxy would be rockin that respect.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel