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 the New York Javascript Meetup (Aug. 2010) with:

ColdFusion 8 Application Specific Mappings Work With The CFComponent Extends Attribute

By Ben Nadel on
Tags: ColdFusion

This blog post is a bit silly because it's merely confirming something that was supposed to work. However, until last night, for some reason, I was under the impression that application-specific mappings in ColdFusion 8 did not work the CFComponent Extends attribute. This wasn't just some wild theory I had - I thought this because I've had a number of problems getting application-specific mappings to work in applications ported from CF7 to CF8 in which frameworks such as ColdSpring were used; even after settings up "ColdSpring" as an application specific mapping, the framework still threw errors about components that it couldn't find.

Last night, I asked about changes to this in ColdFusion 9, and both Ben Forta and Adam Lehman put me straight - it's supposed to work with application-specific mappings in CF8. So, this morning, I put together a small demo to see if I was crazy. I constructed the following directory tree:

/app/
/app/Application.cfc
/app/index.cfm
/com
/com/Base.cfc
/com/Test.cfc

As you can see, the "com" directory is parallel to my "app" directory so, in order to create a CFC in the app, I'm going to need a mapping. But then, the Test.cfc is going to extend the Base.cfc using a mapping as well. Here is my Application.cfc with mapping:

Application.cfc

  • <cfcomponent
  • output="false"
  • hint="I provide application level settings and event handlers.">
  •  
  • <!--- Defnie request settings. --->
  • <cfsetting showdebugoutput="false" />
  •  
  • <!--- Define application-specific mappings. --->
  • <cfset THIS.Mappings[ "/com" ] = (
  • REReplace(
  • GetDirectoryFromPath( GetCurrentTemplatePath() ),
  • "[^\\/]+[\\/]$",
  • "",
  • "one"
  • ) &
  • "com\"
  • ) />
  •  
  • </cfcomponent>

As you can see, I am creating a mapping, "/com", by moving up one directory from the current directory and then appending "com\" to the end.

In my "com" directory, the Test.cfc extends the Base.cfc, so let's look at the Base.cfc first:

Base.cfc

  • <cfcomponent
  • output="false"
  • hint="I am the base component for this package.">
  •  
  • <cffunction
  • name="Test"
  • access="public"
  • returntype="string"
  • output="false">
  •  
  • <cfreturn "I am com.Base.Test()" />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, not much going on. The Test() method simply returns a string containing the mapped method that was executed. Now, let's take a look at the Test.cfc that extends the Base. Notice that the Extends attribute of the CFComponent tag uses the application-specific mapping defined in our Application.cfc:

