Migrate Nashorn to GraalVM - javascript

I am using Nashorn JS engine from OpenJDK 12. Nashorn seems to be deprecated. I am looking which are the available alternatives. I found GraalVM, but I am not sure if this is the best.
How can I execute a GraalVM JavaScript from Java ? Do you have any example ?
With Nashorn was using from Java:
NashornScriptEngineFactory nsef = new NashornScriptEngineFactory();
ScriptEngine engine = nsef.getScriptEngine( BasicDBObject.class.getClassLoader() );
final Bindings binding = engine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
In Nashorn I create a WrappedMongoDatabase which extends AbstractJSObject. There I add some 'virtual' methods to simulate the MongoDB Query language, which does for example getCollection('persons').find()...
Do you know a way to replace the AbstractJSObject in GraalVM?
I had a look to ProxyObject, somehow I couldn't find a way to override the call(Object thiz, Object... args) like in AbstractJSObject.
public class WrappedMongoDatabase extends AbstractJSObject {
#Override
public boolean hasMember(String name) {
return "getCollection".equals( name ) || "createCollection".equals(name)||...;
}
#Override
public Object getMember(final String name) {
if ( hasMember( name ) ){
return new AbstractJSObject() {
#Override
public Object call(Object thiz, Object... args) {
switch( name ) {
case "getCollection":
if (args.length == 1 && args[0] instanceof String) {
return getCollection((String) args[0]);
}
break;
...
}
}
}
}
}
}

GraalVM is a solid way to go. We've been using it for a while now, it runs well, and the JavaScript implementation is far better than Nashorn (or Rhino). In particular, it is ECMA2020 compliant, it supports Node requires (that's huge!), it performs much better, etc...
GraalVM is a big step forward if you're using Nashorn, but it does require some adjustments, which are covered reasonably well in the GraalVM documentation.

Follow GraalVM ScriptEngine
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("polyglot.js.allowHostAccess", true);
bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true);
bindings.put("javaObj", new Object());
engine.eval("(javaObj instanceof Java.type('java.lang.Object'));"); // would not work without allowHostAccess and allowHostClassLookup
Notice nashorn compatibility mode:
These options control the sandboxing rules applied to evaluated JavaScript code and are set to false by default, unless the application was started in Nashorn compatibility mode (--js.nashorn-compat=true).

Related

How to load java script having class, in jvm using nashorn

