Scripting Support in Mustang
By Paul Jensen, OCI Partner
February 2006
Introduction to JSR 223
The upcoming JDK 6.0 (Mustang) release does not approach the magnitude of enhancements in JDK 5.0, but does offer several interesting updates. Among these updates is the incorporation of JSR 223 Scripting for the JavaTM Platform. Essentially, this introduces a standard framework to allow scripting language programs to be executed from and have access to the Java platform. It provides many of the capabilities of BSF (Bean Scripting Framework). As with BSF, JSR 223 provides a common interface for integration of a variety of scripting languages. The specification includes examples incorporating PHP and Javascript. Eventual support for a wide range of scripting languages (BeanShell, Jython, Ruby, etc) is expected. At the time of this writing, the author could find support for only JavaScript and Pnuts languages.
The examples in this article are based on J2SE 1.6.0-rc-b69. J2SE 1.6 includes a modified version of the Rhino JavaScript engine integrated via JSR 223 and a command-line scripting shell, jrunscript
, which leverages this support. The shell is of limited use in most applications, but can be a valuable tool in learning and prototyping in Java and arbitrary scripting languages. The jrunscript
syntax is:
Usage: jrunscript [options] [arguments...]
where [options] include:
-classpath, -cp | Specify where to find user class files |
-D= | Set a system property |
-J | Passdirectly to the runtime system |
-l | Use specified scripting language |
-e |
Accessing the Scripting Engine
The fundamental interface for all scripting engines is javax.script.ScriptEngine
. This interface defines a set of methods expected to be supported by all implementations. An implementation is obtained from the ScriptEngineManager
. ScriptEngine
implementations are deployed as Jar files (typically in lib/ext
). Each such jar contains a text file resource META-INF/services/javax.script.ScriptEngineFactory
defining one or more classes implementing ScriptEngineFactory
. The ScriptEngineManager
utilizes these factories to create specific implementations. The ScriptEngineManager
also provides metadata defining the supported script type and version information which is used to resolve requests for a scripting implementation. Obtaining an engine is straightforward:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine jsEngine = manager.getEngineByName("js");
The primary method of the ScriptEngine, eval()
, executes a script directly passed as a String or accessed through a Reader. These two variations may be additionally parameterized with either a Bindings or ScriptContext object.
- Object eval(String script)
- Object eval(Reader reader)
- Object eval(String script, Bindings n)
- Object eval(Reader reader, Bindings n)
- Object eval(String script, ScriptContext context)
- Object eval(Reader reader, ScriptContext context)
A ScriptContext
associates a ScriptEngine with objects in the Java application. Every engine has a default ScriptContext (which can be altered through the API). It groups objects by scope. Each scope has a related Bindings instance containing these objects. Bindings
is simply a Map<String, Object>
which associates scripting variable names with Java instances.
Two scopes are predefined, ScriptContext.GLOBAL_SCOPE
and ScriptContext.ENGINE_SCOPE
. The first contains attributes shared by all Engines created via a given factory, while the second is specific to the engine implementation. The default context and scope is used by the single argument eval() methods. The Scripting API includes many instances of such convenience methods. A binding or context passed to an eval
effectively replaces the default bindings or context for the scope of the method call.
Additional scopes may be defined by contexts specific to an Engine. For example, the HttpScriptContext
used in web-based applications defines request, session, and applications scopes corresponding to Servlet scopes.
The below example summarizes basic usage of the Scripting API:
- // simple script execution
- jsEngine.eval("print('Hello')");
-
- // bind scripting variables and use in a script
- jsEngine.put("a", 3);
- Integer b = 2;
- jsEngine.put("b", b);
- Object retval = jsEngine.eval("c = a + b; print('a + b = ' + c);");
-
- // retrieve the bindings we previously added
- Bindings bindings = jsEngine.getBindings(ScriptContext.ENGINE_SCOPE);
- boolean containsKey = bindings.containsKey("a");
- System.out.println(containsKey);
Results in:
Hello
a + b = 5
true
The checked ScriptException is thrown by methods of the Scripting API. Depending on the Engine, the errant line and column number of the script will be included in the exception.
Compilable and Invocable
Beyond the based ScriptEngine capability, certain implementations may advertise additional capability by implementing the Compilable
or Invocable
interfaces.
An engine implementing Compilable
supports the re-execution of intermediate (e.g. compiled) code from previous compilations. The compile() method returns a CompiledScript
based on either a String or Reader. The CompiledScript
may be executed repeatedly via its eval() method.
- Compilable eng = (Compilable)jsEngine;
- CompiledScript script = eng.compile("function test() { return 10; }; test();");
- Object compiledOutput = script.eval();
- System.out.println(compiledOutput); // prints 10.0
An engine implementing Invocable
supports the calling of individual functions scripted in the engine via the invoke()
method. Arguments and return values are converted between script and Java types based on the engine implementation. Below is a simple example:
- jsEngine.eval("function test(val) { return val; }");
- Invocable jsInvoke = (Invocable)jsEngine;
- Object invoke = jsInvoke.invoke("test", new Object[] {111.0});
- System.out.println(invoke); // prints 111.0
Scripts may be used to implement arbitrary interfaces without direct dependence on the given interface. The following example illustrates definition of a run()
method which is bound as the implementation of a Runnable
interface at runtime based only on method signature. Below, the invoke()
and run()
calls invoke the same underlying scripted method.
- jsEngine.eval("function run() { print ('running..'); }");
- Object invoke2 = jsInvoke.invoke("run",null); // prints running...
-
- Runnable r = jsInvoke.getInterface(Runnable.class);
- r.run(); // prints running...
-
Summary
While not part of J2SE, the JSR 223 specification describes an optional Web Scripting Framework which includes an HttpScriptContext integrated with an HttpScriptServlet located a proposed javax.script.http package. In fact, PHP and JavaScript integration for web page generation was an initial impetus for the JSR. This scope was expanded over time to be the much more general solution described above. We can anticipate that future versions of J2EE will introduce the originally envisioned Web Scripting Framework.
So what is gained through JSR 223 and how can it be put to use? Certainly scripting is a convenient and efficient means to learn and prototype. In the context of production Java applications, it can also be useful to provide scriptable extension points for sophisticated users. It can also be useful for altering the state of running applications for debugging and monitoring where alternatives such as JMX are not available or sufficient. Naturally, security concerns will dictate reasonable usage for these scenarios.
However, the above only scratches the surface of the capabilities of scripting languages. In general, they offer greater flexibility and power than statically-typed languages like Java. Scripting languages continue to gain in popularity and capability, providing significant productivity gains over Java only solutions (witness Ruby on Rails). JSR 223 defines an integration standard, establishing a foundation for leveraging the strengths of both approaches.
References
- [1] Pnuts engine
http://pnuts.org/snapshot/latest/extensions/jsr223/doc/ - [2] Choosing a Java scripting language: Round two
http://www.javaworld.com/javaworld/jw-03-2005/jw-0314-scripting_p.html - [3] "Groovy Scripting for Java," Mark Volkmann (Object Computing, 2003):
/resources/publications/sett/february-2004-groovy-scripting-for-java - [4] BeanShell tutorial
http://www.beanshell.org/manual/contents.html - [5] JSR-241: The Groovy Programming Language
http://www.jcp.org/en/jsr/detail?id=241