Skip to main content

Extending EL Syntax

Posted by jhook on November 27, 2006 at 2:08 AM PST

I've been working a bit on extending EL, but found that the API is missing a few features desired to fully make the language pluggable by third parties.

First is method invocations. Caching reflected data is so difficult to do without causing some kind of memory leak. I really wish JSE had some kind of ClassLoader listener API as a safer alternative to statically scoped data in web containers. Maybe others have more experience in this area? Anyways, performance of method invocation is pretty fast, it takes the same time to do a.b as to do a.getB()-- averaging 0.0016 ms on my laptop for both.

To be safe, I did add some logic for finding the best match if there are multiple methods with the same name. Tis better than nothing :-)

The more important addition is projections: a.{x|x.b} which means iterate over the Collection (a), for each, get (b).

Projections are useful in the presentation tier for binding sets of properties instead of requiring two parts: items="#{a}" var="v", value="#{v.b}". Actually, it may be more performant with the equivalent projection if you were to do: items="#{a.{x|x.b}}" var="v", value="#{v}". Anyways, there's a subtle difference in my implementation of projections as opposed to others.

Here's our model: A company has many departments, which have many employees. If I wanted to generate a list of employee names, I would do something like this:

#{company.departments.{x|x.employees}.{x|x.name}}

The OGNL equivalent is a little lighter in syntax:

company.departments.{employees.{name}}

..but produces a List of a List of Strings:

[['Bob','John','Sally'],['Jason','Marge','Kelly']]

That's not what we wanted! With the EL implementation, you instead get:

['Bob','John','Sally','Jason','Marge','Kelly']

Which seems more correct to me. The main reason is that EL is supposed to represent pointers to instances, so if I take the pointer (ValueExpression) and call the setValue on it-- I would expect that my target (each employee's name) would be set. So why would I return a List of Lists then?

Another cool feature of projections under the EL-API is the use of MethodExpressions. MethodExpressions work perfectly for hooking into Framework APIs as JSF has with generic actions, validators, etc. So lets say your backing model had a collection of Validators, but you didn't want to express each one separately?

<h:inputText value="#{bean.name}" validator="#{bean.validators.{x|x.validate}}"/>

What I'd really like to see added to the EL-API though is pluggable type conversion and method invocation. Right now you can plug in your own property resolution, but the strange thing is that if you take the expression #{a.b} as a value, you can customize the resolution of (b), but if you take it as a method, then you cannot customize the resolution of (b), so the method must always exist on that instance in accordance with the EL implementation's rules.

To correct these two missing pieces, I would like to see two methods added to the ELResolver API which allow near everything to be customized with:

public Object convertType(ELContext ctx, Object in, Class type) public Object invoke(ELContext ctx, Object base, Object method, Object[] params) public MethodInfo getMethodInfo(ELContext ctx, Object base, Object method, Object[] params)

The execution of these methods would work the same as the other ELResolver methods, requiring the propertyResolved to be set to 'true'. It'd be great then to allow customized extensions of Iterable/Collections/Maps such that one could do:

#{company.departments.asc{x|x.employees.{x|x.name}}}

Which would call into the ELResolver chain with the following:

public Object invoke(ctx, Collection, "asc", new Object[] { closure })

This would allow you to treat closures as value-types for some pretty extreme customization with mixing your own Java code into the execution of 'inlined' EL without requiring full API changes within the JSE.

Basically, these are just some future ideas for EL, whenever it becomes it's own JSR. With Facelets and custom EL-API implementations, such as this one, you can just set one property on the compiler and away you go with the added features.

(I have to start releasing stuff again)