How can I pass a javaScript function to a Java Method to act as a callback (Rhino)
Asked Answered
B

4

10

Basically I'm trying to pass a javaScript function to a Java method to act as a callback to the script.

I can do it - sort of - but the object I receive is a sun.org.mozilla.javascript.internal.InterpretedFunction and I don't see a way to invoke it.

Any ideas?

Here's what I have so far:

var someNumber = 0;

function start() {
   // log is just an log4j instance added to the Bindings
   log.info("started....");
   someNumber = 20;

    // Test is a unit test object with this method on it (taking Object as a param).
    test.callFromRhino(junk);
}

function junk() {
    log.info("called back " + someNumber);
}
Buckskins answered 15/5, 2010 at 23:24 Comment(3)
Is this an applet? If not it's impossible as the JavaScript code is executed client-side, while the Java Code is executed server-side. You're runtime variables are lost in that process. You will need to call the Java through a POST or GET request, passing your data as a request parameter.Deliberative
@Deliberative - Rhino is a JavaScript interpreter written in Java (it is included in the Java 6 JVM as part of the script API).Flyaway
@McDowell: The OP wasn't specific from which runtime he was trying to call the Rhino/JavaScript function.Deliberative
F
10

Implement an interface:

import javax.script.*;

public class CallBack {
  public void invoke(Runnable runnable) {
    runnable.run();
  }

  public static void main(String[] args) throws ScriptException {
    ScriptEngine js = new ScriptEngineManager().getEngineByExtension("js");
    js.getContext().setAttribute("callBack", new CallBack(),
        ScriptContext.ENGINE_SCOPE);
    js.eval("var impl = { run: function () { print('Hello, World!'); } };\n"
        + "var runnable = new java.lang.Runnable(impl);\n"
        + "callBack.invoke(runnable);\n");
  }
}
Flyaway answered 16/5, 2010 at 8:52 Comment(5)
I think the question was not how to invoke a Java method from Javascript - for which you have given an example, but how to pass a Javascript function to a Java method and invoke the function from Java code.Aerostatics
@Christian Semrau - the interface implementation should invoke the JavaScript function (where I call print)Flyaway
McDowell is correct. Thanks! The method callFromRhino can be defined like this and it works! public void callFromRhino(Runnable callback) { callback.run(); }Buckskins
Now I see. The JavaScript implements an interface (Runnable here) and calls the Java method which in turn calls back the script-implemented interface.Aerostatics
how about parameters passing?Hermy
A
7

sun.org.mozilla.javascript.internal.InterpretedFunction implements the interface sun.org.mozilla.javascript.Function. That interface has a method on it called call that takes:

  • a Context
  • a Scriptable to use as the scope
  • a Scriptable to use as the value of this within the function
  • an array of Objects that are the arguments to the function

So, what I suggest is that in java you cast the object you were passed as a sun.org.mozilla.javascript.Function and call call. The first two arguments can be whatever you used from java to start the script in the first place. The way you're using it there, the last two arguments can be null and new Object[0].

Amalita answered 15/5, 2010 at 23:41 Comment(2)
The sun.org.mozilla.javascript.Function is not available in the built-in Rhino engine in Java 6. I'll it with the downloadable rhino jar.Buckskins
Note that in the downloadable Rhino jar, the interface is called org.mozilla.javascript.Function - Sun renamed all the interfaces in the version they ship in their JDK.Amalita
A
2

The solution is actually to invoke it in another script. This sort of works:

import javax.script.*;

public class CallFunction {

    /**
     * @param args
     * @throws Exception oops!
     */
    public static void main(String[] args) throws Exception {
        ScriptEngine js = new ScriptEngineManager().getEngineByExtension("js");
        js.getContext().setAttribute("out", System.out, ScriptContext.ENGINE_SCOPE);
        Object a = js.eval(
                "out.println('Defining function a...');" +
                "function a() {out.println('hello from JavaScript!'); }" +
                "function foobar() {out.println('in foobar() definition');}" +    
                "out.println('Done!.');"
        );

        System.out.println(js.get("a")); // InterpretedFunction
        SimpleBindings bindings = new SimpleBindings();
        bindings.put("foobar",js.get("a"));
        js.eval("foobar();", bindings); // hello from JavaScript
        js.eval("foobar();"); // in foobar() definition
    }
}

When you get back the reference to a function, you need to ask the engine to execute that function for you. And although not pretty, asking js to eval() it for you with a specific set of bindings will actually do the job for you. You need to take care that the variables you're manipulating belong to the right scope; I guess it's easy to make mistakes here.

Aretino answered 20/10, 2010 at 19:9 Comment(1)
You're welcome. I was struggling with the same problem and didn't find a satisfactory answer to your question, so I felt a working answer was warranted. Since the answer I've verified that this works really well, and it seemingly isn't hard to have callbacks calling java, calling functions with parameters. It's really cool! Makes me wonder if it's possible to port node.js to rhino. lol.Aretino
B
1

This example covers implementing java interface with javascript. That's also can be used for invocation of javascript callbacks from java.



package com.hal.research;

import javax.script.*;

public class CallFunction {
    /**
     * define contract for the callback 
     */
    static interface WhatEverYouWant {
        public String testMe(String a, String b);
    }
    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        final ScriptEngineManager scriptManager = new ScriptEngineManager();
        final ScriptEngine js = scriptManager.getEngineByExtension("js");
        js.put("producer", new Object() {
            /**
             * @param call is a callback to be invoked
             */
            public void doSomethingWithIt(WhatEverYouWant call) {
                System.out.println("invoke callback javascript...");
                String result = call.testMe("a", "b");
                // do something with the result ...
                System.out.println("invoke callback...done, result: "+result);
            }
        });
        js.eval(  "var handler = {\"testMe\": function (a,b){return a + \" is concatenated to \"+ b;}};\n"
                + "var callback = new Packages.com.hal.research.CallFunction.WhatEverYouWant(handler);\n"
                + "producer.doSomethingWithIt(callback); ");
    }
}


Book answered 8/9, 2013 at 22:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.