Skip to main content
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Guust Nieuwenhuis and Damien Bruyndonckx and Steven Peeters and Cyril Hanquez
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Guust Nieuwenhuis ( @Lagaffe ) Damien Bruyndonckx ( @damienbkx ) Steven Peeters ( @aikisteve ) Cyril Hanquez ( @Fitzchev )

Using THIS.jarPaths To Create An Application-Specific URL Class Loader In ColdFusion

By on
Tags:

At this last CFUNITED, Rupesh Kumar gave a talk titled, "Extending Java Applications with ColdFusion." In the Q&A portion of the talk, I asked Rupesh if Adobe had any plans to make application-specific Java class loaders in the same way that Application.cfc can currently define its own mappings and custom tag paths. He said that this was something that they were looking into; however, I wanted to see if I could play around with a little proof-of-concept on my own.

Right now, if you want to make new Java classes available to your ColdFusion instance, you have to add the given JAR files to the server's lib folder and then restart the ColdFusion service. While this isn't such a big deal, there is something very nice about keeping all aspects of an application in a single, cohesive location. This increases the portability of the application which, in turn, reduces the chances of you making an error.

To keep the JAR files within the application code base, I was picturing the use of a THIS-scoped JAR path collections property, much in the same vein as the currently-supported custom tag paths and mappings:

Application.cfc Properties

  • this.mappings = []
  • this.customTagPaths = ""
  • this.jarPaths = []

NOTE: I am using an array for jarPaths; I have no idea why customTagPaths gets defined as a list.

Once the "this.jarPaths" is defined within the Application.cfc, any calls to the createObject("java") function made within the same application would automatically check the application-specific JAR paths before it attempted to load the Java class from the core class collection. The way ColdFusion is set up now, however, you can't really override core functions. You can create like-named functions as properties of an object; but, if you were to try and call those custom functions without any scoping, ColdFusion will always assume that you are trying to call the core functions. As such, for this proof of concept, I can't override the createObject() function directly; rather, I have to create a globally-accessible UDF called createJava().

Before we see how this proof of concept is wired together, let's take a look at the kind of implementation that I think might be nice. Here is the simple Application.cfc for my demo:

Application.cfc

<cfcomponent
	extends="BaseApplication"
	output="false"
	hint="I define the application settings and event handlers.">

	<!--- Define the application settings. --->
	<cfset this.name = hash( getCurrentTemplatePath() ) />
	<cfset this.applicationTimeout = createTimeSpan( 0, 0, 5, 0 ) />

	<!---
		Add the current directory to our collection of application-
		specific JAR paths to be used with createJava().
	--->
	<cfset this.jarPaths = [
		"file://#getDirectoryFromPath( getCurrentTemplatePath() )#"
		] />

</cfcomponent>

Ignoring for a moment the fact that this Application.cfc extends BaseApplication.cfc, you can see that this component defines a "this.jarPaths" property and adds the root application directory as a source of application-specific JAR paths.

Now, let's take a look at a demo page executed within the context of this application:

<!---
	At this point, the ColdFusion server has implicitly instantiated
	Application.cfc - our ColdFusion framework component. That has
	created a URL classloader with the application-specific JAR files
	and created a globally-accessible "createJava()" method.

	Create an instance of the java class, HelloWorld.

	NOTE: This calls the default constructor implicitly. We would
	need to get a bit more compliated to be able to pass in
	constructor arguments... which goes beyond my know-how.
--->
<cfset helloWorld = createJava( "HelloWorld" ) />

<!--- Say hello via the new class. --->
<cfoutput>

	Hello: #helloWorld.sayHello()#

</cfoutput>

As you can see, the page is making use of a globally-accessible createJava() method in order to create an instance of our HelloWorld Java class. Then, it calls the sayHello() method on that Java class instance. In doing this, we get the following page output:

Hello: Waaaaazzzzuuuuuuupppp!

The HelloWorld Java class that it is loading is located in the root directory of the application and is picked up using the this.jarPaths Application.cfc property:

HelloWorld.class (As .java File)

public class HelloWorld {

	public HelloWorld(){
		// Constructor code.
	}

	public java.lang.String SayHello(){
		return( "Waaaaazzzzuuuuuuupppp!" );
	}

}

Now that we see how this kind of functionality might be used, let's take a look at how this proof-of-concept is put together. The magic behind this comes from that BaseApplication.cfc that my above Application.cfc was extending. This BaseApplication.cfc creates a URLClassLoader and defines a createJava() method that it stores in the globally-accessible URL scope (a hack used to create globally-accessible variables in ColdFusion).

