username username - 1 month ago 14
Java Question

Java and JSR-223 to run Python (or Ruby) code on a Coldfusion server

I am attempting to run Python code on a Coldfusion server using Java. I am familiar with CFML but an absolute beginner with Java.

I can instantiate the objects and list their methods ok, however I am getting stuck with different object types.

The example I am trying to get to work in Coldfusion is

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class JSR223 {

public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("python");
engine.eval("import sys");
engine.eval("print sys");
engine.put("a", 42);
engine.eval("print a");
engine.eval("x = 2 + 2");
Object x = engine.get("x");
System.out.println("x: " + x);

What I have so far in CFML

ScriptEngine = CreateObject("java", "javax.script.ScriptEngine");
ScriptEngineManager = CreateObject("java", "javax.script.ScriptEngineManager");
ScriptException = CreateObject("java", "javax.script.ScriptException");

The part I am stuck on

ScriptEngine engine = new ScriptEngineManager().getEngineByName("python");

How can I create that in CFML?


engine = ScriptEngineManager.getEngineByName("python");

Gives me an error: variable [ENGINE] doesn't exist

How does the other class, ScriptEngine fit in with this?



I can load other Python classes so I think that the jar is installed correctly. ie with the following code I can dump the interp object.

interp = CreateObject("java", "org.python.util.PythonInterpreter");

However even then calling a method gives me this error



The keyword new invokes the class constructor. ColdFusion does not support new with java objects. Instead, use the psuedo-method init():

The init method is not a method of the object, but a ColdFusion identifier that calls the new function on the class constructor.

A literal translation of that code is to chain the calls. Invoke init() first, to create a new instance. Then call getEngineByName() on that instance:

engine = createObject("java", "javax.script.ScriptEngineManager").init().getEngineByName("python");

Though for better readability, you may want to break it up:

ScriptEngineManager = createObject("java", "javax.script.ScriptEngineManager").init();
engine = ScriptEngineManager.getEngineByName("python");

As an aside, in this specific case, you can technically omit the call to init(). ColdFusion will automatically invoke the no-arg constructor as soon as you call getEngineByName():

...If you call a public non-static method on the object without first calling the init method, ColdFusion makes an implicit call to the default constructor.

Update based on comments:

If engine is not defined, that means the "python" engine was not found. Be sure you have added the the jython jar file to the CF class path (or loaded it via this.javaSettings in your Application.cfc). Once it is registered, the code should work correctly. For some reason it does not work if you load the jar dynamically through ACF's this.javaSettings. However, it works fine if you place the jython jar in WEB-INF\lib and restart CF. Try adding the jar to the physical CF class path, rather than loading it dynamically and it should work correctly.

It also works from CF if you manually register the engine first (see below). Not sure why that extra step is necessary when ScriptEngineManager is invoked in CF, but not from Eclipse.

ScriptEngineManager = createObject("java", "javax.script.ScriptEngineManager").init();
factory = createObject("java", "org.python.jsr223.PyScriptEngineFactory").init();
ScriptEngineManager.registerEngineName("python", factory);
engine = ScriptEngineManager.getEngineByName("python");
// ...

How does the other class, ScriptEngine fit in with this?

Unlike CF, Java is strongly typed. That means when you declare a variable, you must also declare its type (or class). The original code declares the engine variable as an instance of the ScriptEngine class. Since CF is weakly typed, that is not necessary. Just declare the variable name as usual. The getEngineByName() method automatically returns a ScriptEngine object (by the definition in the API).