Skip to main content

The Unified EL from the Trenches

Posted by jhook on March 7, 2006 at 5:34 PM PST

It's always been my plan to write about the new EL-API, but based on some recent blogs and questions online, I thought I'd finally post something.

How does the javax.servlet.jsp.el Relate?

Lets approach this by example with what you know in JSP. With JSP you had:

FunctionMapper fnMapper = ...
VariableResolver varResolver = new VariableResolverImpl(pageContext);
Expression expr = ExpressionEvaluator.parseExpression("${foo.bar}",String.class,fnMapper);
String value = (String) expr.evaluate(varResolver);

With the Unified EL-API, you have:

ELContext ctx = new ELContextImpl(pageContext);
ValueExpression expr = ExpressionFactory.createValueExpression(ctx, "${foo.bar}",String.class);
String value = (String) expr.getValue(ctx);

So what's the difference? Is it just syntactic modifications? A resounding, "No." With the new EL-API, it's all about context!

Problem Areas with Old EL

In hopes of proving some worth, I will take you through an example with c:forEach. In this example our goal is to create a collection of references (Expressions) for evaluation later, like on a succeeding request to the server. We want to display and update some reference from the UI using EL.

<c:forEach items="${bean.list}" var="foo">
    <my:input value="${foo.bar}"/>
</c:forEach>

Here we've created our own pseudo-Struts form tag called 'my:input' that retains reference to '${foo.bar}'. The user posts back to the server and you go evaluate '${foo.bar}'-- but foo is now null? Wait, in the example Java code above, I had EL create me an Expression, why is foo null? Shouldn't it point to the Nth item in the list? It makes sense that I should be able to evaluate these expressions later to have the same meaning as they did in my web page.

To fix this the Unified EL supports what's called, "deferred" expression evaluation. This means that when child expressions, like '${foo.bar}' are created by the new ExpressionFactory, it will check to see if you've already bound reference to 'foo'. Well, we did, we said foo is an item from '${bean.list}'. Here's how we keep those references in the Unified EL.

ExpressionFactory fact = new ExpressionFactory();
ELContext ctx = new ELContextImpl();

ValueExpression items = fact.createValueExpression(ctx, "${bean.list}",..);
ValueExpression var = null;
ValueExpression[] values = new ValueExpression[10];
for (int i = 0; i < 10; i++) { // whatever number of items

    // we want to just have reference to the i'th item
    var = new IndexedValueExpression(items, i);

    // assign the i'th reference to our VariableMapper
    ctx.getVariableMapper().setVariable("foo",var);

    // create an Expression that stores foo = i'th reference
    values[i] = fact.createValueExpression(ctx, "${foo.bar}",...);

    // de-reference the i'th item
    ctx.getVariableMapper().setVariable("foo",null);
}

// grab some coffee and come back later
// basically deferring evaluation

for (int i = 0; i < 10; i++) {
    // each expression is basically the same as ${((bean.list)[i]).bar}
    System.out.println(values[i].getValue(ctx)); // works!
}

With this example, you see that we create expressions that have reference to other expressions. This is so any related expression can be evaluated later, in a composite fashion. It's as magical as Disney.

Flexible Resolution

Probably the coolest thing in the new EL-API is the idea of ELResolvers. For those familiar with JSF, it's a combination of VariableResolvers and PropertyResolvers into one supporting method. This allows implementations and frameworks to take a user's expression like ${user.roles['admin']} and translate that any way they want at runtime by providing custom ELResolvers. Here's the sequence of calls to an ELResolver:

ELResolver resolver = elContext.getELResolver();

// Example: ${user.roles['admin']}

// first need to resolve user-- what is it?
Object user = resolver.getValue(elContext, null, "user");

// user is a security Principal, interesting..
Object roles = resolver.getValue(elContext, user, "roles");

// roles is a special RolesDefinition object...
Object admin = resolver.getValue(elContext, roles, "admin");

// oh, admin is a Role object!
System.out.println(admin);

Well the example shows how the Unified EL can process almost any object graph you want without requiring the EL implementation to know anything about your objects or where the variables come from-- btw, where did 'user' come from?

In implementation, the ELResolver is actually a composite (there we go again on composites). So you could have had a chain of default ELResolvers for Maps, Lists, Array, and Beans-- while also injecting your own ELResolvers for security which specifically handle objects like a 'Principal' or 'RolesDefinition'.

I hope you realize how expressive this can get! I realize this is a really scary thought, but you could use Seam ELResolvers in the same composite as Spring ELResolvers...

Conclusion

While the JEE spec is putting the Unified EL to excellent use with JSF 1.2 and JSP 2.1, you can use the new EL API with your own framework too-- such as using annotations that have EL expression as their values for IoC containers that can (EL)Resolve themselves!