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 cf.Objective() 2009 (Minneapolis, MN) with: Ryan Vikander and Kashif Khan

Creating ColdFusion Proxy Components With ColdFusion 9's GetFunctionCalledName()

By Ben Nadel on
Tags: ColdFusion

A couple of weeks ago, I started experimenting with ColdFusion 9's new objectSave() and objectLoad() functions for serializing and deserializing objects. In that post, I was able to leverage getFunctionCalledName() - another new ColdFusion 9 function - for creating proxy components. Creating an object that composes another object is nothing new; but, ColdFusion 9 makes it so easy that I wanted to break the concept out into its own blog post.

That secret sauce behind creating a proxy component in ColdFusion 9 is the new function, getFunctionCalledName(). This function will return the name of the parent method that is currently being invoked. Since ColdFusion component methods are variables just like anything else, they can be passed around, renamed, and reassigned; using this behavior in conjunction with getFunctionCalledName() allows us to create some powerful proxy functionality with very little code.

Before we demonstrate the proxy behavior, let's define the component that we are going to proxy:

Test.cfc

  • <cfcomponent
  • output="false"
  • hint="I provide two functions for testing proxies.">
  •  
  •  
  • <cffunction
  • name="getValue"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return a value.">
  •  
  • <cfreturn "This is a value." />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getVoid"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I don't return anything of value.">
  •  
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getComponent"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return a reference to the current object.">
  •  
  • <cfreturn this />
  • </cffunction>
  •  
  • </cfcomponent>

This target component demonstrates what I consider to be the three primary use cases for methods:

  1. They return a value.
  2. They return nothing (ie. void).
  3. They return a reference to the parent component, typically for method chaining.

The first two use cases represent nothing more than some complexity in referencing defined values. The third use case, on the other hand, presents a serious problem. If our job is to proxy a given component, we can never let our proxy methods return a reference to the target component. Doing so would break the proxy's encapsulation.

Unfortunately, ColdFusion has no sense of object reference equality; that is, you can't determine if two variables reference the same object. Using constructs like arrayFind() and arrayContains(), we can determine if two components mimic each other; but, we can't actually tell if they are the "same" object. As such, in order to deal with the third use case, we do have to alter our target component slightly, providing it with a unique identifier.

Luckily, even with these issues, creating a proxy object in ColdFusion 9 is still relatively simple. And, now that we've seen our target component, let's take a look at our proxy component. Typically, we would pass our target component into the proxy component during initialization; however, to keep this demo simple, I am creating the target component directly within the proxy component:

Proxy.cfc

  • <cfcomponent
  • output="false"
  • hint="I demonstrate how to proxy a component.">
  •  
  •  
  • <!---
  • To make this demo as easy as possible, we are going to
  • create our target component directly from within our proxy
  • component. Typically, you'd pass the taret component in
  • during initialization.
  • --->
  • <cfset variables.target = new Test() />
  •  
  • <!---
  • When proxying a given component, we need a way to identify
  • the target component such that if a method invocation returns
  • the target component, we can return the proxy component in
  • its stead.
  • --->
  • <cfset this.proxyUUID = createUUID() />
  •  
  • <!--- Copy the UUID flag into the target component. --->
  • <cfset variables.target.proxyUUID = this.proxyUUID />
  •  
  • <!---
  • Create our three proxy methods. Notice that both of our proxy
  • methods just point to the one defined local method. That
  • method will pass on the invocation message based on the name
  • of the invoked method.
  • --->
  • <cfset this.getValue = variables.proxyMethod />
  • <cfset this.getVoid = variables.proxyMethod />
  • <cfset this.getComponent = variables.proxyMethod />
  •  
  •  
  • <!--- ------------------------------------------------- --->
  • <!--- ------------------------------------------------- --->
  •  
  •  
  • <cffunction
  • name="proxyMethod"
  • access="private"
  • returntype="any"
  • output="false"
  • hint="I proxy the invoked method on the target component.">
  •  
  • <!---
  • Pass the invocation message onto the target component
  • and get the result.
  • --->
  • <cfinvoke
  • returnvariable="local.proxyResult"
  • component="#variables.target#"
  • method="#getFunctionCalledName()#"
  • argumentcollection="#arguments#"
  • />
  •  
  • <!---
  • Now that we have the result, we have a few special cases
  • to test for; we might have a void return, which will
  • leave our result undefined. We might also have the target
  • object returned (such as is done with method chaining).
  • --->
  • <cfif isNull( local.proxyResult )>
  •  
  • <!---
  • The proxy invocation returned void. Therefore, we
  • can't return the result without error. As such,
  • return void explicitly.
  • --->
  • <cfreturn />
  •  
  • </cfif>
  •  
  • <!---
  • Check to see if the result is the actual target component
  • (as methods will often do to allow for chaining).
  • --->
  • <cfif (
  • !isNull( local.proxyResult.proxyUUID ) &&
  • (this.proxyUUID eq local.proxyResult.proxyUUID)
  • )>
  •  
  • <!---
  • We don't want to allow access to the proxied
  • target! Return the proxy instead to allow for
  • method chaining.
  • --->
  • <cfreturn this />
  •  
  • </cfif>
  •  
  • <!---
  • If we made it this far then we have no special use-cases
  • for return values; simply echo the value that was returned
  • by the target.
  • --->
  • <cfreturn local.proxyResult />
  • </cffunction>
  •  
  • </cfcomponent>