Test.cfc

  • <cfcomponent
  • extends="com.Base"
  • output="false">
  •  
  • <cffunction
  • name="Test"
  • access="public"
  • returntype="string"
  • output="false">
  •  
  • <!---
  • Return test string from both the super component
  • (from extends attribute) and the current component.
  • --->
  • <cfreturn (
  • "[ #SUPER.Test()# ] " &
  • "I am com.Test.Test()"
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

Ok, so now that we have all of our components in place, let's take a look at the code that ties it all together - the index.cfm page. My Index.cfm page simply creates the Test.cfc component using the application-specific mapping and calls its Test() method:

Index.cfm

  • <!--- Create an object with the mapping. --->
  • <cfset objTest = CreateObject( "component", "com.Test" ) />
  •  
  • <!--- Test component method.. --->
  • <cfoutput>
  • #objTest.Test()#
  • </cfoutput>

When we run this code, we get the following output:

[ I am com.Base.Test() ] I am com.Test.Test()

As you can see, not only did the application-specific mapping let me create the Test.cfc component, it also allowed the Test.cfc component to extend the mapped Base.cfc component.

Like I said before, this is the expected behavior! Looking at it now, it seems so simple; I am not sure what problems I could have possibly been having with this before, but clearly it was an error on my part. There's nothing more frustrating than when silly mistakes cost you time and energy.




Reader Comments

Since I'm not a huge fan of regular expressions (they look like gibberish to me), I tend to go with relative mappings instead.

<cfset this.mappings["/com"] = getDirectoryFromPath(getCurrentTemplatePath()) & "..\com\" />

It looks kinda weird if you dump the mapping, but it still works.

Reply to this Comment

I've had problems in the past when doing app mappings as well - especially when specifying expandpath('.') or expandpath('/') for the file system path in an Application.cfc. Then to make matters worse, then a gateway call is the first to initialize your app, you end up with a cfusion.ear/cfusion.war type path instead of perhaps the expected one of say c:\Inetpub\wwwroot or whatever -

We've thrown out the ability to map at the application level in favor of the cfadmin mapping, which can then be converted to the platform specific path whether its linux or windows - and prevents problems overall.

Reply to this Comment

@Kevin,

Using expandPath() will return the directory path to the page that is currently executing, meaning the mapping will be based on the page that initializes your application.

If you use getDirectoryFromPath(getCurrentTemplatePath()), it will return the directory path of the page where the function is being called, which should always be Application.cfc.

Reply to this Comment

@Tony,

Cool tip. I can't say that I knew that relative paths would even work that way. Thanks!

@Kevin,

Yeah, as @Tony said, ExpandPath() can be iffy because it's related to the requested template, NOT the executing template. That's why GetCurrentTemplatePath() is such a powerful function.

Reply to this Comment

@Kenny,
About platform specific paths, I make it a habit to convert any backslashes to forward slashes in the paths I get back from the various path functions, since forward slashes in CF work on Windows. I can then proceed happily without worrying about path separators in my applications.

Reply to this Comment

@Ryan,

I try to make that a habbit; but, I believe in the latest versions of ColdFusion, I am pretty sure that that no longer makes a difference.

Reply to this Comment

I too was under the impression this did not work.

I think part of the confusion stems from the fact that you cannot use application specific mappings in cfimport tags, so they do not work everywhere you would expect them to.

Reply to this Comment

@Nathan,

Hmmm, maybe that was my problem. I could have sworn it was a cfc extends issue. Maybe it was a collection of problems :)

Reply to this Comment

@Tony -

Would GetBaseTemplatePath() instead of getCurrentTemplatePath() be 'technically' more accurate - as it's the 'Gets the absolute path of an application's base page.' - so it should return your base app path - regardless of where its used in your application -
I understand if your using 'getCurrentTemplatePath' in app.cfc you're good * - but seems getBaseTemplatePath is a handy one if you needed that base reference elsewhere right?

Reply to this Comment

Actually no you're right - I see the error of my ways:

If i'm in a cfc off my webroot say in /model/x - and i dump either getbasetemplatepath OR getCurrentTemplatePath, they both return the same value - neither of which are the path to where the application.cfc lives -

Reply to this Comment

Ok - I'm still not convinced application specific mappings even work and I have verified 10x that I have the 'enable app settings' turned on in cfadmin.

I have a root of say:
c:\websites\webroot
and I specify
<cfset this.mappings["/root"]="c:\websites\webroot"/>
If i try to instantiate an object off the root, or even include a file in the root such as:
<cfinclude template="/root/test.cfm"/>

It gives me 'Could not find the included template /root/test.cfm'

Even through I've just defined it!

Of course, once I go into the cfadmin and add that mapping in for that instance - then it works fine - I think this is my main cause of concern with this technique.

Reply to this Comment

Ok - sorry for the post barrage - but i think it's worth noting:

In the application.cfc where these mappings are defined in the constructor code, attempting to call a cfinclude or a createobject using one of the predefined mappings simply does not work from what i can tell. However, after that, they do seem to take hold - confidence restored (sort of).

Thanks all for your patience - and feedback - these tips helped me get rid of a few mappings in our application and will ease deployment to multiple servers by not having to define new mappings via cfadmin.

Reply to this Comment

@Ben -

Yeah so to be more clear on the implementation that didn't work:

<cfcomponent displayname="Application">

<cfset this.mappings["/root"]="c:\websites\webroot"/>

<cfinclude template="/root/test.cfm"/>

