Riding the Nashorn in Mendix

Ockert van Schalkwyk
Mendix Community
6 min readFeb 18, 2020

--

Mendix runs on Java, which means its compiled. Sure, you can create dynamic content but can you create code dynamically? Many compiled languages support scripting engines and JavaScript being popular nowadays, you’ll find many examples, like Duktape in C/C++ or GoJA in Golang. For Java, there exists various options like Rhino, Graal, etc.

Nashorn, the more performant successor of Rhino, allows you to code reflectively in JavaScript. You can do everything you can normally do in ES5.1, with the added bells and whistles of Java.

Here we are going to see how we can get it up and running, set up an editor for our scripts in Mendix, and find out how we can use this slightly unorthodox way of executing code at runtime in interesting and useful ways.

Setup

First, let’s set up an editing interface

Simple as that

Very simple, nothing to it, just a list of scripts with titles and contents. Next, an editor is useful

Editor

You can use a plain text area or a custom widget for editing your code. It helps a lot if you have a proper text editor widget for Mendix. I haven’t found one yet so I just ported the Ace, which has all the features I want like syntax highlighting, themes, vi keys, buffers, etc.

Next, you’ll need to stub out an execution Microflow and Java action for executing your code

Script Invocation

Again, nothing special. Most of the magic is going to happen on the Java side of things.

The Java Side of Things

Here we create a ScriptEngine, create bindings for it (where you can manually expose Java objects to the script that is going to be executed), attach the context of the Java action instance on a key called root, evaluate the Java action parameter str_script and return true. For this simple implementation, all exceptions can be caught Mendix side.

So now you have a script repository, editor, and method of invocation. Let’s look at what you can do in the engine….

Basic Scripting

Now we can test all of this with a basic script as follows

Hello Nashorn

Here we simply log out to the console using the Mendix logging API as exposed through the public API in a JavaScript loop. That’s pretty cool, let's try something a bit more complicated, a dynamic request handler:

Inline Request Handler

What we are doing here is declaring a couple of variables which are then used as shorthand later on, then calling the Mendix API to add a request handler, specifying the endpoint as foobar(to have it execute on all subpaths, use foobar/), and passing it an instance of an inline implementation of a RequestHandler to be executed when we hit that endpoint. Essentially, a runtime programmable service endpoint. You can change the code inside processRequest, rerun the script, and the behavior will change when you hit /foobar (GC should collect the inline instance assuming Mendix drops the previously assigned instance in its path hashmap or whatever it uses)

You can get pretty creative with this when you start reading the parameters and body of the request and start incorporating Mendix’s ORM CRUD functions.

You can also interact with the database directly from the scripting language, as illustrated in the following example which delivers some statistics on the database in terms of how much disk space is used in total, and by the individual tables

Database Stats Rest Service

You can try this out at https://inovosandbox-sandbox.mxapps.io/dbstat, where the Nashorn that delivers this has been set up as a startup script

Nashorn is fully reflective, meaning you can look at what functions are exposed on a class as follows

The above declares a few functions that help you display a feedback message that lists the com.mendix.basis.action.ContextImpl class instance, which is the context of our Java action.

Class Reflection

This sort of reflection can be used to learn more about classes, even private members and can in fact to used to solve problems, for example

And the Answer Is …

The above can be achieved as follows

For on-premise deployments, the above will deliver the license in JSON format including the maximum number of Administration.Account rows you can create, which is parsed and used to send out an email. It would have been tedious and slow to figure this out in Java. Now you can just convert the prototype into a regular Java action or use it as is.

Remote Execution

It is also possible to execute scripts remotely. Simply expose a service (either Mendix or Nashorn instantiated) with security (manually implemented in Nashorn’s case) that takes the body and executes it. This can be pretty handy as well. The following Makefile pushes the code in ./src/a.js to a sandbox for execution if you would like to try it out

Talk to Me

How can we communicate between a Mendix Microflow and our Nashorn Scripts? A very inefficient way would be to call our script, commit to an object, then retrieve that object after the Java action and use the values stored therein. It is, however, possible to get at the Java action running our script, it’s calling microflow and whatever other microflows or Java actions are involved in the call stack. For example, if microflow MyFirstModule.ivk_a calls MyFirstModule.ivk_b which calls MyFirstModule.ja_exec (our Java action) which executes our script, we can get all of that information including microflow structure, current variables, etc. The way it works is that our Java action will be highest in the stack, as illustrated here

Call Stack

You can verify your action reference with root.getContext().getActionStack()[N].getActionName(), this should return the action name, e.g. MyFirstModule.ivk_0MyFirstModule.ja_exec. The following illustrates interaction with the callstack

Action Stack Interaction

Here we get the action stack, get the caller index, get the caller, and call setVariable() to set a variable. If the variable str_foo exists, it is repopulated, and interestingly enough, if it does not exist, it will be created (which you can verify in the debugger after the Java action step executing this script has been completed)

Using the above you can populate all the regular types you can use in a Mendix microflow and then use them after the script action has completed. You can also try to force other types into the Microflow, but be prepared to watch the debugger somersault as it will have no idea how to render something like an java.awt.Image for example

Performance

We use Nashorn scripts in a production environment under high load for user invoked document generation and for arbitrary nightly tasks with no problems. In fact, it solved some of our problems. Nuff said.

--

--