SOD Inline Scripting

This tutorial takes you through inlining scripts in SOD. Each ingredient type has a corresponding script subsetter that allows customization of SOD's procssing via user written scripts. This can often be a very efficient way to customize your processing recipe beyond the features that come with SOD.

Inline Script Scructure

Inline Scripts in SOD have a simple, yet flexible, structure and are based on the JSR 223 Java Scripting API. Because of this, the particular scripting language is choosen by the user as long as the bindings for that language exist in a way that SOD can find them.

We will start with a simple example of a station subsetting script. This example can be found in the recipies directory of the SOD distribution. For this example, we will create a custom script that accepts stations at high elevation, greater than 1500 meters above sea level. While this is a relatively simple example, it will illustrate a couple of important ideas.

Language Choice

The structure of the subsetter has two parts. The first is an attribute named type that specifies the scripting language to be used. Within the default SOD distribution, there are 2 languages available, javaScript and python. Rhino is a java implementation of javaScript, the well known scripting language usually used in interactive web pages. With Rhino, javaScript can also be used for general purpose scripting. The second, and perhaps more powerful, language is Jython, which is a java implementation of the Python language that runs within the JVM. We will just Jython in this example.

Two other languages that might be of interest to use for scripting subsetters are Groovy and JRuby. While jython, groovy and jruby are probably the three most common, any other language that supports JSR223 can be used.

Script Source

The second part of the scripting subsetters is the body of the tag, which contains the actual script source code. As far as SOD is concerned, this is just text, but this will be passed to the script engine for your language of choice, and so should be valid code for that language. For our example of checking the elevation of the station using Jython, the scripting element would look like this:

<stationScript type="jython">
    if station.getWrapped().getLocation().elevation.value > 1500:
        print "%s  %s\n"%(station.getWrapped().getLocation().elevation, station)
        result = True
    else:
        result = False
</stationScript>

While this code is simple Python code, there are a few SOD specifics that you need to know in order to write a subsetters. The first is that for each subsetter type, several variables are initialized in order to pass the objects relavant to the subsetting. In this example, these variables are "station" and "networkSource". The same class, VelocityStation, is used here as is used in the templates for the printline processes. This has the advantage that there are several useful methods defined on this object, in particular for printing purposes. However, in many cases the raw StationImpl object is more useful when calculations are needed. This is accessed via the getWrapped() method. Reviewing the API documents for the velocity and Impl classes can be very useful in writting scripts. In this example, we simply check the elevation and print out a line if the elevation is above 1500.

The javadocs for each type of script subsetter are below. The variables passed into each script are the same as the arguments to the runScript method. So, for the Station script, two variables are presset in the script, station of type VelocityStation and networkSource of type VelocityNetworkSource.

Return Value

The other direction for data transfer is how the script returns the result to SOD. Unfortunately there are some differences in scripting languages. As a result, SOD checks the return value of the script but if that is not set then SOD pulls the variable named "result" from the script engine after execution. We use this technique in the example. The return value here is a simple boolean, true or false. The other choice is to return a StringTreeLeaf object, which contains both the boolean result and also an optional reason. This is mostly used in cases where you wish to explain failures. The reason will be put into the Fail logger which usually ends up in the file Fail.log.

One small problem can crop up due to the script being inlined in the xml recipe, the text of the script must conform to allowed xml. So for example if we wanted stations close to sea level instead, it would be tempting to do this:

    if station.getWrapped().getLocation().elevation.value < 10:

However, this would not work as the XML processor would think that the less than sign indicated a new XML element. The workaround for this type of issue is to "ampersand encode" the less than sign. The XML processor will expand the &lt; before the script sees it, and it will also be valid XML.

    if station.getWrapped().getLocation().elevation.value &lt; 10: