<< A reactive and performant Spray + Akka solution to "Playing with concurrency and performance in Java and Node.js" | Home | Is Asynchronous EJB Just a Gimmick? >>

Javascript everywhere

I can think of at least two scenarios when you might need to run the same algorithms in both a Javascript and a Java environment.

  • A Javascript client is running in offline mode and needs to run some business logic or complex validation which the server will need to run again later when say the model is persisted and the server needs to verify the consistent valid state of the model,
  • You have several clients which need access to the same algorithms, for example a Javascript based single page application running in the browser and web service which your business partners use which is deployed in a Java EE application server.

You could build the algorithm twice: once in Java and once in Javascript but that isn't very friendly in terms of maintenance. You could build it once in just Java and make the client call the server to run the algorithm, but that doesn't work in an offline application like you can build using HTML 5 and AngularJS, and it doesn't make for a very responsive client.

So why not build the algorithm just once, using Javascript, and then use the javax.script Java package which first shipped with Java SE 7 and was improved with Java SE 8, in order to execute the algorithm when you need to use it from Java? That is precisely what I asked myself, and so I set about building an example of how to do it.

The first thing I considered was deployment and how the browser could access the Javascript. I didn't want anything with complex build processes which for example copy code maintained in a Node.js environment into a repo like Nexus. Rather I wanted to just drop the Javascript into a Java source folder and be able to use it from there. By having Javascript in a source folder of a Java project, it is automatically deployed inside a web archive so I built a little Servlet capable of serving the Javascript to a client over HTTP. Listing 1 shows the Servlet.

@WebServlet("/ScriptLoader.js")
public class ScriptLoader extends HttpServlet {

  protected void doGet(HttpServletRequest request, ...

    String script = request.getParameter("script");

    response.setContentType("text/javascript");

    Classloader cl = this.getClass().getClassLoader();
    try (InputStream is = cl.getResourceAsStream(script)) {
      int curr = -1;
      while ((curr = is.read()) != -1) {
        response.getOutputStream().write(curr);
      }
    }
  }
}

Listing 1: A Servlet capable of reading Javascript deployed in a WAR

Using the Servlet, you can load the Javascript into the browser, using normal HTML:

    <script type="text/javascript" src="ScriptLoader.js?script=rules.js">

Let's take a look at the Javascript algorithm in Listing 2.

;
(function() {
    var _global = this;

    ///////////////////////////////////////////
    // some javascript that we want to be able
    // to run on the server, but also on the
    // client
    ///////////////////////////////////////////
    function rule419(input) {
        return _(input)
        .filter(function(e){ 
            return e.name === "John"; 
        })
        .value().length == 1 ? "OK" : "Scam";
    };

    ///////////////////////////////////////////
    //create and assemble object for exporting
    ///////////////////////////////////////////
    var maxant = {};
    maxant.rule419 = rule419;

    ///////////////////////////////////////////
    //export module depending upon environment
    ///////////////////////////////////////////
    if (typeof (module) != 'undefined' && module.exports) {
        // Publish as node.js module
        module.exports = maxant;
    } else if (typeof define === 'function' && define.amd) {
        // Publish as AMD module
        define(function() {
            return maxant;
        });
    } else {
        // Publish as global (in browsers and rhino/nashorn)
        var _previousRoot = _global.maxant;

        // **`noConflict()` - (browser only) to reset global 'maxant' var**
        maxant.noConflict = function() {
            _global.maxant = _previousRoot;
            return maxant;
        };

        _global.maxant = maxant;
    }
}).call(this);

Listing 2: An example of an algorithm that we want to run in both Java and Javascript environments

Lines 10-16 are the algorithm that we want to run. The rest of the script is standard Javascript boiler plate when you want to create a script that can be run in all kinds of environments, for example, the browser, Rhino/Nashorn, require (e.g. Node.js) or AMD. Lines 27-35 and lines 39-43 aren't really necessary in the context of this article because we only run the code in the browser or Rhino/Nashorn, and we don't really care about being kind enough to provide a function for making our script non-conflicting if some other script is loaded with the same "namespace" (in this case 'maxant'). The algorithm itself, rule419 isn't really that interesting, but notice how it makes use of lodash when it wraps the input by calling the function _(...). That shows that we are able to make use of other modules loaded into the global space who use script patterns similar to that shown in Listing 2. For demo purposes, I have created an algorithm which simply counts the number of times that 'John' is present in the model. The model is an array of objects with the name attribute. In reality, if we are going to go to the extent of writing an algorithm in just Javascript but making it possible to run it both in Java and Javascript environments, I hope that it would be a damn site more complicated that the algorithm shown here :-)

To make the algorithm useful, I have chosen to run it from a mini- AngularJS application. If you aren't familiar with AngularJS then all you need to know is that you can now call rule419 from Javascript like this:

    var result = maxant.rule419(model);

In AngularJS it is recommended to make modules injectable, which can easily be done as shown in Listing 3, when bootstrapping the application. See here for more details.

'use strict';

// Declare app which depends on views, and components
angular.module('app', [
  'ngRoute',
  'app.view1'
])...

//make rules injectable
.factory('maxant', function(){return window.maxant;})
...

Listing 3: app.js - Making rules injectable in AngularJS

The AngularJS controller can then inject the module as shown in Listing 4.

...
.controller('View1Ctrl', ['$scope', '$http', '$routeParams', 'maxant',
  function($scope, $http, $routeParams, maxant) {

    //create a model
    var model = [
                 {name: 'Ant'}, 
                 {name: 'John'}
                ];
...    
    //execute javascript that can also be executed on the server using Java
    $scope.clientResult = maxant.rule419(model);    
...
}]);

