Skip to main content
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Guust Nieuwenhuis
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Guust Nieuwenhuis

Ask Ben: Dynamically Executing ColdFusion Application.cfc Instances

By
Published in , Comments (21)

Here's a challenge. I'm trying to code a solution to replace a hefty, expensive CMS platform. ColdFusion does a lot of stuff and, I believe, can be used to replace that platform. Except for one thing; that CMS can surface CF apps and allow them to use the application.cfc within their directory. An example:

/root/application.cfc
/root/index.cfm
/root/stuff/application.cfc
/root/stuff/module.cfm

My framework, index.cfm, can include module.cfm but then it would only see /root/application.cfc. It would not be able to use what's being set in its local application.cfc. A round-about solution would be to CFHTTP module.cfm (domain/root/stuff/module.cfm) but that would be pretty clunky. How would you solve this?

It is possible to dynamically execute a certain ColdFusion Application.cfc, but you cannot do once you get outside of the original Application.cfc that is getting invoked by the ColdFusion server as the pre-page processor. What this means is that by the time you make it to "index.cfm", it is too late - you are running with the root Application.cfc and the call to module.cfm will use that application file. You can, however, put some logic in your root Application.cfc instance to have it dynamically execute other Application.cfc instances.

I am not sure that I have the best answer for you, but I will give you some information and maybe you can run with it. Before we get into the code, it is important to understand some key concepts about ColdFusion's Application.cfc. For starters, there is nothing special or magical about the Application.cfc file. It is just a ColdFusion component like any other ColdFusion component; it can be manually instantiated and its methods can be invoked. The only thing that makes Application.cfc special is that the ColdFusion server looks for it and if it finds it, it creates an instance of it for every page requests and attempts to fire any application level events on it.

The second key concept to understand about ColdFusion's Application.cfc file is that it is NOT your application. The Application.cfc and the application itself are two extremely different and district concepts. The Application.cfc is merely a component that gets automatically instantiated and basically "listens" for application-level events that the ColdFusoin server announces. The Application, on the other hand, is really a allocated memory space that is referenced using the application name.

Take a look at the following diagram to see a bit more about what I am talking about:

ColdFusion Application Server Application Memory Space / Application.cfc Relationship

This is a simplified work flow, obviously, but you can see that your "application" exists in the ColdFusion memory space. That application then announced "events" such as "ApplicationStart" and "RequestEnd". Those events are then routed to the associated Application.cfc. Now, the way that a given Application.cfc file becomes associated to a given "Application" memory space is through the public Name property of the Application.cfc. NOTE: This is not 100% accurate, but for our purposes, it is pretty good.

Now that we have that out of the way, I am gonna set up the following test directory structure:

./application.cfc
./app1/Application.cfc
./app2/Application.cfc
./sub1/index.cfm
./sub2/index.cfm

Here, we have index.cfm files in sub directories and Application.cfc in parallel directories. We also have an Application.cfc in the root directory. Now, since the sub1/sub2 directories do not have an Application.cfc of their own, if you try to execute those templates, ColdFusion will search up the directories until it finds the Application.cfc in the root and invokes that as the Application.cfc for our index.cfm files.

What we want to do as part of this experiment is to have the index.cfm from "sub1" use the Application.cfc from "app1", and likewise the index.cfm file from "sub2" to use the Application.cfc from "app2". Before we get into the wiring, let's look at each of the applications.

Here is the Application.cfc for "app1":

