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?
Here is another solution that does not require modifying the nashorn jar:
nashorn.jar
(*) as a resource file in your warExample 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:
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.*
andjavax.*
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.