Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Randy Brown and Sebastian Zartner
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Randy Brown Sebastian Zartner

Instantiating Groovy Classes In The ColdFusion Context

By on
Tags:

_Disclaimer: I just started looking into Groovy so a lot of this might be dead wrong. _

When I saw Barney Boisvert demonstrate Groovy at CFUNITED, I became very interested in experimenting with it. One of the first things that I wanted to try was defining classes in Groovy and then using them in ColdFusion. The problem, however, is that the Groovy context and the ColdFusion context don't exactly have bi-directional communication. When the Groovy context is opened in Barney's script (from what I am gathering), certain ColdFusion scopes are bound to the Groovy context such that the Groovy context can read from and write to the ColdFusion context. Once the Groovy context is closed, however, ColdFusion doesn't have a handle on it.

I am probably not understanding this fully, but from I have been reading, one huge benefit of Groovy is that it is lexically bound. Meaning, objects in Groovy can, more or less, access data that was defined in a given context, even after references to that context have been passed out of that context. While I am not explaining that well, this is the same lexical binding feature that makes Javascript closures so unbelievably powerful.

When I read this, I wanted to see if this lexical binding could be leveraged to create a Groovy Class Factory that could be passed into the ColdFusion context in such a way that it would provide ColdFusion with a portal back into the original Groovy context. Essentially, I wanted to create a Groovy proxy that ColdFusion could use to instantiate classes defined in the proxy's original context.

To make this as flexible as possible, I wanted to use some sort of Reflection in which the Groovy Class Factory could create instances based on the given class name without having to hard code a bunch of different constructors. Essentially, I wanted to somewhat mirror ColdFusion's CreateObject() method and allow people to do something like this:

groovyFactory.get( "CLASS_NAME" ).init( arg, .... N )

To do so, I created a factory class that had a single method, get(). This get() method would then create the Class object based on the given class name and store it in a class instance proxy object. This class instance proxy object then had a single method, init(), which would create new instances based on the given constructor arguments. I am sure there is a MUCH better way to do this, but I am wicked new to Groovy, so my init() method is a bit of kludge; it can take only up to 10 arguments which it then translates into a dynamic-length array which is used as the argument collection in the target class' instantiation method.

A bit of a hack, but it totally works. The top of this code sample defines the Groovy context. The ColdFusion code that leverages this context is as the bottom:

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

<!---
	The Groovy script tag caches the Java loader in the Server
	scope. For testing purposes, we are going to clear it out
	(as I think it caches the available classes??).

	NOTE: This might be completely wrong!!
--->
<!--- <cfset structDelete( server, "cfgroovy" ) /> --->


<g:script>

	<!---
		Define the GroovyFactory class. This is the class that
		will allow ColdFusion to instantiate Groovy-bound classes
		once back inside the ColdFusion context. This is because
		Groovy is lexically-bound, meaning that it has access to
		the variables / class in the context in which it was
		defined. As such, once an instance of this is passed out
		of Groovy, it will still have access to this Groovy
		context.
	--->
	class GroovyFactory {

		<!---
			This method returns a Groovy class proxy using the
			given class name such that new instances can be
			instantiated in such a manner:

			GroovyFactory.get( className ).init( arg, ... arg );

			Basically, I just wanted to be able to seperate the
			class name from the list of constructor arguments.
		--->
		public get( String className ){
			return(
				new GroovyFactoryInstanceProxy(
					java.lang.Class.forName(
						className,
						false,
						this.getClass().getClassLoader()
					)
				)
			);
		}

	}


	<!---
		Define the Groovy instance proxy class. This class is a
		proxy for the constructor of the target class. It
		containst the class definition internally such that the
		init() method can be called with a list of N arguments.
	--->
	class GroovyFactoryInstanceProxy {

		<!--- Store the class internally. --->
		private def targetClass = null;

		<!---
			This is the constructor for the proxy. It simply
			stores the target class definition for use in the
			init() method call.
		--->
		public GroovyFactoryInstanceProxy( Class targetClass ){
			this.targetClass = targetClass;
		}

		<!---
			This is the actual factory method for the current
			target class. It takes N arguments (max of 10)
			arguments, which it converts into an array that will
			be used to call the target classes constructor
			(using the reflection method - newInstance()).

			NOTE: I am using 1-10 here because I could not yet
			figure out how to have a variable number of arguments
			that did NOT require them to be an array when called
			from ColdFusion.
		--->
		public init(
			Object a1 = null,
			Object a2 = null,
			Object a3 = null,
			Object a4 = null,
			Object a5 = null,
			Object a6 = null,
			Object a7 = null,
			Object a8 = null,
			Object a9 = null,
			Object a10 = null
			){

			<!---
				Create a local collection of arguments that we
				will use during our constructor call on the
				target class.
			--->
			def arguments = [];

			<!---
				Loop over each argument that was passed to this
				method, and, if it is NOT NULL, then add it to
				the collection of arguments we will use to
				instantiate the target class.

				NOTE: We are using an Closure here who's first
				argument is implicit stored in the variable, "it".
			--->
			[ a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 ].each({
				if (it){
					arguments.add( it );
				}
			});

			<!---
				Return a new instance of the target class using
				the mapped arguments array.
			--->
			return(
				this.targetClass.newInstance( arguments.toArray() )
				);
		}

	}


	<!---
		Store the Groovy Factory instance in the ColdFusion
		variables scope so that we can now access this ENTIRE
		Groovy context (via the Factory) from ColdFusion.
	--->
	variables.groovyFactory = new GroovyFactory();


	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->
	<!--- Groovy Class Definitions ------------------------ --->
	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->


	class Person {

		private def name = "";
		private def hair = "";
		private def gender = "";

		public Person(){
			<!--- Nothing to do here. --->
		}

		public Person(
			String name,
			String hair = null,
			String gender = null
			){

			<!--- Store properties. --->
			this.setName( name );

			if (hair){
				this.setHair( hair );
			}

			if (gender){
				this.setGender( gender );
			}
		}

		public String getName(){
			return( this.name );
		}

		public Object setName( value ){
			this.name = value;
			return( this );
		}

		public String getHair(){
			return( this.hair );
		}

		public Object setHair( value ){
			this.hair = value;
			return( this );
		}

		public String getGender(){
			return( this.gender );
		}

		public Object setGender( value ){
			this.gender = value;
			return( this );
		}

	};



	class Relationship {

		def private Person person1;
		def private Person person2;

		public Relationship(
			Person person1,
			Person person2
			){
			this.person1 = person1;
			this.person2 = person2;
		}


		public String toString(){
			return(
				(
					person1.getName() +
					(
						person1.getHair().length() ?
						(" (" + person1.getHair() + ") ") :
						""
					) +
					" is dating " +
					person2.getName() +
					(
						person2.getHair().length() ?
						(" (" + person2.getHair() + ") ") :
						""
					) +
					" ... awesome!"
				)
			);
		}

	};