BaseApplication.cfc

<cfcomponent
	output="false"
	hint="I am a base Application component meant to be extended by other Application.cfc instances.">


	<!---
		Define the collection of JAR paths to be used for the URL
		class loader in this application.
	--->
	<cfset this.jarPaths = [] />


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


	<!---
		Append the CreateJava() method to the URL collection. While
		this makes NO sense from a semantic standpoint, the way in
		which variables are "discovered" in ColdFusion allows us to
		use the URL scope to create globally accessible functions.
	--->
	<cfset url.createJava = this.createJava />

	<!---
		Because methods copied by reference do not retain their
		original context, we also have to store a reference to THIS
		Application.cfc instance such that he createJava method can
		get access to the URL classloader instance.
	--->
	<cfset url.createJavaContext = this />


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


	<cffunction
		name="createJava"
		access="public"
		returntype="any"
		output="false"
		hint="I create the given Java object using the URL class loader powered by the local JAR Paths. NOTE: This will be called OUTSIDE of the context of this Application.cfc; this is why it makes reference to URL-scope values.">

		<!--- Define arguments. --->
		<cfargument
			name="javaClass"
			type="string"
			required="true"
			hint="I am the Java class to be loaded from the class loader."
			/>

		<!--- Define the local scope. --->
		<cfset var local = {} />

		<!---
			Overwrite the THIS context to fake out the rest of this
			function body into thinking it's part of the original
			Application.cfc instance.

			In a UDF, the variable "this" is already declared as a
			LOCAL variable; as such, all we have to do is overwrite
			it for this link to be created.
		--->
		<cfset this = url.createJavaContext />

		<!---
			Check to see if the URL class loader has been created
			for this page request.
		--->
		<cfif !structKeyExists( this, "urlClassLoader" )>

			<!---
				Create the URL class loader. Typically, we'd need to
				create some sort of locking around this; but, this is
				just a proof of concept.
			--->
			<cfset this.urlClassLoader = createObject( "java", "java.net.URLClassLoader" ).init(
				this.toJava(
					"java.net.URL[]",
					this.jarPaths,
					"string"
					),
				javaCast( "null", "" )
				) />

		</cfif>

		<!---
			Create a new instance of the given Java class.

			NOTE: When we use the newInstance() method, it calls the
			default constructor on the class with no arguments. I
			believe that if we want to use constructor arguments, we
			need to get the actual constructor object.
		--->
		<cfreturn this.urlClassLoader
			.loadClass( arguments.javaClass )
				.newInstance()
			/>
	</cffunction>


	<cffunction
		name="toJava"
		access="public"
		returntype="any"
		output="false"
		hint="I convert the given ColdFusion data type to Java using a more robust conversion set than the native javaCast() function.">

		<!--- Define arguments. --->
		<cfargument
			name="type"
			type="string"
			required="true"
			hint="I am the Java data type being cast. I can be a core data type, a Java class. [] can be appended to the type for array conversions."
			/>

		<cfargument
			name="data"
			type="any"
			required="true"
			hint="I am the ColdFusion data type being cast to Java."
			/>

		<cfargument
			name="initHint"
			type="string"
			required="false"
			default=""
			hint="When creating Java class instances, we will be using your ColdFusion values to initialize the Java instances. By default, we won't use any explicit casting. However, you can provide additional casting hints if you like (for use with JavaCast())."
			/>

		<!--- Define the local scope. --->
		<cfset var local = {} />

		<!---
			Check to see if a type was provided. If not, then simply
			return the given value.

			NOTE: This feature is NOT intended to be used by the
			outside world; this is an efficiency used in conjunction
			with the javaCast() initHint argument when calling the
			toJava() method recursively.
		--->
		<cfif !len( arguments.type )>

			<!--- Return given value, no casting at all. --->
			<cfreturn arguments.data />

		</cfif>


		<!---
			Check to see if we are working with the core data types -
			the ones that would normally be handled by javaCast(). If
			so, we can just pass those off to the core method.

			NOTE: Line break / concatenation is being used here
			strickly for presentation purposes to avoid line-wrapping.
		--->
		<cfif reFindNoCase(
			("^(bigdecimal|boolean|byte|char|int|long|float|double|short|string|null)(\[\])?"),
			arguments.type
			)>

			<!---
				Pass the processing off to the core function. This
				will be a quicker approach - as Elliott Sprehn says -
				you have to trust the language for its speed.
			--->
			<cfreturn javaCast( arguments.type, arguments.data ) />

		</cfif>


		<!---
			Check to see if we have a complex Java type that is not
			an Array. Array will take special processing.
		--->
		<cfif !reFind( "\[\]$", arguments.type )>

			<!---
				This is just a standard Java class - let's see
				if we can invoke the default constructor (fingers
				crossed!!).

				NOTE: We are calling toJava() recursively in order to
				levarage the constructor hinting as a data type for
				native Java casting.
			--->
			<cfreturn createObject( "java", arguments.type ).init(
				this.toJava( arguments.initHint, arguments.data )
				) />

		</cfif>


		<!---
			If we have made it this far, we are going to be building
			an array of Java clases. This is going to be tricky since
			we will need to perform this action using Reflection.
		--->

		<!---
			Since we know we are working with an array, we want to
			remove the array notation from the data type at this
			point. This will give us the ability to use it more
			effectively belowy.
		--->
		<cfset arguments.type = listFirst( arguments.type, "[]" ) />

		<!---
			Let's double check to make sure the given data is in
			array format. If not, we can implicitly create an array.
		--->
		<cfif !isArray( arguments.data )>

			<!---
				Convert the data to an array. Due to ColdFusion
				implicit array bugs, we have to do this via an
				intermediary variable.
			--->
			<cfset local.tempArray = [ arguments.data ] />
			<cfset arguments.data = local.tempArray />

		</cfif>

		<!---
			Let's get a refrence to Java class we need to work with
			within our reflected array.
		--->
		<cfset local.javaClass = createObject( "java", arguments.type ) />

		<!---
			Let's create an instance of the Reflect Array that will
			allows us to create typed arrays and set array values.
		--->
		<cfset local.reflectArray = createObject(
			"java",
			"java.lang.reflect.Array"
			) />

		<!---
			Now, we can use the reflect array to create a static-
			length Java array of the given Java type.
		--->
		<cfset local.javaArray = local.reflectArray.newInstance(
			local.javaClass.getClass(),
			arrayLen( arguments.data )
			) />

		<!---
			Now, we can loop over the ColdFusion array and
			reflectively set the data type into each position.
		--->
		<cfloop
			index="local.index"
			from="1"
			to="#arrayLen( arguments.data )#"
			step="1">

			<!---
				Set ColdFusion data value into Java array. Notice
				that this step is calling the toJava() method
				recursively. I could have done the type-casting here,
				but I felt that this was a cleaner (albeit slower)
				solution.
			--->
			<cfset local.reflectArray.set(
				local.javaArray,
				javaCast( "int", (local.index - 1) ),
				this.toJava(
					arguments.type,
					arguments.data[ local.index ],
					arguments.initHint
					)
				) />

		</cfloop>

		<!--- Return the Java array. --->
		<cfreturn local.javaArray />
	</cffunction>