<cfcomponent
	output="false"
	hint="I define application settings for APP 1.">

	<!--- Define application settings. --->
	<cfset THIS.Name = "SubAppOne" />
	<cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 5, 0 ) />
	<cfset THIS.SessionManagement = true />
	<cfset THIS.SessionTimeout = CreateTimeSpan( 0, 0, 5, 0 ) />

	<!--- Define request settings. --->
	<cfsetting showdebugoutput="false" />


	<cffunction
		name="OnSessionStart"
		access="public"
		returntype="void"
		output="false"
		hint="I run when the user's session begins.">

		<!---
			Create a new varible to hold the number of times
			that the user has hit this application.
		--->
		<cfset SESSION.HitCount = 0 />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="OnRequestStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I run before the requested template gets processed.">

		<!--- Define arguments. --->
		<cfargument
			name="Page"
			type="string"
			required="true"
			hint="I am the requested page."
			/>

		<!--- Set up DSN. --->
		<cfset REQUEST.DSN = {
			Source = "app1_dsn",
			Username = "app1_user",
			Password = "monkey"
			} />

		<!--- Set up mode. --->
		<cfset REQUEST.Mode = "Dev" />

		<!--- Increment session hit count. --->
		<cfset SESSION.HitCount++ />

		<!--- Return out. --->
		<cfreturn true />
	</cffunction>


	<cffunction
		name="OnRequest"
		access="public"
		returntype="void"
		output="true"
		hint="I execute the page template.">

		<!--- Define arguments. --->
		<cfargument
			name="Page"
			type="string"
			required="true"
			hint="I am the requested page."
			/>

		<!--- Include the requested page. --->
		<cfinclude template="#ARGUMENTS.Page#" />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>

</cfcomponent>

As you can see, the "app1" Application.cfc has session management as well as the request event handlers. Notice also that in the OnRequestStart(), it is incrementing the SESSION.HitCount. We are doing this to make sure that the session will stick from request to request.

Ok, now let's look at the Application.cfc in "app2":

<cfcomponent
	output="false"
	hint="I define application settings for APP 2.">

	<!--- Define application settings. --->
	<cfset THIS.Name = "SubAppTwo" />
	<cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 5, 0 ) />

	<!--- Define request settings. --->
	<cfsetting showdebugoutput="false" />


	<cffunction
		name="OnRequestStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I run before the requested template gets processed.">

		<!--- Define arguments. --->
		<cfargument
			name="Page"
			type="string"
			required="true"
			hint="I am the requested page."
			/>

		<!--- Set up DSN. --->
		<cfset REQUEST.DSN = {
			Source = "app2_dsn",
			Username = "app2_user",
			Password = "donkey"
			} />

		<!--- Set up mode. --->
		<cfset REQUEST.Mode = "Production" />

		<!--- Return out. --->
		<cfreturn true />
	</cffunction>

</cfcomponent>

This version of the application is much more simple; it doesn't have any session management and the only event that it listens for in the RequestStart event.

Now, let's take a quick look at one of the index files. They are basically the same, except that one says "App 1" and one says "App 2":

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
	<title>Sub Application 1</title>
</head>
<body>

	<h1>
		Sub Application 1
	</h1>

	<p>
		This is folder "Sub1". It will be using the
		Application.cfc file located in a
		<strong>parallel folder</strong>, "App1".
	</p>

	<h2>
		Application Data:
	</h2>

	<cfdump
		var="#APPLICATION#"
		label="Application - Sub 1 Folder"
		/>

	<h2>
		Session Data:
	</h2>

	<cfdump
		var="#SESSION#"
		label="Session - Sub 1 Folder"
		/>

	<h2>
		Request Data:
	</h2>

	<cfdump
		var="#REQUEST#"
		label="Request - Sub 1 Folder"
		/>

</body>
</html>

Pretty basic - we are just dumping out the APPLICATION, SESSION, and REQUEST scopes to see what application we are in.

Ok, now time to take a look at the root Application.cfc. This is where the magic glue comes into effect. The root Application.cfc is basically acting as a proxy to the sub Application.cfc instances:

