Creating ColdFusion Proxy Components With ColdFusion 9's GetFunctionCalledName()
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:
- They return a value.
- They return nothing (ie. void).
- 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.
Want to use code from this post? Check out the license.
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?
@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 :)
@Ben,
Ok, makes sense to me.
That is neat/cool. Currently can't figure a use for it, but I'm sure I will.
I was chatting about testing for equality between cfc's recently and have written up a few notes.
I like the simplicity of dropping in a uuid as an instance identifier, but thought i'd share how it can be done with java
http://chris.m0nk3y.net/blog/post/testing-for-equality-in-coldfusion
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:
Perhaps this will be useful to some. Certainly made my life easier with what I was doing...
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.
@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:
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):
objA passes its _internalObj variable to objB's equals method.
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.
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:
And finally, doing some more investigating, replacing the entire body of the isEqualTo method with
works exactly the same.
@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?
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.
Ehm, actually it is identityHashCode(). That matters, since Java is obviously case sensitive.
http://download.oracle.com/javase/1.4.2/docs/api/java/lang/System.html#identityHashCode(java.lang.Object)
@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
@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.
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?
wow great work