Writing a custom EL resolver
EL flexibility can be tested by extending it with custom implicit variables, properties, and method calls. This is possible if we extend the VariableResolver
or PropertyResolver
class, or even better, the ELResolver
class that give us flexibility to reuse the same implementation for different tasks. The following are three simple steps to add custom implicit variables:
Create your own class that extends the ELResolver
class.
Implement the inherited abstract methods.
Add the ELResolver
class in faces-config.xml
.
Next, you will see how to add a custom implicit variable by extending EL based on these steps. In this example, you want to retrieve a collection that contains the ATP singles rankings using EL directly in your JSF page. The variable name used to access the collection will be atp
.
First, you need to create a class that extends the javax.el.ELResolver
class. This is very simple. The code for the ATPVarResolver
class is as follows:
Second, you need to implement six abstract methods:
getValue
: This method is defined in the following manner:
This is the most important method of an ELResolver
class. In the implementation of the getValue
method, you will return the ATP items if the property requested is named atp
. Therefore, the implementation will be as follows:
getType
: This method is defined in the following manner:
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:
setValue
: This method is defined in the following manner:
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:
isReadOnly
: This method is defined in the following manner:
This method returns true
if the variable 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:
getFeatureDescriptors
: This method is defined in the following manner:
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:
getCommonPropertyType
: This method is defined in the following manner:
This method returns the most general type that this resolver accepts. The implementation of the getCommonPropertyType
method is as follows:
Note
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.
The getSinglesRankings
method (that populates the collection) is called from the getValue
method, and is defined in the following ATPSinglesRankings
class:
Third, you register the custom ELResolver
class in faces-config.xml
using the <el-resolver>
tag and specifying the fully qualified name 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
:
Note
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 custom resolver is added in the chain of responsibility. 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. 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.
Done! Next, you can simply output the collection items in a data table, as shown in the following code:
Well, so far so good! Now, our custom EL resolver returns the plain list of ATP rankings. But, what can we do if we need the list items in the reverse order, or to have the items in uppercase, or to obtain a random list? The answer could consist in adapting the preceding EL resolver to this situation.
First, you need to modify the getValue
method. At this moment, it returns List
, but you need to obtain an instance of the ATPSinglesRankings
class. Therefore, modify it as shown in the following code:
Moreover, you need to redefine the CONTENT
constant accordingly as shown in the following line of code:
Next, the ATPSinglesRankings
class can contain a method for each case, as shown in the following code:
Since the EL resolver returns an instance of the ATPSinglesRankings
class in the getValue
method, you can easily call the getSinglesRankings
, getSinglesRankingsReversed
, and getSinglesRankingsUpperCase
methods directly from your EL expressions, as shown in the following code:
The complete applications to demonstrate custom ELResolvers
are available in the code bundle of this chapter and are named ch1_2
and ch1_3
.
In order to develop the last example of writing a custom resolver, let's imagine the following scenario: we want to access the ELContext
object as an implicit object, by writing #{elContext}
instead of #{facesContext.ELContext}
. For this, we can use the knowledge accumulated from the previous two examples to write the following custom resolver:
The complete application is named, ch1_6
. The goal of these three examples was to get you familiar with the main steps of writing a custom resolver. In Chapter 3, JSF Scopes – Lifespan and Use in Managed Beans Communication, you will see how to write a custom resolver for a custom scope.