Running your own Java libraries in ColdFusion with style

I've been doing a ridiculous amount of work recently writing up some Java code that I am looking to deploy with Transfer, and in the process have had all sorts of fun running around exploring the internals of ColdFusion.

The issue however was being able to deploy the code with my library.

(As per usual with these posts, you must note that some of these methods are undocumented and liable to change, yadda ya)

I had my JAR file that I wanted to be able to use with my ColdFusion code, but there were several problems

   1. The Java code referenced some ColdFusion, and other related JAR files that were loaded into CF at startup
   2. Some of my objects I wanted to be able to create via CF have constructors that require arguments, which is tricky business.
   3. ColdFusion likes to keep a 'hold' on any Jar it loads up via URLClassLoader, so being able to redeploy/upgrade the code is tricky, as you can't delete the file without resetting the server.

Okay, so we'll start at the beginning – obviously we have the initial aspect of using a URLClassLoader to load in the external series of .class files, or .jar files, as Spike wrote about back in 2004 . (I'm not going to cover that aspect of it here, there is plenty of examples out now on how to use URLClassLoader out on the blogsphere)

This is all fine and dandy, I load in my class, and I try and create an instance, but it will throw an error – you know why? Because the code in my JAR references Java classes that the URLClassLoader doesn't know about.  Namely ColdFusion classes! So it throws a big fat hairy error as it can't see them.

Now, the neat thing is, a URLClassLoader's constructor can take a java.lang.ClassLoader as it's parent – which means it can look to it to try and resolve classes.  So off we run to get the ClassLoader for the ColdFusion context.

There are about half a dozen different ways of doing this, but I did it this way –

cfClassLoader = getClass().getClassLoader();

getClass() gets the java.lang.Class object of the current page or cfc, which is very handy for us, as we can grab the ClassLoader for the CF context, and thereby resolve classes that are loaded by ColdFusion when it starts up.

So now, when we create our URLClassLoader, we can create it like this:

classLoader = createObject("java", "java.net.URLClassLoader").init(URLs, getClass().getClassLoader());

Where URLs is the array of java.net.URL that we need to tell the URLClassLoader what directories and jar files to load in.

Okay, so that's all well and good, but problem number 2 arises.  URLClassLoader allows us to make calls like:

