raganwald
Monday, July 16, 2007
  How to Run Javascript on the JVM in Just Fifteen Minutes
what and why

This post is about executing Javascript inside the JVM without using a browser. Besides the fact that people are talking about running Javascript on the server (again, and again), here’s why my colleagues and I used it on a recent project:

We have some logic that needs to run on the server and on the client, depending on when the application applies it. There is like an incredibly complex form validation involed. Think of a loan application, for example. Zillions of rules like “at least five years at current location or at most three locations in ten years or owns current location for at least one year.” The whole thing forms a big logical expression that needs to be evaluated in such a way that we can report which pieces are missing or do not meet requirements (Declined because income is insufficient and does not state purpose of loan).

There are a couple of ways to handle this. One is to submit the form back to the server for validation. Another is to write everything in Java, but use a sophisticated tool to render the Java into Javascript. Naturally, our team chose a third option, The Rails Way (available for pre-order).

We have a Domain-Specific Language for describing the rules. Business users use the DSL, and another tool writes code from that. We could, in theory, write Java methods for the server and write Javascript for the client. We chose to start with Javascript, and we’ll write Java for the server if running Javascript on the server turns out to be unperformant slow.

In the mean time, we decided that having some Java make a simple function call to a Javascript function and process a simple result was a reasonable first step. As a side benefit, we run all our server-side Javascript unit tests in Java test suites alongside our Java unit tests.

And after some fiddling around, we got Javascript working on the JVM. My bet is that you can get it working too, and it won’t take more than fifteen minutes.

Care to try it?

step zero: the Java Virtual Machine (JVM)

You’ll need JDK 1.5 or 1.6 from Sun. If you already have this, move on to step one. Still reading? You’ll need to do a big install before we go further.

Go to the downloads page and download the latest thing they have on offer with the words “JDK” in it. You won’t need JEE (the framework formerly known as J2EE) for this exercise, but if you know what it is you know enough to decide whether to download it.

Right now, you want JDK 6u2. Go get it and suffer through the installation process.

Step one: Bean Scripting Framework

Java6 has a new framework for running “scripting” languages, and it’s built into Java6. We’re not going to use it today, just because some of you may still need to make stuff work with JDK 1.5 in production. Instead, we’re going to go get the Jakarta Bean Scripting Framework (BSF). You can download it here. We’ll need bsf.jar.

step two: fix gotchas

YMMV, but I found that I couldn’t get BSF working without including the Jakarta Commons-Logging jar. So if you don’t have this floating around, go here and download it. I experimented, and I could ignore everything except commons-logging-1.1.jar. If that was missing, BSF kakked.

step three: Rhino

Since we’re going to run Javascript, we need an interpreter. Rhino to the rescue. Download it. We’ll need js.jar.

step four: keeping things organized

Ready to code? Let’s start with a directory for all of our stuff. Call it hello_javascript. For the sake of keeping thing simple, set up the sub-structure as follows:

hello_javascript
hello_javascript\lib

You may be using a fancy IDE, you may be using a text editor and have to graft your classpaths together with chicken wire. The important thing is that your classpath, besides including all of Java’s required stuff, and your own Java classes, also includes bsf.jar, commons-logging-1.1.jar and js.jar.

We’ll put all three in the lib subdirectory:

hello_javascript\lib\bsf.jar
hello_javascript\lib\commons-logging-1.1.jar
hello_javascript\lib\js.jar

step five: “Hello, Javascript”

Let’s write some Java: create the following subdirectories and put a file called HelloJavascript.java in it:

hello_javascript\com\raganwald\public\HelloJavascript.java

Let’s give it some code:

package com.raganwald.public;

import org.apache.bsf.BSFManager;

public class HelloJavascript {
public static void main (final String[] argv) {
final BSFManager manager = new BSFManager();
final Object jso = manager.eval("javascript", "(java)", 1, 1, "'hello, Javascript'");
System.out.println(jso.toString());
}
}


Run your new Java application. Did you see that? It interpreted some Javascript in the JVM without a browser. Check your watch. Did you need more than a quarter of an hour? I didn’t think so.

You can try more ambitious code:

 manager.eval(
"javascript", "(java)", 1, 1,
"var f = function (what) { return 'hello, ' + what; }; f('Javascript);");


including other files is an exercise left for the reader

I didn’t find an easy way to get Javascript files to include other Javascript files. This isn’t the worst thing in the world, but you certainly don’t want to write anything substantial inside of Java strings. So try experimenting with reading javascript files right off the classpath.

