Supporting script languages in your application
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
valuesimply 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.
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).
Script implementation in different threads, for several reasons) and
boolean expressions do not return a
java.lang.Boolean instance, but rather some bizarre
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.