string = urlclassLoader.loadClass("java.lang.String).getInstance();

To create an instance of a Java object.  Which is all fine and dandy if there is a default constructor, BUT so often Java objects like to take arguments when they are instantiated.

I beat my head against this one for a while, as it is a real pain to try and do this through reflection – and then it dawned on me – ColdFusion has already done the hard work for us, why shouldn't we just take advantage?

So I did some digging around and worked out that when you write:

System = createObject("java", "java.lang.System");

The object 'System' is an instance of the coldfusion java object  'coldfusion.runtime.java.JavaProxy'.  The JavaProxy takes a java.lang.Class as an argument in it's constructor, and then handles all the aspects of interpreting CF calls on Java objects.

So now, when we load up a class we want an instance of in ColdFusion, we do this –

class = getClassLoader().loadClass("myPersonalJavaObject");
       
personalObject = createObject("java", "coldfusion.runtime.java.JavaProxy").init(class);

This gives us access to the object as we would normally through a createObject("java", "className"); call.

So now, as we would normally, if we want to create an instance of the object we can call –

instance = personalObject.init();

Just like we would normally.  But if we have arguments

instance = personalObject.init(myarg1, myarg2);

And ColdFusion will handle the aspect of translating the CF to the Java object for you.

Finally, the issue that, when you've made your URLClassLoader to a JAR file, the system often won't let you delete the file, as it still marks it as being 'under use'.  The way I solved that problem was by using ANT to add a datestamp to the name of my jar flie when it creates it.

This does 2 things:

   1. Gives the file a unique name, so I don't have to worry about deleting the previous JAR, and can do so when the server gets reset, or the garbage collector realises that nobody is using the JAR anymore, and releases it.
   2. Lets me know when looking at the dir where I store my JAR files which file was created first.

Since I know which one was created first – as long as I load the most recent JAR file to the URLClassLoader first, it will be the first one queried for the class files that are being requested, and therefore you can use the objects that you want.

It also means you could also write some nifty code to ignore the previous version by tokenizing the name of the file, but I will leave that aspect up to you.

This code has yet to be committed to the Transfer CVS repository, but assuming it all goes ahead as planned, look in the package transfer.com.util for a JavaClassLoader to see these techniques in action.

I hope that all made sense, and makes life easier for you while you write your own Java code to run alongside ColdFusion.

Leave a Comment

Comments

  • Jim Collins | May 11, 2006

    Very cool. Feel free to take a look at my JavaClassLoader.cfc at
    http://sourceforge.net/projects/cfsynergy . It handles the issue you mentioned – "Some of my objects I wanted to be able to create via CF have constructors that require arguments, which is tricky business."

  • PaulH | May 11, 2006

    holy crap! this looks like it will solve a nagging problem w/some classes in icu4j. thanks a bunch.

  • Mark | May 11, 2006

    I’m sorry Jim, you did some good work with JavaClassLoader.cfc, but I think these techniques are better, and I will tell you why

    1) This code doesn’t deviate from the CF standard of initialising a Java object, which is very nice
    2) On your code, every time you go to load an Java object you create a new URLClassLoader – that seems a bit of a waste to me. This way means you can keep the URLClassLoader around as a singleton and reuse the resource.
    3) Your code doesn’t handle access to static functions well. You have to create an instance of the object. That isn’t always an option.
    4) Your loader doesn’t support access to ColdFusion built in classes and libraries (although it could with a small amount of work), whereas mine does.
    5) If I have 2 jar files that I want to use, and one references the other, your code will fail. Whereas with this technique, you just load them both when you create the URLClassLoader.

    That being said, no disrespect to you Jim, you did great work on the JavaClassLoader.

    Paul – hope that works for you, let me know if you have any issues.

  • jim collins | May 11, 2006

    Mark: No disrespect taken at all, I always say the biggest room in the world is the room for improvement. Pleae send me any and all code and suggestions you have to improve javaclassloader and I will incorporate into the next release. I will certainly incorporate Pauls work assuming its ok with him. It’s obvious his knowledge of Java is much greater than mine, which is pratically none :).

  • Mark | May 11, 2006

    I just commited to CVS the JavaLoader that Transfer uses, which uses these techniques.

    You can find it here:
    http://cfopen.org/scm/cvsweb.php/transfer/com/util/?cvsroot=transfer

    Unfortunatley viewing CF files in CFOpen.org is broken, so you will have to use a CVS browser to grab a copy of the file if you want it.

  • Brian Rinaldi | May 12, 2006

    I second Paul’s "Holy Crap!". I did end up copying your javaLoader.cfc off your transfer site and made a couple very quick modifications to specify the directory to load. This is sweet!

  • Brian Rinaldi | May 12, 2006

    Wrote up a post on how I was able to utilize this method and thought you might be interested. http://www.remotesynthesis.com/blog/index.cfm/2006/5/11/Loading-Java-Libraries-on-the-Fly-and-Parsing-RSS-easily

  • Natalie | June 21, 2006

    How is can help to load commons logins jar file and get rid of org.apache.commons.logging.LogConfigurationException in coldfusion mx7 ??

  • Natalie | June 21, 2006

    I’ve found your article from that forum
    http://www.tek-tips.com/viewthread.cfm?qid=1244340&page=1
    So, I’ve downloaded JavaLoader and have modified my script.
    I’ve put all libraries to the lib folder of my project and want to load that libs.

    Here is the source
    paths = arrayNew(1);
    paths[1] = expandPath("./lib/axis.jar");
    paths[2] = expandPath("./lib/log4j.jar");
    paths[3] = expandPath("./lib/commons-discovery-0.2.jar");
    paths[4] = expandPath("./lib/commons-logging-1.0.2.jar");
    paths[5] = expandPath("./lib/commons-logging-api-1.0.2.jar");
    paths[6] = expandPath("./lib/java2wsdl.jar");
    paths[7] = expandPath("./lib/jaxrpc.jar");
    paths[8] = expandPath("./lib/jrun.jar");
    paths[9] = expandPath("./lib/webservices.jar");*/
    paths[10] = expandPath("./lib/wsdl2java.jar");
    paths[11] = expandPath("service.jar"); /*my java class*/

    //create the loader
    loader = createObject("component", "JavaLoader").init(paths);
    obj = loader.create("service.test");
    ret = obj.init();
    traffic = ret.doit();

    And I’ve got the same exeption.
    What can be doen in Loader else to prevent such exeption ?
    How to make CF to use thta libraries and to not generate such exeptions ?

  • Mark | June 22, 2006

    Natalie,
    Without seeing your code, I’m not entirely sure what the exact problem is.

    It may possibly be that the paths you have created do not actually point to the jar files that you want, and therefore the JavaLoader is using the commons-loggings from ColdFusion.

    This actually gives me a good idea – in that I will build some validation into the JavaLoader to fire an exception if the jar or directory does not exist, and that should help you out.

  • snekse | August 11, 2006

    Cool stuff. Any thoughts on how to hack around the web.xml config to do GZIP compression filtering?

  • Mark | August 11, 2006

    Snekse
    Not sure what you are driving at (I think I have an idea, but I’m not sure), could you be more specific?

    Something like GZIP compression is most likely best handled at a server setting, i.e. getting Apacahe or IIS to do it for you.

    You do have access to the Java Response object, so in theory you could quite probably do it at that level as well. (Just a thought anyway)

  • snekse | August 11, 2006

    Well, I’m running on a shared hosting server, so I don’t have access to the server settings. Typically on a Java app server, you can add filters to the web.xml file in the WEB-INF directory to manipulate the request/response objects that match a certain filter. My thought is that I’d like to compress all the responses I’m returning out of "/SomeDir/*.cfm".

    I guess I didn’t think my original question out very well. I think I was thinking there was a way to maybe do this without dropping something in the Application.cfm file. Not sure why I thought that. I think my brain is dehydrated today 🙂

    Here’s an article talking out setting up filters on a CFMX server.
    http://cfdj.sys-con.com/read/41574.htm

  • Amir | February 4, 2007

    Mark,

    Did you manage to have a play with the src code i uploaded btw?. I’ve working towards a another solution but am curious why the classloader was not happy.

  • Mark | February 5, 2007

    Unfortunately Amir, I haven’t had a chance, but when I do, I will respond to your post in the forum.

  • Russ Michaels | October 11, 2007

    snekse I don’t think your host would appreciate you modifying their web.xml either.