Script Thy Java App
What you would like to do is to provide an API so your customers can implement their own algorithms. But providing a Java API for your application is a huge complication for any non trivial project.
Fortunately there is another possible solution - make your application scriptable. In Java 6 standard support for scripting engines was introduced (JSR 223: Scripting for the JavaTM Platform). There are many possible applications of this technology but this tutorial will focus on a very specific scenario.
Let’s assume you decide to allow your customers to write extensions for your application in any scripting language they like as long as it is supported on Java 6. These scripting languages have to provide a script engine that implements JSR 223 Scripting APIs.
To make this possible you have to provide a way for your application to run custom scripts in any language. Also you have to provide an application internal object model that will be accessible to the custom scripts.
The following example will use a trivial scenario that will prove the point. The object model to be exposed to the scripts is just a simple counter object (Counter.java):
package com.littletutorials.om;
public class Counter {
private int i = 0;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
All the custom scripts will have to provide an implementation of an application specific interface. In our case the scripts will be treated as plugins (Plugin.java):
package com.littletutorials.om;
public interface Plugin {
public void execute();
}
At this point we have everything we need for our customers: an API (the object model - Counter) and a standard interface for their scripts (the Plugin interface). Internally we also need a way to load the scripts and execute them. Of course for a real application you can implement a very sophisticated architecture for script configuration, discovery, initialization and execution. But for this tutorial we are going to implement a very simple solution based on restrictions:
- all the scripts will reside in one folder
- only one script of each type is allowed
- all the scripts will be called “plugin.ext” where the extension is language dependent (js, py, rb, groovy…)
The name is used to identify a plugin script and the extension to identify the scripting engine.
Here is our script plugin manager combined with our application (ScriptPluginApp.java):
package com.littletutorials.scripting;
import java.io.*;
import java.util.*;
import javax.script.*;
import com.littletutorials.om.*;
public class ScriptPluginApp {
private static final String PLUGIN_PREFIX = "plugin";
private static final String VAR_COUNTER = "counter";
private static final String VAR_PLUGIN = "plugin";
// prepare the application object model
Counter counter = new Counter();
// prepare plug-in repository (just a list)
List<Plugin> plugins = new ArrayList<Plugin>();
private List<Plugin> loadPlugins(File pluginDir)
throws ScriptException, FileNotFoundException {
File[] pluginScripts = pluginDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(PLUGIN_PREFIX) ? true : false;
}
});
// create the script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
for (File script: pluginScripts) {
String name = script.getName();
String ext = name.substring(name.indexOf(".") + 1, name.length());
ScriptEngine engine = factory.getEngineByExtension(ext);
// pass the object model
engine.put(VAR_COUNTER, counter);
// evaluate the plug-in script
engine.eval(new java.io.FileReader(script.getAbsolutePath()));
// store the plug-in
com.littletutorials.om.Plugin p = (Plugin) engine.get(VAR_PLUGIN);
plugins.add(p);
}
return plugins;
}
private void executePlugins() {
for (Plugin p: plugins) {
p.execute();
}
// check the effect
System.out.println("Final counter = " + counter.getI());
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.out.println("Wrong number of arguments! " +
args.length + " present, 1 expected.");
System.exit(1);
}
File pluginDir = new File(args[0]);
if (! pluginDir.isDirectory()) {
System.out.println("Plugin folder does not exist: " +
pluginDir.getAbsolutePath());
System.exit(2);
}
ScriptPluginApp app = new ScriptPluginApp();
// load plug-ins
app.loadPlugins(pluginDir);
// execute plug-ins
app.executePlugins();
}
}
Keep in mind this is just a sample application. Don’t take it as a model for exception handling. Follow the exception handling policy shown here.
There are two steps left:
- download the scripting engines and the scripting language implementations
- write the scripts as plugins in as many languages as you want
JSR 223 supported scripted can be downloaded from https://scripting.dev.java.net/ and this page also has links to the sites hosting supported language implementations. The samples in this tutorial require the default JavaScript implementation (no download needed) and also support for Groovy, Jython and JRuby.
- Groovy: http://groovy.codehaus.org/
- Jython: http://jython.sourceforge.net/
- JRuby: http://jruby.sourceforge.net/
Download everything you need, read the instructions and note all the jar files you will need to include in the class path.
Now is time to write the scripts and place them all in a “plugins” folder. Each script will implement the Plugin interface and will create an instance of that implementation. Here they are:
JavaScript: plugin.js
println("Initialize JavaScript plugin");
var plugin = new com.littletutorials.om.Plugin() {
execute: function() {
i = counter.getI();
println("JavaScript initial counter = " + i);
counter.setI(i + 1);
}
};
Groovy: plugin.groovy
import com.littletutorials.om.*;
class GroovyPlugin implements Plugin {
private Counter counter;
GroovyPlugin(Counter c) {
counter = c;
}
void execute() {
int i = counter.getI();
println("Groovy initial counter = " + i);
counter.setI(i + 1);
}
}
println("Initialize Groovy plugin");
plugin = new GroovyPlugin(counter);
Jython: plugin.py
from com.littletutorials.om import *
class JythonPlugin(Plugin):
def __init__(self, counter):
self.counter = counter
def execute(self):
i = counter.getI()
print "Jython initial counter = " + repr(i)
counter.setI(i + 1)
print 'Initialize Jython plugin'
plugin = JythonPlugin(counter)
JRuby: plugin.rb
include_class 'com.littletutorials.om.Counter'
include_class 'com.littletutorials.om.Plugin'
class RubyPlugin
include Plugin
def initialize(c)
@counter = c
end
def execute()
i = @counter.getI()
puts "JRuby initial counter = #{i}\n"
@counter.setI(i + 1)
end
end
puts "Initialize JRuby plugin\n"
$plugin = RubyPlugin.new($counter)
To run the application type this:
java -cp {the big class path with all the jars} com.littletutorials.scripting.ScriptPluginApp {plugins folder}
You should enjoy an output like this:
Initialize Jython plugin Initialize Groovy plugin Initialize JavaScript plugin Initialize JRuby plugin Jython initial counter = 0 Groovy initial counter = 1 JavaScript initial counter = 2 JRuby initial counter = 3 Final counter = 4
I hope this was instructive and fun. If you have a bit of time try to implement the plugin in another scripting language and maybe post it here.
For a tutorial on scripting Scala applications read:
Script Your Scala Application with JRuby, Jython, Groovy and JavaScript





Mother of God, install a CAPTCHA
nice tutorial, thanks
but a counter class should rather have increment() method - using setter (counter.set(i+1)) smells like rotten JavaBean, uh, ugly smell, uh…
cheers
Tomek