Skip to main content

Supporting script languages in your application

Posted by mister__m on April 24, 2005 at 1:13 PM PDT

It's been over a month since we added generic script support to genesis, but it was such an interesting experience I've actually considered writing this entry part of my TODO list. I've finally managed to do it, so let's go to the main point before you all fall asleep.

Since its inception, genesis has been using JXPath in order to allow users to express conditions that controlls when fields are made visible or not, enabled/disabled, cleared, when a method should be called by the framework etc. JXPath is not exactly a script language, but rather a XPath implementation that works on JavaBeans as well. However, since I was particularly familiar with it, expressions written in it were clear and concise, it is possible to invoke arbritary Java methods from expressions and to export custom function libraries, it was very easy to use in our internal framework code, quite performant and had support for compiling expressions it was picked up as the way to express conditions.

Although we did like JXPath a lot, there were some reasons we thought it was worth supporting other options:

  • XPath is tricky. For example, testing a boolean property using code such as value simply tests for the existence of a property named value, but not if it is actually true. You have to do value = true()
    for this, which is usually not what developers will try at first unless they really know these XPath specifics. It was never our intention that developers had to know this kind of things.
  • People shouldn't be forced to learn JXPath to use genesis.
  • People already know other script languages, such as JavaScript or EL, for example. It would be good if they could apply this knowledge while using genesis.

Since we decided we wanted to support new script languages, we had to come up with a design for that. Basically, instead of using an available abstraction as is, we wanted to make sure there was no performance degradation for our JXPath users and we were already aware of standardization efforts on this area - JSR 223 to be precise - that were not (and still are not) available for use. This motivated us to come up with a more generic design, in package net.java.dev.genesis.script. The general-purpose classes in this package are:

  • Script: the main abstraction of the package, represents a script language implementation. It has methods that allows one to obtain an instance of ScriptContext (explained below) and to compile expressions.
  • ScriptFactory: responsible for creating concrete implementations of Script
  • ScriptContext: this interface allows one to perform context, script language specific tasks, such as obtaining the value of a variable, declaring variables, registering custom function libraries and evaluate expressions
  • ScriptExpression: represents a compiled expression that can be evaluated in a context

The reason I said these are general-purpose classes is because there are other classes in this package that are more specific to genesis "business" itself, such as abstract support for genesis functions, but I won't explain these here though.

Now, back to the main plot, the next step was to add a JXPath implementation and that is what package net.java.dev.genesis.script.jxpath does. It wasn't hard at all since most concepts we abstracted are directly supported by JXPath in a quite direct way (such as generating a compiled expression for example).

The next step was to add support for two popular script languages: BeanShell and JavaScript. Although we could use our own infrastructure to build that, we were looking for a more general way of abstracting the usage of such languages and decided to use an already existing solution for that. BSF, the Bean Scripting Framework, an Apache Jakarta Project, proved to be a good way of plugging these languages into our infrastructure with minimum effort. Our general adapter package for BSF is net.java.dev.genesis.script.bsf and BeanShell simply worked perfectly with just that adapter. JavaScript required a minor tweak to work - class net.java.dev.genesis.script.bsf.javascript.BSFJavaScriptEngine - since it had problems with evaluating expressions in "child" threads (basically, its context is thread-specific, but genesis calls methods in a Script implementation in different threads, for several reasons) and boolean expressions do not return a java.lang.Boolean instance, but rather some bizarre org.mozilla.javascript.NativeBoolean object. Since this adapter class ended up being rather small anyway (71-line long, counting all blank lines and with the LGPL header taking up 18 lines), it was certainly worth it.

After we had support for at least 3 languages - we are still not sure how many BSF supported languages will simply work out of the box -, we still wanted to support another language web developers were familiar with. EL, the Expression Language first introduced by JSTL and later adopted by JSP, was chosen. Since EL as defined in package javax.servlet.jsp.el is just a SPI, we needed an implementation of it in order to support EL in genesis. The Apache Jakarta project provides an implementation of EL called Commons EL which we used in our solution.

As we started working in an implementation, we were amazed to find out most things we needed were defined in the SPI and not in the implementation itself. It is really rare for "young" specifications to have this level of support at the "standard" level, so I must congratulate the EL folks on that. Implementing package net.java.dev.genesis.script.el was mainly a simple task, except for the fact the SPI does not allow expressions to be pre-compiled without a FunctionMapper instance - which is not available when we do it - and it does not allow instance methods to be exported as functions. We got around the first limitation by using a specific method in Commons EL (as shown in ELExpression), but the second one would require EL specification changes and is a limitation our users will have to face for now.

So, it turns out supporting script languages in an application or framework is not a very hard task and it took us a few days to walk through the whole process, which included selecting the languages to support, study their documentation, look for an existing abstraction, modify our build process and a bunch of other tasks. In the future, when JSR-223 becomes final and an open-source implementation becomes available, it will be even easier to support script languages.

Related Topics >>