Listing 4: Controller code making use of the injected module in order to run the algorithm in question

Lines 2-3 inject the module named maxant and line 12 then uses the rule419 function and puts the result into scope so that it can be displayed to the user.

The harder part of this exercise is getting the Javascript to be runnable in a Java environment like a Java EE application server or a batch program. This Java 7 link and this Java 8 link give examples of how to run Javascript from Java. I've encapsulated code from those examples to provide a very simple API which Java application code can use to run the Javascript.

//instantiate the engine
Engine engine = new Engine(Arrays.asList("lodash-3.10.0.js", "rules.js"));

//prepare data model - the input to the javascript
Person[] people = new Person[]{new Person("John"), new Person("John")};

//invoke engine
String result = engine.invoke(people, "maxant", "rule419");

//evaluate output
assertEquals("Scam", result);

Listing 5: Calling the Javascript algorithm from Java

Listing 5 starts on line 2 by instantiating the abstraction that I have created (see Listing 6). It simply needs a list of Javascript file names which it should load into the scripting engines' space. The two Javascript files used in this example live in the src/main/resources folder so that Maven packs them into the JAR/WAR that is built. That makes them accessible via the Classloader as shown in Listings 1 and 6. Listing 5 then continues on line 5 by creating some kind of data model which is the input to the algorithm. The Person class has an attribute named name which is private, but nonetheless used by the algorithm. In Java we would use getter/setter methods to access that data, but notice line 13 of Listing 2 just references the attribute by its name and not by a bean-style accessor method. More on that shortly... Line 8 of Listing 5 then executes the algorithm, by telling the engine to invoke rule419 of the maxant module, using people as the input. The result of the Javascript execution which happens under the hood can then be used, for example on line 11. It doesn't have to be a String, it could also be a Java object which the rule returns.

The implementation behind that simple API is shown in Listings 6, 7 and 8. That code can be thought of as library code.

public Engine(List<String> javascriptFilesToLoad) {
  ScriptEngineManager engineManager = new ScriptEngineManager();
  engine = engineManager.getEngineByName("nashorn");
  if (engine == null) {
      //java 7 fallback
      engine = engineManager.getEngineByName("JavaScript");
  }

  //preload all scripts and dependencies given by the caller
  for (String js : javascriptFilesToLoad) {
      load(engine, js);
  }

  referenceToJavascriptJSONInstance = engine.eval("JSON");
}

Listing 6: The implementation behind the neat API, part 1

Listing 6 shows the constructor which first instantiates a ScriptEngineManager from the javax.scripting package and uses it to create the real Javascript engine which executes the algorithm in question, rule419. Notice the fallback used for making the code compatible with Java 7 and 8. I didn't try it but probably just getting the engine by the name "JavaScript" suffices... The constructor does two more things: it loads all the scripts which the caller wants in scope and then gets itself a reference to the Javascript JSON object which is used later for converting JSON to Javascript objects.

private void load(ScriptEngine engine, String scriptName) {
  //fetch script file from classloader (e.g. out of a JAR) 
  //and put it into the engine
  ClassLoader cl = this.getClass().getClassLoader();
  try (InputStream script = cl.getResourceAsStream(scriptName)) {
      engine.eval(new InputStreamReader(script));
  }
}

Listing 7: The implementation behind the neat API, part 2

Listing 7 is called from Listing 6 and shows how the Javascript files are loaded using the Classloader and then sent to the ScriptEngine on line 6. Finally, Listing 8 shows the process of invoking the Javascript.

public <T> T invoke(Object input, String module, 
                              String functionNameToExecute) {

  final Invocable invocable = (Invocable) engine;

  //convert
  ObjectMapper om = new ObjectMapper();
  String dataString = om.writeValueAsString(input);
  Object data = invocable.invokeMethod(
                          referenceToJavascriptJSONInstance, 
                          "parse", dataString);

  //execute
  Object moduleRef = engine.get(module);
  Object result = invocable.invokeMethod(moduleRef, 
                                  functionNameToExecute, data);
  return (T) result;
}

Listing 8: The implementation behind the neat API, part 3

First, lines 6-7 use Jackson to convert the Java model into a JSON string. That JSON string is then parsed inside the ScriptEngine on lines 9-11, which is equivalent to calling JSON.parse(dataString) in Javascript. The conversion from Java to JSON and then Javascript isn't strictly necessary since Rhino/Nashorn know how to use Java objects. But I've chosen to do it that way so that you can skip wrapping all the attributes in accessor code like getXYZ(...). This way, the Javascript can simply access fields like this: input[2].name which gets the third elements attribute called name, rather than say input.get(2).getName(), like you would have to do in plain old Java.

There is one thing to note: as far as I can tell, Rhino and Nashorn are not thread-safe, so you might need to think about that in any implementation that you write. I would probably use a pool of Engine objects, each pre-loaded with the necessary scripts, because it is the instantiation and setup that takes the longest amount of time.

So, to conclude, it is indeed possible to write algorithms once and run them in both Javascript and Java environments, but unlike Java and its "write once, run anywhere" slogan, the algorithms are written in Javascript.

All the code for this demo is available at Github/maxant/javascript-everywhere.

Copyright ©2015, Ant Kutschera

Social Bookmarks :  Add this post to Slashdot    Add this post to Digg    Add this post to Reddit    Add this post to Delicious    Add this post to Stumble it    Add this post to Google    Add this post to Technorati    Add this post to Bloglines    Add this post to Facebook    Add this post to Furl    Add this post to Windows Live    Add this post to Yahoo!



Add a comment Send a TrackBack