Google Fail
Google Fail

Reputation: 113

How do I write a Java method, to be called from server side JavaScript, that takes a JS callback function as an argument?

I'm trying to write a Java API that will be called from server side JavaScript running under the JDK 7 JS engine. The idea is to give consumers the ability to write JS code like below, registering a callback that is later executed by another call to a Java method:

myJavaObj.registerCallback(function (param) {
  // do stuff here
});
myJavaObj.methodThatTriggersCallback();

Here is some test code I'm using:

import javax.script.Invocable;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class JSTestClient {

    private String successCallback;

    public void handleSuccess(String successCallback) {
        this.successCallback = successCallback;
    }

    public void doStuff() throws ScriptException, NoSuchMethodException {
        ScriptEngineManager manager = new ScriptEngineManager();
        Invocable engine = (Invocable) manager.getEngineByName("JavaScript");
        engine.invokeFunction(successCallback, "TEST SUCCESS");
    }
}
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class Main {
    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        String js =
                "var client = new Packages.JSTestClient();\n" +
                "client.handleSuccess(function (response) {\n" +
                "  java.lang.System.out.println(response);\n" +
                "});\n" +
                "client.doStuff();";
        try {
            engine.eval(js); // Expecting this to output "TEST SUCCESS"
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

But when I run this I get a java.lang.NoSuchMethodException because it is interpreting the string:

function (response) {
    java.lang.System.out.println(response);
}
as a function name. Is there a way to create a callback like this or do I need to use some other convention?

Upvotes: 1

Views: 176

Answers (1)

Andreas
Andreas

Reputation: 159114

Your handleSuccess method takes a String as the argument, but you're calling it with a JavaScript function object. You need to change it to accept a Function.

Since you're using Java 7, you are using the modified Mozilla Rhino engine, where the implementation classes were moved from package org.mozilla.javascript to sun.org.mozilla.javascript.internal.

To get your code running, replace the JSTestClient with this:

import sun.org.mozilla.javascript.internal.Context;
import sun.org.mozilla.javascript.internal.Function;

public class JSTestClient {

    private Function successCallback;

    public void handleSuccess(Function successCallback) {
        this.successCallback = successCallback;
    }

    public void doStuff() {
        this.successCallback.call(Context.getCurrentContext(), null, null,
                                  new Object[] { "TEST SUCCESS" });
    }
}

Be aware that Java 8 changed to the Nashorn engine, and the code for interacting with Nashorn is entirely different.

Upvotes: 1

Related Questions