<cfcomponent
	output="false">

	<!---
		Create the target Application.cfc instance. Check to
		see which directory is currently executing. Based on
		that, we will figure out which App instance to create.
	--->
	<cfset VARIABLES.TargetAppNumber = Right(
		REReplace(
			GetDirectoryFromPath( CGI.script_name ),
			".{1}$",
			"",
			"one"
			),
		1
		) />


	<!---
		Now that we have the target app, create the appropriate
		Application.cfc in the parallel folder.
	--->
	<cfset VARIABLES.TargetApp = CreateObject(
		"component",
		"app#VARIABLES.TargetAppNumber#.Application"
		) />


	<!---
		We need to "borrow" the settings from the target
		application. To do this, we will loop over all the
		pulic keys and copy over all non-method values.
	--->
	<cfloop
		item="VARIABLES.TargetAppKey"
		collection="#VARIABLES.TargetApp#">

		<!--- Check to see if this is a method. --->
		<cfif NOT IsCustomFunction( VARIABLES.TargetApp[ VARIABLES.TargetAppKey ] )>

			<!--- This is a public value - copy it. --->
			<cfset THIS[ VARIABLES.TargetAppKey ] = VARIABLES.TargetApp[ VARIABLES.TargetAppKey ] />

		</cfif>

	</cfloop>



	<!---
		ASSERT: At this point, we should have copied over all
		the public properties of the Application.cfc including
		the application and session settings. And, since THIS
		application does not have any of those settings, those
		target ones should take over.
	--->


	<cffunction
		name="OnSessionStart"
		access="public"
		returntype="void"
		output="false"
		hint="I run when the user's session begins.">

		<!--- Check to see if our target has this method. --->
		<cfif StructKeyExists( VARIABLES.TargetApp, "OnSessionStart" )>

			<!--- Return call to target application. --->
			<cfreturn VARIABLES.TargetApp.OnSessionStart() />

		</cfif>

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="OnRequestStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I run before the requested template gets processed.">

		<!--- Define arguments. --->
		<cfargument
			name="Page"
			type="string"
			required="true"
			hint="I am the requested page."
			/>


		<!--- Check to see if our target has this method. --->
		<cfif StructKeyExists( VARIABLES.TargetApp, "OnRequestStart" )>

			<!--- Call this on the target application. --->
			<cfif VARIABLES.TargetApp.OnRequestStart( ARGUMENTS.Page )>

				<!---
					Now that the target's OnRequestStart() method
					has run, we need to check to see if the
					target has the OnRequestStart() method. It is
					possible that it doesn't exist at this point
					because it either NEVER existed or because it
					was deleted manually. Either way, if it does
					not exist, we have to delete it from THIS
					component in case a web service call is
					coming through.
				--->
				<cfif NOT StructKeyExists( VARIABLES.TargetApp, "OnRequest" )>

					<!---
						No OnRequest() in the target. Delete it
						from THIS instance so it doesn't execute as
						an event method.
					--->
					<cfset StructDelete( THIS, "OnRequest" ) />

				</cfif>

				<!--- Return out. --->
				<cfreturn true />

			<cfelse>

				<!---
					The target application returns false, so
					let's return false as well. This will stop
					the rest of the page from executing.
				--->
				<cfreturn false />

			</cfif>

		<cfelse>

			<!---
				The target app doesn't have this method. Again,
				however, we have to check to see if the target
				has an OnRequestStart() method. If it does not,
				we have to delete This version in case a web
				service call is coming through.
			--->
			<cfif NOT StructKeyExists( VARIABLES.TargetApp, "OnRequest" )>

				<!---
					No OnRequest() in the target. Delete it
					from THIS instance so it doesn't execute as
					an event method.
				--->
				<cfset StructDelete( THIS, "OnRequest" ) />

			</cfif>

			<!--- Return out. --->
			<cfreturn true />

		</cfif>
	</cffunction>


	<cffunction
		name="OnRequest"
		access="public"
		returntype="void"
		output="true"
		hint="I execute the page template.">

		<!--- Define arguments. --->
		<cfargument
			name="Page"
			type="string"
			required="true"
			hint="I am the requested page."
			/>

		<!--- Call target application. --->
		<cfset VARIABLES.TargetApp.OnRequest( ARGUMENTS.Page ) />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>

</cfcomponent>