</g:script>



<!--- Create a girl. --->
<cfset sarah = groovyFactory.get( "Person" ).init(
	"Sarah",
	"Brunette",
	"Female"
	) />

<!--- Create a boy (using a slightly different syntax). --->
<cfset ben = groovyFactory.get( "Person" ).init()
	.setName( "Ben" )
	.setHair( "Brunette" )
	.setGender( "Male" )
	/>

<!--- Create a relationship. --->
<cfset relationship = groovyFactory.get( "Relationship" ).init(
	sarah,
	ben
	) />

<!--- Output the relationship string. --->
<cfoutput>
	#relationship.toString()#<br />
</cfoutput>

As you can see, I have my two Factory / Proxy classes as the top of the Groovy context, followed by a Groovy class definitions for Person and Relationship. After the Groovy Factory class is defined, an instance of it is created and stored back into the ColdFusion Variables scope. Once outside of the Groovy context, ColdFusion then uses this Groovy Factory instance (stored in the implicit Variables scope) to create two Person instances and a Relationship instance.

Running the above code, we get the following output:

Sarah (Brunette) is dating Ben (Brunette) ... awesome!

As you can see, by using the Groovy Factory, the ColdFusion context is successfully instantiating Groovy classes from outside of the Groovy context. Since the Groovy Factory class was defined in the Groovy context, it can reference that Groovy context even once outside the Groovy context. As such, once it is passed into the ColdFusion context, it can act as a tunnel back into the original Groovy context.

I'm not sure where I'm going with this; but, I really like the idea of being able to create Groovy classes from within ColdFusion. If for no other reason, it will make experimenting with Groovy a lot easier.

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

Reader Comments

25 Comments

Don't forget that you don't need getters/setters in Groovy unless you want custom behaviour (like setters returning 'this'). So you can strip all the getters from your code and just use the properties. And you don't have to call them either, you can create a person like this:

[cfset person = ... /]
[cfset person.hair = "Brunette" /]

and it'll invoke the setHair method.

Clearing server.cfgroovy will, in fact, reset the CFGroovy context, but it's rarely necessary. CFGroovy automatically reloads it's context for each new script body, so when you change your script, you'll start from scratch. The only reason you'd need to wipe server.cfgroovy is if you needed to load new JARs or something, but you'd have to restart your container as well, which would make it moot.

And don't forget things like Person.enterRelationship(otherPerson) to create relationships between objects, rather than doing it manually.

I wrote up a blog post expounding on some related topic that didn't seem to fit a comment very well. It's at http://www.barneyb.com/barneyblog/2009/09/17/groovy-objects-in-cfml-a-la-ben/

111 Comments

Just making sure you know that the getters and setters are actually implicit in Groovy, so as long as you have the

def Name

groovy would create

getName()
setName( 'Ben' )

for you.

Also, if you wanted to set "Name" on init, you could do

new Person( [ name: 'Ben' ] ) (and no need for a constructor either...it automagically sets 'Ben' to the name property.)

15,663 Comments

@Barney,

Ah, good points. Yeah, I had the server variable in there for a while, but then I commented it out and found that it was working fine. I left it in the code example, just for... documentation I guess. I think at first, I thought maybe it was causing some method signature errors, but in the end, it was not.

I will check out your blog post, thanks.

@Gareth,

Thanks, I'll check out that link.

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