Integrating Java with Scripting Languages

Here at Carfey, we’ve used Java’s native scripting API in our upcoming job scheduler to allow our clients to deploy new scripts at any time they wish, without redeploying their applications. Combined with our advanced configuration support, we think we’ve found a killer combination of flexibility and power.

The scripting API was outlined in JSR 223 and delivered in Java 6. By default, Java comes packaged with the Rhino implementation of Javascript, so I’ll use that one in all the examples below. There are a variety of languages available, though, and you can choose whichever makes you most productive.

What’s in the API?

The API gives Java programmers a generic framework for invoking scripts and also using Java objects in your scripts. We’ve found this extremely useful in our scheduler, since we can allow users to run all types of scripts within Java, including Python, Javascript and Groovy. Since the scripting API makes setting context and invoking scripts generic, we just need to know how to get a handle on the proper scripting engine to do our work.

public static void main(String[] args) throws Exception {
    scriptTest("JavaScript", "print('hello world')");
}

static void scriptTest(String type, String script) throws ScriptException {
    ScriptEngineManager factory = new ScriptEngineManager();
    ScriptEngine engine = factory.getEngineByName(type);

    // let's get some work done!
    Object scriptResult = engine.eval(script);
    System.out.println("\nResult is: " + scriptResult);
}

Output:

hello world
Result is: null

Really pretty simple. We just grab a script engine from the factory and evaluate our script.

Likewise, setting context within the script is simple. For example, if we wish to calculate the fibonacci sequence in Javascript and we supply the position in the sequence, we could do so by using ScriptEngine.put() combined with eval():

public static long fibonacci(int n) throws ScriptException {
    ScriptEngineManager factory = new ScriptEngineManager();
    ScriptEngine engine = factory.getEngineByName("JavaScript");
    engine.put("n", n);
    String script =
        "function fib(n){ return n < 2 ? n : fib(n-1) + fib(n-2); }\n" +
        "fib(n);"; // this will be returned

    Number result = (Number) engine.eval(script);
    return result.longValue();
}

You'll notice that the last value in the script is returned to our Java caller. This is a bit messy in a case like this. It would be preferable to invoke the desired method directly. Fortunately, this is possible in a generic fashion as well using Invocable:

public static long fibonacciDirect(int n) throws ScriptException,
                                                NoSuchMethodException {

    ScriptEngineManager factory = new ScriptEngineManager();
    ScriptEngine engine = factory.getEngineByName("JavaScript");
    String script = 
        "function fib(n){ return n < 2 ? n : fib(n-1) + fib(n-2); }";

    engine.eval(script); // we must evaluate the script first
    Invocable inv = (Invocable) engine; // most engines will be invocable
    Number result = (Number) inv.invokeFunction("fib", n);
    return result.longValue();
}

If you wish to invoke methods on Java objects, it's as simple as providing the object in the script engine scope and using the scripting language's native syntax to make the method call:

public static void mapPut() throws ScriptException, NoSuchMethodException {
    Map map = new HashMap();
    ScriptEngineManager factory = new ScriptEngineManager();
    ScriptEngine engine = factory.getEngineByName("JavaScript");
    engine.put("map", map);
		
    Object result = engine.eval("map.put('key', 'value')");
    System.out.println("Map: " + map);
}

Output:

Map: {key=value}

Now that's powerful. We now have full interop from Java to the scripting language of our choice without any nasty legwork like we deal with when using JNI or other technologies.

There is plenty more to this Java scripting API. There are issues of scope, implementing interfaces, instantiating Java objects from within a scripting language, etc., but these are advanced features that we don't always care about. With all the powerful features available, the Java scripting API is a natural choice for cases where you need dynamic scripting embedded within your application.

A good primer on the scripting API can be found here: Java Scripting Programmer's Guide

We've used the scripting API along with our job parameter configuration support in our upcoming job scheduler so that our clients can quickly write new jobs without redeploying their applications. If you have suggestions for features or would like more information, post a comment and we will follow up.

Leave a Reply

Your email address will not be published. Required fields are marked *


− three = 3

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>