Adding back Nashorn to Apache Sling on Java 15 or newer
Backwards compatiblity is of the utmost importance for Apache Sling. Existing applications should ‘just work’ when running on top of newer versions of the Sling Starter or other Sling-based applications.
An interesting challege comes up when needing to adjust to newer version of the Java runtime. Overall Java does a very good job of staying backwards compatible but there are scenarios where consumers need to adapt.
One such case is the removal of the built-in Nashorn engine in Java 15. This is documented in JEP 372 and the main reason is the effort needed to keep the engine up-to-date with ECMAScript changes. The project is still maintained though as a GitHub project at https://github.com/openjdk/nashorn so it is possible to add it back to a Sling deployment.
This post documents the main steps needed to update an existing bundle to adds back the Nashborn engine to a Sling application running on Java 15 or newer.
Maven dependencies
Assuming that you already have a working OSGi bundle built with Maven, you first need to add the
nashorn dependency to your pom.xml:
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.6</version>
</dependency>
Make sure to check if there are newer versions availabler in Maven Central.
The Nashorn scripting engine has very few dependencies but id does depend on ASM. If you prefer the bundle to be self-contained you should manually manage the ASM dependency and ensure you use the latest version. When declaring multiple dependcies I prefer defining a property first
<asm.version>9.7</asm.version>
We can now exclude the transitive ASM dependencies and add them manually to our pom.xml. The
dependencies block now becomes
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.6</version>
<exclusions>
<exclusion>
<groupId>org.ow2.asm</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-analysis</artifactId>
<version>${asm.version}</version>
</dependency>
Embedding Nashorn
We can now use the bnd-maven-plugin to embed all the needed classes to our bundle.
The following configuration, whether added to the bnd.bnd file or to the configuration section of
the bnd-maven-plugin, will embed Nashorn and its dependencies:
-includeresource: \
@nashorn-core-*.jar!/!META-INF/MANIFEST.MF,\
@asm-*.jar!/!META-INF/MANIFEST.MF
To verify that the embedding is correct, build the project once and ensure that the Nashorn and ASM classes are included in the resulting bundle:
$ mvn package
$ jar tf target/your-bundle.jar | grep nashorn | wc -l
$ jar tf target/your-bundle.jar | grep asm | wc -l
Deploying the bundle
If you have the sling-maven-plugin
installed you can build and deploy bundle in one go using mvn package sling:install.
Otherwise you can build it and use other mechanisms such as the Apache Felix Web Console.
After deploying you can use the Web Console scripting status, by default available at http://localhost:8080/system/console/slingscripting to verify the available scripting engines. If the bundle is correctly deployed and started you should see a listing similar to
OpenJDK Nashorn 15.6
-------------------------------------
- Language : ECMAScript, ECMA - 262 Edition 5.1
- Extensions : js
- MIME Types : application/javascript, application/ecmascript, text/javascript, text/ecmascript
- Names : nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript
Congratulations, you have now deployed the Nashorn scripting engine to your Sling application.
A note on accessing scripting engines in Sling
Java applications normally instantiate the javax.script.ScriptEngineManager
directly to access the available scripting engines. This works by virtue of the ServiceLoader
mechanism. Unfortunately, that mechanism does not work out-of-the-box in OSGi environments. My
colleague David Bosschaert has written a blog post the topic, including solutions for the
general problem - java.util.ServiceLoader in OSGi.
But for the specific scenario in Sling we do something simpler - obtain a reference to the
javax.script.ScriptEngineManager service from the OSGi service registry, as registered
by the Apache Sling Scripting Core bundle.
You can then use the scripting engine to as usual to evalute inputs.
public class NashornTestServlet extends SlingSafeMethodsServlet {
@Reference
private ScriptEngineManager scriptEngineManager;
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
ScriptEngine engine = scriptEngineManager.getEngineByName("nashorn")
String input = "var result = 'Hello from Nashorn!'; result;";
Object result = engine.eval(input);
// do something with the result
}
}
Sample project
A project demonstrating the above steps is available in the Sling Samples repository at https://github.com/apache/sling-samples/tree/master/nashorn-external.