andersonbd1
andersonbd1

Reputation: 5386

Can I bundle a newer version of Nashorn in a war?

Unless I'm missing something here, this version of Nashorn appears to have some bugs:

$ jjs -v
nashorn 1.8.0_45

it chokes on using multiple integrals of 3 digits or more as property keys:

$ echo 'var c = JSON.parse("{\"123\": \"a\", \"456\": \"b\"}"); print(c["123"])' | jjs; echo
jjs> java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 7

2 digits works fine:

$ echo 'var c = JSON.parse("{\"12\": \"a\", \"45\": \"b\"}"); print(c["12"])' | jjs; echo
jjs> a

3 digits and 2 digits gives a different error:

$ echo 'var c = JSON.parse("{\"123\": \"a\", \"45\": \"b\"}"); print(c["123"])' | jjs; echo
jjs> undefined

a 3 digit and a string work fine:

$ echo 'var c = JSON.parse("{\"123\": \"a\", \"foo\": \"b\"}"); print(c["123"])' | jjs; echo
jjs> a

it all works fine using this version:

$ jjs -v
nashorn 1.8.0_121

$ echo 'var c = JSON.parse("{\"123\": \"a\", \"456\": \"b\"}"); print(c["123"])' | jjs; echo
jjs> a

Anyway, the above snippets are just a way to demonstrate an issue I'm having in my webapp. My question is - is there a way to bundle this newer version of nashorn in my webapp so that I don't need to request a java upgrade on the server?

Upvotes: 3

Views: 545

Answers (2)

Hugues M.
Hugues M.

Reputation: 20467

Here is another solution that does not require modifying the nashorn jar:

  • bundle nashorn.jar (*) as a resource file in your war
  • use a child-first/parent-last class loader such as this one
  • load the engine via this class loader

Example servlet that implements the approach above, and then tries to evaluate your script with both the JRE's Nashorn, and the bundled Nashorn:

@WebServlet("/nashorn")
public class NashornDemoServlet extends HttpServlet {

    private static final ClassLoader CL;

    static {
        // In my case nashorn.jar is under WEB-INF/classes (resources root)
        URL nashornURL = NashornDemoServlet.class.getClassLoader().getResource("nashorn.jar");
        CL = new ParentLastURLClassLoader(Collections.singletonList(nashornURL));
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        String script = "var c = JSON.parse(\"{\\\"123\\\": \\\"a\\\", \\\"456\\\": \\\"b\\\"}\"); c[\"123\"]";

        ScriptEngine nashorn = new ScriptEngineManager(getClass().getClassLoader()).getEngineByName("nashorn");
        try {
            Object result = nashorn.eval(script);
            out.println("### JRE Nashorn result: " + result);
        } catch (Exception e) {
            out.println("### JRE Nashorn failed!");
            e.printStackTrace(out);
        }

        try {
            Class<?> clazz = CL.loadClass("jdk.nashorn.api.scripting.NashornScriptEngineFactory");
            Object factory = clazz.newInstance();
            ScriptEngine engine = (ScriptEngine) clazz.getMethod("getScriptEngine").invoke(factory);
            Object result = engine.eval(script);
            out.println("\n### Bundled Nashorn result: " + result);
        } catch (Exception e) {
            out.println("### Bundled Nashorn failed!");
            e.printStackTrace(out);
        }
    }
}

Result using tomcat 8, on JRE 8u45:

### JRE Nashorn failed!
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 7
    at java.util.Arrays.rangeCheck(Arrays.java:120)
    at java.util.Arrays.fill(Arrays.java:2868)
    at jdk.nashorn.internal.runtime.BitVector.setRange(BitVector.java:273)
    ...
    at java.lang.Thread.run(Thread.java:745)

### Bundled Nashorn result: a

Web app project tree:

enter image description here

Before that I also tried simply bundling nashorn.jar under WEB-INF/lib without custom class-loader trick (hoping the usual child-first class loader of servlet container would be enough), but that did not work. I suppose this is because Tomcat follows this rule from the servlet spec:

Servlet containers that are part of a Java EE product should not allow the application to override Java SE or Java EE platform classes, such as those in java.* and javax.* namespaces, that either Java SE or Java EE do not allow to be modified.

"Such as", so it seems jdk.* also falls in that category (in any case, Tomcat does seem to exclude Nashorn). So yeah, bring your own ClassLoader 😉

(*) Make sure you can legally do that. Maybe consider using a jar from an OpenJDK build, not copied from an Oracle Java installation directory. Maybe consider not including yourself it but providing instructions to add the file to the war you distribute (it's just a zip file), etc.

Upvotes: 7

user65839
user65839

Reputation:

In principle, I would expect that you should be able to take the nashorn.jar from the newer JRE's \lib\ext folder and run it through something like a maven-shade-plugin (depending on your build system) to relocate the packages to a different name (so as not to conflict with the existing ones in the underlying JVM you want to want it on). You'd probably also want to change the META-INF/services/javax.script.ScriptEngineFactory in the new jar that you are making accordingly, and also change the values in the NashornScriptEngineFactory.class so that it has a different scripting engine name to use. Then you could add that as a library to your war file, and then create your version of the scripting engine using new ScriptEngineManager().getEngineByName("my-nashorn"); or whatever name you gave to it.

Alternatively, you might try to compile Nashorn yourself from its source code, again perhaps modifying it slightly to give it your own engine name. I'm not sure whether or not that'd be easier, since I haven't tried either approach.

Both of those approaches assume:

  1. That the bug you need fixed is something that was fixed in the nashorn code itself, as opposed to something actually fixed in the underlying JVM.
  2. That doing so is within the licensing agreement. I haven't looked into it, though it looks that the source code is GPL 2. But I'm no open source expert.

I would expect that just updating the version of Java on the server to fix known bugs (and security patches!) would be have to be significantly easier, but if you need to run some specific version of some specific code on that specific old buggy edition of Java, you need to provide that specific code yourself somehow.

Upvotes: 2

Related Questions