At the top, you can see that I am creating a UUID (universally unique identifier) in order to be able to uniquely identify our target component. But, after that, setting up our proxy methods is nothing more than copying the one local method - proxyMethod() - into three different slots, one copy per target method. We can use the same method instance to proxy all of the target methods thanks to ColdFusion 9's getFunctionCalledName(). By examining the method invocation name at run-time, it allows us to create a single point of entry to any number of target methods.

To see this component proxy in action, take a look at this test code:

  • <!--- Create our proxy component. --->
  • <cfset proxy = new Proxy() />
  •  
  • <!--- Execute each method to make sure they don't error. --->
  • <cfset proxy.getValue() />
  • <cfset proxy.getVoid() />
  • <cfset proxy.getComponent() />
  •  
  • <!--- Output some values. --->
  • <cfoutput>
  •  
  • <!--- Get a real value. --->
  • Value: #proxy.getValue()#<br />
  •  
  • <!--- Get a void. --->
  • Void: #isNull( proxy.getVoid() )#<br />
  •  
  • <!--- Get the component. --->
  • Component: #getMetaData( proxy.getComponent() ).name#
  •  
  • </cfoutput>

Here, we are testing to see if the proxy methods work without error. Then, we are examining the return values. When we run the above code, we get the following output:

Value: This is a value.
Void: YES
Component: testing.getfunctioncalledname.Proxy

As you can see, the simple values both worked. But more importantly, the third use case returned the Proxy object rather than the target object - encapsulation of the proxy object was maintained.

In this demo, I'm not doing anything more than creating a simple proxy around the target component. This demo, in and of itself, does not represent a valid use case. What you'd really want to do is add logic to your proxy component to augment the functionality of the target object creating features like logging, persistence, or even event triggering. But, the point was really just to show you how simple it is to create proxy objects with ColdFusion 9's new getFunctionCalledName() function.




Reader Comments

Two comments.

1) You could change this,

<cfset this.getValue = variables.proxyMethod />
<cfset this.getVoid = variables.proxyMethod />
<cfset this.getComponent = variables.proxyMethod />

possibly to a call that gets the metadata on variables.target. You could then loop over the functions and dynamically set your pointers. That way if you add X to Test() you won't have to update Proxy.

2) Any reason though why you just don't use onMissingMethod?

Reply to this Comment

@Ray,

Definitely a good point about looping over the target methods. Ultimately, I think that's the way you're gonna want to go to make the proxy flexible.

As far as onMissingMethod(), I definitely have used that to accomplish the same kinds of stuff. What I really like about this approach, however, is that you can kind of pre-package the functionality into methods. Then, the task becomes more about "wiring" the component than it does about "routing" incoming method requests. The different is probably mostly emotional; but, I find working with CFFunciton tags perhaps a bit more easy to model mentally than with parsing missingMethodName arguments.

Probably 6 of one, half-a-dozen of the other :)

Reply to this Comment

You can test for pointer reference equality not directly though. I only have Railo installed on my Machine so I can't definitively say it will work across the board but both ColdFusion and Railo arrays extend the Java.util.Vector class as far as I am aware which is the key to the solution. The solution is to use the Java contains method available on the Collection type objects. In Railo 3.1.2.001 the following works:

  • <cfset myObject1 = CreateObject( "component", "Test" )/>
  • <cfset myObject2 = CreateObject( "component", "Test" )/>
  • <cfset myObject3 = myObject1/>
  •  
  • <cfset arr = [myObject1]/>
  • <!--- The following dumps: true --->
  • <cfdump var="#arr.contains(myObject1)#"/>
  • <!--- The following dumps: false--->
  • <cfdump var="#arr.contains(myObject2)#"/>
  • <!--- The following dumps: true --->
  • <cfdump var="#arr.contains(myObject3)#"/>

Perhaps this will be useful to some. Certainly made my life easier with what I was doing...

Reply to this Comment

As a follow up to my comment my "solution" does not work in CF8... all the above cases result in the dump being true. Perhaps ColdFusion trying to be too clever? I'm glad I tried it in Railo first though.