I created a subdirectory called javascript:

hello_javascript\javascript

And you can read Javascript into your strings or Stringbuffers with some fairly simple code, thanks to a utility built into BSF:

import org.apache.bsf.util.IOUtils;

// ...

static String readScript(final String fileName) throws Exception {
final FileReader in = new FileReader(fileName);
return IOUtils.getStringFromReader(in);
}


That reads some script into a string. You can then prepend it to whatever you want to evaluate. Note that if you want to set up some sort of simple checking to make sure that you don’t “include” the same file twice, you will need to write yourself a little framework for that, perhaps using a Set to keep track of what you’ve already loaded.

garbage in, garbage out




Prototype and Scriptaculous are the Javascript libraries that make slick transitions and UI effects easy one-liners. Prototype does more than just make an application look good: it adds Ruby and Smalltalk-like methods for handling Hashes, Arrays, and the DOM.

This book is one of the fastest ways to get up to speed on taking Javascript to the next level.

This is nice, and with a little work you could make a program that reads paths to Javascript files off the commend line and executes them. But to make things really interesting, you want to find a way to get Java data into your JavaScript and do something useful with the results, not just print it as a String.

BSF provides a way to inject objects into the scripting language’s environment, so you could use that facility. When writing automated unit tests for that particular project, I chose a simpler route: I serialized the data into JSON and used that to call a Javascript function directly via BSF:

manager.eval("javascript", "(java)", 1, 1, 
"myJavascriptFunction(" + myJSONString + ");");


This is a really bad idea if your JSON is handed you from an insecure source, such as a public web page calling you back via XMLHttpRequest, but if you trust your source, this works wonderfully.

Now what do you do with the result? If you are generating something esoteric like a Javascript function, I have no idea. In my own case, I return all values as simple trees of Hashes (Javascript objects without any special methods) and Arrays. I convert those into Java trees of Maps and Arrays:

import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.ScriptableObject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// ...

static List unwrapNativeArray (final NativeArray na) {
return new ArrayList<Object> () {{
for (int i = 0; i < na.getLength(); ++i) {
add(unwrapNative(na.get(i, null)));
}
}};
}

static List unwrapPrototypeArray (final ScriptableObject sObj) {
return new ArrayList<Object> () {{
final List<Object> sObjIds = Arrays.asList(sObj.getAllIds());
for (int i = 0; sObjIds.contains(i); ++i) {
add(unwrapNative(sObj.get(i, null)));
}
}};
}

static Map unwrapObject (final ScriptableObject sObj) {
return new HashMap<String, Object> () {{
for (Object id: sObj.getAllIds()) {
put(id.toString(), unwrapNative(sObj.get(id.toString(), null)));
}
}};
}

static Object unwrapNative (final Object obj) {
if (obj instanceof NativeArray) {
return unwrapNativeArray((NativeArray) obj);
}
else if (obj instanceof ScriptableObject) {
final ScriptableObject sObj = (ScriptableObject) obj;
final List<Object> sObjIds = Arrays.asList(sObj.getAllIds());
if (sObjIds.contains("keys")) { // a prototype enumerable/hash
return unwrapObject(sObj);
}
else if (sObjIds.contains("flatten")) { // a prototype enumerable/array
return unwrapPrototypeArray(sObj);
}
else return unwrapObject(sObj);
}
else return obj;
}

Check your watch. Are you still under fifteen minutes? Great!

Labels: ,

 

Comments on “How to Run Javascript on the JVM in Just Fifteen Minutes:
I think you meant to link to Google Web Toolkit, not Google Gears.
 
Nice post!

var f = function (what) { return 'hello, ' + what; }; v('Javascript');

I think you meant to call 'f', not 'v'...

This sounds like fun. At one point, I wrote a simple boolean expression parser in Java, for externalizing complex authorization rules. While it was fun, and I learned a bit, this would have been much nicer. It would certainly have opened up the bounds on what was possible.

So, is any of your server-side JavaScript looking functional? :)
 
Both fixed, thanks!

All of my Javascript looks functional almost all of the time. In this case, it was 100% functional: I was trying to guarantee that exactly the same logic would be evaluated in every possible context.
 
I've heard that functional code can be a good way to describe validation rules, and apparently you think so too, but I'd really love a fuller explanation and some examples.
 
Reg, how about throwing in Mahlee on there so you can write simple multi-threaded JavaScript to reduce latency?

I expect it wouldn't take you more than a day to get it working. I can supply the JavaScript parts and a small test suite.

