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 Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Chandan Kumar

ColdFusion 10 - Invoking ColdFusion Closures From Within A Java Context

Posted by Ben Nadel
Tags: ColdFusion

I don't know too much about Java programming. But, ColdFusion 10 now allows us to load additional Java class files on a per-application basis. Instead of always having to put JAR files into the core ColdFusion lib folder, we can now keep our application-specific JAR files alongside our application-specific ColdFusion code. This is pretty awesome; but, when it comes to communication between ColdFusion objects and Java objects, things can get pretty tricky. And what about objects like the new ColdFusion 10 closures? How do these non-components work within a Java context?

NOTE: At the time of this writing, ColdFusion 10 was in public beta.

Before we look at any Closure stuff, let's first look at the new per-application Java integration. In the Application.cfc ColdFusion framework file, we can now specify directories that contain our application-specific JAR and CLASS files. We can also tell ColdFusion to monitor those directories for changes (ie. new files or compiling) and, whether or not to load new Java classes when they come into being.

Before we look at the Application.cfc settings, let me just explain how I even got custom Java class files in the first place. As I said before, I don't know that much about Java programming; so, what I demonstrate below is simply what I was able to get working after a bunch of Google searching and much trial an error.

For these few demos, I created a folder called, "src" to store my raw Java code:

  • java
  • java / src /
  • java / src / com /
  • java / src / com / bennadel /
  • java / src / com / bennadel / cf10 /

All of my demo ColdFusion 10 Java files are in this nested "cf10" package. I then created a "lib" folder. This is the folder into which my compiled Java CLASS files will be saved:

  • java
  • java / lib /

Then, I created my first Java code file (in the cf10 package directory):

Friend.java

  • // Define the namespace at which our Java Class will be found.
  • package com.bennadel.cf10;
  •  
  • public class Friend {
  •  
  •  
  • // Declare properties.
  • private String name = "";
  •  
  •  
  • // I return an initialized component.
  • public Friend( String name ){
  •  
  • this.name = name;
  •  
  • }
  •  
  •  
  • // I greet the given person.
  • public String greet( String otherName ){
  •  
  • return(
  • "Hello " + otherName + ", I'm " +
  • this.name + ", it's super nice to meet you!"
  • );
  •  
  • }
  •  
  •  
  • }

As you can see, this Java class is fairly simple. It starts off by declaring the class package. I don't fully understand how this name-spacing works; but, when the code gets compiled, the Java byte code is put into a directory structure that mimics the package hierarchy.

