I have no idea why you would actually do this, but the fact that you can is super cool.
1st we download Jython , the Java implementation of Python
2nd we drop the jython.jar file in the same dir as the below code.
3rd we use the JavaLoader.cfc to load up the jar file.
4th watch Python run in ColdFusion!
This is a CF translation of the Java embedding tutorial at http://www.jython.org/docs/embedding.html
<cfscript>
//set he path
paths = ArrayNew(1);
paths[1] = expandPath("jython.jar");
//create the loader
loader = createObject("component", "JavaLoader").init(paths);
//create the Python Iterpreter
interp = loader.create("org.python.util.PythonInterpreter").init();
//set standard output and errors out to the screen
interp.setOut(getPageContext().getResponse().getOutputStream());
interp.setErr(getPageContext().getResponse().getOutputStream());
//top line
writeoutput("Hello, brave new world <br/>");
//do some python
interp.exec("import sys");
interp.exec("print sys");
pyInt = loader.create("org.python.core.PyInteger").init(42);
interp.set("a", pyInt);
//this now prints out to the screen!
interp.exec("print a");
interp.exec("x = 2+2");
//get some values back out of python
x = interp.get("x");
writeoutput("x: " & x & "<br/>");
writeoutput("Goodbye, cruel world <br/>");
</cfscript>
Okay, so I'm just hyped up about using the JavaLoader to do some weird and neat stuff - but hey, it's really cool, allright!
Well, I *was* going to go to the gym, but noooo, I got pestered by a bunch of people to actually produce a generic version of my JavaLoader that I talked about in a previous blog post . You people know who you are, and you should be ashamed. Ashamed that you forced me to produce more open source code. Shame on you indeed.
Now that I've got all that drivel out, I've released a generic version of the Java Class loader for loading external JAR files. Be they ones you created, an open source library, or a library you created to work with *another* jar library - all is possible with this tool.
The key features of this CFC are:
- It can load several libraries at once, thereby allowing linking between Java libraries
- It allows access to ColdFusion core Java files, as well as the Java libraries that are loading with ColdFusion
- It utilises the coldfusion.runtime.java.JavaProxy, which enables ColdFusion to do Object constructor arguments as you would normally with
createObject("java", "javaObject").init(myArg);, as well as being able to give access to static fields on Java objects that cannot be instantiated.
So, how do we use it? Well, there is an example in the download , but I'll give you a run down here.
Say we have a jar file, 'toolbox.jar', and we went and wrote our own code base and stored that in 'ourcode.jar' that referenced objects in toolbox.jar.
First we need to create an Array with the absolute file paths to each of these in it, so we would go:
paths = ArrayNew(1);
paths[1] = expandPath("toolbox.jar");
paths[2] = expandPath("ourcode.jar");
Then we create our JavaLoader, passing in the array, so it knows what to load -
loader = createObject("component", "JavaLoader").init(paths);
So now, we can create a instance of an object from ourcode.jar, say, for example the 'OurCode' class
If we were to just want to create a default (no arguments) instance of OurCode, we could now go:
ourcode = loader.create("OurCode").init();
Just like we would on a createObject("java", "OurCode").init() call, if we had access to the 'OurCode' class.
But say, for example, OurCode also had the option of taking an int and a String as an argument - we could now go:
ourcode = loader.create("OurCode").init(15, "I love Strings");
Again, same as we would on a createObject("java", "OurCode").init(15, "I love Strings") call, if we had access to the 'OurCode' class.
But what if we wanted to get complicated, I mean, we said that ourcode.jar referenced code in toolbox.jar, so maybe OurCode may want to take a ToolBox class instance as an argument - this is all possible. We have loaded both JARs into the loader, so we can get to both instances, and we use CF to make the constructor work, like so:
toolbox = loader.create("ToolBox").init();
ourcode = loader.create("OurCode").init(toolbox);
Again, same as we would on a createObject("java", "OurCode").init(toolbox) call, if we had access to the 'OurCode', 'ToolBox' classes.
Oh, and I almost forgot, what if we want to access static methods, or values? Still the same thing as createObject - we just don't call an init() on the object we get on the create() call, like so -
Utils = loader.create("Utils");
resultFromStaticMethod = Utils.doThisThing("hello");
Again, same as we would on a createObject("java", "Utils") call, if we had access to the 'Utils' class.
That is actually it, in terms of using the JavaLoader, but this opens up a huge area of development where in theory you could actually write most of your object development in Java, and then just have the display tier in CF (I'm not advocating the idea, but you *could* do it), not to mention you now have access to almost every Java lib out there that doesn't require you to install anything J2EE, like a servlet etc - which is a very powerful thing.
Hope you all enjoy. Any questions or comments, either send me an email , or respond to the blog post.
Details on the project can be found here.
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.
Really short 'didn't know you could do that' moment.
Playing around with createTimeSpan() today in a little test bed, and gave this a try and it worked -
<cfscript>
now = now();
dts = createTimeSpan(0,2,0,0);
then = now + dts;
</cfscript>
<cfoutput>
#TimeFormat(Now(), "HH:mm:ss")# : #TimeFormat(then, "HH:mm:ss")#
</cfoutput>
And look at that, that just added 2 hours to the time.
Normally I would have done that with a dateAdd(), but this actually quite quick.