GraalVM JavaScript

THIS FEATURE IS CURRENTLY IN BETA

Introduction

Platform uses ‘Rhino’ (Mozilla) JavaScript Engine (JS Engine) to run JavaScript inside the Platform. Rhino engine has certain limitations; it does not support the java script features - promises and await. Therefore, we have evaluated other JavaScript engines and identified Graal JS (by GraalVM) as the successor for Rhino Engine. Graal JS is fully compatible with the latest ECMA script 2020 specification. Its performance is better than Rhino.

For instance, the example below shows how promises can be created with the async-await functionality. Rhino engine does not support it, however, Graal JS does support it.

Copy
var menuPicklist = rbv_api.getPicklist("XYZHotel", "Item", null);
var menu = rbv_api.jsonToString(menuPicklist);
// The following function returns a promise that resolves if the customer
// orders something from the menu:
function order_food (order) {
    let promise = new Promise((resolve, reject) => {
    if(menu.includes(order)) {
        resolve();
    }else {
        reject('This item is not on the menu.');
  }
});
    return promise;
}
// The following function returns a promise that resolves if the customer
// pays 20 or more for the food:
function make_payment (payment) {
    let promise = new Promise((resolve, reject) => {
    if(payment >= 20) {
        resolve();
    }else {
        reject('Your order costs 20.');
  }
});
    return promise;
}
// await can only be used inside an async function
async function eat(order, payment){
    // waiting for the promises to resolve
    try{
        await order_food(order);
        ("Order received by the customer.");
        rbv_api.println("Collect payment.");
        await make_payment(payment);
        rbv_api.println("Payment received.");
    }
    // Catching errors or rejected promises
    catch (error){
        rbv_api.println(error);
    }
}
// Customer places his/her order and specifies the amount to pay
// Play around with these parameters to fully understand what is going on.
eat("Pizza", 10);

Enabling Graal JS

Currently, Platform includes both, Rhino Engine and Graal JS engine to run java script. However, Rhino Engine support will be discontinued in the future once the Graal JS integration is fully stabilized.

A new shared property UseGraalJS has been added in the Add/Remove Features section. This is a boolean (checkbox) property.

  • If checked or set to TRUE, Graal JS support is enabled at platform level and tenant administrators can choose the JS engine to run their java script against.

  • If unchecked or set to FALSE, Graal JS support is disabled at platform level and platform forces the java script to run against Rhino JS engine.

  • The default is unchecked/FALSE.

In conjunction with the above shared property, a new tenant level property, Switch To Graal JS has been added to switch JS engine selection. This tenant property is shown only when the above shared property is checked.

If this checkbox is left unchecked, then the default value for this property is considered as FALSE, and Rhino JS engine is selected by default. A Tenant Administrator should select this checkbox to enable Graal JS runtime at tenant level.

Graal JS (Community Edition) does not support setting a time limit for context. To work around setting the max time for java script execution, a new shared property, MaxJSStatements has been introduced in the Limits -> Triggers section to control the statement limit. The default value for this property is 10000. For more information, see Shared Properties.

In formula debug, java script engine selection has been added to enable debug the formula using either Rhino or Graal JS. Whenever the java script engine selection changes, the formula is debugged against the selected JS engine.

Invoking an external java class using Graal JS

The procedure to invoke an external java class in Platform using Graal JS is as follows:

  1. Copy the jar file into <tomcat_home>/lib folder.

  2. Consider the sample code below.

    • In the Shared Properties tab, add org.test.sample.helloworld to the CustomClassFilter text field.

      Copy
      package org.test.sample;
      public class helloworld {
         public static void main(String[] args) {
             // TODO Auto-generated method stub
             helloworld hw = new helloworld();
                     hw.concat(args[0],args[1]);
         }
         public String concat(String a,String b)
         {
             System.out.println(a+b);
             return a.concat(b);
         }
      }
    • Log in as a customer, and add the below code in the script editor (For example: Object Script trigger)

      Copy
      var test = new Packages.org.test.sample.helloworld();
      var test2 = test.concat("Hello","world");
      rbv_api.println(test2);
    • Invoke the trigger. Observe and ensure that the output is correct.