</cfcomponent>

The bulk of this BaseApplication.cfc code is my toJava() method which I am using to easily cast ColdFusion arrays to typed-Java arrays for use within my URLClassLoader. Beyond that, there's not too much going on. The trickiest thing that I am doing is overriding the THIS scope of the createJava() method in order to "fake" the method into thinking it is being executed as part of the current Application.cfc instance.

Right now, I'm not making any use of caching. Theoretically, you'd probably want to cache the URL class loader in the Application or Server scope such that it doesn't have to be recreated on every single page request; however, for this proof of concept, I'm simply lazy-loading the URL class loader whenever the createJava() method gets called.

The fact that ColdFusion is built on top of Java is easily one of the most awesome aspects of the language; this let's us leverage some really amazing 3rd party projects coming out of the Java world. Now, as great as this is already, I think being able to organize those 3rd party Java projects on an application-specific basis would make this significantly more useful. Hopefully, we'll see some kind of functionality like this in future releases of ColdFusion.

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

Reader Comments

354 Comments

Just a note - you talk about your method of creating implicit UDFs, and your sample call calls it as if you did, but in your BaseApplication you end up using the URL scope instead.

15,663 Comments

@Ray,

Yeah, good point; I didn't mean to mislead anyone here. I am putting the UDF inside the URL scope so as to leverage the way ColdFusion looks for variable references. The URL scope is one of the scopes that gets crawled when a non-scoped variable is referenced in the code. So, technically, you could also use:

url.createJava()

... however, since URL can be implied (for lack of a better term), you can *sort of* call the createJava() as if it were a globally-accessible core function.

