3

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?

Edit:

 engine = ScriptEngineManager.getEngineByName("python");
 writeDump(engine);

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

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

ScriptEngine ScriptEngineManager

Update

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

 java.lang.NullPointerException
username
  • 57
  • 8
  • *run Python code on a Coldfusion server* Out of curiosity, is it something that could/should be done in pure cfml? – Leigh Oct 26 '16 at 01:17
  • This is more of an exercise than anything else. I would like to have the ability to access python libraries that don't exist in CFML. – username Oct 26 '16 at 12:42

2 Answers2

2

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).

Community
  • 1
  • 1
Leigh
  • 28,424
  • 10
  • 49
  • 96
  • I edited the question to show where it wasn't working. I'm not clear how the ScriptEngine class fits into this. – username Oct 26 '16 at 12:43
  • If `engine` is not defined, that means [the python engine was not found](http://docs.oracle.com/javase/7/docs/api/javax/script/ScriptEngineManager.html#getEngineByName(java.lang.String)). Does the original example work for you in java? – Leigh Oct 26 '16 at 12:53
  • jython is working ok. I added cfdumps of ScriptEngine and ScriptEngineManager. – username Oct 26 '16 at 13:19
  • (Edit) *jython is working ok* Not necessarily. The `javax.script.*` are core libraries, ie always exist. Just because those classes are working, does not mean Jython is working. It is a custom library which has to be loaded explicitly. If you are getting undefined, then it is not loaded/working. – Leigh Oct 26 '16 at 13:32
  • Did you add the jython jar to the CF class path? ACF supports dynamic jar loading via Application.cfc / this.javaSettings and IIRC Lucee supports it via createObject(). – Leigh Oct 26 '16 at 13:39
  • 2
    Tested Jython and looks good. Can create org.python.util.PythonInterpreter in CFML. – username Oct 26 '16 at 14:49
  • Weird. FWIW, I just tested your code and get the same result with jython-standalone-2.7.0.jar. It works from Eclipse, but for some reason the same code does not load the engine in ACF. The problem is not the basic code, since using `getEngineByName("js")` works fine with JRE 1.8. Seems like something is not being initialized when you use it from CF. Still looking around... – Leigh Oct 26 '16 at 15:13
  • See updated answer. Summary: It works if a) jar is placed in web-inf\lib OR b) you register it with the ScriptEngineManager (not sure why that extra step is needed in CF, but not Eclipse). – Leigh Oct 26 '16 at 16:33
  • @Miguel-F - Thanks. Still not sure why you have to register it manually in CF specifically :/ It almost defeats the purpose of using ScriptEngineManager. Unless maybe it does something extra? Otherwise, it would be less code to just create a PyScriptEngineFactory and call `engine = factory.getScriptEngine();`. – Leigh Oct 26 '16 at 18:59
1

I was able to get Leigh's example working in Lucee using the following code and putting the jar in the same directory as the .cfm file. I originally put it in the same folder as the Lucee jar but it was only partially working.

ScriptEngineManager = createObject('java','javax.script.ScriptEngineManager').init();
factory = createObject('java','org.python.jsr223.PyScriptEngineFactory','jython-standalone-2.7.0.jar').init();
ScriptEngineManager.registerEngineName("python", factory);
engine = ScriptEngineManager.getEngineByName("python");

engine.eval("x = 2 + 2");
x = engine.get("x");

writeDump(x);
username
  • 57
  • 8
  • Thanks for posting the working Lucee code. RE: *originally put it in the same folder...*. You should be able to put jar files anywhere that is accessible to the CF engine. Maybe you forgot to specify the full path to the jar file in createObject()? – Leigh Oct 26 '16 at 18:48
  • That could be it. I didn't specify the full path in createObject, however the initial object creation still worked. – username Oct 26 '16 at 19:33
  • Yes, any of the `javax.script.*` classes would always work. That lib is bundled with the JRE and does not require any extra jars. For custom lib's, like jython, it is a good to specify a full path to the jar, even if it is not required. Then you do not have to guess how ACF/Lucee resolves the relative path. If portability is a concern, just use ExpandPath("./jython-standalone-2.7.0.jar"). – Leigh Oct 26 '16 at 21:24