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:

Extending Java Objects With ColdFusion Components

By Ben Nadel on
Tags: ColdFusion

This morning on Twitter, David Boyer brought up a very interesting concept: extending Java objects in ColdFusion components. Essentially, what I think he wanted to do was to be able to create a ColdFusion component that acts, in part, as a proxy to a given Java object instance, while at the same time, allowing standard ColdFusion methods to be defined and invoked. After suggesting that he try using an onMissingMethod() approach, I was inspired to do a little bit of experimenting on my own.

Right off the bat, I knew that type casting was going to be a problem. Since ColdFusion is a typeless language (for the most part), communication from ColdFusion to Java sometimes requires some manual assistance; method arguments, when passed from ColdFusion to Java, often times need to be explicitly cast to the appropriate Java data types. Unfortunately, there are so many methods in Java that have the same number of arguments but have different data types, I can't see a way to easily automate type casting. This becomes especially true when the arguments are "simple" data types that cannot be programmatically discerned from ColdFusion.

As an example, take a look at the indexOf() method for the Java string. Among its many variations, it has the following two method signatures:

  • indexOf( int )
  • indexOf( string )

These are both simple values, and in ColdFusion, are probably both being represented as strings. Trying to automatically cast such values would be a nightmare since I would have no way of knowing if the person was searching for the numerical digit (string) or the character with the given unicode value (int). As such, for this approach, I am simply off loading the burden of type-casting to the calling context rather than the proxy context.

That said, the first thing I did was create a base JavaProxy.cfc ColdFusion component. This component takes a given Java object and prepares the component to behave as the Java object proxy. It does this by compiling a list of invocation strings to be used in the onMissingMethod() method. These invocation strings are based on the name of the Java method and the number of arguments that it accepts. So for example, the Java method "foo" which takes two argument would be indexed as:

"foo2"

... with an invocation value of:

"foo(
arguments.missingMethodArguments[ 1 ],
arguments.missingMethodArguments[ 2 ]
)"

You'll notice that the invocation value, which will be executed with an Evaluate() function call, is designed to work in the context of the onMissingMethod() method with the appropriately named arguments.

This approach works with ColdFusion 8 and ColdFusion 9; however, CF9 also gives us the ability to determine a given method's runtime name using the function getFunctionCalledName(). As such, if we were running this code on a ColdFusion instance, I figured it would be nice to actually generate real method references to the proxied objects. This way, if the ColdFusion component was CFDump'd out, all the available methods could be seen.

Here is my JavaProxy.cfc. This is meant to be a base ColdFusion component that other components extend:

JavaProxy.cfc

  • <cfcomponent
  • output="false"
  • hint="I provide a generic proxy to a given Java object.">
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I initialize the component.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="target"
  • type="any"
  • required="true"
  • hint="I am the Java object that is being proxied."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Store the target Java object. --->
  • <cfset variables.target = arguments.target />
  •  
  • <!---
  • Create a collection of invocation stubs used to invoke
  • the method by proxy. Since a method might have multiple
  • modes of execution, these will be keyed by the name of
  • the method AND the number of arguments.
  • --->
  • <cfset variables.targetStubs = {} />
  •  
  • <!--- Get the methods found in this component. --->
  • <cfset local.javaMethods = variables.target.getClass().getMethods() />
  •  
  • <!---
  • Check to see if this version of ColdFusion can get the
  • name of the function being invoked.
  •  
  • NOTE: We have to use Try/Catch here since this function
  • does NOT show up in the native function list.
  • --->
  • <cftry>
  • <cfset local.canReferenceMethod = true />
  • <cfset getFunctionCalledName() />
  •  
  • <cfcatch>
  • <cfset local.canReferenceMethod = false />
  • </cfcatch>
  • </cftry>
  •  
  • <!---
  • Loop over the native Java methods to create string-based
  • stubs for invocation.
  • --->
  • <cfloop
  • index="local.javaMethod"
  • array="#local.javaMethods#">
  •  
  • <!--- Create the stub based on the method name. --->
  • <cfset local.methodStub = local.javaMethod.getName() />
  •  
  • <!--- Get the arguments used for invocation. --->
  • <cfset local.javaArguments = local.javaMethod.getGenericParameterTypes() />
  •  
  • <!--- Add the argument count to the stub name. --->
  • <cfset local.methodStub &= arrayLen( local.javaArguments ) />
  •  
  • <!---
  • Build up an argument for each java argument -
  • remember, this will be invoked via onMissingMethod(),
  • so we need to take into account that code context.
  • --->
  • <cfset local.invokeArguments = [] />
  •  
  • <!--- Create an index argument for each java argument. --->
  • <cfloop
  • index="local.argumentIndex"
  • from="1"
  • to="#arrayLen( local.javaArguments )#"
  • step="1">
  •  
  • <!---
  • Create a reference to the onMissingMethod()
  • argument as available in the onMissingMethod()
  • parameters.
  • --->
  • <cfset arrayAppend(
  • local.invokeArguments,
  • "arguments.missingMethodArguments[ #local.argumentIndex# ]"
  • ) />
  •  
  • </cfloop>
  •  
  • <!---
  • Now, fold the missing method argument references into
  • the stub file for execution and store it by name.
  • --->
  • <cfset variables.targetStubs[ local.methodStub ] = (
  • "variables.target." &
  • local.javaMethod.getName() &
  • "( " &
  • arrayToList( local.invokeArguments, ", " ) &
  • " )"
  • ) />
  •  
  •  
  • <!---
  • Check to see if ColdFusion can get the get the name
  • of the method being reference. If so, we can use
  • the method stub to make an actual "static" version
  • of this method.
  •  
  • NOTE: Skip any method that is already defined in the
  • component as these might be methds overriden in an
  • extending instance.
  • --->
  • <cfif (
  • local.canReferenceMethod &&
  • !structKeyExists( this, local.javaMethod.getName() )
  • )>
  •  
  • <!--- Create the stub reference. --->
  • <cfset this[ local.javaMethod.getName() ] = this.onMissingMethodStub />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- Return this object reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onMissingMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I handle invocation messages that don't map directly to a static function.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="missingMethodName"
  • type="string"
  • required="true"
  • hint="I am the name of the missing method."
  • />
  •  
  • <cfargument
  • name="missingMethodArguments"
  • type="any"
  • required="true"
  • hint="I am the collection of missing method arguments."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Build the invocation stub name. --->
  • <cfset local.stubName = (
  • arguments.missingMethodName &
  • arrayLen( arguments.missingMethodArguments )
  • ) />
  •  
  • <!--- Invoke the method and return the result. --->
  • <cfreturn evaluate(
  • variables.targetStubs[ local.stubName ]
  • ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onMissingMethodStub"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I am the generic stub invocation wrapper that can be used actual handles to Java methods. NOTE: For ColdFusion 9 only.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • Build the invocation stub name based on the name of
  • THIS method as it is being invoked at runtime.
  • --->
  • <cfset local.stubName = (
  • getFunctionCalledName() &
  • arrayLen( arguments )
  • ) />
  •  
  • <!--- Invoke the method and return the result. --->
  • <cfreturn evaluate(
  • variables.targetStubs[ local.stubName ]
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, this base component provides two methods, onMissingMethod() and onMissingMethodStub(). The onMissingMethodStub() method is the method reference that will be recycled once per unique Java method name in ColdFusion 9. Rather than relying on the onMissingMethod(), ColdFusion 9 uses the stub method, in conjunction with the getFunctionCalledName() function, to determine which compiled invocation string to evaluate().

I like this approach because it determines the available invocation values at instantiation time rather than at invocation time. I assume this will have some performance benefit since the actual method invocation requires little more than a struct lookup.

Now that we have our base JavaProxy.cfc, let's look at how it might be extended. To demonstrate component extension, I created a "String" object that extends the Java string object:

MyString.cfc (Extends JavaProxy.cfc)

  • <cfcomponent
  • output="false"
  • extends="JavaProxy"
  • hint="I extend the Java string object.">
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I initialize this component.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="target"
  • type="string"
  • required="true"
  • hint="I am the Java object being proxied."
  • />
  •  
  • <!---
  • Pass the string off to the super constructor such that
  • THIS object will be prepared to proxy.
  • --->
  • <cfset super.init( arguments.target ) />
  •  
  • <!--- Return this object reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="indexOf"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="I get the index of the given string, and adjust for one-based indexing.">
  •  
  • <!--- Define arguemnts. --->
  • <cfargument
  • name="value"
  • type="string"
  • required="true"
  • hint="I am the value being searched for."
  • />
  •  
  • <!--- Return the value with a ColdFusion-based offset. --->
  • <cfreturn (
  • this.onMissingMethod( "indexOf", arguments ) +
  • 1
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, the MyString.cfc component starts off by passing the target Java object off to the super constructor in order to configure the component to behave as a proxy. It then defines a single method, indexOf(), to translate the Java-based string index (zero-based) to a ColdFusion-friendly string index (one-based). When I override the indexOf() method, you'll notice that I am passing the invocation off to the super's onMissingMethod(), which invokes the core Java method. You'll also notice that I am JavaCast()ing the string that I pass off to the core Java method; like I said before - I am putting the JavaCast() responsibility on the calling context.

To test this MyString.cfc, I put together this script:

  • <!--- Create an instance of the string proxy. --->
  • <cfset myString = createObject( "component", "MyString" ).init(
  • "That Tricia sure is a hottie!!"
  • ) />
  •  
  • <!--- Get the index of "that" --->
  • <cfset thatIndex = myString.indexOf( "That" ) />
  •  
  •  
  • <!--- Output index (should be "1"). --->
  • <cfoutput>
  •  
  • String: #myString.toString()#<br />
  •  
  • <br />
  •  
  • Index: #thatIndex#<br />
  •  
  • </cfoutput>

This test script invokes two Java methods - toString() and indexOf(). The indexOf() method is being overridden by my extending component; the toString() method, on the other hand, is being called directly on the "base" component. When we run this code, we get the following output (in both CF8 and CF9):

String: That Tricia sure is a hottie!!

Index: 1

As you can see, it works like a charm. The only noticeable difference between the two ColdFusion servers is that if I output the component key list, ColdFusion 8 gives me this:

ONMISSINGMETHOD
INDEXOF
ONMISSINGMETHODSTUB
INIT

And, ColdFusion 9, which uses the getFunctionCalledName() in conjunction with stub methods, gives me this:

notify
wait
hashCode
codePointCount
replace
charAt
codePointAt
endsWith
equalsIgnoreCase
contains
concat
valueOf
offsetByCodePoints
intern
equals
trim
subSequence
replaceAll
notifyAll
replaceFirst
lastIndexOf
contentEquals
matches
toUpperCase
length
copyValueOf
split
startsWith
init
getChars
toCharArray
compareTo
indexOf
compareToIgnoreCase
isEmpty
toString
regionMatches
getClass
codePointBefore
format
onMissingMethodStub
onMissingMethod
getBytes
substring
toLowerCase

Thanks to Dave for bringing this very interesting topic up for discussion. I think there's probably a bunch of ways to improve this approach; but, I kind of like where it is going. I think the biggest hurdle is always going to be the ColdFusion-to-Java type casting. If you have any ideas on how to make that better, please feel free to jump in.




Reader Comments

Good approach :) I like how you took advantage of CF9's extra functionality as well. This is one of those things that doesn't come up that often but when it does it really feels like there should be an easier way to do it.

Here was my basic approach that I knocked up pretty quickly http://gist.github.com/473892 where I cheat a little bit with the JavaCast()'ing and didn't allow for multiple methods with the same name.

My need for this was due to my work on CfTracker where I'm adding support for storing stats using rrd4j (based on RRDTool). One of it's Java classes had 42 set methods that I'd like to expose but didn't fancy writing that many in my CFC that wraps around it.

Thanks for having a go Ben :)

@David,

Very cool to use the actual invoke() method on the Java Method object. I hadn't even thought of that approach. The only think you'll run into, which is also what I ran into, is the type casting. As I think you said, you hit a wall when you can't cast to a non-core data type. Very clever approach, though!

@Matt,

I was at your CFML / Groovy plugin presentation at cf.Objective(). Very slick stuff! As an aside, one thing I found very interesting was something I think you called "flash memory"; there was something you said that could retain information across a single re-post or something?

Right Ben--in Grails (and Spring, and Mach-II, and lots of other frameworks) there's something called the "flash scope" which is a place you can put data that will be available during the current request and one subsequent request.

In cases where you don't want Bad Stuff(tm) to happen if someone hits refresh, but you want data from the request available after the request completes, you do a client-side redirect (so the URL changes to something that won't cause issues upon refresh) and use the flash scope to retain the data.