15,663 Comments

@Nelle,

It is getting cached as a public variable of the Application.cfc instance for that page request. Application.cfc gets re-created on every single page request; as such, we are also re-creating our URLClassLoader on each page request.

This is not so efficient; ideally, you'd probably want to cache the URLClassLoader in the Application or Server scope so that it only has to get created when, say, onApplicationStart() is executed.

290 Comments

@Ben: You said up top:

"Right now, if you want to make new Java classes available to your ColdFusion instance, you have to add the given JAR files to the server's lib folder and then restart the ColdFusion service."

For years now, my approach has been to define my government agency's(*) own directory in the classpath via ColdFusion Administrator or JRun Administrator, depending on how CF was installed.

(*) As always, I can't say what agency I work for without tons of disclaimers that I don't speak for the agency and that the agency isn't endorsing any commercial product, such as ColdFusion.

For the sake of illustration, let's refer to the directory with a Unixy environment variable name, such as $OURLIB. That one extra directory in the classpath has been all we've ever needed. It allows us to have numerous packages, using standard Java naming conventions, of the form $OURLIB/gov/ouragencyname/xxx, where xxx is whatever's unique about the package. But we keep them in our own folder, not the server's lib folder, so we don't have to track which files are ours and which ones belong to ColdFusion. It's tidier to have them separate like that.

In test and production, we can also package the class files up into $OURLIB/xxx.jar files, and that works just fine too.

But, as you indicated, we have to restart the ColdFusion service to pick up new classes and jar files.

I find this very vexing. ColdFusion interfaces to the class loader in such a way that, if a CFM file changes (and you don't have trusted cache turned on), CF recompiles it to a new class file and loads it, even though it's already loaded. Whenever I create new versions of my own class files, however, Java refuses to load the new versions because the old versions are already in memory.

With Java CFXs in CF 4.0, I used to be able to say reload="Yes/No/Auto", but that stopped working in CF 4.5. It's sad to pine for a feature I had 5 levels of ColdFusion ago. Maybe I just don't know the Java class loader well enough, but I would love to be able to put a new version of a class or jar file out to the server and not have to restart the ColdFusion service to pick up the new version.

Could you point me in the right direction about how to force the class loader to pick up my new class and jar files? How does CF do it???

Surely it can't be all of those gobbledy-gooky file names in the cfclasses directory, can it? Could CF be forcing the reload of classes by generating unique file names for every version of a CFM file? Seriously? If true, that seems kinda gross.

Sorry if this question is too simple and everyone else but me already knows the answer.

15,663 Comments

@Steve,

Sounds like a good strategy with adding agency-specific JAR directory; even though you have to restart the ColdFusion service, at least you are keeping your JARs in a cohesive, application-specific way.

As far as your caching issue, I think a Java Class loader would help since the JAR file isn't actually going through the ColdFusion system directly (the URL Class Loader is what's loading the JAR). That said, I've never actually built my own Class files (except for this actual demo). As such, I've never gone head-to-head with a JAR-caching issue. Sorry I don't have any advice on that matter.

A number of people have asked for this kind of functionality. There is some tricky functionality with Class Loaders (in the order in which they search the class loader chain for JAR files).

@Nelle,

Very interesting post (Mark Mandel is one bright dude!). I am not sure I follow his application-timeout issue though - if an Application were to timeout, I'd have to assume that all session had already timed out as well. As such, I can't see how the garbage collection issue isn't met (meaning that the URL Class Loader and all of the instances it has created have been de-referenced).

That said, very good to point that out to us, or at least me, since I had no idea that the URL Class Loader caused this kind of behavior.

5 Comments

Dear Ben,

again a very nice post. Thanks for it!

I have got a problem to get access to classes, which comes from JAR-Files.
If I use your method for normal CLASS-Files all works fine, but if I try to get them out of a JAR-File, which means, that I've done something like:

<cfset this.jarPaths = [

"file://#getDirectoryFromPath( getCurrentTemplatePath() )#ProjektFolder\",

"file://#getDirectoryFromPath( getCurrentTemplatePath() )#ProjektFolder\lib\testjar.jar"

] />

in the Application.cfc.

I've got an error message from the BaseApplication.cfc in the createJava-Function every time I try to create Java-Objects from the Jar-File:

java.lang.ClassNotFoundException: Test
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at coldfusion.runtime.StructBean.invoke(StructBean.java:511)
at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2300)
...

Maybe it is only a syntax-problem?

Thanks for any help,
Isabel.

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