Configurable Properties

The following shared properties can be configured based on the JS engine.

Shared

Property

Rhino JS

Graal JS

Comments
MaxJSTimeMs Yes NA Maximum time for which the script is allowed to run in the JS engine. Currently, this property is only applicable for Rhino engine.
MaxJSStatements NA Yes Maximum number of script statements that are allowed as per the context.

This property is applicable only for Graal JS engine.

MaxFormulaSize Yes Yes Maximum formula size.
JSOptimizationLevel Yes NA Optimization parameter for Rhino engine.
CustomClassFilter Yes Yes Specifies predicate for allowing Java classes from the JS engine.

Compatibility

Existing Rhino scripts should be able to run on Graal VM JavaScript as well. In case there is java interoperability inside the script, refer the Migration section to update the script, and this is optional to comply with Graal JS syntax.

For ServerSide APIs - follow the method parameter types as given in the documentation, otherwise it will throw type cast errors.

Graal JS features such as promises, modules, etc. are not supported in Rhino. Therefore, Graal JS script with these features cannot run on Rhino. Otherwise, the scripts should run on both JavaScript Engines.

Migration

Both, Rhino and GraalVM JavaScript engines support a similar set of syntax and semantics for Java interoperability. Significant differences relevant to migration are listed below.

  • (Optional) Java.type(typename) instead of java.a.b.c.typename

    GraalVM JavaScript does not put available Java classes in the JavaScript scope. You have to explicitly load the classes using Java.type(typename).

    Example:

    Copy
    var HashMap = Java.type('java.util.HashMap');
    var map = new HashMap();
    map.put(1, "a");
    map.put(2, "b")
    var mapSize = map.size();
    rbv_api.println("Map Size " + mapSize);
  • Console Output of Java Classes and Java Objects

    GraalVM JavaScript provides a built-in print function. It tries to special-case its behavior on Java classes and Java objects to provide the most useful output.

  • JavaScript vs. Java Strings

    GraalVM JavaScript uses Java strings internally to represent JavaScript strings. This makes it impossible to differentiate/identify if a specific string was created by JavaScript or by Java code. In GraalVM JavaScript, the JavaScript properties take precedence over Java fields or methods.

    Example:

    Copy
    var javaString = Java.type('java.lang.String');
    var strVar = new javaString("Welcome to Graal JS");
    var strLength = strVar.length
    rbv_api.println("String length: " + strLength);

 

GraalVM Compiler

Graal VM Compiler jars are added to the Platform to aid in for executing JavaScripts. In order to use GraalVM compiler, ensure the following:

  • Specify the JAVA_OPTS (--upgrade-module-path).

  • Add the following commands to the setenv.bat file located in your bin directory of your tomcat folder to use the GraalVM compiler.

    set "JAVA_OPTS=%JAVA_OPTS% -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=%CATALINA_HOME%/rollbase/lib/graal-sdk-21.1.0.jar;%CATALINA_HOME%/rollbase/lib/truffle-api-21.1.0.jar --upgrade-module-path=%CATALINA_HOME%/rollbase/lib/graalvm_compiler/compiler-21.1.0.jar;%CATALINA_HOME%/rollbase/lib/graalvm_compiler/compiler-management-21.1.0.jar"
Note:
  • The performance of GraalVM transcends over Rhino especially in the case of complex or time consuming javascripts.
  • It is also reported that Rhino utilized a considerable amount time for initial requests while the subsequent requests were performed faster. However, while executing any javascript, GraalVM performs better when the peak time is reached.

  • As Platform utilizes Stock JDK (OpenJDK11), the GraalVM JavaScript engine performance is fine tuned by using GraalVM compiler.

  • Considerably, there are certain limitations on the features and performance for GraalVM JavaScript when used with GraalVM Community Edition JVM and OpenStock JDK as compared to the GraalVM Enterprise Edition.

  • Few options in GraalVM JavaScript around performance fine tuning are experimental, hence it is not recommended for any usage on production instance.