In the interest of full disclosure, I can’t think of a very practical use for this facet of Coldfusion/J2EE interoperability. It’s nice to know that, should an emergency arise in which I need to execute a UDF in a JSP or face some horrible consequence, I will now be able to rise to the occasion; however, my employer might not share in my comfort, since this purely academic, theoretical work was done entirely on the company’s dime.
What I wanted to accomplish with this exercise was to create an anonymous inner class that would execute some logic defined in a Coldfusion page, and I wanted to do so without relying on any external libraries, Java or Coldfusion. To get us started, let’s take the example of listing out the files in a given directory, only showing files that match a certain extension (in this case, “cfc”). One way of doing this in Java is with the following code:
|
1
2
3
4
5
| String[] fileNames = (new java.io.File("/home/me/some/directory")).list(new java.io.FilenameFilter() {
public boolean accept(java.io.File file, String name) {
return name.endsWith(".cfc");
}
}); |
This code creates what’s called an “anonymous inner class”. One usually sees anonymous inner classes when doing GUI work (or at least that’s where I saw them back when I did some GUI stuff back in the late 90’s) with EventListener objects and such. These classes are simply informal extensions of existing classes where you explicitly fill out the methods declared as ‘abstract’ in the parent. The accept method of the java.io.FilenameFilter class is abstract, so in order to instantiate an object we must first fill out that method.
These anonymous inner classes cannot be created in Coldfusion (as far as I can tell). If you absolutely need an anonymous inner class, you have to do some JSP hacking, like so:
|
1
2
3
4
5
6
| <!--- call the jsp that sets up the 'filefilter' object in the 'request' scope --->
<cfset GetPageContext().include("anonymous.jsp")/>
<!--- get a file object for the given directory --->
<cfset directory = createObject("java", "java.io.File").init("/home/me/some/directory")/>
<!--- create the fileList array containing the filtered file list --->
<cfset fileList = directory.list(request.filefilter)/> |
|
1
2
3
4
5
6
| <% // here we're just setting the 'filefilter' object to an instance of the anonymous inner class
request.setAttribute("filefilter", new java.io.FilenameFilter() {
public boolean accept(java.io.File file, String name) {
return name.endsWith(".cfc");
}
}); %> |
Note: as a general rule, if you’re passing things back and forth from Coldfusion pages to JSPs through the request object, make sure you always use variable names that are all lower case, otherwise one context or the other might not pick it up.
Okay, now the fun part: What if we want to use our own UDF for an anonymous inner class? Well, when you create a function in Coldfusion, the compiler creates a subclass of the coldfusion.runtime.UDFMethod class; the function is subsequently called through an invoke method. In order to execute the logic wrapped up in the cffunction tag, we’re going to have to use the Java Reflection API — but first, let’s look at the Coldfusion page that will be creating the UDF:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| <!--- first we create our UDF for filtering filenames --->
<cffunction name="filterFunction" returntype="boolean">
<!--- the method signature must match that of java.io.FilenameFilter.accept(java.io.File, java.lang.String) --->
<cfargument name="fileObject" required="true"/>
<cfargument name="filename" required="true"/>
<!--- for this example we're listing all files that end with '.cfm' --->
<cfreturn filename.endsWith(".cfm")/>
</cffunction>
<!--- we need to add a couple objects to the request scope so that the jsp can see them --->
<!--- remember to use lowercase names so the process doesn't hork --->
<cfset request.pageobject = getPageContext().getPage()/>
<cfset request.myfunction = filterFunction/>
<!--- now we process the jsp, which will put our function in an anonymous inner class --->
<cfset GetPageContext().include("udffilter.jsp")/>
<!--- list out the directory (using the current directory) using our new filterobject --->
<cfset directoryListing = createObject("java", "java.io.File").init(expandPath(".")).list(request.filterobject)/>
<cfloop index="k" from="1" to="#ArrayLen(directoryListing)#">
<cfoutput>#directoryListing[k]#</cfoutput><br/>
</cfloop> |
And the corresponding JSP, which is absolutely rife with reflection code:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| <%
try {
// the following must be declared 'final' so they can be accessed in the anonymous inner class
// first we pull the udf out of the request object, followed by the page object
final Object o = request.getAttribute("myfunction");
final Object page = request.getAttribute("pageobject");
// the 'args' array will hold the arguments we need to invoke the udf's underlying UDFMethod.invoke method
final Object[] args = new Object[4];
// we use the java reflection api to pull out the method we want to call
Class[] paramTypes = new Class[] { Object.class, String.class, Object.class, args.getClass() };
final java.lang.reflect.Method method = o.getClass().getMethod("invoke", paramTypes);
// here we set up the actual anonymous inner class
java.io.FilenameFilter filter = new java.io.FilenameFilter() {
// note the method signature is the same as the one declared in our .cfm
public boolean accept(java.io.File file, String name) {
args[0] = o;
args[1] = null;
args[2] = page;
args[3] = new Object[] { file, name };
boolean returnValue = false;
try {
// invoke the actual udf. the returntype is 'bool', which is wrapped with a java.lang.Boolean
returnValue = ((Boolean) method.invoke(o, args)).booleanValue();
} catch (Exception e) { }
return returnValue;
}
};
// the filter has been created. assign it to the request scope and let's get out of here
request.setAttribute("filterobject", filter);
} catch (Exception e) {
// generic error 'handling'
out.println(e.toString());
}
%> |
It’s somewhat tricky to follow if you’ve never used the Java Reflection API, but there it is: a Coldfusion UDF invoked from a JSP. I can’t speak about its performance, its maintainability, or its potential applications, but it’s certainly ‘neat’. I only hope my employer concurs.