Reputation: 1726
I have a java class like below
public class StaticBean {
public static String returnString(int num){
String json = "[{\"name\" : \"John Doe\", \"age\" : 30}]";
return json;
}
}
In the below test, i have two engine instances.
Upon execution, i see that it is not possible to copy the bindings instance onto another and use it for execution in the same engine or different engine. Even if i do copy, the results doesn't match with the ones i get if i use the same engine/binding.
@Test
public void testParsingStringObjects() {
Bindings b = engine.createBindings();
b.put("ndb", getBindingObject(StaticBean.class, engine));
engine.setBindings(b, ScriptContext.ENGINE_SCOPE);
String source = "print('Definition in engine instance 1');"
+ "var SysU = {};\n"
+ "SysU.returnObject = function returnObjectJS(){\n"
+ "var string = ndb.returnString(1);\n"
+ "return JSON.parse(string);\n" + "}\n"
+ "SysU.returnString = function returnStringJS(){\n"
+ "var string = ndb.returnString(1);\n"
+ "print('String returned by the java function SysU.returnString() '+string);\n"
+ "return string;\n" + "};\n"
+ "print('====================Using the same engine instance for execution====================');\n"
+ "(function (){" + "var json = {};\n"
+ "print(\"String Returned in Caller SysU.returnString(): \"+SysU.returnString());\n"
+ "print(\"Object Returned in Caller SysU.returnObject(): \"+SysU.returnObject());\n"
+ "print(\"**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): \"+JSON.stringify(SysU.returnObject()));\n"
+ "print('Adding the object in another ( json.ext = SysU.returnObject();) ...');\n"
+ "json.ext = SysU.returnObject();\n"
+ "print(\"Added JSON object which is stringified to display JSON.stringify(json): \"+JSON.stringify(json));\n" + "})();";
try {
engine.eval(source);
Bindings oldEngineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
Bindings localBindings = engine2.getBindings(ScriptContext.ENGINE_SCOPE);
Bindings newBindings = engine.createBindings();
oldEngineBindings.put("fileName","oldEngine");
localBindings.put("fileName","localEngine");
newBindings.putAll(oldEngineBindings);
newBindings.putAll(localBindings);
oldEngineBindings.putAll(localBindings);
ScriptContext ctxt = new SimpleScriptContext();
ctxt.setBindings(oldEngineBindings, ScriptContext.ENGINE_SCOPE);
engine.setContext(ctxt);
engine.eval(""
+ "print('====================Using the same engine with original binding ====================');\n"
+ "(function (){" + "var json = {};\n"
+ "print(\"String Returned in Caller SysU.returnString(): \"+SysU.returnString());\n"
+ "print(\"Object Returned in Caller SysU.returnObject(): \"+SysU.returnObject());\n"
+ "print(\"**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): \"+JSON.stringify(SysU.returnObject()));\n"
+ "print('Adding the object in another ( json.ext = SysU.returnObject();) ...');\n"
+ "json.ext = SysU.returnObject();\n"
+ "print(\"Added JSON object which is stringified to display JSON.stringify(json): \"+JSON.stringify(json));\n" + "})();");
ctxt.setBindings(newBindings, ScriptContext.ENGINE_SCOPE);
engine.setContext(ctxt);
engine.eval(""
+ "print('====================Using the same engine with copied new binding ====================');\n"
+ "(function (){" + "var json = {};\n"
+ "print(\"String Returned in Caller SysU.returnString(): \"+SysU.returnString());\n"
+ "print(\"Object Returned in Caller SysU.returnObject(): \"+SysU.returnObject());\n"
+ "print(\"**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): \"+JSON.stringify(SysU.returnObject()));\n"
+ "print('Adding the object in another ( json.ext = SysU.returnObject();) ...');\n"
+ "json.ext = SysU.returnObject();\n"
+ "print(\"Added JSON object which is stringified to display JSON.stringify(json): \"+JSON.stringify(json));\n" + "})();",newBindings);
ctxt.setBindings(oldEngineBindings, ScriptContext.ENGINE_SCOPE);
engine2.setContext(ctxt);
engine2.eval(""
+ "print('====================Using a different engine instance with original binding ====================');\n"
+ "(function (){" + "var json = {};\n"
+ "print(\"String Returned in Caller SysU.returnString(): \"+SysU.returnString());\n"
+ "print(\"Object Returned in Caller SysU.returnObject(): \"+SysU.returnObject());\n"
+ "print(\"**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): \"+JSON.stringify(SysU.returnObject()));\n"
+ "print('Adding the object in another ( json.ext = SysU.returnObject();) ...');\n"
+ "json.ext = SysU.returnObject();\n"
+ "print(\"Added JSON object which is stringified to display JSON.stringify(json): \"+JSON.stringify(json));\n" + "})();");
} catch (ScriptException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Is this an accepted behavior? or a bug?. I should be able to copy the binding and use them in a different scope in the same engine or different engine instance and yield the same results.
I am testing on Java8u101
Results when you run the test. ReturnObject() function seems to fail when the bindings or the engine instance change.
Definition in engine instance 1
====================Using the same engine instance for execution====================
String returned by the java function SysU.returnString() [{"name" : "John Doe", "age" : 30}]
String Returned in Caller SysU.returnString(): [{"name" : "John Doe", "age" : 30}]
Object Returned in Caller SysU.returnObject(): [object Object]
**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): [{"name":"John Doe","age":30}]
Adding the object in another ( json.ext = SysU.returnObject();) ...
Added JSON object which is stringified to display JSON.stringify(json): {"ext":[{"name":"John Doe","age":30}]}
====================Using the same engine with original binding ====================
String returned by the java function SysU.returnString() [{"name" : "John Doe", "age" : 30}]
String Returned in Caller SysU.returnString(): [{"name" : "John Doe", "age" : 30}]
Object Returned in Caller SysU.returnObject(): [object Object]
**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): [{"name":"John Doe","age":30}]
Adding the object in another ( json.ext = SysU.returnObject();) ...
Added JSON object which is stringified to display JSON.stringify(json): {"ext":[{"name":"John Doe","age":30}]}
====================Using the same engine with copied new binding ====================
String returned by the java function SysU.returnString() [{"name" : "John Doe", "age" : 30}]
String Returned in Caller SysU.returnString(): [{"name" : "John Doe", "age" : 30}]
Object Returned in Caller SysU.returnObject(): [object Object]
**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): undefined
Adding the object in another ( json.ext = SysU.returnObject();) ...
Added JSON object which is stringified to display JSON.stringify(json): {}
====================Using a different engine instance with original binding ====================
String returned by the java function SysU.returnString() [{"name" : "John Doe", "age" : 30}]
String Returned in Caller SysU.returnString(): [{"name" : "John Doe", "age" : 30}]
Object Returned in Caller SysU.returnObject(): [object Object]
**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): undefined
Adding the object in another ( json.ext = SysU.returnObject();) ...
Added JSON object which is stringified to display JSON.stringify(json): {}
EDIT :-
Found this thread https://bugs.openjdk.java.net/browse/JDK-8067642 . This mentions something about foreign objects being instances of ScriptObjectMirror . I used the typeof operator to display the type of object returned in the cases that failed and succeeded and both times they were ScriptObjectMirror but the stringify works as expected if i use the original bindings object in the context.
EDIT 2:-
Added a very simple test to demonstrate the above. Kinda like a TLDR for the above :) . Executing the below demonstrates that putAll() on a bindings object does not work as we expect it to.
@Test
public void testParsingObjects() throws ScriptException {
String source = "var Func = {};\n"
+ "Func.getJavaScriptObject = function(){"
+ "var jsString = '{\"foo\":\"bar\"}';\n"
+ "return JSON.parse(jsString);"
+ "};";
String executor = "(function(){ "
+ "var obj = Func.getJavaScriptObject();"
+ "print(JSON.stringify(obj));"
+ " })();";
System.out.println("Executing source...");
engine.eval(source);
System.out.println("\nUsing the same binding instance and engine\n");
engine.eval(executor);
Bindings originalBinding = engine.getBindings(ScriptContext.ENGINE_SCOPE);
Bindings copiedBinding = engine.createBindings();
copiedBinding.putAll(originalBinding);
System.out.println("\nUsing the copied binding instance and engine\n");
engine.eval(executor,copiedBinding);
}
Result of execution.
Executing source...
Using the same binding instance and engine
{"foo":"bar"}
Using the copied binding instance and engine
undefined
Upvotes: 3
Views: 1886
Reputation: 111
Here is the code I am using the share compiled JavaScript code between ScriptContext instances used by different threads. The main benefit here is only compiling the code once, although I also benefit from not needing to stream the code from the REST API multiple times. I did not include the REST part for brevity.
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;
import java.util.ArrayList;
import java.util.List;
public class ContextDemo {
static CompiledScript codeLib;
static ScriptEngine engine;
static ScriptContext context;
static List <Future<String>> taskResults;
static ExecutorService executor;
static List <Callable<String>> tasks = new ArrayList<Callable<String>> ();
public static void main(String[] args) {
try {
// Initialize workers and execute
run(4);
} catch(InterruptedException | ExecutionException | ScriptException e) {
System.out.println(e.getMessage());
}
}
static void run(int workers) throws InterruptedException, ExecutionException, ScriptException {
// Initialize engine and initial context
engine = new ScriptEngineManager().getEngineByName("nashorn");
context = new SimpleScriptContext();
engine.setContext(context);
// Compile a JavaScript object with a function
codeLib = ((javax.script.Compilable)engine).compile("var lib = { func1: function(n, s) { return 'thread number ' + n + ': ' + s; } };");
// Create executor with specified number of workers
executor = Executors.newFixedThreadPool((int)workers);
for (int i = 0; i < workers; i++) {
tasks.add(workerLambda(i));
}
// Invoke worker pool
taskResults = executor.invokeAll(tasks);
// Iterate futures list and report results
for (int i = 0; i < workers; i++) {
Future < String > f = taskResults.get(i);
if (f.isDone()) {
System.out.println(f.get());
} else {
System.out.println("Thread " + i + " not done");
}
}
// Shutdown the executor
executor.shutdown();
}
static Callable <String> workerLambda(int n) {
int workerNum = n;
// Thread-specific script context initialization
SimpleScriptContext threadContext = new SimpleScriptContext();
threadContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
try {
// Inject compiled code library into thread-specific ScriptContext
codeLib.eval(threadContext);
} catch (ScriptException e1) {
System.out.println(e1.getMessage());
}
// Return the lambda
return () -> {
// Call the injected object method and return the result
return (String)engine.eval("lib.func1(" + workerNum + ", 'Hello!');", threadContext);
};
}
}
This outputs:
thread number 0: Hello!
thread number 1: Hello!
thread number 2: Hello!
thread number 3: Hello!
Upvotes: 1
Reputation: 111
I wish I could provide a working solution to this, but I am suspecting it may be a Nashorn bug. As evidence, I submit this link to an old JDK bug:
https://bugs.openjdk.java.net/browse/JDK-8023363
Test.java (below) is provided in the link as evidence that both issues (lack of key presence in Map and inability to execute function after .putAll() into new Bindings) are "Not an Issue". Except that I tested the same code and got different results:
Test.java follows:
import javax.script.*;
import java.util.Map;
public class Test {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine scriptEngine = m.getEngineByName("nashorn");
Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
scriptEngine.eval("function func(x) { print('I am func ' + x); }");
// print stuff exposed in engineBindings
for (Map.Entry<?,?> entry : engineBindings.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
Bindings localBindings = scriptEngine.createBindings();
// copy all exposed from other bindings
localBindings.putAll(engineBindings);
// put additional variable
localBindings.put("event", new Object());
scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE);
scriptEngine.eval("func(event)");
}
}
Upvotes: 0