private ScriptEngine engine;
public CallJs() {
ScriptEngineManager manager = new ScriptEngineManager();
engine = manager.getEngineByName("nashorn");
}
2017-01-17
I have found that in several of my projects lately that I need to duplicate the same logic in the client that already exists in the server side validation methods. My server side code is usually written in Java while the clients are web pages coded in JavaScript. Given that these exchanges happen using JSON it makes sense that the code should also be written in Java Script.
Prior to Java 8 the JavaScript interpreter was Rhino from Mozilla. Since then a "native" implementation has been added to the JVM called Nashorn. As of Java 8 this is a ECMAScript 5.1 compatible JavaScript engine with hooks into the Java runtime environment. Future releases will support ES6.
To start using Javascript in your application you will need to create a Nashorn engine to process the scripts. I save this for later use.
private ScriptEngine engine;
public CallJs() {
ScriptEngineManager manager = new ScriptEngineManager();
engine = manager.getEngineByName("nashorn");
}
Evaluating a script is pretty straight forward. We take the engine and pass the script to the eval method. The result is returned as a Java object. The interpretation of the object is up to you.
public Object execute(String js) {
Object result = null;
try {
result = engine.eval(js);
} catch (ScriptException e) {
e.printStackTrace();
}
return result;
}
For example we can evaluate a JSON object.
@Test
public void shouldReturnObject() {
Object result = subject.execute("JSON.parse('{\"key\": \"value\"}')");
assertTrue(result instanceof java.util.Map);
assertEquals("value", ((java.util.Map<String, String>)result).get("key"));
}
So now this is where it get interesting. We can also load a script from our resource path and use it within our Java services.
/**
* Determine if the address conforms to our validation rules.
*/
function isValidShippingAddress(address) {
// Only US addresses are supported now.
if (address.country && address.country === "USA") {
// We cannot shipt to a PO Box.
if (address.poBox) return false;
// We need a locality.
if (!address.zipCode || !address.state || !address.city) return false;
// And a street address.
if (!address.street1) return false;
return true;
} else {
return false;
}
}
/**
* Multiple argument function.
*/
function makeAddress(country, zip, state, city, address1, address2) {
var me = {};
me.country = country;
me.zip = zip;
me.state = state;
me.city = city;
me.address1 = address1;
me.address2 = address2;
return me;
}
Create a method to load scripts from local resources and then invoke the functions in the Javascript files.
public void compile(Reader reader) {
try {
engine.eval(reader);
} catch (ScriptException e) {
e.printStackTrace();
}
}
public Object call(String methodName, Object... args) {
Object result = null;
try {
Invocable invoce = (Invocable) engine;
result = invoce.invokeFunction(methodName, args);
} catch (NoSuchMethodException | ScriptException e) {
e.printStackTrace();
}
return result;
}
So we now have the ability to load and call the functions.
@Test
public void shouldBeValid() {
subject.compile(read("address.js"));
UsAddress address = new UsAddress();
address.setZipCode("45030");
address.setCity("Harrison");
address.setState("Ohio");
address.setStreet1("100 Harrison Avenue");
Boolean valid = (Boolean) subject.call("isValidShippingAddress", address);
assertTrue(valid);
}
@Test
public void shouldAllowMultipleArgs() {
subject.compile(read("address.js"));
Object obj = subject.call("makeAddress", "USA", "45030", "Ohio",
"100 Harrision Avenue", "");
assertNotNull(obj);
ScriptObjectMirror mirror = (ScriptObjectMirror) obj;
assertTrue(mirror.containsKey("country"));
}
I found almost everything I needed to know about Nashorn in the guide Riding the Nashorn: Programming JavaScript on the JVM. A quick introduction and then a thorough treatment of the Nashorn implementation.
A demonstration project called jsbridge is available in my GitHub.