Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with:

ColdFusion 10 - Accessing The Call Stack With CallStackGet()

By Ben Nadel on
Tags: ColdFusion

When your application is running, the ColdFusion server maintains information about the currently executing functions and the context in which they were invoked. This data structure is known as the "call stack" or "execution stack". If you've ever had a ColdFusion application raise an exception, you're probably familiar with the "TagContext" property of the ColdFusion error object. This is gives us access to the call stack that existed at the time the exception was raised. ColdFusion now gives us two functions for accessing the call stack outside of errors: callStackGet() and callStackDump().

NOTE: At the time of this writing, ColdFusion 10 was in public beta.


 
 
 

 
  
 
 
 

The new function, callStackGet(), returns an array of data structures. Each data structure within this array contains the template name, relevant line number, and the name of the function being invoked. To see how this stack compares to the one found in a ColdFusion error object, take a look at the following code:

  • <cfscript>
  •  
  •  
  • // First, we'll see what the call-stack from a ColdFusion
  • // error object looks like. For this, we'll have to raise an
  • // exception and catch it.
  • showErrorStack = function(){
  •  
  • try {
  •  
  • // Raise an exception so we can access the call stack.
  • throw( type = "DemoError" );
  •  
  • } catch ( DemoError error ){
  •  
  • writeDump( var = error, label = "Error" );
  •  
  • }
  •  
  • };
  •  
  •  
  • // Now, we'll see what the call-stack from the new ColdFusion 10
  • // callstackGet() function looks like.
  • showCallStack = function(){
  •  
  • writeDump( var = callstackGet(), label = "Call Stack" );
  •  
  • };
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • // Show both call stack versions.
  • showErrorStack();
  • writeOutput( "<br />" );
  • showCallStack();
  •  
  •  
  • </cfscript>

As you can see here, we're simply outputting the two call stacks - the one exposed within an error and the one returned from callStackGet(). When we run this code, we get the following page output:


 
 
 

 
 The call stack returned from callStackGet() vs. the call stack returned in a ColdFusion error object. 
 
 
 

The ColdFusion error object clearly exposes more information; but, the callStackGet() result is much easier to work with.

While call stack data is typically hidden from high-level programming, now that it is exposed, we can start to leverage it in interesting ways. The biggest advantage that comes to mind is the ability to create ColdFusion user defined functions (UDFs) that need to access information about their calling context.

Right now, ColdFusion provides a built-in function for getting the currently-executing template:

getCurrentTemplatePath()

This function is awesome! I use it in every single ColdFusion application that I build. One of the ways in which I use it is to dynamically calculate the root directory of my application (from within my Application.cfc ColdFusion framework component):

  • rootDirectory = getDirectoryFromPath( getCurrentTemplatePath() );

This line of code works, but it's far too verbose. What I'd really like is a function that returns the current template directory:

getCurrentTemplateDirectory()

While this would have been impossible or processing-intense in previous versions of ColdFusion, the new callStackGet() function makes this easy. By programmatically accessing the call stack, we can quickly and efficiently create user defined functions (UDFs) that require information about the calling context.

To experiment with this new feature, I'm going to create two user defined functions that leverage the execution stack:

  • getCurrentTemplateDirectory() - This returns the directory path (with trailing "/") of the currently executing template.
  • expandFilePath( relativePath ) - This resolves the given path relative to the currently executing path.

To set this demo up, I've created a ColdFusion application framework file that defines a missing-template handler and the two user defined functions:

