 |
The Unified EL from the Trenches
Posted by jhook on March 07, 2006 at 05:34 PM | Comments (15)
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!
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Jacob, thanks for the post. Have I got closer to my understanding of the role of EL in
JSP? Barely ;) The code with the loop that you posted - is it what I am supposed to write
in my submit handler? This is cool stuff, I use the same string that I used on a page and I
read its value. But what value is it? Is it just a reference to
((bean.list)[i]).bar that existed before JSP page was rendered, or does it
contain a value submitted from the request? I am hoping for the latter, but if this value
is actually submitted from the request, when did it it happen? If JSP is regarded as a
render-only tecnhology, then every request is supposed to be a render request. How
${foo.bar} gets updated if it is updated at all?
Please level with me. I want to render a value from some hierarchical bean structure
using EL, and I want to update that bean with the value from a request when I submit an
HTML form. Can I do that with Unified EL and plain JSP (no JSF)? This can easily be done
the Struts way, just name the input elements according to their nesting structure and then
parse request
keys on submit. This can be easily done for plain JSP, the only issue is to distinguish
render request from submit request, but this is not complex too. Any POST request can be
considered submit request, also any request (even GET request) that contains a certain
well-known key.
The second code sample - how does the resolver know where to get the values from? From
session, from request, somewhere else? EL-API is independent from JSP and JSF, but is it
independent from servlet API?
Using EL expressions in Java code is cool, but I was looking for something more down to
Earth :)
Posted by: michael_jouravlev on March 07, 2006 at 06:29 PM
-
Michael, I appreciate that you're asking the right questions:
This is cool stuff, I use the same string that I used on a page and I read its value. But what value is it? Is it just a reference to ((bean.list)[i]).bar that existed before JSP page was rendered, or does it contain a value submitted from the request?Yes, it's a reference to the item in the bean's list. This expression can be stored, matched with the posted parameter of <my:input name="test" ../>.
Can I do that with Unified EL and plain JSP (no JSF)? Yes, your tag would have to set the reference on some backing bean for use later for deferred evaluation
This can easily be done the Struts way, just name the input elements according to their nesting structure and then parse request keys on submit. This can be easily done for plain JSP, the only issue is to distinguish render request from submit request, but this is not complex too. Not so. This would only work if you always knew what the root object was that you were setting on. This can't always be assured-- such as if you wanted to pass variables between tags/pages-- how are you going to know the root object the developer was originally referencing? If this was easy, we wouldn't have needed to rev both JSP and JSF and we would be calling Hans Bergsten a liar :-)
Any POST request can be considered submit request, also any request (even GET request) that contains a certain well-known key.This is true, but you aren't always guaranteed to know the root element-- that would require additional assertions within the MVC framework or object models that would be known to the framework-- this is not a requirement of JSF-- hence it's ability to interact with data from wherever or from multiple locations (Seam/Spring/Sessions/Requests/My own custom framework/etc). Secondly, expressing/exposing your object model's reference as URL parameters in a 1:1 fashion isn't always desirable. WebWork does it this way and has catches in place to hide parts of the model as required by the developer. With JSF, there's a degree of indirection where the component has a value and a identifier. The UI passes values that match the identifier, never exposing the 'magical' value expression created in the page.
The second code sample - how does the resolver know where to get the values from? From session, from request, somewhere else? EL-API is independent from JSP and JSF, but is it independent from servlet API?It's up to the resolvers to pull variables from wherever it wants-- so JSP has built-ins for resolving variables from the request/session/etc. But, those special Servlet/JSP resolvers are part of those specs. Again, the EL-API does not have any dependencies on the servlet API and can be used with any framework, provided they include a single ELResolver that exposes the proper variables to EL.
Posted by: jhook on March 07, 2006 at 09:01 PM
-
Thanks Jacob for your EL implementation.
I've got it from the Tomcat 6 CVS.
I'm using it in my screen scraping framework to read/write variables.
Making my own resolvers was a real pleasure (eg: a resolver that returns beans from a Spring ApplicationContext)
I found it simpler and safer than using Rhino expressions.
You can see the french flight search app that uses it:
http://www.illicotravel.com
Olivier Allouch
Posted by: nopjn on March 08, 2006 at 08:50 AM
-
>> I use the same string that I used on a page and I read its value.
>> But what value is it? Is it just a reference to ((bean.list)[i]).bar
>> that existed before JSP page was rendered, or does it contain
>> a value submitted from the request?
> Yes, it's a reference to the item in the bean's list. This expression
> can be stored, matched with the posted parameter of .
How does the matching process happen? Do I manually read posted parameter from the request
and then compare it to the reference?
>> Can I do that [setting posted value on a server-side bean automatically]
>> with Unified EL and plain JSP (no JSF)?
> Yes, your tag would have to set the reference on some backing bean
> for use later for deferred evaluation
Jacob, I am sorry I am not getting this, apparently my English is not good enough. Deferred
evaluation is ok, I got it, you explained how '${foo.bar}' can refer to the previously
introduced 'foo' reference. What I am asking about is automatic update of backing bean's
property from the request, like Struts automatically populates ActionForm's property using
request values, like JSF automatically populates backing bean's properties. Is this automatic
population possible in JSP 2.1 (not in JSF)?
>> This can easily be done the Struts way, just name the input elements
>> according to their nesting structure and then parse request keys
>> on submit. This can be easily done for plain JSP, the only issue
>> is to distinguish render request from submit request, but this is
>> not complex too.
> Not so. This would only work if you always knew what the root object
> was that you were setting on.
What root object? Are you talking about JSF? I am asking about plain JSP. Afaik, it does
not have "root objects", instead it has scoped variables. Please, try to switch from JSF
mindset to Struts mindset, though this might hurt a little :) In Struts, you may use the
following tag:
<html:text name="myform" property="obj1.nested2.prop3"/>
It is rendered as "input" HTML element:
<input type="text" name="obj1.nested2.prop3" value="...">
When the page is being rendered, Struts tag reads value from
"myform.getObj1().getNested2().getProp3()" and fills out the "value" attribute of "input"
element. When the page is submitted, element name is submitted as request key. Struts parses
request key and sets the corresponding server-side property in the bean hierarchy using value
from the request: "myform.getObj1().getNested2().setProp3(value)". Simple.
Did you want to say that I have to know the form bean I am setting values on? But there are
no form beans in plain JSP. What I have is a nested property in an regular Java bean. Consider
the following syntax:
<html:text property="mybean.obj1.nested2.prop3"/>
There is no form bean in the above example. "mybean" is just a bean in any scope
(explicitly specifying the scope might be beneficial).
My only question is: is the above Struts-like bean population possible in JSP 2.1, yes or
no? If not, why? The only issue is to decide *when* the population process should happen,
because supposedly JSP has only one phase, the render phase, but this issue is an easy
one.
> This can't always be assured-- such as if you wanted to pass variables
> between tags/pages-- how are you going to know the root object
> the developer was originally referencing?
I don't want to pass variables between pages. All I want is to populate server-side bean in
whatever scope with input value from request. Period.
>> Any POST request can be considered submit request, also any request
>> (even GET request) that contains a certain well-known key.
> This is true, but you aren't always guaranteed to know
> the root element
Again, I don't know what root element you are talking about. I am talking about JSP, not
JSF. I am talking about good old scoped variables.
> Secondly, expressing/exposing your object model's reference
> as URL parameters in a 1:1 fashion isn't always desirable.
> WebWork does it this way and has catches in place to hide parts
> of the model as required by the developer. With JSF, there's
> a degree of indirection where the component has a value and
> a identifier. The UI passes values that match the identifier,
> never exposing the 'magical' value expression created in the page.
This might be a valid observation and a more secure approach, but how about letting an
application developer decide what is desirable and what is not? This Struts nested bean
approach works fine in most cases. If a developer wants more protection he'd choose JSF. But
if developer is happy with a simpler model, why deliberately taking it away only because it
"isn't always desirable"? Promoting JSF by holding back JSP features does not seem proper to
me.
Posted by: michael_jouravlev on March 08, 2006 at 09:42 AM
-
Michael, first off, there's no loss in features with JSP. Secondly, I think you're missing this issue:
// parent page
<c:set var="prop" value="#{obj1.nested1}"/>
// some included page/component
<html:text property="#{prop.nested2}"/>
Now-- is the server going to know what 'prop.nested2' is? Nope. With the EL and deferred evaluation, it would know 2 things: you want 'prop.nested2' and prop is actually 'obj1.nested1'.
With your struts example, the 'mybean.obj1.nested2.prop3', 'mybean' would have to be known by Struts, or at least where to get it from-- correct?. So if we are promoting reusable components and pages/content-- always explicitly saying 'mybean.obj1.nested2.prop3' really doesn't do much for promoting reuse does it? It'd be nice to simply pass a reference to a page and have everything 'just' work.
I realize this is confusing :-) But to users, it's even more confusing as to why things didn't 'just' work in the first place. With the new EL, they do 'just' work.
Posted by: jhook on March 08, 2006 at 09:54 AM
-
> Now -- is the server going to know what 'prop.nested2' is? Nope.
> With the EL and deferred evaluation, it would know 2 things: you
> want 'prop.nested2' and prop is actually 'obj1.nested1'.
This I have understood, thanks.
> With your struts example, the 'mybean.obj1.nested2.prop3',
> 'mybean' would have to be known by Struts, or at least where
> to get it from -- correct?. So if we are promoting
> reusable components and pages/content-- always explicitly
> saying 'mybean.obj1.nested2.prop3' really doesn't do much
> for promoting reuse does it?
Why, in many cases this works fine. Every page or included fragment has its own set of
objects that it works with. Of course, components have to share data, but not in the large
scale. Of course, I do appreciate the possibility to use references.
> It'd be nice to simply pass a reference to a page and have
> everything 'just' work.
> I realize this is confusing :-) But to users, it's even more
> confusing as to why things didn't 'just' work in the first place.
> With the new EL, they do 'just' work.
Having references in JSP working like real Java references is a good thing. I am all for
this, I am buying it. But you still have not answered my main question :) With
referencing/dereferencing or without it, can I automatically set server-side beans
(possibly nested) from the request like I am able to do in Struts? If not, why exactly? If
yes, how exactly this process happen?
You tell me that JSP does not lose its features and now has real references. That is great,
that is helpful. I am asking about new features like adding an input phase (postback in
JSF-speak) to JSP. Now if you just explained how these neat EL references can be used as
rvalues in JSP tags to post request data to the server (if it is possible at all), I'd be the
happiest man :)
Posted by: michael_jouravlev on March 08, 2006 at 10:25 AM
-
Another quick question: what container do you recommend to try upcoming JSP 2.1 features?
Posted by: michael_jouravlev on March 08, 2006 at 10:37 AM
-
On the first request, when you page renders, the ValueExpression Objects are available for consumption by custom JSP tags. This is what JSF does, and the next rev of JSTL will do.
For you to have that post-back functionality, you would need to retain that ValueExpression for the succeeding request. This is not automatic, hence the version revs of JSF and JSTL.
In struts-- let me throw this off the top of my head-- I would have DynaActionForms that are session scoped. Upon rendering, the new struts html tags I set the ValueExpressions to that backing bean. It just serves as a container then for references to any model data on the next request. When the next request is made, Struts pulls up the DynaActionForm from the session and loops through the passed parameters and assign them to the pre-set ValueExpressions from the last request. This would be a pretty cool way to have struts update any objects in near any context.
Posted by: jhook on March 08, 2006 at 10:43 AM
-
I'm impartial about the JSP container, the glassfish container is stable and proven for JEE 5 use now.
Posted by: jhook on March 08, 2006 at 10:44 AM
-
> On the first request, when you page renders,
> the ValueExpression Objects are available
> for consumption by custom JSP tags. This is what
> JSF does, and the next rev of JSTL will do.
Check! Got this.
> For you to have that post-back functionality,
> you would need to retain that ValueExpression
> for the succeeding request. This is not automatic,
> hence the version revs of JSF and JSTL.
What does it mean: "hence the version revs of JSF and JSTL." Version revisions? But JSF and
JSTL are different things, are you comparing them? I am lost here again.
What if instead of referencing new fancy ValueExpression objects I want to refer to good
old nested bean in whatever scope? The engine (whatever engine it is) does not need to retain
these values, this is my task as an applicaiton developer. On succeeding request the engine
would just try to pull out this bean. If it is not there, then it is not there, it becames my
problem.
Are you telling me that the only way to use #{} syntax and postback functionality is to use
ValueExpression objects, and someone must manage them? Why cannot I use regular scoped beans?
Or why cannot these ValueExpression objects be created automatically as wrappers for regular
scoped objects?
Your Struts example sounds interesting. But it requires updating Struts for upcoming JSP
2.1. This is unlikely to happen :) Also, I am not asking about making Struts work. I used it
only to make an example. What I am asking for is updating an upcoming JSP spec for existing
servlet/JSP practices. Why do you insist on using ValueExpression objects with #{} syntax? Why
cannot I use regular nested beans? These are not Struts artifacts, they are servlet/JSP
artifacts. Alternatively, you can check instantiated ValueExpression objects and if you cannot
find an appropriate one, you can fall back to a regular nested scoped bean and get/set value
on it.
Returning to current state of JSP 2.1 spec. I can define JSP tag in a TLD as
accepting deferred expression, right? Will JSTL be updated to accept deffered expressions? If
yes, how am I expected to define the values that these expressions refer to? Do I need to
create corresponding ValueExpression objects for them just like you suggested for Struts form?
Apparently, if the objects corresponding to deferred expressions are ValueExpression objects,
it is possible to set them from request automatically. How this process occurs if JSP does not
have explicit input/postback phase?
Jacob, I know that I am getting annoying, but I just want to get the clear understanding on
what is happening with JSP and can it do what I want it to do :) Maybe I need to switch my
mindset too.
Posted by: michael_jouravlev on March 08, 2006 at 11:37 AM
-
Apparently, if the objects corresponding to deferred
expressions are ValueExpression objects, it is possible to set them from
request automatically. How this process occurs if JSP does not have
explicit input/postback phase?
ValueExpressions are primarily used to point to data stored in the
request/session/application scopes. In your case, you must have some
data stored in the session or application scope, since that's the
only way (using only JSP) to make the same piece of data
available both when rendering a JSP page, and when submitting the
followup request (GET or POST) to that same JSP page.
In order to suck data out of the incoming followup request, and push
it into some data stored in request/session/application scope, you need
some code to do it. JSP 2.1 doesn't do this automatically. Of course,
when you do such a thing you're going to need validation, conversion,
and eventually all the other goodies that make up JSF. Therefore, we
decided not to put such code into JSP 2.1 because, to do it right, you'd
end-up replicating JSF.
I suspect, however, that you want something simpler. Maybe you don't
need validation, conversion, events and all the other JSF features. You
just want to slam some followup request data into the
request/session/application scope. Such code would not be hard to
write, and it would be useful to share. Here's one possible sketch.
Define some rules for converting the Strings in the
Enumeration returned from
ServletRequest.getParameterNames() into
#{} expressions. You'll have to be careful,
because, obviously, not everything in that Enumeration should
be an expression.
Write some code to apply the rules from the previous
step to the incoming request. For each expression generated
in this manner, call setValue() on it, passing the
corresponding value you get back from
ServletRequest.{getParameter,getParameterValues}().
Obviously, you'll have to clean this up a bit to handle corner cases.
If you get it done, let us know, it might make sense to put it into the
next JSP spec, if it can be done cleanly.
Ed Burns JSF spec-co-lead
Posted by: edburns on March 09, 2006 at 10:10 AM
-
> ValueExpressions are primarily used to point to data stored
> in the request/session/application scopes. In your case, you
> must have some data stored in the session or application scope,
> since that's the only way (using only JSP) to make the same
> piece of data available both when rendering a JSP page, and when
> submitting the followup request (GET or POST) to that same JSP page.
I prefer to use session-scoped beans these days, so this is not an issue for me. On the other hand, it should be possible to recreate the object tree on submit if objects aren't there. The type of the root object in the tree should be sufficient, should not it?
> In order to suck data out of the incoming followup request,
> and push it into some data stored in request/session/application
> scope, you need some code to do it. JSP 2.1 doesn't do this
> automatically.
>
> Of course, when you do such a thing you're going to need
> validation, conversion, and eventually all the other goodies
> that make up JSF. Therefore, we decided not to put such code
> into JSP 2.1 because, to do it right, you'd end-up replicating JSF.
>
> I suspect, however, that you want something simpler.
> Maybe you don't need validation, conversion, events and all
> the other JSF features. You just want to slam some followup
> request data into the request/session/application scope.
Yes, bingo!
> Such code would not be hard to write, and it would be useful
> to share. Here's one possible sketch.
>
> Define some rules for converting the Strings in the Enumeration
> returned from ServletRequest.getParameterNames() into #{} expressions.
> You'll have to be careful, because, obviously, not everything
> in that Enumeration should be an expression.
>
> Write some code to apply the rules from the previous step
> to the incoming request. For each expression generated in this
> manner, call setValue() on it, passing the corresponding value
> you get back from ServletRequest.{getParameter,getParameterValues}().
Umm... First of all, please clarify usage of #{} expressions in JSP tags. I can use them if tag accepts defferred expression as references to other Java objects. But I cannot submit them back to server using JSP tags, #{} expressions are automatically submitted only from JSF tags. Right?
Therefore, when request corresponding to JSP page comes in, #{} expressions are not processed by new JSP engine at all? They are processed only during rendering. Therefore, I have to parse them out from request... and convert to ValueExpression objects? This sounds like a plan. This is what I wanted the engine was doing for me. Now turns out that I have to implement this functionality myself ;-) Thanks Ed, I'll give it a try and report on the progress.
Posted by: michael_jouravlev on March 09, 2006 at 11:00 AM
-
Now I need to find a way to inform server that a particuar request parameter corresponds to #{} expression. How would I do that? Struts sticks object keypath into "name" attribute, but Struts tags generate full HTML element, so it is simple. All I have in plain JSP is #{} expression in any arbitrary place of HTML template.
Out of my head I see two choices. Before rendering I might be able to check if #{} expression is enclosed into "input" element. Then I would build a map that corresponds "name" attribute of input element to #{} expression. Or should I do this on compile stage and stick a map into the code of resulting servlet?
Another option is to hack "name" attribute in the "input" element during rendering phase to match object keypath. Will I be able to do this if the markup is not generated by me? Anyway, this is some food for thought. Thanks again!
Posted by: michael_jouravlev on March 09, 2006 at 11:13 AM
-
Actually, the route of converting strings to EL expressions totally moots the point of deferred evaluation and how it actually works. You would, in fact, have to serialize the ValueExpression object.
Posted by: jhook on March 09, 2006 at 11:21 AM
-
> Actually, the route of converting strings to EL expressions
> totally moots the point of deferred evaluation and how
> it actually works. You would, in fact, have to serialize
> the ValueExpression object.
I don't feel like serializing VE object. I think I can recreate a VE object coresponding to #{} expression on every submit if I have a type for root object in the keypath. I can instantiate the root object and use reflection to obtain types of child objects to instantiate full tree. BeanUtils or Jonathan Lehr's class is a good starting point. Obviously, newly created object won't correspond to existing ones, so referencing won't work. I understand that referencing is the cornerstone of deferred evaluation of expressions, and #{} is the syntax for deferred expressions. The proposed change might break (not for session- or app-scoped objects) the logical relationship between #{} syntax and deferred evaluation.
Upcoming spec already has inconsistencies like having #{} working for JSF on submit but not for JSP. My proposal eliminates this inconsistency, but creates another one: #{} expressions can be submitted from JSP tags, but they might not participate properly in deferred evaluation of expresions. So, the net balance is not changed ;-) Obviously, I prefer my idea because it allows to submit data from JSP pages with much less hassle than now. I think that if this behavior is clearly understood and documented, JSP users will agree to suffer the consequences of broken deferred evaluation in favor of easier postback.
Posted by: michael_jouravlev on March 09, 2006 at 11:54 AM
|