4

I used the following code with the Rhino JavaScript engine in Java:

@Test
public void testRhino() throws ScriptException {
    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("rhino");
    final String raw = "I am the raw value injected";
    final ScriptContext ctx = new SimpleScriptContext();
    ctx.setAttribute("raw", raw, ScriptContext.ENGINE_SCOPE);

    String script = "var result = 'I am a result';";
    script += "java.lang.System.out.println(raw);";
    script += "'I am a returned value';";

    final Object res = engine.eval(script, ctx);
    System.out.println(ctx.getAttribute("result"));
    System.out.println(res);
}

The output of the script (using Rhino) is:

I am the raw value injected
I am a result
I am a returned value

Within the Nashorn JavaScript engine, I get no value for the result:

@Test
public void testNashorn() throws ScriptException {
    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("nashorn");
    final String raw = "I am the raw value injected";
    final ScriptContext ctx = new SimpleScriptContext();
    ctx.setAttribute("raw", raw, ScriptContext.ENGINE_SCOPE);

    String script = "var result = 'I am a result';";
    script += "java.lang.System.out.println(raw);";
    script += "'I am a returned value';";

    final Object res = engine.eval(script, ctx);
    System.out.println(ctx.getAttribute("result"));
    System.out.println(res);
}

returns

I am the raw value injected
null
I am a returned value

How can I access the value of the result variable of the ScriptContext using the nashorn engine?

2 Answers 2

5

If you use ScriptEngine.createEngine API to create ENGINE_SCOPE Bindings, it'll work as expected:

import javax.script.*;

public class Main {
  public static void main(String[] args) throws Exception {

    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("nashorn");
    final String raw = "I am the raw value injected";
    final ScriptContext ctx = new SimpleScriptContext();

    // **This is the inserted line**
    ctx.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);

    ctx.setAttribute("raw", raw, ScriptContext.ENGINE_SCOPE);

    String script = "var result = 'I am a result';";
    script += "java.lang.System.out.println(raw);";
    script += "'I am a returned value';";

    final Object res = engine.eval(script, ctx);
    System.out.println(ctx.getAttribute("result"));
    System.out.println(res);
 }
}
Sign up to request clarification or add additional context in comments.

1 Comment

NashornScriptEngine#createBindings() returns either a new SimpleBindings(), if global-per-engine is set, or createGlobalMirror() if it is not set. Running with either -Dnashorn.args=--global-per-engine, or adding the line System.setProperty("nashorn.args", "--global-per-engine"); before the engine is created, will both cause ctx.getAttribute("result") to again return null. Working around Nashorn's global is one of the biggest pains in embedding Nashorn in a Java application.
3

Nashorn treats the Bindings stored in ScriptContext as "read-only". Any attempt to set a variable stored in a Bindings object (or to create a new variable) will result in a new variable being created in nashorn.global which shadows the Bindings parameter by that name.

You can use the engine to "evaluate" the variable, using this code:

System.out.println( engine.eval("result", ctx) );

This is, however, quite ugly. "result" is first compiled into a script, and then that script is evaluated, to return the variable's value. Fine for testing, but perhaps a little too inefficient for a general solution.

A better, but perhaps more fragile method, is to extract the "nashorn.global" variable, and query it for the desired value.

Bindings nashorn_global = (Bindings) ctx.getAttribute("nashorn.global");
System.out.println( nashorn_global.get("result") );

See also my hack/answer in Capturing Nashorn's Global Variables for automated way of moving nashorn.global values back to a Map<String,Object> after evaluating a script.

3 Comments

It is possible create a new Bindings object using ScriptEngine.createBindings API. This creates a Binding that is directly backed by Nashorn's Global instance - which reflects read/writes into JS global scope always.
@A.Sundararajan If -Dnashorn.args=--global-per-engine is set, then the problem remains even if you use ScriptEngine.createBindings().
With --globals-per-engine, there is only one underlying JS/Nashorn global object per engine. All ENGINE_SCOPE bindings will share the same underlying Nashorn global instance! As you've observed above, engine.createBindings() in --global-per-engine case would just return a SimpleBindings (not a Bindings backed by a fresh Nashorn Global instance).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.