ELContext,
ELResolver,
FunctionMapper
and VariableMapper
are NOT specific to JSF!
An expression ${foo.buzz.bizz} describes
a base object foo with a property buzz that
has a property bizz.
The EL (Expression Language) or (Unified Expression Language) is just a
set of conventions used to write expressions like, #{foo}, #{foo.buzz},#{foo.buzz.bizz}, etc. Obviously, these strings
doesn't mean anything as long as a parser will not break them down to pieces
and interpret them via a set of rules. So, an application (named parser) uses a
grammar defined by EL to check if a string is well-formed and represents an expression (the EL unit of work).
Basically, EL recognizes two kind of expressions: value expressions and method
expressions (invoke a method) . Fortunately, it isn't necessary to write an
EL parser, because there are at least two open source implementations available
under the relatively permissive Apache License: el-api.jar jasper-el.jar and juel-xxx.jar.
But, in order to use these libraries in a new context (environment), we need to provide a javax.el.ELContext
(the context/environment required to evaluate the expression). By its nature,
EL can be used in any environment (e.g. in/out Java EE environment). So, these value/method
expressions (e.g. #{foo.buzz})
and the environment should be somehow connected or linked or, with other words,
they should be mutually aware. This is
the main job of ELContext! ELContext is
responsible to provide the mechanism through which all relevant context for
creating or evaluating an expression is specified. How ELContext is created depends on the
underlying technology. For example:
·
in JSP we have JspContext.getELContext()
·
in JSF (and other technologies) the ELContext creation is
controlled via ELContextListener
so that applications and frameworks can ensure their own context objects are
attached to any newly created ELContext.
For example, if you check the source code of Oracle Mojarra (main JSF
implementation), then you can notice the com.sun.faces.el.ELContextListenerImpl, which
is the implementation of the contract defined by javax.el.ELContextListener.
Now, the ELContext
is materialized via three objects, as follows (in Mojarra, the concrete
implementation of ELContext
is com.sun.faces.el.ELContextImpl):
·
javax.el.ELResolver
- This is responsible to resolve the parts (variables and properties) of an
expression. For an expression #{foo.buzz}
the ELResolver
would need to resolve foo
to an object and then decide what buzz represented on that object. For example, in JSF, the
ELResolver is
used to interact with the model (managed beans, implicit objects, etc).
·
javax.el.FunctionMapper
- This is responsible to resolve functions of the form ${someprefix:somefunction()} to a public, static method
encapsulated by the type java.lang.reflect.Method.
OmniFaces has a nice implementation of a custom FunctionMapper,
but this is another discussion.
·
javax.el.VariableMapper
- Basically, the variable mapper is a simple class required only to store ValueExpression
instances in a map. Via VariableMapper
elements operating in different contexts that uses the same ELContext can share
value expressions. For example, JSP can set an expression and JSF to use it.
Now, let's focus on ELResolver.
As its name suggest, the ELResolver
is responsible to resolve expressions, or, with other words to turn expressions
into Object
references. For example, when a ELResolver
gets an expression of type ${foo.buzz.bizz} (describes
a base object foo with
a property buzz that
has a property bizz),
it will have to resolve:
base: null
property: foo
base: foo (as
resolved above)
property: buzz
base: buzz (as
resolved above)
property:
bizz
So, the resolver is asked to resolve the expression conforming to javax.el API (not
related to JSF) which provides classes for resolving several common types
(arrays, beans, maps and lists - JSF novices tend to believe that EL
expressions and ELResolver
are useful only in conjunction with beans, which is not true). These are
further aggregated via javax.el.CompositeELResolver
(this is another topic).
Each time an expression needs to be resolved, JSF will call the default
expression language resolver implementation. Each value expression is evaluated behind the scenes by the getValue() method.
When the <el-resolver>
tag is present, the pointed custom resolver is added in the chain of responsibility (next to the
existing resolvers). The EL implementation manages a chain of resolver
instances for different types of expression elements. For each part of an
expression, EL will traverse the chain
until it finds a resolver capable to resolve that part - a single expression is
totally resolved by a single resolver or after a "team work" of
several resolvers. The resolver capable of dealing with that part will pass
true to the setPropertyResolved()
method; this method acts as a flag at the ELContext level. Furthermore, EL
implementation checks, after each resolver call, the value of this flag via the
getPropertyResolved()
method. When the flag is true,
EL implementation will repeat the process for the next part of the expression.
So, the chain of responsibility
contains a set of ELResolvers
which are applied sequentially for each part of an expression until one of the
resolvers is capable to resolve that part. For example, Oracle Mojarra comes
among others with the following ELResolvers:
·
com.sun.faces.el.ImplicitObjectELResolver
- as its name suggest, this resolver is capable to deal with JSF implicit
objects (check them here).
For example, the FacesContext
can be programmatically accessed as:
FacesContext fc =
FacesContext.getCurrentInstance();
But, FacesContext is available in page also via
the implicit object #{facesContext}
which is an EL expression. Being an EL expression means that it must be
resolved by an ELResolver,
and, in this case, that resolver is ImplicitObjectELResolver. Let's see the relevant code for
accomplish this:
// Mojarra
2.2.9 source code - com.sun.faces.el.ImplicitObjectELResolver
public class
ImplicitObjectELResolver extends ELResolver implements ELConstants{
// a map that holds the implicit objects names
protected static final Map<String,
Integer> IMPLICIT_OBJECTS;
static {
// the strings that can be used in
expressions to indicate a certain implicit
// object, e.g. #{facesContext}
String[] implictNames = new String[]{
"application",
"applicationScope", "cc",
"component", "cookie",
"facesContext",
...
};
...
}
public Object getValue(ELContext
context,Object base, Object property)
throws ELException {
//
there is no base needed (e.g. #{foo.facesContext} is wrong !
// if foo was resolved that it become base,
and now this resolver will return
if
(base != null) {
return null;
}
if
(property == null) {
String message =
MessageUtils.getExceptionMessageString
(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"property");
throw new PropertyNotFoundException(message);
}
Integer
index = IMPLICIT_OBJECTS.get(property.toString());
if
(index == null) {
return null;
} else
{
// the FacesContext and ExternalContext
are needed to resolve most of implicit
// objects, so they are obtained here to
avoid code duplication
FacesContext facesContext =(FacesContext)context.getContext(FacesContext.class);
ExternalContext
extCtx = facesContext.getExternalContext();
switch
(index) {
...
case FACES_CONTEXT:
context.setPropertyResolved(true);
return facesContext;
...
default:
return null;
}
}
}
// other methods
}
So, the most important method of an ELResolver is the getValue(). Basically, this method is
responsible:
- to identify the part of the expression that it can resolve;
- invoke the ELContext#setPropertyResolved()
to signal that it can resolve or not the part of the expression;
- if it can resolve the part of the expression, then resolve it
(practically, perform the programmatic steps necessary to obtain the result
expected by the expression author)!
·
com.sun.faces.el.ManagedBeanELResolver
- as its name suggest, this is responsible to resolve the part of an expression
that points a managed bean. So, in an expression as #{foo.buzz}, this resolver expects to
identify the foo as
the name of a managed bean. Of course, it doesn't know that, so it has to perform
certain searches (e.g. try to find the bean name in request/session/application
scope - these are basically maps, and managed beans names are keys in these
maps).
·
com.sun.faces.el.ResourceELResolver
- this is responsible to resolve expression that indicates resources (e.g. CSS,
JS, images) via expressions of type #{resource['library:resource']} and #{resource['resource']}.
Basically, it will identify the resource and uses the ResourceHandler#createResource() to
create it and return its request path.
There are more ELResolvers,
but I believe you understood the idea (as an exercise, you can also check com.sun.faces.el.FacesResourceBundleELResolver).
Practically, in JSF, most expressions are linked to managed beans, implicit
objects and resources, but this is not mandatory. Per example, in a dummy case,
we may want to link the
#{atp} expression to a static
method:
public class
ATPSinglesRankings {
public static List<String>
getSinglesRankings(){
List<String> atp_ranking= new
ArrayList<>();
atp_ranking.add("1 Nadal, Rafael
(ESP)");
...
return atp_ranking;
}
}
For this, our custom ELResolver
(named ATPVarResolver)
will override among others the getValue()
method.
1 private static final String PLAYERS =
"atp";
2 ...
3 @Override
4 public Object getValue(ELContext ctx, Object
base, Object property) {
5
6 if ((base == null) &&
property.equals(PLAYERS)) {
7 ctx.setPropertyResolved(true);
8 List<String> values = ATPSinglesRankings.getSinglesRankings();
9 return values;
10 }
11 return null;
12 }
Let's inspect this code:
Line 6: The #{atp}
expression doesn't need a base, so we ensure that the base is null - with other
words, we don't accept something like, #{foo.atp} (tip: null base should always be resolved as a value
expression). The atp string acts as a
"reserved" word... think that only #{atp} expression can be resolved by this ELResolver, not #{ATP}, or #{aTp}, or #{ATPlayers}, etc.
Basically, JSF will apply the chain of available resolvers until it hits the ATPVarResolver... no
other resolver will be able to link the #{atp} expression with the static getSinglesRankings().
So, notice that the atp
variable is matched in a simple manner, via String#equals() method. The third argument passed by JSF in getValue() should be exactly the atp string. In a
expression of type #{foo.buzz},
first the property will be foo,
afterwards, buzz.
For the foo ,
the ELResolver
class acts as a VariableResolver,
while for the buzz
(since there is the base foo)
the ELResolver
class acts as a PropertyResolver.
Line 7: If the base is null
and the third argument of getValue() is atp then this resolver is capable to resolve this
expression. It must signal this to ELContext, so the chain of resolvers can be interrupted
for this part of the expression, and the next part of the expression (if any;
none in our case) will enter in the chain. For signaling that the #{atp} expression can
be resolved by this resolver we need to invoke the ELContext#setPropertyResolved(true). As long
as this flag is false,
JSF will try to apply the next resolver from chain.
Line 8-9: So, is time for the ATPVarResolver to accomplish its main goal, which consist
in resolving the #{atp}
expression, or, with other words, to turn expressions into Object references - in
this case, to turn #{atp}
into List<String>.
Practically, at this point, we invoke the getSinglesRankings() method and return the
result. Generally speaking, here we place the code necessary to link that part
of the expression with an object (constant, collection, primitive, object,
managed bean, resource, etc). This code is basically the programmatic approach
of accessing that artifact.
Line 11: Think that other expressions may pass through this resolver,
not just #{atp}.
Obviously, the ATPVarResolver
will not be able to resolve them. In such cases we return null.
The getValue()
is the most important method of an ELResolver, but there
a few more methods to discuss:
·
public
abstract Class<?> getType(ELContext context,
Object base,Object property)
This method identifies the most general acceptable type for our
property. The scope of this method is to determine if a call of the setValue() method is
safe without causing a ClassCastException
to be thrown. Since we return a collection, we can say that the general
acceptable type is List.
The implementation of the getType()
method is as follows:
private final
Class<?> CONTENT = List.class;
...
@Override
public
Class<?> getType(ELContext ctx, Object base, Object property) {
if (base != null)
return null;
}
if (property == null) {
throw new PropertyNotFoundException(error_message);
}
if ((base == null) &&
property.equals(PLAYERS)) {
ctx.setPropertyResolved(true);
return CONTENT;
}
return null;
}
In our case, the most general acceptable type for #{atp} is java.util.List. This
means that if setValue()
method will allow us to set a value to this expression then that value must be
of type List.
·
public
abstract void setValue(ELContext context, Object base,
Object
property, Object value)
This method tries to set the value for a given property and base. For
read-only variables, such as atp,
you need to throw an exception of type PropertyNotWritableException. The implementation of the setValue() method is
as follows:
private static
final String PLAYERS = "atp";
...
@Override
public void
setValue(ELContext ctx, Object base, Object property, Object value) {
if (base != null) {
return;
}
ctx.setPropertyResolved(false);
if (property == null) {
throw new PropertyNotFoundException(error_message);
}
if (PLAYERS.equals(property)) {
// In case of a writable base-property you
have to check here the type of the
// passed values, and if they are ok, then
set them. Since we have a read-only
// variable, there is no need to do that,
we just signal that this is a read-only
// variable via an exception. Normally,
before calling setValue(), the developer
// should check if this is a read-only resolver
via isReadOnly() method.
throw new PropertyNotWritableException(error_message);
}
}
·
public
abstract boolean isReadOnly(ELContext context,
Object base,
Object property)
This method returns true
if the resolver is read-only and false
otherwise. Since the atp
variable is read-only, the implementation is obvious. This method is directly
related to the setValue()
method, meaning that it signals whether it is safe or not to call the setValue() method
without getting PropertyNotWritableException
as a response. The implementation of the isReadOnly() method is as follows:
@Override
public boolean
isReadOnly(ELContext ctx, Object base, Object property) {
return true;
}
·
public
abstract Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext
context, Object base)
This method returns a set of information about the variables or
properties that can be resolved (commonly it is used by a design time tool (for
example, JDeveloper has such a tool) to allow code completion of expressions).
In this case, you can return null.
The implementation of the getFeatureDescriptors()
method is as follows:
@Override
public
Iterator<FeatureDescriptor>
getFeatureDescriptors(ELContext
ctx, Object base) {
return null;
}
·
public
abstract Class<?> getCommonPropertyType(ELContext context, Object base)
This method returns the most general type that this resolver accepts.
The implementation of the getCommonPropertyType()
method is as follows:
@Override
public
Class<?> getCommonPropertyType(ELContext ctx, Object base) {
if (base != null) {
return null;
}
return String.class;
}
So, our custom ELResolver
is ready, but we have to instruct JSF to use it. By default, JSF will try to
resolve the #{atp}
expression via the current chain of resolvers, and it not aware of ATPVarResolver
resolver. In order to add our resolver in the chain of resolvers, we need to
add the below configuration in faces-config.xml:
<application>
<el-resolver>beans.ATPVarResolver</el-resolver>
</application>
When JSF will find this configuration it will add the ATPVarResolver
resolver next to the existing ones. So, when #{atp} need to be resolved, JSF will
"traverse" the chain of resolvers, and, at some moment, it will find
the ATPVarResolver as
the proper resolver for the atp
variable.
So, do not forget to register your custom ELResolver class in faces-config.xml using the <el-resolver>
tag and specifying the fully qualified name (FQN) of the corresponding class.
In other words, you add the ELResolver
class in the chain of responsibility, which represents the pattern used by JSF
to deal with ELResolvers.
Done! Next, you can simply output the collection items in a data table,
as shown in
the following code:
<h:dataTable
id="atpTableId" value="#{atp}" var="t">
<h:column>
#{t}
</h:column>
</h:dataTable>
!Pay attention to cases when an expression can be
correctly evaluated by multiple resolvers. For example, if you had in the
application a managed bean of like below:
@Named
@RequestScoped
public class
Atp {
...
}
Then #{atp}
can be resolved to an instance of Atp bean also. Is like you are creating an instance of Atp. So, depending on
which resolver will resolve first the #{atp} you can obtain the list of players or something
like beans.Atp@b98ce3,
which represents an instance of Atp
class exposed via toString()
method.
How do you know if the ELResolver
class acts as a VariableResolver
class (these two classes are deprecated in JSF 2.2) or as a PropertyResolver
class? The answer lies in the first part of the expression (known as the base
argument), which in our case is null
(the base is before the first dot or the square bracket, while property is
after this dot or the square bracket). When the base is null, the ELResolver class acts as a VariableResolver class;
otherwise, it acts as a PropertyResolver
class. In Mojarra, you can study the source code of com.sun.faces.el.VariableResolverImpl
and com.sun.faces.el.PropertyResolverImpl.
The complete application is available here.
Niciun comentariu :
Trimiteți un comentariu