ColdFusion 10 - Invoking ColdFusion Closures From Within A Java Context, Part II
Earlier, I blogged about my initial exploration of invoking ColdFusion closures in a Java context. I was able to do this by initializing my Java objects with a dynamic ColdFusion proxy. This worked, but was rather junky - after all, who wants to provide special object initialization for every single Java object? But what if I only had to provide the ColdFusion proxy once, at Application start? After some further exploration, it looks like I can use Java's static properties as way to define a package-wide ColdFusion proxy.
NOTE: At the time of this writing, ColdFusion 10 was in public beta.
NOTE: Before we look at the code, it's worth mentioning that ColdFusion does provide core ColdFusion Component (CFC) proxy functionality; however, in order to use it, you have to jump through a lot of hoops in compiling and making sure all of the correct JAR files are being referenced. I tried to do this for about an hour before I gave up.
In this approach, we're going to use a ColdFusion component to provide an invocation proxy to our ColdFusion closures and component methods. This proxy defines two methods:
- callClosure()
- callMethod()
The first invokes a ColdFusion closure using the given arguments; the second invokes a ColdFusion component method using the given method name and arguments.
ColdFusionProxy.cfc - Our ColdFusion Invocation Proxy
<cfscript>
// NOTE: Script tags added purely for Gist color-coding only.
component
output="false"
hint="I proxy the invocation of a ColdFusion closures and component methods so that they can be more easily invoked from Java."
{
// I get called from Java to invoke a ColdFusion closure. Since
// the Java interface defines this method signature as
// java.lang.Object[], we will only get one argument that is a
// Java Array.
function callClosure( javaArguments ){
// The first argument being passed-in is the ColdFusion
// closure to invoke.
var operator = javaArguments[ 1 ];
// We need to convert the Java arguments into a ColdFusion
// argument collection that can be used to invoke the closure.
var invokeArguments = {};
// Move the Java arguments in to the closure arguments.
for (var i = 2 ; i <= arrayLen( javaArguments ) ; i++){
invokeArguments[ i - 1 ] = javaArguments[ i ];
}
// Invoke the closure with the translated argument collection
// and pass the return value back to the calling context.
return(
operator( argumentCollection = invokeArguments )
);
}
// I get called from Java to invoke a ColdFusion component method.
// Since the Java interface defines this method signature as
// java.lang.Object[], we will only get one argument that is a
// Java Array.
function callMethod( javaArguments ){
// The first argument is the component instance. The second
// argument is the method name.
var target = javaArguments[ 1 ];
var methodName = javaArguments[ 2 ];
// We need to convert the Java arguments into a ColdFusion
// argument collection that be used to invoke the method.
var invokeArguments = {};
// Move the Java arguments in to the method arguments.
for (var i = 3 ; i <= arrayLen( javaArguments ) ; i++){
invokeArguments[ i - 2 ] = javaArguments[ i ];
}
// Invoke the ColdFusion component method with the translated
// argument collection and pass the return value back to the
// calling context.
return(
invoke( target, methodName, invokeArguments )
);
}
}
// NOTE: Script tags added purely for Gist color-coding only.
</cfscript>
In order to make these methods as flexible as possible, they have to accept a variable number of arguments. And in order to translate that concept to a strongly-typed language like Java, we have to pass the arguments through as a Java array. Here, you can see how the ColdFusion methods are being defined in the Java Interface:
ColdFusionProxy.java - Our Java Interface To The ColdFusion Proxy
// Define the namespace at which our Java Class will be found.
package com.bennadel.cf10;
// Here, we need to define an interface to our ColdFusion Proxy
// component so that we can create a dynamic Java proxy. Since Java
// does not allow for the level of dynamic-structure that ColdFusion
// allows, we have to actually define interfaces to our ColdFusion
// components so that we can build a static Java object around it.
public interface ColdFusionProxy {
// This is a proxy to closure invocation. This takes a variable
// number of arguments, the first of which is the closure.
public java.lang.Object callClosure( Object...argv );
// This is a proxy to our ColdFusion component method invocation.
// This takes a variable number of arguments, the first of which
// is the component, the second of which is the method name.
public java.lang.Object callMethod( Object...argv );
}
The notation "Object...argv" allows the Java methods to be invoked with a variable-number of arguments.
Now that we have a ColdFusion component that provides ColdFusion-based closure and method invocation, we have to store it somewhere. Rather than storing it in each Java instance that I create (as I did in my previous blog post), we're going to store it as a static property of a shared Java class in my custom Java package.
To do this, I created the class - Core.java. This Java class does nothing more than define a static property to hold our ColdFusion proxy:
Core.java - A Static Java Holder For Our ColdFusion Proxy
// Define the namespace at which our Java Class will be found.
package com.bennadel.cf10;
// Import classes for short-hand references.
import com.bennadel.cf10.ColdFusionProxy;
public class Core {
// For this class, we are going to declare a static property to
// hold an instance of our ColdFusion proxy object. This way,
// all Java classes in this package can use this to invoke
// ColdFusion component methods and closures from witihn the
// Java context.
private static ColdFusionProxy coldfusionProxy = null;
// I provide a way to set the ColdFusion proxy. This needs to be
// done in order for the rest of the package classes to be able
// to invoke ColdFusion methods.
public static void setColdFusionProxy( ColdFusionProxy proxy ){
coldfusionProxy = proxy;
};
// I provide a way for the package classes to access the core
// ColdFusion proxy.
public static ColdFusionProxy getColdFusionProxy(){
return( coldfusionProxy );
};
}
As you can see, we have a static property, coldfusionProxy, that starts out as null / undefined. As such, we're going to have to set it at some point. In this demo, I'm going to set this property when the ColdFusion application is initialized. In the following ColdFusion application framework component, take a look at the onApplicationStart() event handler:
Application.cfc - Our ColdFusion Framework Component
<cfscript>
// NOTE: Script tags added purely for Gist color-coding only.
component
output="false"
hint="I define the application settings and event handlers."
{
// Define our standard Application settings.
this.name = hash( getCurrentTemplatePath() );
// I'm setting the Application Timeout to be SUPER LOW since I
// was getting some odd caching issues with the CLASS files.
// ColdFusion didn't seem to always pickup the changes to the
// file. By setting the application timeout very low, I was able
// to force the per-app Java libraries to reload.
this.applicationTimeout = createTimeSpan( 0, 0, 0, 3 );
// Define our per-application Java library settings. Here, we
// can tell ColdFusion which ADDITIONAL Java libraries to load
// when the application starts.
//
// NOTE: Since I am doing this demo and writing Java code at the
// same time, I have the "watchInterval" very low. This will
// continuously scan the loadPaths[] directories looking for
// updated JAR and CLASS files.
this.javaSettings = {
loadPaths: [
"./lib/"
],
loadColdFusionClassPath: true,
reloadOnChange: true,
watchInterval: 2
};
// I initialize the application.
function onApplicationStart(){
// When the application starts-up, we have to initialize our
// custom Java libraries with an instance of the ColdFusion
// proxy component. This will enable all of the custom CLASS
// files to invoke ColdFusion component methods and closures.
createObject( "java", "com.bennadel.cf10.Core" )
.setColdFusionProxy(
createDynamicProxy(
new lib.com.bennadel.cf10.ColdFusionProxy(),
[ "com.bennadel.cf10.ColdFusionProxy" ]
)
)
;
// Return true so the application can load.
return( true );
}
// I initialize the request.
function onRequestStart(){
// Check to see if we need to manually reset the application.
if (structKeyExists( url, "initApp" )){
// Explicitly reset the application.
this.onApplicationStart();
}
// Return true so the request can load.
return( true );
}
}
// NOTE: Script tags added purely for Gist color-coding only.
</cfscript>
When our ColdFusion application starts up, I am taking our ColdFusionProxy.cfc, generating a dynamic Java proxy from it using the createDynamicProxy() function, and then storing it as a static property on our Core.java class. Now, any Java class in my cf10 package can access this ColdFusion proxy using the Core class' static property.
To demonstrate this approach, I've created the CFArray.java class. This extends the core ArrayList class and adds an each() method that applies an operator (ie. ColdFusion closure) to each element in the collection.
CFArray.java - A Java Class That Invokes A ColdFusion Closure
// Define the namespace at which our Java Class will be found.
package com.bennadel.cf10;
// Import classes for short-hand references.
import com.bennadel.cf10.Core;
import com.bennadel.cf10.ColdFusionProxy;
import java.util.Iterator;
// For this demo, we'll create a class that extends the core
// ArrayList class. This way, we can get all of the benefits of
// the ColdFusion Array plus whatever methods we want to add.
public class CFArray extends java.util.ArrayList {
// I return an initialized component.
public CFArray(){
// Property initialize the core ArrayList.
super();
}
// I iterate over the elements in the array and invoke the given
// opertor on each element.
public CFArray each( Object operator ){
// Get our core ColdFusion proxy - this is how we will invoke
// the ColdFusion closure.
ColdFusionProxy proxy = Core.getColdFusionProxy();
// Get the iterator for our ArrayList.
Iterator iterator = this.iterator();
// Keep track of the iterations in our iterator - to be
// passed-in as one of our arguments.
int iteration = 0;
// Loop over the entire collection.
while (iterator.hasNext()){
// For this item, invoke the closure. Since it's hard to
// invoke the ColdFusion closure directly from Java,
// we'll use our ColdFusion proxy to invoke it. For this,
// our closure will be the first argument; then,
// arguments 2..N will be the operator arguments.
proxy.callClosure(
operator,
iterator.next(),
++iteration
);
}
// Return this object reference for method chaining.
return( this );
}
}
If you look in the each() method, you'll see that the CFArray.java class makes a reference to the Core.getColdFusionProxy() method. This static method returns a reference to the static property we set on application-start: our ColdFusion proxy. Using this proxy, the CFArray.java class can then invoke the given ColdFusion closure with the given Java arguments.
Now that we have our ColdFusion proxy and our static holder in place, let's make use of them in some ColdFusion code. In the following demo, I'm going to instantiate the CFArray.java class and invoke the method, each(), providing a ColdFusion closure:
<cfscript>
// Create our custom Array class. When we initialize it, we have
// to pass in our Closure proxy which will act as a tunnel when
// we invoke our ColdFusion closure from Java.
friends = createObject( "java", "com.bennadel.cf10.CFArray" ).init();
// Initialize our custom ColdFUsion array with some values.
arrayAppend(
friends,
[ "Tricia", "Joanna", "Sarah", "Anna" ],
true
);
// Iterate over each item in the array. Notice that friends.each()
// is a JAVA method that we passing a COLDFUSION CLOSURE into.
friends.each(
function( friend, index ){
writeOutput( "#index#) Hey #friend#, what it be like?" );
writeOutput( "<br />" );
}
);
</cfscript>
As you can see, nothing in this demo code has to know anything about the ColdFusion proxy - it simply calls a Java method and passes in a ColdFusion closure. The proxy-based invocation is all happening behind the scenes.
When we run the above code, we get the following page output:
1) Hey Tricia, what it be like?
2) Hey Joanna, what it be like?
3) Hey Sarah, what it be like?
4) Hey Anna, what it be like?
Hella sweet! It worked like a charm.
This approach still requires manual configuration (storing the ColdFusion proxy on application start); however, this is a world better than the approach I was using in the last blog post. At least this time, only one, isolated part of my ColdFusion application has to know about the proxy - the rest of the application can operate as if no special steps need to be taken for ColdFusion/Java integration.
Want to use code from this post? Check out the license.
Reader Comments
This is so tight, Ben. I'm going to need to watch this video presentation at least 3 times again while running and studying your code in my local just to really understand this powerful code combination. You're the Man! Thanks again for your daily down-to-earth help for the not-so-java-savvy coldfusioners like me.
Ben, can you say why we need to learn two languages? I'm fighting at work to claim cf is great, but if we have to write in java any way, why not simplify my code and only use java?
- Devil's Advocate
@Marty,
My pleasure! It took me a while to get to this. I had a really sad moment when I had to Google how to loop over stuff in Java :) I kept wanting to do a for-in loop; but didn't even know if you *could* do that in Java :D Had to settle on an Iterator approach.
Definitely Java is a lot of trial and error for me.
@Randall,
I think ColdFusion makes a lot of stuff easy. It abstracts away all of the complexities of typing in Java. But, at the same time, there are some excellent existing libraries in Java that can really help solve complex problems. So, while I probably won't be writing too much Java myself, the per-application JAR file loading is really gonna be a game-changer for me personally. In the past, I've shy'd away from including JAR files since they required going to the ColdFusion administrator and I really like keeping everything modular. Now, with the ability to keep JAR files right in your code-base, there's some awesome extensibility WITH modularity.
I agree that CF is propably sufficient 95% of the time, but sometimes CF doesn't have functionality provided and then it's great to have something in java to call and use. Java is so freaking big - seems like they have a solution to everything.
This feels a little like Microsoft helping Apple, or Darth Vader helping Luke Skywalker ;-)
Ben! You The Man! I've used Coldfusion and Java plenty times together. A sweet Marriage and unlimited power. muwhahaha!!! okay getting a little carried away but seriously this is a nice example of how easy it is to work with them both.
I haven't done any coldfusion development recently but I am surely excited about CF10 and the excellent new features offered. Give it up for Closures peoples! hoo! hoo! hoo!
Good read Ben! :)
Hi, if I not using <cfscript> code </cfscript> in my application.cfc and intead of that I using the CF Tags <cfcomponent output ....> </cfcomponent> how can I integrate the java class code to add a java class path?
My coode look like this:
<cfcomponent
output="true"
hint="I define application settings and event handlers.">
<!--- Define the application settings. --->
<cfset this.name= "CARSA">
<cfset this.applicationTimeout = createTimeSpan( 0, 0, 30, 0 ) />
<cfset this.sessionManagement = true />
<cfset this.sessionTimeout = createTimeSpan( 0, 0, 80, 0 ) />
<cffunction name="onApplicationStart" output="false" returntype="void">
<!--- ODBC's --->
<cfset APPLICATION.Datasource="carsa_odbc">
</cffunction>
</cfcomponent>
Nice example Ben ...
I've been working on my Java chops quite heavily as of late and one thing I've come to realize is that much of the 'properly' written CF code is close to the same LOC as it is with Java.
For instance compare two very well written bases such as http://coldspringframework.org/ and http://www.springsource.org/ ... for most of the boilerplate you will see a great deal of similarity.
Of course CF makes it a lot easier for 'front-end' requirements as there's no need to compile - CF is already compiled ...
However, with all the improvements that have been made for build-automation, continuous integration and all the JVM langs that support either dynamic and/or static-typing - Java's benefits are quite significant.