Won't work -
However, in an onApplicationStart Method - once the Constructor has been setup - using an include or any other referernce to the mapping seems to work fine - seems you just can't use it w/in the constructor block it seems - as the mappings must not be set until the contstructor has completed (?).

Reply to this Comment

@Ben -

Yup - works flawlessly now! You just can't attempt to reference the mappings w/in the pseudo construct.

Reply to this Comment

@Kevin,

Glad you got it working. Yeah, seems like an odd limitation. I guess it has to do with when the settings get applied.

Reply to this Comment

Ok - so here's the ultimate rub:

You need these mappings defined in the cfadmin ANYWAY - if you're going to be working with flex remote objects!

I just found that out - worth a blog post on it's own.

So regardless of the ability to map them w/in the application, when you want flex to interact with them and have the remoting-config.xml 'use-mappings' turned on, you still need to define the path in the cfadmin.

Back to square 1 again ;) having to define them in cfadmin.

Reply to this Comment

@Kevin,

So, just so we're on the same page, what you're saying is that the Extends attribute of the remote-access CFC doesn't work with app-specific mappings?

Reply to this Comment

Well, if you're talking access="remote" on the test.cfc itself and you attempt to call that using flex remote Object call - using an alias of 'com.Test', you may find that it fails unless you specify '/com' mapping in the cfadmin. From what I just found. I had app specific mappings in the application itself, but tried to call a component via flex and got a 'Could not find the ColdFusion Component or Interface' error in the cflogs. Once I added the mapping to the cfadmin, the flex remote call worked again (as I had removed the mapping from cfadmin to prove that the application mappings were the only mappings being taken).

Reply to this Comment

@Kevin,

Unfortunately application specific mappings won't work for flash remoting since the call isn't coming from the application, so you'll still need to create the mapping within your cfadmin.

What I do is create the mapping during onApplicationStart() by using the cfadmin API. Works pretty well, but your application will need to be initialized before the flash remoting will work, which I don't think is too much to ask.

Reply to this Comment

@Tony,

When you say flash remoting, you are specially referring to AMF-style remoting, correct? If the Flash / FLEX movie makes a call using standard HTTP calls, it should work like any page request in the applicaiton.

Reply to this Comment

@Kevin,

Gotcha. I'm not too familiar with FLEX for Flash remoting. I know there are like 3 different ways to connect to a given URL in FLEX, using different underlying technologies. I think some work and some will not work with the app-specific mappings.

Seems odd, though, as the remote object should have access to the underyling application??

Reply to this Comment

@Tony - Thanks for confirmation on that - yeah seems a bit inconsistent that it's not at the application level but more at the app server level then. I agree, the best method would be through admin api - which we're doing for enough other things as well.

I hope this wasn't too far off the original topic - but VERY good information regardless, and not sure how 'well' documented some of these are.

Reply to this Comment

I found that as an alternative you can setup a virtual directory for your site.

I worked this out after not being able to set a dynamic mapping for Mach-II's directory, as it is required in the cfcomponent's extend method for Application.cfc.

I have tested it in Apache on my development computer, however am pretty sure that it will work for IIS (as this is the approach taken to setup CFIDE for each site).

For anyone that is curious on how to do this for Apache: Create an Alias for the folder and then set the folder permissions via a directory node. The following has worked for me:

Alias /[Mapping] "[Folder Location]"
<directory "[Folder Location]">
Options FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</directory>

Reply to this Comment

We use application level variables to map our CFCs, like so:
<cfset application.cfc.udf = createObject("component","cfc.UDF") />

These are run once variables in the onApplicationStart(). Would I be able to use application level mappings (this.mappings) to define my cfc paths?

Reply to this Comment

Just to add another item to this thread - I came accross a reference in code from Adobe that had this:

getPageContext().getServletContext().getRealPath("/")

This seems to work nicely to get the 'root' directory path as well - and in a clean format.

Reply to this Comment

It returned the absolute path to the root of my application - which is a struggle to get at from various parts of an app if needed

i.e. c:\inetpub\wwwroot\mysite

Reply to this Comment

@Kevin,