As for the validation stuff, having the validation procedure as a member of a suitable super class is a good idea. Having the validation rules as instance methods is generally a bad idea (less so in dynamic languages, but only slightly less so).
 
As for the validation stuff, having the validation procedure as a member of a suitable super class is a good idea. Having the validation rules as instance methods is generally a bad idea (less so in dynamic languages, but only slightly less so).

Did I accidentally imply that objects or object-oriented programming is involved in validating rules?

Java requires that all executable code live inside things called "methods," even if they are really global procedures with module scope, so if and when we need to write Java code, there will be methods.

But at the moment, the code exists as Javascript functions which are very different things indeed.
 
Did I accidentally imply that objects or object-oriented programming is involved in validating rules?

I don't think so. I hope it didn't seem that I was implying you'd made that implication.

I was merely agreeing that the validation rules work very well as functions and, for the avoidance of doubt, that if you are using objects then the rules still shouldn't be methods.

I think validation has more in common with constraint propagation than invariance checking (for which strong types are better).

My guess is that you have found out the same thing from your description of the validation that you're doing. I'd be interested to know if my guess is right.
 
I hope it didn't seem that I was implying you'd made that implication.

System Failure! Cause: Stack Limit Exceeded dues to failure to implement Tail Call Optimization and "640K is enough for anybody, 99 nested function calls are enough for anybody" thinking...

I was merely agreeing that the validation rules work very well as functions and, for the avoidance of doubt, that if you are using objects then the rules still shouldn't be methods.

One problem is that certain types of applications, this one included, really give end users the right to create new kinds of things.

In this case, they can come to work in the morning and declare there's a new set of rules, say they decide that families of employees have different loan eligibility.

I am reluctant to imagine this scenario as requiring a new "class" in a language like Java where classes are heavyweight. But I am also reluctant to Greenspun the same thing.

One compromise is to use something like the Strategy pattern, so you can compose an entity out of different parts. The eligibility rules can live inside of an "Employee Family Eligibility" object and be used where appropriate.

This is not just talk. In the actual design, there is a row in a table for each validation expression, and there is a join in the database between applicants and the rules that apply to them.
 




<< Home
Reg Braithwaite


Recent Writing
Homoiconic

Share
rewrite_rails / andand / unfold.rb / string_to_proc.rb / dsl_and_let.rb / comprehension.rb / lazy_lists.rb

Beauty
IS-STRICTLY-EQUIVALENT-TO-A / Spaghetti-Western Coding / Golf is a good program spoiled / Programming conventions as signals / Not all functions should be object methods

The Not So Big Software Design / Writing programs for people to read / Why Why Functional Programming Matters Matters / But Y would I want to do a thing like this?

Work
The single most important thing you must do to improve your programming career / The Naïve Approach to Hiring People / No Disrespect / Take control of your interview / Three tips for getting a job through a recruiter / My favourite interview question

Management
Exception Handling in Software Development / What if powerful languages and idioms only work for small teams? / Bricks / Which theory fits the evidence? / Still failing, still learning / What I’ve learned from failure

Notation
The unary ampersand in Ruby / (1..100).inject(&:+) / The challenge of teaching yourself a programming language / The significance of the meta-circular interpreter / Block-Structured Javascript / Haskell, Ruby and Infinity / Closures and Higher-Order Functions

Opinion
Why Apple is more expensive than Amazon / Why we are the biggest obstacles to our own growth / Is software the documentation of business process mistakes? / We have lost control of the apparatus / What I’ve Learned From Sales I, II, III

Whimsey
The Narcissism of Small Code Differences / Billy Martin’s Technique for Managing his Manager / Three stories about The Tao / Programming Language Stories / Why You Need a Degree to Work For BigCo

History
06/04 / 07/04 / 08/04 / 09/04 / 10/04 / 11/04 / 12/04 / 01/05 / 02/05 / 03/05 / 04/05 / 06/05 / 07/05 / 08/05 / 09/05 / 10/05 / 11/05 / 01/06 / 02/06 / 03/06 / 04/06 / 05/06 / 06/06 / 07/06 / 08/06 / 09/06 / 10/06 / 11/06 / 12/06 / 01/07 / 02/07 / 03/07 / 04/07 / 05/07 / 06/07 / 07/07 / 08/07 / 09/07 / 10/07 / 11/07 / 12/07 / 01/08 / 02/08 / 03/08 / 04/08 / 05/08 / 06/08 / 07/08 /