I have a JavaScript script that looks something like this:
function run(database) {
var result = database.query("query", "some resource name");
//operations on result
return result;
}
and I have Java code that executes the script that is something like this:
public Object execute(String script, Database database) {
NashornScriptEngineFactory nsef = new NashornScriptEngineFactory();
ScriptEngine engine = nsef.getScriptEngine();
try {
engine.eval(script);
Invocable invocable = (Invocable) engine;
return invocable.invokeFunction("run", database);
} catch(ScriptException e) {
throw new RuntimeException(e);
}
}
Database
is an interface which contains several method definitions, but does not contain the query
method. I am calling execute
with an implementation of Database
, call it DatabaseImpl
, that does have the query
method. This will be polymorphic, and the script is expected to know what methods are available on the Database
instance passed to it. I decided against using generics with this since they are erased at runtime and so the JavaScript has no way of using them anyway, so it's up to the script writer to get the types right.
However, when I run this code, I get the following exception:
javax.script.ScriptException: TypeError: database.query is not a function in <eval> at line number 25
Basically, the gist is, I have an object which implements an interface, and call a method that the particular instance implements, but is not part of the interface definition. My impression is that this should still work, but it does not. It doesn't make much sense to me that I would need to subcast within the script to have access to the query
method (is that even possible?), so why am I getting this error? Is it because the method isn't available from the interface definition? Is there a workaround?
Thanks.
This is the main class:
package so;
import java.io.InputStreamReader;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
public class Nashorn {
public static void main(String[] args) {
try (InputStreamReader in = resource()) {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(in);
Invocable invocable = (Invocable) engine;
Database database = new DatabaseImpl();
Object x = invocable.invokeFunction("run", database);
System.out.println(x);
} catch (Exception e) {
e.printStackTrace();
}
}
private static InputStreamReader resource() throws Exception {
return new InputStreamReader(Nashorn.class.getResourceAsStream("db.js"), "utf-8");
}
}
Interface and implementation
package so;
public interface Database {
void connect();
}
package so;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DatabaseImpl implements Database {
@Override
public void connect() {
System.out.println("Connecting");
}
public List<?> query(String ... stmt){
List<String> lst = new ArrayList<>();
lst.addAll(Arrays.asList(stmt));
lst.addAll(Arrays.asList("A","B","C"));
return lst;
}
}
The javascript file (so/db.js)
function run(database) {
var result = database.query("query", "some resource name");
//operations on result
return result;
}
Running results in:
[query, some resource name, A, B, C]
It basically works.