Evaluating Spring EL Expressions Against Multiple Contexts

10 Apr

Any descriptor-(e.g. XML)-driven java API begins with field access. This is a given: you have to provide your users some way to tell you what field(s) on a bean your API should act on. But why settle for simple property access? Especially for APIs that perform content generation, it’s hard to ignore the convenience offered by something like JXLS’ Expression Language.

However, implementing a robust EL evaluation solution for your users can be less than trivial. Spring EL, or “SPEL” as they call it, is a good starting point for an out-of-the box solution. It gives the ability to basically break the actual evaluation of an EL statement into a 4 line call, like in this example that I’ve shamelessly copied from the official documentation:

Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);

An ExpressionParser converts a string expression to an Expression object. When you get the value of a parsed expression you do so within an EvaluationContext. This EvaluationContext will be the single object from which all properties and variables within your string of EL are accessed.

Keyword: single.

That’s no good, because most likely an API capable of evaluating EL expressions will need to be able to do so against multiple beans. Now, there are a couple official-ish workarounds for this, but they each fall a bit short (tl;drs can skip the next 4 paragraphs for a solution):

Using a map as your evaluation context, you can enable access of multiple contexts by forcing the user to prefix property access with some name that they register each context under. This works since the standard MapAccessor property accessor packaged with SPEL allows access of map values by name. In the above example, if the user had registered the Nikola Tesla inventor bean under the name “tesla”, and you had placed it into a map with that key, and used the map as the EvaluationContext, you could have accessed the same property as the first example by calling “tesla.name”.

But with this solution, just looking up “name” no longer works. That’s unfortunate. Spring Web Flow accomplishes something a bit closer to ideal: you can specify a field in your Flow XML either qualified with a scope, like “flowScope.thePropertyIWant”, or without a prefix, like “thePropertyIWant”. The expected result is that if you provide a scope qualifier, the property will be pulled from that scope, and if you don’t provide a qualfier, it will search scopes in order until the property is found, or return null if not found at all. This is a good pattern to follow, but not trivial in implementation.

Spring Web Flow requires a couple things to make this work. First, they have to consolidate all their contexts onto a single bean to use as the single EvaluationContext. In this case they use their RequestContext interface which hoards pretty much every tidbit of information of or pertaining to a single request. This is where the qualified property access comes from: for example, if you’re looking for “flowScope.thePropertyIWant”, it works because there’s a map-returning getter method called getFlowScope() chilling on that mega bean which (hopefully) contains your “thePropertyIWant” property. The scope searching part (if you didn’t qualify your property name) is handled by their ScopeSearchingELResolver.

That all works fine for Web Flow which has a static number and set of contexts to search through. But when developing your own API, you’re likely going to have multiple contexts registered by the user, so you won’t be able to hard-code ordered access to these. The most obvious solution would be to use implement the context as a map (like previously described), and wire in a single PropertyAccessor that searches through the contexts in the order that they were registered. However, this only works for Web Flow because they knew that all the searchable contexts were maps. Things get more complex when you have to handle searching within context objects that could be of any class. But that’s what PropertyAccessors were for in the first place, so what you need is the ability to both search through all contexts in order, and also utilize all registered PropertyAccessors in determining if a property exists on each context.

The solution turns out to be to handle all EvaluationContext searching within the EvaluationContext itself. To this end, I created a NamedScopeEvaluationContext. The class and accompanying test case can be acquired as a mavenized example project from our SVN here: http://svn.springjutsu.org/examples/multi-scope-spel-example/

Implementing is straight-forward. Instead of using SimpleEvaluationContext when evaluating SPEL, use the provided NamedScopeEvaluationContext implementation. Register any number of contexts to it by calling namedScopeEvaluationContext.addContext(“contextName”, contextObject); You can either qualify property access like “contextName.propertyName” to ensure access from a specific context, or just use “propertyName”, and it will search within the contexts in the order they were registered, utilizing all of the PropertyAccessors that you registered with the context.

No comments yet

Leave a Reply

Your email address will not be published.