Now that I have my Friend.java file, I need to compile it. Since I'm running on Mac OSX, I apparently already have the Java Compiler installed. So, I went into the root of my "src" directory and ran the "javac" compiler from the Terminal (command line):

  • ben-2:src ben$ pwd
  • /Sites/bennadel.com/testing/coldfusion10/java/src
  • ben-2:src ben$ javac -classpath . -d ../lib/ com/bennadel/cf10/*

I don't understand what the "-classpath ." option does; but, it was required to get all of this stuff to work. The "-d" option tells the compiler where to store the compiled Java class files. In this case, I want to store the Java class files in the "lib" directory (remember, I'm currently in the "src" directory). The final argument simply tells the compiler to compile all of the files in my cf10 package.

Once the compiler has run, I now have a Friend.class file in the following location:

  • java / lib / com / bennadel / cf10 / Friend.class

Awesome! Now that we have our Java class files ready to be loaded into our ColdFusion application, let's finally take a look at our Application.cfc file. This file is in the "java" directory, alongside my "lib" directory.

Application.cfc - Our ColdFusion Application Framework

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

The Java settings are defined as a property of the Application.cfc component instance. Here, we can define what Java classes and JAR files to load, how often to scan the given directories for changes, and whether or not to load those changes into the ColdFusion class loader. Notice that I am simply defining the "lib" folder - I don't need to define the nested CLASS files.

I had some trouble with these settings. They seemed to be really responsive for a while; then, out of nowhere, I stated to get all kinds of caching issues. Even with a watchInterval time of 2 seconds, my updated CLASS files were not taking effect. When I lowered the applicationTimeout to be 3 seconds, this seemed to bust the CLASS caching.

Naturally, you wouldn't have such a small Application timeout in production; but, for this research and development, there was no harm in keeping the timeout low to constantly be reloading the Java class files.

Once the ColdFusion application has loaded the custom Java class files, using them in ColdFusion is as easy as using any other Java class:

  • <cfscript>
  •  
  •  
  • // Create our Friend Java class. Notice that it is found at
  • // the package we defined in our CLASS file.
  • friend = createObject( "java", "com.bennadel.cf10.Friend" ).init(
  • "Tricia"
  • );
  •  
  • // Get the greeting from Tricia.
  • greeting = friend.greet( "Ben" );
  •  
  • // Output String value returned from the Java context.
  • writeOutput( "Greeting: " & greeting );
  •  
  •  
  • </cfscript>

Here's I'm instantiating the Java class, Friend, and invoking the greet() method. Notice that my class path is the same as the package path I defined within the .java file. When we run the above code, we get the following output:

Greeting: Hello Ben, I'm Tricia, it's super nice to meet you!

Groove-sauce! It worked perfectly!

Ok, now that we can easily load a custom Java class into our ColdFusion application, let's try to take it up a level - let's try to call a ColdFusion closure from within a Java context.

Unfortunately, this isn't very easy. ColdFusion closures aren't Components in the way that we typically think about them. They are Java objects under the hood; but, there's no documentation on how to invoke a closure (or a ColdFusion user defined function for that matter) from a Java context. Luckily, ColdFusion does provide a way to proxy ColdFusion components in a Java context.

This is my first time looking into this concept, so I'm absolutely sure there is a more straightforward way to bridge this communication gap. But for this demo, the best approach that I could come up with (that worked) was to create a ColdFusion component that acts as an invocation proxy to our ColdFusion closure. Since we have a way to invoke ColdFusion components from a Java context, we'll invoke a component method which handles the closure invocation for us.

To do this, we have to create our ColdFusion proxy and a Java Interface for that ColdFusion proxy. The Interface allows ColdFusion to generate a static Java class that mimics the dynamic architecture of our ColdFusion components. The ColdFusion component is rather simple. All it does is provide one method, callClosure(), which takes Java arguments and invokes the given closure:

ClosureProxy.cfc - Our ColdFusion Component For Proxied Closure Invocation

  • component
  • output="false"
  • hint="I proxy the invocation of a ColdFusion closure so that the closure can be invoked from Java."
  • {
  •  
  •  
  • // I get called from Java to invoke the stored closure. Since the
  • // 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 )
  • );
  •  
  • }
  •  
  •  
  • }

As you can see, this just takes an array of arguments, separates the Closure from the Closure parameters, and invokes the closure.

In order to create a Java proxy for this ColdFusion component, we have to use the ColdFusion function:

createDynamicProxy( CFC, Interfaces )

Here, we provide a ColdFusion component instance (or path) and an array of Java Interfaces. In order to do this, I created the following Java file:

ClosureProxy.java - Our Java Interface For Our ColdFusion Component

  • // 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 component
  • // so that we can create a dynamic 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 ClosureProxy {
  •  
  • // Since we don't know what type of closure is going to stored
  • // in our proxy (or how it will be called), we'll have to accept a
  • // variable-number of arguments in the form of an Array.
  • public java.lang.Object callClosure( Object...args );
  •  
  • }

As you can see, all this does is identify the callClosure() method as one that takes a variable number of objects. And, if you look back to our ClosureProxy.cfc, you can see that we take accept these variable number of objects as a Java array.

Now, imagine for a moment that we have created a Java class that extends that ArrayList (what ColdFusion Arrays use under the hood). And, to this sub-class, we are adding the method, .each(). This each() method accepts a ColdFusion closure that will be invoked on each element within the internal collection.

Before we look at the code behind the ArrayList sub-class, however, let's look at the ColdFusion code that makes use of it:

  • <cfscript>
  •  
  •  
  • // I am a utility function that returns a dynamic Java proxy for
  • // the ClosureProxy.cfc ColdFusion component. This returns a Java
  • // object that adheres to the interface "ClosureProxy".
  • function javaClosureProxy(){
  •  
  • return(
  • createDynamicProxy(
  • new ClosureProxy(),
  • [ "com.bennadel.cf10.ClosureProxy" ]
  • )
  • );
  •  
  • }
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • // 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(
  • javaClosureProxy()
  • );
  •  
  •  
  • // Initialize our custom ColdFUsion array.
  • arrayAppend(
  • friends,
  • [ "Tricia", "Joanna", "Sarah", "Kit" ],
  • 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>

Here, we are creating an instance of the custom Java class, com.bennadel.cf10.CFArray. This is our ArrayList sub-class. When we instantiate it, we have to initialize it with an instance of our Proxy object - this is junky monkey, but we have to use this proxy to invoke the ColdFusion closure. Once we do this, we can then populate the CFArray instance and call .each(), passing in our ColdFusion closure. And, 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 Kit, what it be like?

As you can see, the ColdFusion closure was passed into and successfully executed within a Java context; both the "friend" and the "index" parameters were properly passed to the closure at invocation time.

Now that we have a high-level understanding of how this communications tunnel works, let's look at the CFArray Java class to see how the ClosureProxy comes into play:

CFArray.java - Our ArrayList Sub-Class

  • // 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.ClosureProxy;
  • 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 {
  •  
  •  
  • // This will hold our ColdFusion proxy for Closure invocation.
  • private ClosureProxy closureProxy;
  •  
  •  
  • // I return an initialized component.
  • public CFArray( ClosureProxy closureProxy ){
  •  
  • // Property initialize the core ArrayList.
  • super();
  •  
  • // Store the ColdFusion closure proxy.
  • this.closureProxy = closureProxy;
  •  
  • }
  •  
  •  
  • // I iterate over the elements in the array and invoke the given
  • // opertor on each element.
  • public CFArray each( Object operator ){
  •  
  • // 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.
  • this.closureProxy.callClosure(
  • operator,
  • iterator.next(),
  • ++iteration
  • );
  •  
  • }
  •  
  • // Return this object reference for method chaining.
  • return( this );
  •  
  • }
  •  
  •  
  • }

As you can see, the each() method loops over the internal ArrayList collection; and, for each element, it uses the ClosureProxy instance to encapsulate the ColdFusion closure invocation:

  • this.closureProxy.callClosure( operator, iterator.next(), ... );

In this way, we can invoke a ColdFusion closure from a Java context without having to know how to invoke the closure at a technical level.

This is pretty cool; but, at the same time, it's wicked junky, right? We have to instantiate our Java class with a reference to our ClosureProxy instance. Clearly, this is not a good solution; but it's the only thing I could figure out so far. I'm gonna keep tinkering with this to see if I can come up with something that is a bit more seamless.




Reader Comments

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.