Reply to this Comment

@Chris,

Here's how to test for CFC reference equality using the underlying Java layer in CF9 without writing a Java class to handle it:

  • component
  • {
  • _internalObj = getPageContent().getPage();
  • // get the CFC's compiled page class instance;
  • // we can call its underlying Java methods
  •  
  • /**
  • * Proxy for the internal object's equals method.
  • * CF9 does not allow us to name a method 'equals'
  • * as it is a reserved keyword.
  • * It's honestly pretty silly, because you can
  • * assign the isEqualTo method to this.equals later.
  • **/
  • function isEqualTo(obj)
  • {
  • return obj.equals(_internalObj);
  • }
  • isEqualTo.getMetadata().name = "equals";
  • // getMetadata is a method on the UDFMethod Java class,
  • // but you could call getMetadata(isEqualTo)
  • // and it'd be the same.
  • this.equals = isEqualTo;
  • structDelete(this, "isEqualTo");
  • // reassigning it to the name "equals" is actually
  • // an important part of making this work
  • // as you'll see below.
  • }

That's all there is to it.

You might look at this and think "won't that cause an infinite loop?" Well here are the steps that are taken when you call objA.equals(objB):

1) objA passes its _internalObj variable to objB's equals method.
2) objB calls objA's _internalObj equals method and passes its own _internalObj to that method, returning the result.

As you can see, with the isEqualTo method being renamed to "equals" it allows us to bounce the function execution between the two variables' methods in order to reach a scenario where we are essentially calling objA._internalObj.equals(objB_internalObj), resulting in a correct reference equality comparison between the two variables without actually exposing their internal objects (the compiled CFComponent Java class) outside the CFC itself.

Reply to this Comment

Actually looking at my code and my comment about not "exposing their internal objects ... outside the CFC itself." is incorrect, as an object passed to the equals method could have its own equals method written to intercept the internal object passed to it. The following change closes that hole:

  • function isEqualTo(obj)
  • {
  • // if equals is null, then we're at the Java object OR if it's not null then we can check the hashCode equality to see if it's THIS method and not some imposter method or a Java object's field.
  • if (isNull(obj.equals) || this.equals.hashCode() == obj.equals.hashCode())
  • return obj.equals(_internalObj);
  • else
  • // if we reach this point then the object references will never be equal.
  • return false;
  • }
  • isEqualTo.getMetadata().name = "equals";
  • this.equals = variables.isEqualTo;
  • structDelete(this, "isEqualTo");

Reply to this Comment

And finally, doing some more investigating, replacing the entire body of the isEqualTo method with

  • return objectEquals(this, obj);

works exactly the same.

Reply to this Comment

@Chris,

Thanks for digging into the Java stuff; left a comment on your end.

@Stu,

Yeah, my first instinct was to also try using an array to get equality in that round-about way. In CF9 (Adobe), they equality is more of a duck-typing approach than a reference-approach. That actually seems like a really large difference in behavior between Railo and CF. I wonder if that was intended or if the Railo engineers didn't think CF would do it by value (I am not sure I would think it was a by-value approach either).

@Graeme,

You might look at this and think "won't that cause an infinite loop?"

... Ha ha ha, that's exactly the first thing I thought! You should have seen me sitting here trying to mime the activity back and forth with my hands, checking to see which references were used where.

Dangy! I didn't know about the objectEquals() method. Thanks for pointing that out to me - that's would have been much much easier. Uggg, is it time to sit down and read the CF9 manual?

Reply to this Comment

As for pointer reference equality, I know that MXUnit uses the java.lang.System.identityHashcode() method for its implementation of assertSame(). It seems to be working perfectly across different platforms.

Reply to this Comment

@Ben, @Graeme

Building a simple proxy at the moment, and though don't fully understand all that is going on I have it working.

so if I understand things correctly would

return objectEquals(this, obj);

replace the UUID check you do?

@Ray

I've only use onMissingMethod a couple of times. In this proxy how would I implement.

thanks

Reply to this Comment

@Kevin,

Having dug into ColdFusion's Java bytecode; the simplest way I can explain it is that objectEquals(obj1, obj2) invokes the java.lang.Object.equals(obj) method on obj1 and is given obj2 as its parameter. (It does a little bit more than that but those details aren't pertinent here.)

So essentially any time you want to verify whether two different ColdFusion variables are referencing the exact same object, objectEquals will be able to give you the answer to that. So yes, if you're using UUIDs to track object identity so you can use it to compare whether two variables reference the same object, you can replace it with objectEquals.

Reply to this Comment

I just found this post and was wondering (please don't laugh too hard if it's a dumb question), can this technique be used to proxy web services as well?

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.