@Matt,

So, just so I don't go completely misunderstanding you, this "Flash Scope" isn't part of J2EE, right? It's part of given frameworks?

Right, it's a function of the frameworks. In Mach-II's case it's implicit and is used when you do what we refer to as a "redirect persist," but in Grails and some other frameworks it's more explicit, meaning you can put stuff in the flash scope directly.

In Mach-II you can specify what data you want to persist across the redirect, but you don't do anything like <cfset flash.foo = foo /> or anything along those lines.

@Matt,

Ahh, ok, I get it. I had thought it was part of anything based on Java applications. Thanks for the clarification.

Very cool exploration, Ben. But someone's gotta say this, so it might as well be me - the easiest way to extend a Java object is with another Java object. I can think of some cool use cases for this technique, but I'd hate to see people using it just to avoid having to deal Java.

@Jaime

Good point. It's probably far better to just extend the Java class itself rather than going through all this trouble. It'd just that I feel far more comfortable and confident in Coldfusion that I do with Java.

Like I mentioned above, this post sprouted out of my work on CfTracker and needing to expose 42 set methods. I'm considering an alternative where I wouldn't need those set methods, but it'd still be nice to expose them in case anyone else wanted to use the code.

I'd welcome any volunteers to help me out and have a go ;)

@Jaime,

Yes, it would definitely be easier to just extend a native Java object if you are working primarily in Java. But, you start to run into all kinds of problems if you need that "extending" Java object to communicate with ColdFusion.

For example, imagine part of the Component needed to accept a "behavior" ColdFusion component that would, at some point, be invoked from within the Java object:

variables.myBehaviorComponent.doSomething()

... that opens up a world of hurt and requires you to jump through all kinds of invocation hoops to get it to work.

That said... it was just fun to try :P