Let's step through what is happening. At the very top of the Application.cfc, we are executing the pseudo-constructor. This is the code, outside of functions, that executes automatically when the CFC is instantiated. In this code, we are looking at the script name (the requested template) to see which sub folder we are in. From that information, we are grabbing the number (ex. 1, 2) and using that to manually instantiate one of the sub Application.cfc instances.

Now, just instantiating this component does nothing since ColdFusion isn't looking at it for application level events or properties. That is why, next, we iterate over our target Application.cfc and dynamically, at runtime, copy over its public properties (NOT methods) to the root Application.cfc. This will copy properties such as:

  • TargetApp.Name (application name - to associate with memory space)
  • TargetApp.ApplicationTimeout (application timeout property)
  • TargetApp.SessionManagement (session management property)

It is important that the root Application.cfc does NOT have any application-specific properties, and more specifically, it cannot have a NAME property. If it does, ColdFusion will associate it with a different "application" memory space. The magic here, though, is that as we are copying over the target app's properties, we are going to be copying over its Name property, which will make ColdFusion associate our root Application.cfc with the "sub" Application.cfc's memory space.

That's the magic right there. If you are not clear on that point, think of it as stealing someone's wallet. Let's say I steal Ben Forta's wallet and now I have his ID. We both look like a "Ben" and then I start to announce myself as "Ben Forta" AND I have the ID associated with that name, so to the external world, that's all they know. In the programming world, we have one Application.cfc "stealing" the identity of another Application.cfc and announcing itself as such.

Once we have the proper identity, the root level event listeners (ex. OnSessionStart(), OnRequestStart()) are just turning around and invoking the events on the target application. I have not defined every application event, just enough to get the idea of what is going on. It's pretty straight forward. The only tricky part is handling the OnRequest() event. The very existence of the OnRequest() event listener has serious implications on the page flow, specifically when it comes to web service calls. That is why we are taking extra pains to remove the OnRequest() event listener from the root Application.cfc is the sub Application.cfc does not have it.

That's all there is to that. Not a lot going on, just a little bit of magic sauce and whole lot of sexy. And, just so you can see that it is all working properly, here is a screen shot of /sub1/index.cfm:

Root Application.cfc Executing Sub Application.cfc 1

... and here is a screen shot of /sub2/index.cfm:

Root Application.cfc Executing Sub Application.cfc 2

As you can see, each sub index.cfm file is using the properties of the correct Application.cfc file. Also notice that the session in Application 1 is sticky and the HitCount is property updated (reads 7 in screen shot).

I know this doesn't exactly answer your question, but maybe it gives you some food for thought.

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

Reader Comments

14 Comments

@Ben

All I can you are a magic man, love the way you explain things. I have been using CF top to bottom but your post always clear many ambiguities.

Will Love if someone add your posts in CF live docs.

Wish to meet you face to face.

15,902 Comments

@Sana,

Thank you very much! That really means a lot and inspires me to keep going! I really try very hard to explain things as best I can. I get some flack from time to time with the amount of comments I put in my stuff, but its comments like yours that make me feel it is all worth it.

As far as meeting, are you going to CFUnited?

132 Comments

Maybe I'm confused, but this seems terribly over complicated... and backwards. Why doesn't your "subapplication" just extend your "root application" ?

Our entire application platform works this way. Every conference application/website is a single giant CF application separated by subapp names. They share ColdSpring, Transfer, services et al. this way. It takes a little more discipline since each subapp's variables exist in application[subAppName], session[subAppName] etc., but facades deal with that nicely.

Sean has a nice post about this too:
http://corfield.org/entry/Extending_Your_Root_Applicationcfc

15,902 Comments

@Elliott,

I am not sure that Sean's method would even apply here. In the question posed, the Application.cfc and the code files were in parallel directories. This wasn't a matter of extension - this was a matter of invocation of a specific non-parent Application.cfc component. Unless I have something wrong, I think this is a very different scenario.

132 Comments

@Ben

I don't quite get how the directory structure relates to this at all. Just because your applications are in parallel directories doesn't mean that they can't all extend some super component that brings in shared things.

