How to wrap a ValueExpression in EL 1.0 and 2.2
The problem
A new Class was introduced in the EL API 2.2: javax.el.ValueReference. In addition javax.el.ValueExpression now provides the following method:
public ValueReference getValueReference(ELContext context)
{
return null;
}
This makes wrapping of a javax.el.ValueExpression (which is actually a very common thing in projects like MyFaces or OpenWebBeans) not really an easy task if you have to support both EL versions in one project, because there are a lot of unanswered questions:
- Which EL version should I use to compile the code?
- Should the ValueExpression wrapper implement getValueReference()?
- Do I need one wrapper or two?
- How can I even determine the EL version at runtime?
- …
To find a solution for this problem we have to take a closer look at the Java classloading mechanism:
What does the ClassLoader do?
After a lot of experimenting with different classpath configurations, I found out some pretty interesting things. For the experiments I used the following custom ValueExpression implementation:
import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.el.ValueReference;
/**
* @author Jakob Korherr
*/
public class MyValueExpression extends ValueExpression
{
@Override
public ValueReference getValueReference(ELContext context)
{
return new ValueReference("base", "property");
}
@Override
public String toString()
{
return "toString() of MyValueExpression";
}
@Override
public Object getValue(ELContext context)
{
return null;
}
@Override
public void setValue(ELContext context, Object value)
{
}
// ... other methods cut for clarity ...
}
Of course, you must compile this class using EL 2.2, because otherwise javax.el.ValueReference will not be present and you will get a compile error.
Now if you use this custom ValueExpression in any scenario in an EL 2.2 environment, everything will work just fine. However, the interesting part is using this class in an EL 1.0 environment, because here javax.el.ValueReference is NOT available.
At first I thought I will get a NoClassDefFoundError as soon as I use a MyValueExpression instance with EL 1.0. Thus I created the following test:
public static void main(String... args)
{
ValueExpression m = new MyValueExpression();
System.out.println(m);
}
However, it went well and I got “toString() of MyValueExpression” although my class references javax.el.ValueReference, which was not on the classpath.
Then I added private ValueReference valueReference; to MyValueExpression and ran the test again. To my surprise, it worked well again!
After a lot of tests, I found out just 2 scenarios which did not work with EL 1.0:
1) Calling a method on MyValueExpression which creates a new ValueReference or tries to call methods of it. OK, this can obviously not work with EL 1.0. You will get a java.lang.NoClassDefFoundError: javax/el/ValueReference
2) Calling m.getValueReference(). Here you will get the following error, because obviously this method is not available in EL 1.0: java.lang.NoSuchMethodError: javax.el.ValueExpression.getValueReference(Ljavax/el/ELContext;)Ljavax/el/ValueReference;
Solution
Because of the above findings you can just use EL 2.2 as compile dependency in your project and everything will work just fine also with EL 1.0, as long as you:
1) do not call ValueExpression.getValueReference()
2) do not call any method on ValueReference
3) do not create an instance of ValueReference
…outside of the getValueReference() method of your javax.el.ValueExpression implementation. Inside this method, you can do all of the above, because you will only get into this method if you’re using EL 2.2 at runtime!
Thus you can use this wrapper without any problems on EL 1.0:
import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.el.ValueReference;
/**
* @author Jakob Korherr
*/
public class ValueExpressionWrapper extends ValueExpression
{
private ValueExpression wrapped;
public ValueExpressionWrapper(ValueExpression wrapped)
{
this.wrapped = wrapped;
}
@Override
public ValueReference getValueReference(ELContext context)
{
// You will only get here if EL 2.2 is available at runtime.
// Thus you can use ValueReference without any problems!
return wrapped.getValueReference(context);
}
@Override
public Object getValue(ELContext context)
{
return wrapped.getValue(context);
}
@Override
public void setValue(ELContext context, Object value)
{
wrapped.setValue(context, value);
}
// ... other methods cut for clarity ...
}
Using this wrapper in a pure EL 2.2 environment will not cause any problems when calling getValueReference() on the wrapper, because javax.el.ValueReference is provided by EL 2.2 at runtime.
Using this wrapper in a pure EL 1.0 environment will have the effect that getValueReference() is not visible at runtime and thus it will never get called and not cause any problems.
2 Replies to "How to wrap a ValueExpression in EL 1.0 and 2.2"
somebody on Juli 25, 2011
If I remember the the jvm spec correctly than this is not covered by the spec and therefore might not work on certain other jvm/classloader impls (ibm, jrockit, openjdk). Take a look here #7, 12.4.2 http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html#44459
I would be rather defensive using such tricks…
Tweets that mention Jakob Korherr's Blog » How to wrap a ValueExpression in EL 1.0 and 2.2 -- Topsy.com on Dezember 15, 2010
[...] This post was mentioned on Twitter by Matthias Wessendorf and Markus Eisele, Jakob Korherr. Jakob Korherr said: How to wrap a ValueExpression and support both – EL 1.0 and 2.2 – at once: http://goo.gl/R4vtI [...]