Looks good. Does it matter where you call that from? Are you calling from within Application.cfc?

Reply to this Comment

No doesn't seem to matter
- however it does return the my 'cfusion.ear\cfusion.war\' path if I have an app under that area vs. outside - so may be useful depending on your situation.

Try before you buy I guess.

Reply to this Comment

Kevin, that doesn't work if you are using virtual directories in IIS. I have my dev app on e:\myapp and when I put in the code getPageContext().getServletContext().getRealPath("/")

It returned c:\inetpub\wwwroot so it looks for the default webroot not the app root.

So far I haven't been able to get this working. Even Ben's example doesn't work because I get the E:\ back but not E:\myapp so all my mappings go to E:\. That doesn't work of course.

Reply to this Comment

So exactly where do I put the mappings? I tried in the whole "this" area of the app.cfc but that doesn't work. I put it in the onRequestStart and that doesn't work.

Reply to this Comment

@Don,

I have had serious problems with files paths with virtual directories because the URL no longer aligns itself nicely with the actual file path (which doesn't include the virtual directory).

As far as the mappings, you have to put them in the pseudo constructor of the Application.cff (at the top, outside of any CFFunction tags). The biggest trick is that the map key *must start with* "/". If it doesn't it won't work.

Reply to this Comment

I'm a little late to this discussion but ran into it while trying to solve a shared server mapping issue. In the case Ben discusses at the beginning, the mapping works as shown, but what if I were using a App.cfc in a subdirectory off of /app/, say for an admin secure section and still want my cfc's in /com off the root? I guess I'm stuck using mappings from CF Admin and not App.cfc mappings?

I dont' see how I could set the /cfc from App.cfc in the admin sections. The code as shown would add /app/ to the mapping.

root/app/admin/Application.cfc
root/app/Application.cfc
root/cfc/allmysharedcfc's

root/app2/Application.cfc
root/app2/admin/Application.cfc
use same /cfc/allmysharedcfc's

Doug

Reply to this Comment

@DougS,

I am not sure I understand what your issue is. Even if you have an application coming off the primary app, the mapping to the component directory would still work, so long as the sub-app defined the appropriate mappings.

Reply to this Comment

I guess I wasn't very clear. Nothing new there my wife says ;>) My point is that using your example directory setup..

/app/
/app/Application.cfc
/app/index.cfm
/com
/com/Base.cfc
/com/Test.cfc

and <cfset THIS.Mappings[ "/com" ] = (
REReplace(
GetDirectoryFromPath( GetCurrentTemplatePath() ),
"[^\\/]+[\\/]$",
"",
"one"
) &
"com\"
) />

works fine IF you are in the /app/ directory and want to set the mapping to wwwroot/com.
But if you move the Application.cfc to /app/secure/, it sets the mapping to /app/com. How would I go about setting the mapping in Application.cfc from the /app/secure/ directory, or any sub-directory to map to wwwroot/com? I want to always map to the /com in the root directory using Application.cfc, so the cfc's can be used by several applications.

Reply to this Comment

Doug,

In my application.cfc, I'lll often set the base directory before setting up my mappings. Usually something like this:
this.baseDirectory = replaceNoCase(replace(getCurrentTemplatePath(), "\", "/", "all"), "app/secure/Application.cfc", "");

The inner replace ensures all the slashes are forward, to make the outer replace work cross platform. Then, for the mappings:
this.mappings["\com"] = this.baseDirectory & "com";

Reply to this Comment

@DougS,

Ahhh, I see what you're saing. Yeah, you'd have to go up an additional directory when setting up your mapping path. @Ryan has a good pointer there in getting the root-directory first and then building your mappings off of that.

Reply to this Comment

Thanks guys. I just tried Ryan's suggestion and that works for me. I've always thought a detailed blog or writeup of "mappings" would be a really good idea and a brief discussion with some people at RIAUnleashed Boston confirmed to me that others are having some sort of problem with mappings. There are snippets of good blogs around discussing certain aspects of mapping, but no really good detailed discussion covering mapping on shared servers, dedicated servers, etc. all in one place.

Thanks Ben and Ryan.

Doug

Reply to this Comment

@DougS,

I am not sure the type of server should matter (shared, dedicated, etc.) as the only thing that is important is file paths. The above technique should work well in any situation, as far as I know.

I was up at RIAUnleashed as well; it was a good conference!

Reply to this Comment

G'day, Ben. Long-time reader, first-time poster. Sorry to be dragging this up after so long, but I haven't been able to find a definitive "yes, it's possible"/"no, it's not possible" answer anywhere (and I thought you might be able to help).

Can a per-application mapping be used when extending the Application.cfc? Example, I've got the following:

{1}/webroot/site-a/Application.cfc
{2}/webroot/site-a/ApplicationProxy.cfc
{3}/webroot/site-a/subdir/Application.cfc

I declare a per-app mapping in Application.cfc{1}
that points to the directory 'site-a' (the site root, so to speak). I call the mapping 'siteroot'. ApplicationProxy.cfc{2} extends Application.cfc{1} (no problems there). I then try to have /subdir/Application.cfc{3} extend ApplicationProxy.cfc{2} using:

extends="siteroot.ApplicationProxy"

My experience so far suggests this is not possible. It works, obviously, if the 'siteroot' mapping is created through the CFAdmin.

If I've done a bad job at explaining things, here's a guy trying for the same outcome: http://stackoverflow.com/questions/307423/extending-application-cfc-in-a-subdirectory

I should also mention I'm on CF8.

Thanks in advance, Ben.

Reply to this Comment

@Nic,

The ApplicationProxy.cfc approach does work (this approach was suggested by Sean Corfield a while back). However, Application.cfc (in any directory) cannot take advantage of any per-application mapping as the mappings have *not* yet been defined at the time the Application.cfc is instantiated.

Now, you can use the ApplicationProxy.cfc approach IF you don't use mappings (at least not mappings defined in the same app - CFAdmin-based mappings are fine). The problem with this is that it makes the path to the root App a bit more of a pain to get, especially if the path changes from your local Dev environment to the live Production environment.

If you want to get *creative*, you can do this with CFInclude. Too much to explain here - I'll try to post about this on Monday.

Reply to this Comment

Thanks for replying, Ben; your explanation makes perfect sense.

I'm now faced with my Monday-morning dilemma for the week: leave our structure as is and continue to rely on the CFAdmin for mappings, OR, flatten our Application.cfc heirarchy (moving the overridden methods from the subs to the parent), and do away with CFAdmin-based mappings altogether. (sigh) I need a coffee...

Reply to this Comment

@Nic,

No problem my man. I'll blog something tomorrow morning that might help (at least as a work-around).

Reply to this Comment

Hi,

Just need a small advise...

If I have a site by the name "Site".
Components are in the components folder like this:

Site\components,

then in cf8, I need to create a mapping to access the cfcs in components folder....

but in cf9, we can directly access using the notation "components." without creating any application specific mappings for this particular folder....

Please do correct me if I am wrong.........

Reply to this Comment

@Viky,

You can access your 'components' directory using dot-notation in both CF8 and CF9 without having to create a mapping, provided your path to the directory always starts from the web-root.

So, if your site structure looks like this: http://yoursite/components/yourCFC.cfc

...you can instantiate yourCFC.cfc using 'components.yourCFC' (this works right back to CF7).

One reason you might want to create a mapping is if your site sits off the web-root when running under different environments. For example, if you have a testing server that has the following structure:

http://testingserver/yoursite/components/yourCFC.cfc

...you'd always need to change your code from 'components.yourCFC' to 'yoursite.components.yourCFC' (and vice-versa) when migrating your site between servers.

Creating a mapping on both servers means your code can always stay as 'yourmapping.yourCFC', where 'yourmapping' points to /components/ on one server, and /yoursite/components/ on the testing server.

I hope I've made sense :)

Reply to this Comment

Yet another gotcha ...

Always use forward slashes instead of backslashes ... it will pay off if you have to migrate your site from Windoze ...

:-)

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.