Application.cfc - Our ColdFusion Application Framework Component

  • <cfscript>
  • // NOTE: The CFScript is purely for Gist color-coding. Remove.
  •  
  • component
  • output="false"
  • hint="I define the application settings and event handlers."
  • {
  •  
  •  
  • // Define the application settings.
  • this.name = hash( getCurrentTemplatePath() );
  • this.applicationTimeout = createTimeSpan( 0, 0, 5, 0 );
  •  
  •  
  • // I handle missing ColdFusion template (CFC | CFM) exceptions.
  • function onMissingTemplate(){
  •  
  • // Include the demo file no matter what we do. This way, we
  • // can see how expandPath() relates to the requested URL.
  • include "callstack2.cfm";
  •  
  • // Return true so no 404 errors are thrown.
  • return( true );
  •  
  • }
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  • // The following is a *hack* to provide globally-accessible
  • // user-defined functions by overriding the use of the URL scope.
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • // I expand the given file path relative to the calling template.
  • url.expandFilePath = function( relativeFilePath ){
  •  
  • // Get the callstack - the template calling this function
  • // will be the 2nd element in the array.
  • var callstack = callstackGet();
  •  
  • // Convert the calling template into a Java URI so that we
  • // can resolve the relative file path against it.
  • var callingUri = createObject( "java", "java.net.URI" ).init(
  • javaCast( "string", callstack[ 2 ].template )
  • );
  •  
  • // Resolve the relateive path.
  • relativeUri = callingUri.resolve(
  • javaCast( "string", relativeFilePath )
  • );
  •  
  • // Return the resolved, relative template file path.
  • return( relativeUri.toString() );
  •  
  • };
  •  
  •  
  • // I get the template directory of the calling template. This
  • // is logically equivalent to:
  • //
  • // getDirectoryFromPath( getCurrentTemplatePath() );
  • //
  • url.getCurrentTemplateDirectory = function(){
  •  
  • // Get the callstack - the template calling this function
  • // will be the 2nd element in the array.
  • var callstack = callstackGet();
  •  
  • // Return the directory of the calling template.
  • return(
  • getDirectoryFromPath( callstack[ 2 ].template )
  • );
  •  
  •  
  • };
  •  
  •  
  • }
  •  
  • // NOTE: The CFScript is purely for Gist color-coding. Remove.
  • </cfscript>

As you can see, I've scoped both the user defined functions (UDFs) to the URL scope; this is to make them globally accessible, as if they were native ColdFusion functions. I've also configured the onMissingTemplate() handler to always include the same file. This is so we can compare the new expandFilePath() UDF to the native expandPath() function.

Notice that both user defined functions access the second item within the call stack value. Since the call stack includes the currently-executing context as the first element, the calling context is always the second element.

With this Application.cfc in place, I created the following demo code:

  • <cfscript>
  •  
  •  
  • // Try getting the current directory.
  • writeOutput( "Current Directory: <br />" );
  • writeOutput( getCurrentTemplateDirectory() );
  •  
  •  
  • writeOutput( "<br /><br />" );
  •  
  •  
  • // Try getting the expanded FILE - this is relative to the
  • // currently executing template.
  • writeOutput( "Expanded File Path: <br />" );
  • writeOutput( expandFilePath( "./expandedFile.cfm" ) );
  •  
  •  
  • writeOutput( "<br /><br />" );
  •  
  • // --- CONTROL CASE --- //
  •  
  •  
  • // Try getting the expanded PATH - this is relative to the
  • // currently requested Script (URL).
  • writeOutput( "Expanded Path: <br />" );
  • writeOutput( expandPath( "./expandedPath.cfm" ) );
  •  
  •  
  • </cfscript>

Here, we're invoking the two user defined functions as well as the expandPath() function. I'm including the expandPath() function to demonstrate that it performs relative to the requested script, not to the currently executing file. When we run the above code at the following URL:

http://localhost:8500/coldfusion10/callstack/--MISSING--/404.cfm

NOTE: This template does not actually exist - it will be caught by the onMissingTemplate() application event handler.

... we get the following page output:

Current Directory:
/Sites/bennadel.com/testing/coldfusion10/callstack/

Expanded File Path:
/Sites/bennadel.com/testing/coldfusion10/callstack/expandedFile.cfm

Expanded Path:
/Sites/bennadel.com/testing/coldfusion10/callstack/--MISSING--/expandedPath.cfm

As you can see, we were able to access the template path of the calling context as a means to calculate the currently active directory. You can also see that our "missing" template affected the expandPath() function, but not the expandFilePath() function.

To me, creating user defined functions is where the callStackGet() function is really going to shine. As you can hopefully see from the two UDFS in this demo, being able to access information about the calling context is pretty powerful in that it can greatly simplify some algorithms.




Reader Comments

Most people who definitely are into fashion are very addictive about the hottest trends. It not the fault of the developer if they have clients that can seem to get as a result their designs. To create something extraordinarily attractive is what they want to attain, even if they are very costly. This is why quite a couple of people can afford to order what is considered to be a collector item.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.