viernes, noviembre 18, 2011

What a wicked game you play, To make me feel this way, What a wicked thing to do, To let me dream of you ( Wicked Game - Chris Isaak)



In my previous post I was talking about how to create a service using Jdk service provider. In current post I am going to talk about how to implement your own Jsr223 Script Engine. And as you will see, script engines are a kind of Jdk service providers, so instead of creating your own set of interfaces, we are going to see how to implement a service provider of an already defined service. 

Moreover we are going to see how we can convert a scripting language expression evaluator (in this case Spring EL) to be Jsr-223 compliant.

First of all for those who do not know what is Jsr-223 specification (or Java Scripting API), Jsr-223 is a scripting language independent framework for using script engines from Java code. Thanks of Jsr-223, we only use javax.script package instead of importing language specific classes.

For example using Spring EL scripting language, requires you import in your business class at least two Spring classes ExpressionParser and SpelExpressionParser, and if you want to use another scripting language, more specific vendor classes should be added, so your code is tied to script parser used.

If you use Java Scripting API, two classes are required, ScriptEngineFactory and ScriptEngine, regardless of the language used. And this occurs because ScriptEngine is an interface returned by ScriptEngineFactory service.

Let's start coding:

First class to implement is ScriptEngineFactory and is responsible of creating a new ScriptEngine. It must implement ScriptEngineFactory interface and its implementation will be the service provider of Scripting API.



First part of class is where we are defining what is the name of script language used to get the engine, the version of language (I have chosen Spring version), and version of engine.

Then three special methods:

  • getMethodCallSyntax: returns a String which can be used to invoke a method of a Java object using the syntax of the supported scripting language. In case spEL, the equivalent of calling a Java method from script, is using Types with special T operator.
  • getOutputStream: returns a String that can be used as a statement to display the specified String using the syntax of the supported scripting language. In case of spEL, there is no output stream so an unsupported exception is thrown.
  • getProgram: returns a valid scripting language executable progam with given statements. As previous method, an unsupported exception is thrown.

and finally the most important method getScriptEngine, which must return an implementation of ScriptEngine interface. In this case the Spring EL script engine. Let's see how spEL ScriptEngine is implemented.


First of all class extends from AbstractScriptEngine, which provides implementation for several of the variants of the eval method. Compilable interface is implemented too. ScriptEngines that implements this interface can compile scripts so any further execution of expression does not require any recompilation.

Most important method is eval which should cause immediate script execution using vendor library.

Main purpose of this class is adapt Jsr-223 classes to Spring EL classes. If you look carefully this class, it contains a private inner class that extends CompiledScript. This class is responsible of storing compiled expression, so subsequent calls of eval method requires no reparsing. This class is returned when compile method is called.

As final note spEL language contains some features that go beyond a standard script like registering Java functions into script, referencing Spring Beans, expression templating or navigation through properties of root objects. Of all of these features I think it would be useful having root objects support in Jsr-223 implementation, so I have coded a special attribute named "root". So if you set an attribute to script context with name "root",  attribute's content will be treated as root object.

Before implementing this solution I wondered another possibilities like not implementing root objects, or using Adapter pattern implementing two interfaces ScriptContext and EvaluationContext, but I dismissed because there were duplicated methods (different signature, same behaviour), and from developer's point of view  could be a bit confusing. Moreover this approach implies that caller should know that are running spEL script. Also I planned of using MethodResolver or PropertyResolver but was rejected because spEL would have been extended.

With attribute approach, if you are not using root objects, there are no differences between using Scripting API with Javascript, Groovy or spEL.

And finally service configuration. Create META-INF/services directory into resources folder, and create javax.script.ScriptEngineFactory file; this is the full qualified name of service interface with next content:

And that's all about implementation, with next unit tests you will see clearly how is used:

First one is testing that SpelScriptEngine is returned by ScriptEngineManager.

Not complicated, it is used as you would use for retrieving Javascript engine. Short name is used as engine identifier.

And next unit test contains some assertions about how some Spring EL features are used with ScriptEngine rather than SpelExpressionParser.

Notice how in first test, SpelScriptEngineImpl.ROOT_OBJECT constant is used, so Inventor instance is specified as root object.

Hope you found this post useful.

Feel free to use this code, I have created a git repository so you can download, branch, ... https://github.com/maggandalf/spEL223

Also I have uploaded project as zip file. Download Code.

Music: http://www.youtube.com/watch?v=UAOxCqSxRD0

2 comentarios:

Anónimo dijo...

Nice read. Bad example of singleton pattern, though:


@Override
public ScriptEngineFactory getFactory() {
if (factory == null) {
synchronized (this) {
if (factory == null) {
factory = new SpelScriptEngineFactory();
}
}
}
return factory;
}

The second if-statement will be optimized away, since the optimizer only checks the current thread and does not take multi-threading into account.

Alex dijo...

Thank you very much for reading my blog, I partially agree with your comment. Thanks of you I have seen the error.

This is not the best strategy for implementing singleton pattern; I have an Eclipse plugin that creates automatically patterns, the problem was that when I wrote this example I used an old version of this plugin, newer versions resolves this singleton pattern problem. But see that post http://en.wikipedia.org/wiki/Double-checked_locking and you will see that implementing Singleton pattern is not as easy as one can think.

Anyway thank you very much again because you have made me see that I installed the wrong version of plugin.