Tuesday 12 May 2015

Lucee 5: using a Java class file

G'day:
Here's one that bit me on the bum with Lucee, and took me a while to sort out. Not least of all because of some... err... communication challenges I had along the way trying to get help on the issue. I'll get to that.


One of the most convenient features of ColdFusion for me - a dedicated CFML programmer and only an occasional Java hacker - is that on those rare occasions I do need to knock together some quick Java functionality, all I need to do is to write a quick class (although there's nothing quick about me and "writing Java classes"), sling it into the /WEB-INF/classes directory, and then I could just create instances of said class from CFML. I use this for "my" ClassViewer class which has proven invaluable to me over the last decade or so when trying to work out WTF is going on with CFML's interaction with Java. On any of my CFML servers one of the first things I do is chuck a copy of that ClassViewer.class into /WEB-INF/classes, and then it's there when I'm shufti-ing around:

// cv.cfm
cv = createObject("java", "ClassViewer");
try {
    1/0;
}catch (any e){
    writeOutput("<pre>#cv.viewObject(e)#</pre>");    
}

Easy. Oh, and this gives stuff like this:

public class coldfusion.runtime.DivideByZeroException
 extends coldfusion.runtime.ExpressionException
 extends coldfusion.runtime.NeoException
 extends java.lang.RuntimeException
 extends java.lang.Exception
 extends java.lang.Throwable
 extends java.lang.Object
 extends {
 /*** CONSTRUCTORS ***/
 public coldfusion.runtime.DivideByZeroException()


 /*** METHODS ***/
 public int getErrNumber()

 public final void setLocale(java.util.Locale)

 public java.lang.String getMessage()

 // [...]

(full output in this Gist: https://gist.github.com/adamcameron/fd5ab8db7c534acc68ac).

It's just easy. And has always "just worked".

Until Lucee 5, that is.

For reasons as yet unexplained coherently & honestly by Lucee, this functionality no longer works.

Fortunately the solution is easy... one just needs to stick the class in a JAR file and pop it in the /WEB-INF/lib directory instead. I have to admit I actually didn't know how to do two things regarding Java up until trying to work around this exercise:

  • how to implement namespacing;
  • how to make a JAR

Sad admissions indeed! Well I knew the rudimentaries of both, but had never needed to put them into practice, so didn't know the details.

Just in case you're as thick as me when it comes to Java, here's what I learned. Firstly a baseline:

class Greeter {
    public static String gday(String who){
        return "G'day " + who;
    }
}

If I compile that, I can just stick Greeter.class file into /WEB-INF/classes, and use it in my CFML, eg:

// sampleBasic.cfm

greeter = createObject("java", "Greeter");
writeOutput(greeter.gday("Zachary"));


And we get:

G'day Zachary

Next... namespacing it...

package me.adamcameron.miscellany;

class Greeter {
    public static String gday(String who){
        return "G'day " + who;
    }
}

And now this class needs to go in /WEB-INF/classes/me/adamcameron/miscellany before we can use it:

greeter = createObject("java", "me.adamcameron.miscellany.Greeter");

Note how the package needs to be expressed everywhere.

Finally, making it into a JAR instead. This uses the same code, but I needed to home Greeter.java in the correct subdirectory structure (me\adamcameron\miscellany\Greeter.java), recompile it, then run it through JAR:


C:\src\java>jar cf miscellany.jar me\adamcameron\miscellany\Greeter.class

C:\src\java>

This creates miscellany.jar, and to use that, I pop it into /WEB-INF/lib (not /WEB-INF/classes, nor /WEB-INF/lib/me/adamcameron/miscellany).

That was all pretty easy. However note that whilst the class-based solutions (both non-packaged and packaged versions) work A-OK on ColdFusion (all versions), Railo (all versions), and Lucee 4.5; they do not work on Lucee 5. On Lucee 5 the only option that works is the JAR version. This is not exactly a hardship, but it's worth knowing.

I tried to get some sort of acknowledgement of there being an issue here from Lucee, but that didn't pan out. Micha's dismissive, excuse-making, unhelpful attitude really irked me here: LDEV-315. He's basically blamed me, Tomcat, all other CFML engines (including Lucee 4.5 I guess) but not any action on his part which might have caused this regression. According to him Lucee 5 is working correctly, and all the other ones are behaving the way they do purely by coincidence (and different vendors implementing the same coincidence). At the same time, inferentially suggesting to Tomcat and Servlet docs have it wrong. He's beginning to sound like Rupesh from the Adobe ColdFusion Team in his excuse-making squirming.

I can surmise that the way the Lucee classloader has been rewritten for OSGi support either necessitates this change, or there's a glitch in it which causes it. I could not find OSGi docs making any claims one way or the other, unfortunately.

As I say on the ticket, at the very least Lucee need to document this change, but Micha - who's generally a bit averse to appropriate levels of documentation in the first place - didn't even accept that. The ticket is closed with the familar (from the Adobe bug tracker, anyhow) "Closed/Won't Fixed/Can't be arsed". This is a pity.

Anyway, at least I know about packaging and JARing in Java now. Win!

--
Adam