Here is my simple java script test.js with class :
class Car {
constructor(name, year) {
this.name = name;
this.year = year;
}
}
myCar = new Car("Ford", 2014);
Print("done")
Here is my java code which will try to load test.js
public class Controller {
public static void main(String[] args) {
try {
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
ScriptEngine engine = factory.getScriptEngine("--language=es6");
engine.eval(Files.newBufferedReader(Paths.get("<Path-to-test-js>/test.js"), StandardCharsets.UTF_8));
} catch (Exception _e){
System.out.println(_e);
}
}
but its throwing exception :
javax.script.ScriptException: <eval>:1:0 Expected an operand but found class
class Car {
^ in <eval> at line number 1 at column number 0
Why its throwing exception here.
I am not able to run nashorn with my scripts , i think class is not supported.
If anyone is planning to use nashorn do not use it :
I removed class from my scripts and i tested its performance , its very slow. not even to close to expected performace of my application.
Even for study purpose i will not recommend it as its getting absolute.
Nashorn, the JavaScript engine in the OpenJDK, has been deprecated in JDK 11 by JEP 335 and has recently been scheduled to be removed in a future JDK version by JEP 372.

Jint: using CLR object 's properties in Javascript functions

I have been testing the jint library and hit a snag. Given this class in C#:
public class Foo
{
public string Name { get; } = "Bar";
}
And this code:
Engine engine = new Engine(x => x.AllowClr());
object rc = _engine
.SetValue("foo", new Foo())
.Execute("foo.Name.startsWith('B')")
.GetCompletionValue()
.ToObject();
I get the error: 'Jint.Runtime.JavaScriptException: 'Object has no method 'startsWith'''
This works, however:
"foo.Name == 'Bar'"
So can I get the former to work?
Support for extension methods is added here. But can't get it to work with the .NET string extension methods directly, it does work with an intermediate extensions class.
Update: The string methods like StartsWith aren't real extension methods indeed.
Looks like startsWith is already supported natively now. Replaced GetCompletionValue with the suggested Evaluate
// Works natively with Jint 3.0.0-beta-2032
Engine engine = new Engine();
bool result = engine
.SetValue("foo", new Foo())
.Evaluate("foo.Name.startsWith('B')")
.AsBoolean();
I've tried to add the string extension methods, but that doesn't seem to work. But using your own class for the extension methods and use it that way does work.
public static class CustomStringExtensions
{
public static bool StartsWith(this string value, string value2) =>
value.StartsWith(value2);
}
Engine engine = new Engine(options =>
{
options.AddExtensionMethods(typeof(CustomStringExtensions));
});
bool result = engine
.SetValue("foo", new Foo())
.Evaluate("foo.Name.StartsWith('B')")
.AsBoolean();
I've asked about the native extension methods support here, as I'm curious if and how it is supposed to work.

Is it possible to import javascript files to a java ScriptEngine

I am using nashorn java ScriptEngine. I would like to evaluate a script which includes other scripts. I know I can use the load directive directly in the javascript itself, but I would prefer to import or load it directly from the java code instanciating the scriptEngine.
Is there a way to do this ? Something like :
void evaluateScript(String scriptName, String dependency) {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine jsEngine = factory.getEngineByName("nashorn");
jsEngine.load(depency); // does not exist.
jsEngine.eval();
}
I see the "load" function does not exist. How could I achieve this?
Thanks
Actually I found the answer myself: as mentioned in the comment, it is possible to call several eval with different scripts, same engine, and the engine will keep the evaluated scripts in its context. So here is my code:
public void executeScript(String scriptName, String[] dependencies) {
try {
FileReader script = new FileReader(scriptName);
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine jsEngine = factory.getEngineByName("nashorn");
if(dependencies != null) {
for (String dependency : dependencies) {
FileReader dependencyFile = new FileReader(dependency);
jsEngine.eval(dependencyFile);
}
}
jsEngine.eval(script);
}
}
I can define functions in my dependencies and use them in the script of name scriptName.
javax.script.ScriptEngine has many "eval" methods - there are java.io.Reader accepting eval methods like this -> eval
You can pass a java.io.FileReader or a jdk.nashorn.api.scripting.URLReader to load script from a file or a URL.

Supply JavaScript date to Nashorn script

I am working on an API in Java that allows users to write scripts and access a specific set of methods that are passed in (in the form of an API object) by the Nashorn script engine.
I want to, in the JavaScript, call a function getDate(), which will return some arbitrary date (as a native JavaScript date) that's provided from the Java side.
I have tried simply putting an org.java.util.Date on the API object, but that won't behave like a JS date. The goal is to make this as simple as possible for end-users who are experienced with JS.
Java Example:
public class MyAPI {
public void log(String text){
System.out.println(text);
}
public Date getDate(){
// Return something that converts to a native-JS date
}
public static void main(){
// MyClassFilter implements Nashorn's ClassFilter
ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine(new MyClassFilter());
((Invokable) engine).invokeFunction("entryPoint", new MyAPI());
}
JavaScript example
function entryPoint(myApi){
var date = myApi.getDate();
myApi.log(date.getMinutes());
}
jdk.nashorn.internal.* packages are nashorn internal implementation classes. There is no guarantee that these won't be changed or be removed between JDK versions. Besides with a security manager around, accessing these packages from java code directly would result in SecurityException being thrown! With jdk9 modular jdk, these packages are not exported packages from nashorn module and so javac won't even compile your code in jdk9!
I'd recommend using JS wrapper (solution 1) in the answer by user "ug_". If you do have to call from Java, you can use API exported from jdk.nashorn.api.scripting package.
If "engine" is your javax.script.ScriptEngine of nashorn, then you can do something like the following:
import jdk.nashorn.api.scripting.*;
..
public Object getDate() {
// get JS Date constructor object - you can get once and store
// as well/
JSObject dateConstructor = (JSObject) engine.eval("Date");
// now do "new" on it
return dateConstructor.newObject();
}
With that, your JS script can call "getDate()" on your API object and get a JS
Date object. Note that you can also pass constructor arguments to newObject
method call (it is a Java variadic method).
The Nashorn engine has objects it uses internally which represent the Javascript objects. As you have guessed the java.util.Date != new Date() (in javascript). The engine uses a class called jdk.nashorn.internal.objects.NativeDate to represent its JS date.
If I were building this out I would not have the NativeDate constructed in the Java but instead have a wrapper in Javascript for the MyApi object which would contain a few other native JS methods, such as getDate().
var MYAPI_JAVASCRIPT = {
log: function() {
print(arguments);
},
getDate: function() {
return new Date();
}
}
You could then pass that object as the method parameter.
However if your really set on using the NativeDate in your Java code then you can construct one like so:
public NativeDate getDate() {
return (NativeDate) NativeDate.construct(true, null);
}

Throwing a JavaScript exception from C++ code using Google V8

I'm programming a JavaScript application which accesses some C++ code over Google's V8.
Everything works fine, but I couldn't figure out how I can throw a JavaScript exception which can be catched in the JavaScript code from the C++ method.
For example, if I have a function in C++ like
...
using namespace std;
using namespace v8;
...
static Handle<Value> jsHello(const Arguments& args) {
String::Utf8Value input(args[0]);
if (input == "Hello") {
string result = "world";
return String::New(result.c_str());
} else {
// throw exception
}
}
...
global->Set(String::New("hello"), FunctionTemplate::New(jsHello));
Persistent<Context> context = Context::New(NULL, global);
...
exposed to JavaScript, I'ld like to use it in the JavaScript code like
try {
hello("throw me some exception!");
} catch (e) {
// catched it!
}
What is the correct way to throw a V8-exception out of the C++ code?
Edit: This answer is for older versions of V8. For current versions, see Sutarmin Anton's Answer.
return v8::ThrowException(v8::String::New("Exception message"));
You can also throw a more specific exception with the static functions in v8::Exception:
return v8::ThrowException(v8::Exception::RangeError(v8::String::New("...")));
return v8::ThrowException(v8::Exception::ReferenceError(v8::String::New("...")));
return v8::ThrowException(v8::Exception::SyntaxError(v8::String::New("...")));
return v8::ThrowException(v8::Exception::TypeError(v8::String::New("...")));
return v8::ThrowException(v8::Exception::Error(v8::String::New("...")));
In last versions of v8 Mattew's answer doesn't work. Now in every function that you use you get an Isolate object.
New exception raising with Isolate object look like this:
Isolate* isolate = Isolate::GetCurrent();
isolate->ThrowException(String::NewFromUtf8(isolate, "error string here"));

Categories

Resources