ColdFusion 8 Application Specific Mappings Work With The CFComponent Extends Attribute

Posted June 11, 2009 at 9:00 AM

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

 Launch code in new window » Download code as text file »

  • <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

 Launch code in new window » Download code as text file »

  • <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

 Launch code in new window » Download code as text file »

  • <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

 Launch code in new window » Download code as text file »

  • <!--- 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.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page



Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Jun 11, 2009 at 10:15 AM // reply »
18 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.


Jun 11, 2009 at 10:16 AM // reply »
25 Comments

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.


Jun 11, 2009 at 10:25 AM // reply »
18 Comments

@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.


Jun 11, 2009 at 10:37 AM // reply »
25 Comments

@Tony

That's more than likely where I went wrong - I never did clearly understand that one -
*thanks


Jun 11, 2009 at 10:40 AM // reply »
6,516 Comments

@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.


Jun 11, 2009 at 12:06 PM // reply »
7 Comments

@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.


Jun 11, 2009 at 12:08 PM // reply »
25 Comments

@Ryan -

Yeah thanks, I've found that to be the case -


Jun 11, 2009 at 12:10 PM // reply »
6,516 Comments

@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.


Jun 11, 2009 at 1:26 PM // reply »
29 Comments

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.


Jun 11, 2009 at 3:51 PM // reply »
6,516 Comments

@Nathan,

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


Jun 12, 2009 at 11:46 AM // reply »
25 Comments

@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?


Jun 12, 2009 at 11:52 AM // reply »
25 Comments

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 -


Jun 12, 2009 at 12:20 PM // reply »
25 Comments

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.


Jun 12, 2009 at 12:22 PM // reply »
6,516 Comments

@Kevin,

Where in the Application.cfc are you defining this?


Jun 12, 2009 at 12:32 PM // reply »
25 Comments

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.


Jun 12, 2009 at 12:36 PM // reply »
25 Comments

@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 (?).


Jun 12, 2009 at 12:58 PM // reply »
6,516 Comments

@Kevin,

Yeah, from what I have played with:

http://www.bennadel.com/blog/1583-ColdFusion-8-Per-Application-Settings-Get-Partially-Cached-And-There-s-Nothing-You-Can-Do-About-It-.htm

Not only MUST the mappings be defined inside the pseudo construct (code inside cfcomponent, outside cffunction), you need to wait until you are in an event handler (ex. OnRequest()) before you can use them.


Jun 12, 2009 at 3:53 PM // reply »
25 Comments

@Ben -

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


Jun 12, 2009 at 4:28 PM // reply »
6,516 Comments

@Kevin,

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


Jun 12, 2009 at 4:49 PM // reply »
25 Comments

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.


Jun 12, 2009 at 4:55 PM // reply »
6,516 Comments

@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?


Jun 12, 2009 at 6:21 PM // reply »
25 Comments

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).


Jun 13, 2009 at 11:22 AM // reply »
18 Comments

@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.


Jun 15, 2009 at 8:43 AM // reply »
6,516 Comments

@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.


Jun 15, 2009 at 8:57 AM // reply »
6,516 Comments

@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??


Jun 15, 2009 at 10:45 AM // reply »
25 Comments

@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.


Jun 27, 2009 at 12:28 AM // reply »
45 Comments

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>


Jun 29, 2009 at 8:50 AM // reply »
6,516 Comments

@Andrew,

Interesting technique. I have not played with virtual mappings very much.


Jun 30, 2009 at 11:44 AM // reply »
6 Comments

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?


Jun 30, 2009 at 1:34 PM // reply »
6,516 Comments

@Drew,

Yes.


Jul 7, 2009 at 1:12 PM // reply »
25 Comments

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.


Jul 7, 2009 at 1:19 PM // reply »
6,516 Comments

@Kevin,

Does this return a "/" starting path? Or an expanded path?


Jul 7, 2009 at 2:56 PM // reply »
25 Comments

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


Jul 7, 2009 at 5:06 PM // reply »
6,516 Comments

@Kevin,

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


Jul 7, 2009 at 5:43 PM // reply »
25 Comments

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.


Jul 7, 2009 at 5:55 PM // reply »
6,516 Comments

@Kevin,

Ok, sounds good. I'll have to check it out.


Don
Aug 28, 2009 at 3:28 PM // reply »
33 Comments

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.


Don
Aug 28, 2009 at 3:54 PM // reply »
33 Comments

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.


Sep 2, 2009 at 9:21 AM // reply »
6,516 Comments

@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.


Nov 14, 2009 at 2:40 PM // reply »
4 Comments

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


Nov 15, 2009 at 7:05 PM // reply »
6,516 Comments

@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.


Nov 16, 2009 at 10:37 AM // reply »
4 Comments

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.


Nov 16, 2009 at 11:33 AM // reply »
7 Comments

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";


Nov 16, 2009 at 12:06 PM // reply »
6,516 Comments

@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.


Nov 16, 2009 at 12:19 PM // reply »
4 Comments

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


Nov 16, 2009 at 12:24 PM // reply »
6,516 Comments

@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!


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 20, 2009 at 11:32 PM
Five Months Without Hungarian Notation And I'm Loving It
I've used headless camel case for years for not only ColdFusion variables, but also SQL tables and fields... pretty much everything involving code. I also subscribe to the "don't abbreviate and clea ... read »
Nov 20, 2009 at 11:00 PM
Five Months Without Hungarian Notation And I'm Loving It
@Marcel, Yeah, I always err on the side of longer but more readable variable names. As for the camel casing of CF methods and the headless camel casing of custom items, I get around this by always ... read »
Nov 20, 2009 at 10:56 PM
Five Months Without Hungarian Notation And I'm Loving It
I use the following and love it: my.namespace.MyComponents.functionMethodsOrUDF() CONSTANT_VALUES_OR_PROPERTIES One thing I always try is to CamelCaseBuiltInColdFusionFunctions() so others can tell ... read »
Nov 20, 2009 at 5:38 PM
Learning ColdFusion 8: CFImage Part I - Reading And Writing Images
Hi Ben, Great article. I've been looking around to see if ColdFusion image engine can programatically create the following "wrap around" effect: http://www.creativepro.com/article/photoshop-s-she ... read »
Nov 20, 2009 at 5:35 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Dave: I talked to Gert he suggested: <cfhttp method="get" url="http://{some cf website}" result="stuff" addtoken="yes" /> Note the addition of cfhttp attribute addtoken. That should persist y ... read »
Nov 20, 2009 at 5:23 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Todd, Ahh, gotcha, yeah that makes sense. ... read »
Nov 20, 2009 at 5:17 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
Ben, sorry if I didn't make this clear. You can make it work like that if you want, just put <cfset session.foo = 1> (and <cfset application.foo = 1>) in your OnRequestStart() and it reve ... read »