Notice:

This page has been converted from a dynamic Wordpress article to a static HTML document. As a result, some content may missing or not rendered correctly.

JavaScript Via Java On The Back Of A Rhino ~ Wed, 23 Jan 2013 18:24:03 +0000

I have been formulating a project with a couple of at odds requirements:

  1. It must interface with an Oracle database
  2. It should be easily extensible via a language others wouldn't mind

Requirement #2 leads me to JavaScript, it being the most common programming language available. So, I could do this project as a Node.js based project, but the Oracle driver is a pain to setup since it depends on the "instant client" binaries and SDK (particularly on OS 10.7 or later; unless you like running Node.js in 32-bit mode). There is also a JDBC driver for Node.js, but it's incomplete, hasn't been worked on it two years, and would add Java as a dependency. Thus, why not skip straight to Java? Oh, but there's that pesky problem of wanting to use JavaScript for extensibility.

Enter Rhino. Rhino is a JavaScript library for Java that provides a JavaScript interpreter. As a side note, it sounds like Rhino may be superceeded by a new library, Nashorn, in late 2013. In any event, I want to share what I have learned. The documentation for Rhino is a bit difficult to follow, so a simple introduction is warranted.

This brief introduction will:

  1. Read an external JavaScript file
  2. Create an instance of the Rhino interpreter
  3. Use the Rhino interpreter to provide a new host object to the JavaScript script
  4. Execute the JavaScript script

    The source code in this article is available for download. The zip contains of the code in this article, the Rhino library, and a Bash script to build and test the code.

Let's first look at meat of the program:


package com.jrfom;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.File;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

public class JavaScriptRunner {
    public static void main(String[] args)
        throws java.lang.Exception,
        java.io.IOException,
        java.io.FileNotFoundException
    {
        if (args.length < 1) {
            System.err.println("Must pass in a JavaScript file to run.");
            System.exit(1);
        }

        File scriptFile = new File(args[0]);
        if (!scriptFile.exists()) {
            System.err.println("Script file '%s' does not exist.".format(args[1]));
            System.exit(1);
        }

        // Read in the JavaScript file.
        byte[] buffer = new byte[512];
        StringBuilder sb = new StringBuilder();
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(scriptFile), 512);
        int bytesRead = bis.read(buffer);
        while (bytesRead != -1) {
            sb.append(new String(buffer));
            bytesRead = bis.read(buffer);
        }
        String script = sb.toString();

        // Initialize the interpreter.
        Context context = Context.enter();
        context.setLanguageVersion(Context.VERSION_1_8);
        Scriptable scope = context.initStandardObjects();
        ScriptableObject.defineClass(scope, Foo.class);

        if (!context.stringIsCompilableUnit(script)) {
            Context.exit();
            System.err.println("Script is not compilable!");
            System.exit(1);
        }

        // Try to run the JavaScript through the interpreter.
        try {
            context.evaluateString(scope, script.trim(), null, 1, null);
        } finally {
            Context.exit();
        }
    }
}

Lines 40 through 56 are where Rhino is loaded, the new host object defined (line 43), and the script executed (line 53). The rest of the code is merely checking for error conditions and reading in the JavaScript file.

So, what is this new host object, Foo? It is a plain Java object that extends an object provided by the Rhino library:


package com.jrfom;

import org.mozilla.javascript.ScriptableObject;

public class Foo extends ScriptableObject {
    private String bar;

    public Foo() {}

    @Override
    public String getClassName() {
        return "Foo";
    }

    public void jsConstructor() {
        this.bar = "";
    };

    public void jsSet_bar(String value) {
        this.bar = value;
    }

    public String jsGet_bar() {
        return this.bar;
    }
}

Our Foo class follows some Rhino specific requirements in order to expose our Java methods to the JavaScript being interpreted. First, we define the JavaScript class name by the getClassName method. Next, we define the constructor that will be called when we do new Foo() in our JavaScript; if the Java class doesn't have a jsConstructor method then one will be derived. From defineClass:

If no method is found that can serve as constructor, a Java constructor will be selected to serve as the JavaScript constructor in the following manner. If the class has only one Java constructor, that constructor is used to define the JavaScript constructor. If the the class has two constructors, one must be the zero-argument constructor (otherwise an EvaluatorException would have already been thrown when the prototype was to be created). In this case the Java constructor with one or more parameters will be used to define the JavaScript constructor. If the class has three or more constructors, an EvaluatorException will be thrown.

Finally, we define getters and setters for our one property by prepending jsGet or jsSet, respectively, to method names. We could also define static methods on our Foo object by prepending jsStaticFunction to method names. Additionally, we can create global JavaScript functions via our Java class by prepending jsFunction to method names (but I'd shy away from this one to prevent polluting the global space).

So, now that we have the Java written, what about the JavaScript? Here's a simple "hello world" type example:


"use strict;"

var foo = new Foo();

foo.bar = "This is a string";

java.lang.System.out.println(foo.bar + " from JavaScript!");

Running this script through our Java program will result in "This is a string from JavaScript!" being printed to stdout via the Java System object. Notice that we don't have to reference our Foo object in any special way, other than instantiating an instance of it. This works just like the standard Array object, except it doesn't have a literal syntax.

Notice, also, how we have used a standard Java library in our JavaScript. Rhino provides a new host object named Packages. The properties of this Packages object are all of the Java libraries available to the JVM under which the program is running. In our script, we use an alias, java, defined by Rhino to access the Packages.java objects. Further information about this, and other scripting additions, can be read in the Scripting Java article.

This isn't as sexy as Node.js. But it does provide a convenient way to include JavaScript into a Java based application that is more flexible than the javax.script interface.

Code,  Java,  JavaScript,  Oracle,  Technology