/system/SharedApplication.cfc
/app1/Application.cfc extends system.SharedApplication
/app2/Application.cfc extends system.SharedApplication
...

Your root application.cfc ends up being nothing more than a dynamic proxy down into the sub applications. Of course the problem with that is that only one "sub application" can be active at a time, so if his CMS wanted to run two module.cfm files from two completely different applications it wouldn't work because your proxy only allows one application to be active at a time.

I'm saying to think about this completely different and invert the problem. If each sub application extends a super application and they all store shared variables inside the shared application scope, then instead of "surfacing" the applications by trying to run their Application.cfc files and their templates directly, you can store shared components and variables directly in the application scope (since they're the same application) and access them directly in this management CMS application the question presented.

So in your management application, which extends the SharedApplication.cfc as well...

application["mysubapp1"].someComponent.someMethod(...)

Where someComponent is really part of the mysubapp1 sub application, but since it lives in the application scope, which every sub app shares, the management app can access it directly.

Then your CMS isn't trying to swizzle in including templates across applications, and you're no longer bound to having only one active sub application at a time... since they're all the SAME application! They just segment off their variables to provide the illusion of running in their own environment.

15,902 Comments

@Elliott,

I am sorry, I am not quite seeing how this works. Maybe we are talking about different problems? Or maybe its just Friday and my brain is fried (the likely response).

Here is the test case I have:

./application.cfc
./app1/Application.cfc
./app2/Application.cfc
./sub1/index.cfm
./sub2/index.cfm

If I call the script sub/index.cfm, how does the extension help? The root level Application.cfc doesn't extend something (since its the "1" in a 1-to-many relationship). I don't see how the sub application can know anything since its not in the same directory or a parent directory, but rather a parallel directory.

Using the directories I just outlined, can you explain the relationships to me?

132 Comments

sub/index.cfm doesn't actually exist in your layout, but from what your code does...

./application.cfc
./app1/Application.cfc
./app2/Application.cfc
./sub1/index.cfm
./sub2/index.cfm

If we call sub1/index.cfm it really just ends up inside the app1 application right? Since the root application.cfc dynamically loads up the Application.cfc for "app1". So why not make Sub1 extend App1?

This is easily solved like this:

/app1/Application.cfc
/app2/Application.cfc
/sub1/Application.cfc extends app1.Application
/sub2/Application.cfc extends app2.Application

I really don't get why you're using this dynamic proxy.

Extending the parent application does exactly what your code does, without the proxying of events.

15,902 Comments

@Elliott,

Ooooh, I see what you're saying. I didn't realize you would have to modify the directory structure.

As to why I would do this? I wouldn't - I was trying to answer his question :) But, yeah, I like what you are doing there.

Sometimes, I get get so lost in the implementation that I forgot to simply suggest something else.

132 Comments

Similarly, and this is more toward what I understood the original poster to be asking, since I'm not sure how your solution solves what the question asked...

The question seems to be asking, from what I understand, how can you cfinclude a template in a completely different CF application, but make it have all the application variables it would have had if it was called directly (hence the comment about CFHTTP).

The asker presented a layout like this:

/root/application.cfc
/root/index.cfm
/root/stuff/application.cfc
/root/stuff/module.cfm
/root/stuff2/application.cfc
/root/stuff2/module2.cfm

They want to be able to cfinclude the module.cfm from inside index.cfm, but have the Application.cfc from "stuff" app still run. Now we can do this by making /root/application.cfc extend stuff.Application, but then we can't use module2.cfm since it's Application.cfc never gets called. Your solution doesn't solve this problem either.

The only way to really do this is with CFHTTP as the poster mentioned, pageContext.include(), or by making this whole thing one *giant* application that shares components.

That last option is the solution I was presenting. Making all the applications share variables and components.

So if we have...

/root/Application.cfc
/root/stuff1/Application.cfc extends root.Application.cfc
/root/stuff1/module.cfm
/root/stuff1/SomeModel.cfc
/root/cms-manager/Application.cfc extends root.Application
/root/cms-manager/do-stuff.cfm

