Reputation: 5386
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
Reputation: 20467
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.
Upvotes: 7
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:
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