Using JavaScript Inside JVM to Navigate Data Graphs

When I was looking for a suitable language for traversing graph data structures in Arastreju, I quickly came across JavaScript, which is expressive, flexible, simple and widely used. Originally I considered to create yet another DSL, but this would have restricted the users to those navigation, filtering and traversal operations I happened to foresee at this time. With JavaScript instead, the users have the flexibility and opportunities of a whole programming language to extend missing functionality I had initially not in mind.

Great JavaScript support inside the JVM can be expected with Java 8 and Nashorn, the JavaScript engine based on Rhino using invoke dynamic. Especially I’m looking forward to run Node.js inside the JVM.

But of course it has been possible to integrate JavaScript in a Java program since a long time. Mozilla’s Rhino has been released in the late ‘90s and since Java 6 it is bundled with (Sun’s/Oracle’s) JDK – slightly modified and repackaged into ‘com.sun.script.javascript’. Personally I prefer to embed and use the original Rhino instead of the built-in script engine. And so do I in this post.

Initialization of JavaScript engine

The starting point for any JavaScript integration is the creation of a org.mozilla.javascript.Context. When created with Context.enter() this context object will be bound to the current thread until Context.exit() is called.

try {
  Context ctx = Context.enter();
  Scriptable scope = ctx.initStandardObjects();
  ...
  ...
} finally {
  Context.exit();
}

The call of initStandardObjects() creates the “built-in” basic objects as “Function”, “Object” and the data types. The Scriptable returned can act as our JavaScript global object

Execute scripts

Now it is really easy to execute some JavaScript inside this context, say we had a file “my-script.js”:

Reader in = new FileReader('my-script.js');
ctx.evaluateReader(scope, in, "my-script.js", 1, null);

The parameters mean:

  1. The scope this script runs in – we use our global scope
  2. A java.io.Reader object providing the script
  3. The name of the script or input source for debug info
  4. The line number for debug info
  5. Some information about a ‘Security Domain’ – I never set one, but always used null

If we had just a single statement to evaluate, we could call evaluateString() instead:

ctx.evaluateString(scope, " x = y + z; ", "inline script", 1, null);

Of course we won’t achieve anything by just executing some JavaScript that way. We need interaction between our Java program and the nested scripts, a way to provide input to the script engine and yield output from a script to the Java world.

Making Java objects accessible from JavaScript

Basically any Java object can be put in a JavaScript scope and all its fields and methods will be available for scripts in that scope.

Here we have a Java class NodeSet containing some nodes of a data graph and some methods to operate on this graph.

public class NodeSet {

  public NodeSet walk(String edgeName) {
    ...
  }

  public boolean matches(String expression) {
    ...
  }

  public void result() {
    ...
  }

}

An instance of this class is made accessible from the scripts using this instruction:

NodeSet nodes = new NodeSet(...);
scope.put("initialNodeSet", scope, nodes);

Inside the script this node set can be handled as any usual JavaScript object to navigate along the edges of the nodes and create a resulting node set or to check if at least one of the nodes matches to a given expression.

friends = initialNodeSet.walk('foaf:knows');
if (friends.matches('Beeblebrox')) {
  ...
}

Calling functions from Java code

A lot of JavaScript’s power derives from the possibility to pass a function as argument to a function call. An adequate way to filter nodes in a flexible way in JavaScript would be a filterfunction that receives another function as argument, which will be called for any element in the node set.

initialNodeSet.filter(new function(n) {
    return n.matches('improbability'));
});

The question is, how to implement the “filter” method of the NodeSet class in Java, especially how the signature of this method should look like. One approach to implement a Java Script callback function in Java, would be to use the Rhino specific Function type as parameter:

public NodeSet filter(org.mozilla.javascript.Function f) {
    Context context = Context.getCurrentContext();
    Scriptable scope = context.newObject(f.getParentScope());
    Scriptable thisObj = f.getParentScope();        
    for (Node node : internalNodes) {
        // call the function and fetch the return value
        Object[] params = new Object[] { node.asNodeSet() };
        Object value = f.call(context, scope, thisObj, params);
        ...
    }
    return new NodeSet(...);
}

Here we iterate over all nodes in Java and supply it as parameter to the invocation of the JavaScript function. Additionally to parameters, context and scope the call method takes an argument which evaluates to the JavaScript object referenced by “this” inside the function.

Usage of JavaScript for graph navigation in Arastreju

In Arastreju we have embedded Rhino just in the way described above, to allow traversing in an imperative way through the semantic graph and fetch some results. Here is an (non-optimized) example for obtaining the cities where a person’s company is located.


  // start with a node set containing all companies
  start = query("'rdf:type'='common:Organization'");

  // query the node representing the current user
  me = query("QN='${user.person}'");

  // filter companies where the current user is employed
  myCompanies = start.filter(function (company) {
    return company.walk('common:hasEmployee').matches(me);
  });

  // get the cities 
  myCompaniesSites = myCompanies.walk('common:hasSites').walk('common:isInCity');

  // yield this node set as result
  myCompaniesSites.result();

If interested, you can see the details of the JavaScript integration in Arastreju on GitHub in the SGE module (package org.arastreju.sge.query.script).

Leave a comment