stuff1.Application creates a SomeModel and stores it at application["stuff1"].someModel

module.cfm does some user processing and finally calls:

<cfset application["stuff1"].someModel.create(data)>

So now in our do-stuff.cfm in the manager application we can do:

application["stuff"].someModel.create() as well, and even though that model isn't really part of our application, we can still access the live object because it's stored in the same CF application!

Now, you can't really cfinclude the module.cfm into the cms-manager, but the logic of it is really encapsulated inside the SomeModel which is accessible in both the manager AND the stuff1 application. Using this method we can keep going and add "stuff2" and stuff3, etc.

As a side note, a common way to handle this is to store ColdSpring somewhere shared like application.coldSpring and then each sub application makes it's own ColdSpring instance that does setParent(application.coldSpring). Then put all the shared services in the parent ColdSpring.xml. In fact, this is how ModelGlue: Unity works. :)

41 Comments

Could be the sub-application is compiled and you don't have access to the source.

Anyway, I really saw it more of a brain teaser than anything, myself.

15,902 Comments

@Elliott,

Sorry about yesterday. I have been working the past few weekends and at about 5pm yesterday, my brain was just fried. I was having trouble following what you were saying. I like the solution you are suggesting. You are correct in that my answer was not actually answering the askee's question. I didn't know how. I was just trying to give some idea that might inspire.

I have never built a system as complex as the one you are describing, so I will take your word for it - it looks pretty good.

1 Comments

Maybe slightly off topic, but is it possible to dynamically extend an application.cfc that is in a sub folder? basically I was wondering if it was possible to run multiple applications off a single coldfusion webroot that was specified by folders.. e.g.

/webroot
/webroot/demoapp1/application.cfc
/webroot/demoapp1/sub1/index.cfm
/webroot/demoapp2/application.cfc
/webroot/demoapp2/sub2/index.cfm
/webroot/demoapp3/application.cfc
/webroot/demoapp3/sub3/index.cfm

I'm trying to work with an application that runs the same code base, but is customizable for a client, and was looking to setup some "demo" sites that ran off a single coldfusion instance. I'm just not sure as to how to get coldfusion to set/recognize the webroot accordingly for each application (if this is possible at all?). Previously with application.cfm it was just a matter of including the relative parent's application.cfm, but now that we've moved to applciation.cfc, I'm finding it a bit tricky in relpicating it for multiple applications under the single webroot.

Thanks in advance,

Jason

5 Comments

In my toplevel app cfc I set:

<cfset this.applicationTimeout = createTimeSpan(0,0,20,0)>
<cfset this.sessionManagement = true>
<cfset this.sessionTimeout = createTimeSpan(0,0,19,0)>

But the copy of the public value THIS[] in the sub app cfc is:

SESSIONTIMEOUT 0.0131944444444
SESSIONMANAGEMENT true
LOGINSTORAGE session
SETCLIENTCOOKIES true
APPLICATIONTIMEOUT 0.0138888888889

wtf?

15,902 Comments

@Mark,

I am not sure what your question is? Are you concerned about the way the TimeOuts are showing up? They are merely fractions of a day.

15,902 Comments

@Mark,

The CreateTimeSpan() function takes days, hours, minutes, and seconds as its four arguments and then returns the number of days that accounts for. Most of the time, this is a faction of a day. For example, an 8 hour session timeout:

CreateTimeSpan( 0, 8, 0, 0 )

... would give you:

0.33333333333

... or 1/3 of a day.

18 Comments

In the example where you extend a top-level Application.cfc with a sub-directory Application.cfc; can you modify parameters *other than name* and still share the application scope? E.g., could I have a subdirectory with session management disabled but still share the same Application scope as my top level Application.cfc?

15,902 Comments

@Brian,

I believe that would work perfectly. You can definitely use conditional logic to set the session management and timeout so I believe that would transfer to different sub-classes as well.

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