By Ryan Lubke,
Jennifer Ball,
and Pierre Delisle, August 2005
|
|
|
Those of you who have been developing with JavaServer Pages (JSP)
technology have probably used its simple expression language to access data from
your JSP pages. Since the introduction of this expression
language, other Java-based web technologies have been released and have
become quite popular with the Java web application developer
community.
One of these is the UI component
framework, JavaServer Faces technology.
To support its powerful
features, this framework needed its own expression
language. However, this
expression language presented some problems when used with some JSP
tags. Therefore, the specification writers and expert groups of
the Java webtier technologies collaborated on a new, unified expression
language to help align these technologies by adopting the features
offered by the JavaServer Faces expression language. These
features include the ability to use expressions to set the value of
external object properties as well as get data from them and to invoke
methods. At the same time, the webtier team made the
language pluggable and extensible so that advanced developers can
add custom resolvers able to evaluate expressions not already supported by the
expression language.
This article uses the demo, webtier-sample,
to illustrate the
capabilities of the unified expression language. We recommend
that you download the demo and take a look at the code. You
will need to register at java.net to download the example, but
registration is free of charge. To download the example, go to https://javaserverfaces.dev.java.net/
, follow the instructions for downloading and unpacking the nightly bundle,
and find the example in the samples/webtier-sample
directory. This
example runs on Sun's Java System Application Server PE 9.0,
code-named glassfish. You can download glassfish from https://glassfish.dev.java.net/.
Contents
Expression Language
Beginnings
A Separate EL for
JavaServer Faces Technology
Conflicting
Expression Language Syntax
Features of the Unified EL
Migrating to the Unified EL
Now, It's Your Turn
Expression Language
Beginnings
The unified expression language started out as the JavaServer Pages
Standard Tag Library (JSTL), version 1.0
expression language, originally called SPEL (Simplest
Possible Expression Language). True to its name, this expression
language made life easier
for page authors by providing a simple,
easy-to-use way to access external data objects. For example, the
expression ${customer.name}
is used to look up the customer
JavaBeans component
and call the getName
method to retrieve the value of the name
property. This value is then rendered into the page.
By using this expression
language, page authors could drastically reduce
the amount of scripting in their pages, resulting in greater
productivity, easier maintenance, and a flatter learning curve in terms
of page development. Over the
years, the expression language has
evolved to include more advanced functionality, while still maintaining
its simplicity.
Because of the popularity and success of the expression language in the
community, the JSP 2.0 expert group included it in its specification
while making it more flexible. With the integration of the
expression language into JSP 2.0, not only could page authors use the
expressions inside of standard tag attributes (as they could before),
but they
could also use them in template text as well as with
custom tags. In addition, they could use functions created by a
tag author to invoke static methods not supported by the
current expression language.
A Separate EL for
JavaServer Faces Technology
During the development of JSP 2.0, JavaServer Faces technology was
released. The primary feature of this technology is its
flexible and
extensible UI component model, which includes: a set
of classes that define UI components; a mechanism for handling events
generated by the
components; and a process for converting and validating component
data. It also includes a set of component tags that are used to
map directly
to
individual, stateful, server-side UI component objects. Therefore, this
technology needed an expression language that could be
used to wire events generated by the
components to server-side code, bind the components to server-side data
objects, and register data validators and converters with the
components.
While the expression language included in JSP 2.0 technology offers
many advantages to the web application developer, it clearly didn't
satisfy all the needs of those developing with JavaServer Faces
technology. From the viewpoint of these developers, the primary
limitation of the JSP EL is that its expressions are evaluated
immediately, meaning that the JSP container immediately parses and
resolves the expression when it processes the page and returns a
response.
Immediate evaluation is sufficient when a JavaServer Faces
page is rendered for the first time. However, after the user
enters values into the UI components and submits the page again, those
values are converted, validated, and propagated to server-side data
objects, and component events are processed. In order to convert
data to
the proper type, validate it, store it in an
external object, and process events in an orderly fashion, the
JavaServer Faces life
cycle is split into separate phases to handle each of these
tasks. Therefore, it must be possible to evaluate
expressions at different phases of the life cycle rather than
immediately, as is done in JSP technology.
Another problem with the immediate evaluation of expressions is that it
is done in read-only mode. JavaServer Faces components need to be
able to get
data from server-side objects during the rendering phase and set the
data in the server-side objects during postback.
Finally, JavaServer Faces components need a way to invoke methods on
server-side objects during various stages of the life cycle in order to
validate data and handle component events. JSP functions are not
sufficient because they can only be used to call static methods defined
in a TLD file; they cannot be used to dynamically invoke public methods
on objects.
For all these reasons, a more
powerful expression
language was created that included the following new features:
- Deferred expressions, which can be evaluated at different stages
of the page life cycle
- Expressions that can set data of external objects as well as get
that data
- Method expressions, which can invoke methods that perform event
handling, validation,
and other functions for the JavaServer Faces UI components
Conflicting
Expression Language Syntax
The new expression language worked well for the purposes of
JavaServer Faces component
tags. When using component tags with JSTL tags, however, page
authors would find that the expression language used by the JSTL tags
would conflict with that used by the component tags. Consider the
following example, which shows a forEach
tag containing an inputText
tag, which represents a text field component:
<c:forEach var="book" items="${BooksBean.books}"> ... <h:inputText id="quantity" value="#{book.quantity}" ... /> ... </c:forEach>
Notice that the items
attribute uses JSP EL syntax, whereas the value
attribute of the inputText
component tag uses the JavaServer Faces EL syntax. This
causes two problems:
- As the iteration is processed, the JavaServer Faces
implementation tries to
create a component object with the same quantity ID every time the inputText component tag is
encountered during the iteration. This fails because duplicate
component IDs are
not allowed.
- Even if duplicate IDs were allowed, this iteration still has
problems
because of the fact that the expression referenced by items is
evaluated
immediately. This results in the book
variable being available
only when the component is rendered during the render response phase,
not
during the other phases of the JavaServer Faces life cycle. Therefore,
the
values entered by the user into the components would not be updated to
the external data object.
Because of these incompatibilities, unifying these expression languages
was essential to the success of Java-based web component technologies.
Features of the Unified EL
The new unified EL essentially represents a union of the JSP and
JavaServer Faces expression languages and largely benefits JavaServer
Faces technology. Other
Java-based web component technologies benefit too because the unified
EL is more pluggable and flexible as a result of unifying the
expression languages. In addition to the expression language
features that were already available in the JSP EL, the unified
EL has the following features:
- Deferred evaluation of expressions
- Support for expressions that can set values and expressions that
can invoke methods
- Support for using JSTL iteration tags with deferred expressions
- A pluggable API for resolving expressions
Immediate vs. Deferred Evaluation of Expressions
Immediate evaluation means that the JSP engine evaluates expressions
immediately when a page is rendered. These expressions take the
form ${expr}, and can
only be used within template text or as the value
of a JSP tag attribute that can accept run-time expressions.
The following example shows a tag whose value attribute references an
expression that gets the total price from the the
session-scoped bean named cart:
<fmt:formatNumber value="${cart.total}"/>
The JSP engine evaluates the expression, ${cart.total},
converts it, and
passes the returned value to the tag handler.
Expressions that are evaluated immediately are always read-only value
expressions.
The expression shown above can only get the total price from the cart
bean; it
cannot set the total price.
Deferred evaluation means that the technology using the unified EL
takes over the responsibility of evaluating the expression from the JSP
engine and evaluates the expression at the appropriate time during the
page life cycle. These expressions take the form #{expr} and can only be used by
tag attributes that accept deferred value expressions. In the
case of a JavaServer Faces application, the
controller can evaluate the expression at different phases of the life
cycle depending on how the expression is being used in the page.
The following example shows a JavaServer Faces inputText tag, which
represents a text field component into which a user enters a value. The
inputText tag's value attribute references an
expression that points to the quantity
property of the book bean.
<h:inputText id="quantity" value="#{book.quantity}" />
For an initial request of the page containing this tag, the JavaServer
Faces implementation evaluates the #{book.quantity}
expression during the render response phase of the life cycle. During
this phase, the expression merely accesses the value of quantity
from the book bean, as is
done in immediate evaluation.
For a postback, the implementation evaluates the
expression during apply request values, process validations, and update
model phases, during which the value is retrieved from the request,
validated, and propagated to the book
bean.
Value and Method Expressions
The unified EL supports two kinds of expressions: value expressions and
method expressions. Value
expressions reference data, which might
be in the form of a bean property or other data structure or a literal
value. Method
expressions refer to public methods, which are invoked when the
expression is processed.
Value expressions can be further categorized into rvalue and lvalue
expressions. Rvalue expressions
are those that can read data, but
cannot write it. Lvalue
expressions can both read and write
data. Expressions that use immediate evaluation syntax are always
rvalue expressions. Value expressions that use deferred
evaluation syntax can act as both
rvalue and lvalue expressions. Consider these two value
expressions:
${book.quantity} #{book.quantity}
The first one uses immediate evaluation syntax, whereas the
second one uses deferred evaluation syntax. The first expression
accesses the quantity
property, gets its value, and the value is added to
the response and rendered on the page. The same thing happens
with the second expression if it is evaluated during an initial
request. In this case, both expressions are rvalue expressions.
During a postback, however, the second expression can be used to set
the value of the quantity
property with user input. In this case, the
expression is an lvalue expression.
One of the motivating factors for creating the JavaServer Faces EL was
the need for
support of lvalue expressions because of the multi-phase life cycle.
Now, the unified EL also
supports these
expressions. Lvalue expressions use deferred evaluation
syntax
because they can be evaluated at different stages a page's life cycle,
depending on where the expression is used, as was shown in the previous
section.
Another motivation for creating the JavaServer Faces EL was support of
method
expressions. These expressions are now supported by the
unified EL, which means that they
are not exclusively for use JavaServer Faces applications. But
let's look at an example that uses JavaServer Faces components to see
how method expressions can be utilized in that
technology.
A JavaServer Faces component tag uses
method expressions to invoke methods that perform some processing for
the component that the tag is representing on the page. For the
standard components, these
methods are necessary for
handling events that the components generate and validating component
data. The following example includes two component tags: one that
represents a text field component and another one that represents a
button component.
... <h:inputText id="quantity" value="#{book.quantity}" validator="#{book.validateQuantity}"/> ... <h:commandButton id="update"action="#{book.submit}" /> ...
The validator attribute
of the inputText
component tag references a method,
called validateQuantity,
in the bean, called book.
The TLD that
defines the inputText tag
specifies what signature the method referred
to by the validator
attribute must have. The same is true of the
book.submit method
referenced by the action
attribute of the
commandButton tag. The TLD
specifies that the submit
method must
return a string that specifies with page to navigate to next after the
button represented by the commandButton
tag is clicked.
The validateQuantity
method is invoked during the process validation phase of
the life cycle, whereas the submit
method is invoked during the invoke
application phase of the life cycle. Because a method can be
invoked during different phases of the life cycle, method expressions
must always use the deferred evaluation syntax.
The good news for the page author and the application developer is that
they can continue to use expressions, JavaBeans components, and other
objects the way they always have. The component writer and
application architect do have to do things a little differently now in
order to take advantage of the new unified EL, but the new way of doing
things is actually quite simpler. The section on migrating to the
new unified EL at the end of this document gives you an idea of how
custom components, tag handlers, and TLDs are created now.
Compatibility of Deferred Expressions in Iteration Tags
As described in Conflicting
Expression Language Syntax, using tags with
deferred
expressions inside of a JSTL iteration tag was not possible prior to
the unified EL. To get this use case to work in the unified EL,
the page author must use a deferred expression for the items attribute
of the forEach tag, as
shown in this
piece of the forEach tag
from webtier-sample.jsp:
<c:forEach var="book" items="#{BooksBean.books}"> ... <h:inputText id="quantity" value="#{book.quantity}" ... /> ... </c:forEach>
By using a deferred evaluation expression in the items
attribute
of the forEach tag, the page
author can be sure that the iteration variable books will
be
processed as an expression that refers to the proper book item within
the book
collection that the forEach tag is iterating over.
What this means for the JavaServer Faces input components contained in
the
forEach tag is that whatever values the user enters into
these components
will be updated to the proper object properties at the appropriate
stage of the JavaServer Faces life cycle.
See the webtiersample.jsp
page for the complete forEach
tag example,
which contains expressions using both immediate and deferred evaluation
syntax.
Pluggable Resolver Mechanism
So, now that you know all about using expressions, you might wonder how
they are resolved. In other words, how does the JSP engine or the
JavaServer Faces implementation know that the expression ${customer.name} points to a
name property of a
JavaBeans component called customer?
The unified EL specification defines a generic ELResolver class and a
set of ELResolver
implementations that define how to resolve
various kinds of expressions. The set of standard resolvers
include those that resolve expressions that point to arrays, JavaBeans
components, lists, maps, and resource bundles.
Web application developers can also provide their own custom ELResolver
implementations to resolve expressions that can't be handled by the
standard resolvers and plug these resolvers into their
applications. Examples of pluggable resolvers include those that
handle:
- Connections to resources such as JNDI and JDBC resources
- Implicit objects
- Methods that don't follow JavaBeans conventions
Writing a custom resolver and including it in your application can be
quite simple. In general, it
involves:
- Writing an implementation of ELResolver
- Registering the ELResolver
implementation with the application
- Using expressions in the page
Writing an ELResolver Implementation
To find out how to implement ELResolver,
we'll take a look at the
custom ColorELResolver
class, which resolves expressions that reference
a custom implicit object called ColorImplicitObject.
This
implicit object can do a lot of things with colors, such as take the
following expression and return the hexadecimal code that corresponds
to the color LightGrey, as defined in an external text file:
${Color.LightGrey.hex}
The standard ELResolver
class defines five methods, all of which are
abstract,
meaning that ColorELResolver
and any custom
EL resolver must implement all of these methods, which are:
- getValue(ELContext context,
Object base, Object property)
- getType(ELContext context, Object
base, Object property)
- isReadOnly(ELContext context,
Object base, Object property)
- setValue(ELContext context,
Object base, Object property, Object
value)
- getCommonPropertyType(ELContext
context, Object base)
- getFeatureDescriptors(ELContext
context, Object base)
The purpose of the getValue
method is to resolve the specified property
object on the specified base object and return the result of the
resolution. For example in the expression #{books.quantity},
books is the base object
(a bean in this case) and quantity
is a property of
the books bean. The
returned value is the value of the quantity property.
In the webtiersample.jsp
page, the stylesheet classes use expressions
that reference ColorImplicitObject,
as shown here:
<style type="text/css"> ... .oddRow {background-color: ${Color.LightGrey.hex};} ... </style>
Within a forEach tag that
iterates over the list of books and renders
them into a table, the class
attribute of the HTML tr
tag uses an
rvalue expression to calculate whether the current row is an
odd-numbered row or an even-numbered row:
<tr
class="${(stat.index % 2)
== 0 ? "evenRow" : "oddRow"}">
The oddRow and evenRow variables in the
expression refer to the stylesheet
classes that the page defines. As the expression shows, if the
row is odd-numbered, its cells will have the LightGrey color
obtained from ColorImplicitObject.
The following figure shows the table displayed in the webtier-sample.jsp
page.
Figure 1: Table of Books Displayed on webtier-sample.jsp Page
When the expression "${Color.LightGrey.hex}"
is encountered, the
following happens:
- The getValue method
of ColorELResolver is
called to resolve the base object, Color, to the ColorImplicitObject
instance.
- The getValue method
is called again to resolve the second part of
the expression, LightGrey.
It resolves this part of the
expression by invoking the fromName
method of ColorImplicitObject
with
the argument LightGrey in
order to locate the color LightGrey in a text
file that lists all the available colors.
- The fromName method
creates a map of the names of the colors and
associated ColorRGB
objects, which encapsulate the red, green, and blue
values of each color.
- From the map it creates, fromName
finds the LightGrey color and returns its ColorRGB object.
- Finally, the standard BeanELResolver
implementation (which is part of the unified EL API) is called to
resolve the last part of
the expression, hex,
causing the getHex method
of ColorRGB to be
called. This method returns the hexadecimal value of
LightGrey.
After getValue resolves
each part of the expression, it sets the
propertyResolved flag of
the ELContext to true so
that no other resolvers are consulted to
resolve that part of the expression.
Please refer to the ColorELResolver.java
file to see how the getValue
method is implemented.
The getType method must
return the expected type of the object to which the expression
resolves. This method is primarily used by tools to discover the
acceptable type of the object in situations such as when the tool must
set the type of the object using the setValue method.
If this resolver handles lvalue expressions, then it must
implement the isReadOnly
method to
return false; otherwise
it must return true from
this method.
The setValue method, as
its name suggests, sets the value of the object
referenced by the expression. If the expression points to a
property on a bean, in general, it finds the base object and its
property, gets the method that sets the value for this property, and
invokes this method with the specified value.
The ColorELResolver class
does not support lvalue expressions. Therefore, its setValue method throws PropertyNotWritableException if
it is invoked with a base of ColorImplicitObject
or ColorRGB or with a
property of Color.
In terms of an lvalue expression used by a JavaServer Faces component
tag, the setValue
method is called during the
postback of the page. Assuming the user entered a value into a
text field (for example) and then submitted the page, control is
transferred to the
apply request values phase, during which the setSubmittedValue method
of the component object associated with the text field is called. After
the user input is
validated during the process validations phase, the update model values
phase begins. At this point, the component object supplies a reference
to the ValueExpression
object that represents the lvalue expression. The ValueExpression
object was created during the the initial
request for the page. The JavaServer Faces implementation calls
the ValueExpression
object's setValue
method,
which will in turn call setValue
on the appropriate ELResolver
instances until the
expression is resolved.
The getCommonPropertyType
and getFeatureDescriptors
methods are
intended to assist tools by providing general information about the
objects or properties to which the expressions refer. This
information is provided using JavaBeans
component introspection APIs.
The getFeatureDescriptors
method can return null,
which indicates that the
resolver does not handle the given base object or that the results are
too complex to be handled with this method. In the latter case,
getCommonPropertyType is
used instead.
The getFeatureDescriptors
can also return an iterator over a
collection of FeatureDescriptor
objects, each of which contains
information about one of the base class methods that are relevant to
the resolver. As a resolver writer, you are free to add any
descriptor information you think would be useful to tools. The
getFeatureDescriptors
method of the ColorResolver class returns an
iterator over a collection of FeatureDescriptor objects containing the
names of the available colors.
At the very least, you should set the following two values in each
FeatureDescriptor object:
- The ELResolver.RESOLVABLE_AT_DESIGN_TIME
flag must be set to true or false
- The ELResolver.TYPE
flag
must
be set to the return type of the method in
question
The RESOLVABLE_AT_DESIGN_TIME
flag, as its name
suggests, indicates whether it is safe to resolve this property at
design time. This is important because there are instances in
which it would not be advisable to attempt to resolve the property
at design time, such as when the resolver needs access to a resource
that is only available at runtime.
The getCommonPropertyType
method returns the most general type that the
resolver will accept for a property that it resolves. For
example, a resolver that resolves bean properties might return
Object.class because this
type would be the most general type of a bean
property. If you don't know what the most general type is, you
can return null from this
method.
Registering the Resolver
After creating your resolver, you need to register it with your
application. If you are going to be using the resolver in a JavaServer
Faces application, you use its
application configuration file (usually called faces-config.xml )
to register the resolver.
If you are using the resolver with a JSP
application that does not use any JavaServer Faces tags, you
use a context listener to register the resolver. If your
application contains a mixture of JSP tags and JavaServer Faces tags,
both of which might use expressions that your resolver will handle, you
should register the resolver using the application configuration
file. Because the JavaServer Faces technology-specific resolvers
are added to the JSP container, the JSP tags have access to the
resolver registered by way of the configuration file. Let's go over
using the context listener first.
Registering the Resolver Using a Context Listener
To register your resolver with your JSP application, you need to
implement the contextInitialized
method of ServletContextListener
such
that the EL resolver is added to the JspApplicationContext before
any
requests are received from the client. The following
contextInitialized method
registers the ColorELResolver
class:
public void contextInitialized(ServletContextEvent servletContextEvent) { ServletContext context = servletContextEvent.getServletContext();
JspApplicationContext jspContext = JspFactory.getDefaultFactory().getJspApplicationContext(context); jspContext.addELResolver(new ColorELResolver()); servletContextEvent. getServletContext().log("ColorELResolver registered"); }
This resolver will be added to the chain of EL resolvers represented by
a
CompositeELResolver
implementation in the order in which you register them. This
chain of EL resolver instances is consulted to resolve each
expression. Custom EL resolvers are added to the chain after the
standard ImplicitObjectELResolver
instance included with the JSP container and after any resolvers used
specifically by the JavaServer Faces component tags.
Registering the Resolver Using a JavaServer Faces Application
Configuration File
To register an EL resolver with a JavaServer Faces application, you
merely need to
specify its fully-qualified class name with an el-resolver element in
the application's configuration file, as shown here:
<el-resolver>
webtier-sample.ColorELResolver
</el-resolver>
This resolver is added to the chain of resolvers represented by Faces
EL Resolver for JSP, which is an implementation of CompositeELResolver and
resolves expressions used by JavaServer Faces tags contained in JSP
pages. It is added to the chain of resolvers mentioned in the
previous section directly after the ImplicitObjectELResolver
included with the JSP container. If you
are registering more than one EL resolver, you need to add them to
the configuration file in the order you want them added to the chain of
resolvers, which determines the order in which they are
resolved. Please refer to Figure 2-3 of the JSP 2.1
Specification to see a picture of the chain of resolvers available to a
JSP container.
Using the Expression in the Page
Using the EL resolver is as simple as adding an expression that the
resolver can handle to your page, as shown in webtiersample.jsp, which
uses ColorELResolver to
set the colors of the table cells:
... <style type="text/css"> ... .oddRow { background-color: ${Color.LightGrey.hex}; } ... </style> ... <table> ... <c:forEach items="#{BooksBean.books}" var="book" varStatus="stat"> <tr class="${(stat.index % 2) == 0 ? "evenRow" : "oddRow"}"> <td> <h:outputText id="title" value="#{book.title}"/> </td> ... </c:forEach> </table> ...
Migrating to the Unified
EL
For those of you who already have JSP and JavaServer Faces applications
that use the
previous versions of the EL, you can be assured that your applications
are completely backward compatible, except for a couple rare corner
cases. Aside from these cases, you don't need to make any
changes to run your applications with the latest versions.
If you plan to modify your current applications to use the new EL,
however, there are some things you need to do, which this section
explains.
These changes primarily affect those developing JavaServer Faces
applications. The good news is, though, that the page author and
application developer don't need to do anything differently in
JavaServer Faces 1.2
applications vs. version 1.1 or
earlier applications. The component writer and the application
architect do have some changes to make, which include:
- Updating the TLD defining the custom component tags with the new
version number and the new deferred-value or deferred-method elements
- Editing custom component tag handlers to use the new APIs
- Editing custom component classes to use the new APIs
This section also explains how to modify an application that is not
backward compatible so that it will not generate an error.
Making Changes to a Custom Tag TLD
If you create custom tags, you will need to make
some changes to your TLD files in order to migrate your custom tags to
use the unified EL expressions. The most important one is to
change the jsp-version
element of your TLD files to 2.1. In fact, if your custom tag is
a JavaServer Faces custom tag, this change will require making all of
the other changes outlined in this section and the rest of the
Migrating to the Unified EL section of this document. If your
custom tag is a JSP tag, you also need to make any necessary changes
outlined in the section on backwards compatibility.
If your custom tags are only JSP custom tags,
however, there really isn't anything further you need to do unless you
want to modify your tag attribute definitions to accept deferred
expressions. If you want a tag attribute to accept a deferred
expression instead of a dynamic one (one that uses ${} syntax), simply replace the
rtexprvalue element with
either the deferred-value
or deferred-method
element as explained in this section. If you want the tag
attribute to accept both deferred and dynamic expressions,
keep the rtexprvalue
element and add either the deferred-value
or deferred-method
element. Most likely, you won't find a use for deferred
expressions in a straight JSP application; these kinds of expressions
mainly benefit JavaServer Faces technology because of its multi-phase life
cycle.
For JavaServer Faces custom tags, you must replace
the rtexprvalue
element with either deferred-value
or deferred-method,
depending on
whether the
corresponding property accepts value expressions or method expressions.
The following TLD defines a custom component tag
based on version 1.1:
<taglib> <!-- ============== Tag Library Description Elements ============= --> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <short-name>DemoTaglib</short-name> <tag> <name>simple</name> <tag-class>example.SimpleComponentTag</tag-class> <body-content>JSP</body-content> <attribute> <name>someProperty</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <name>validator</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </tag> </taglib>
The following TLD defines the same custom component tag for
JavaServer Faces technology 1.2 and JSP 2.1:
<taglib><!--============== Tag Library Description Elements ============= --> <tlib-version>1.1</tlib-version> <jsp-version>2.1</jsp-version> <short-name>DemoTaglib</short-name> <tag> <name>simple</name> <tag-class>example.SimpleComponentTag</tag-class> <body-content>JSP</body-content> <attribute> <name>someProperty</name> <required>true</required> <deferred-value> <type>java.lang.String</type> </deferred-value> </attribute> <attribute> <name>validator</name> <required>false</required> <deferred-method> <method-signature> void validate(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object) </method-signature> </deferred-method> </attribute> </tag> </taglib>
Note that the jsp-version
element is now 2.1. This indicates to the JSP 2.1 container whether or
it should try to create a ValueExpression
or MethodExpression
instance and pass it to the tag handler or pass an expression string as
a literal value to the tag handler. If the version number is 2.1, it
does the former.
The other change is that rtexprvalue
has been replaced with the deferred-value element for the someProperty property and with deferred-method for the validator property. The deferred-value element tells
the JSP container that the property accepts deferred value expressions.
The deferred-method
element tells the JSP container that the validator property accepts
method expressions. Therefore, the container creates a ValueExpression object for someProperty and a MethodExpression object for the
validator property and
passes these objects to the tag handler. The type subelement of deferred-value indicates the
type to which the expression must evaluate. The method-signature subelement of
deferred-method specifies the signature that the corresponding
method must have.
Making Changes to the Custom Component Tag Handlers
If you develop custom JavaServer Faces components and you've made the
changes to your TLDs as outlined in the previous section, you also need
to migrate your tag handler classes, including performing the following
tasks:
- Make the class extend UIComponentELTag
instead of UIComponentTag
- Change ValueBinding
to ValueExpression and MethodBinding to MethodExpression
- Simplify the setProperties
method by removing the condition that checks if the expressions is
value-binding enabled. By default, all JavaServer Faces tag
attributes can take either value or method expressions.
Here are the relevant pieces of a tag handler class using the version
1.1 APIs:
public class SimpleComponentTag extends UIComponentTag {
String someProperty; String validator;
public void setSomeProperty(String someProperty) {
this.someProperty = someProperty; }
public void setValidator(String validator) {
this.validator = validator; } ... protected void setProperties(UIComponent component) { super.setProperties(component); if (someProperty != null) { if (isValueReference(someProperty)) { ValueBinding vb = FacesContext.getCurrentInstance(). getApplication().createValueBinding(someProperty); component.setValueBinding("someProperty", vb); } else { component.setProperty(Boolean.valueOf(someProperty)); } } if(validator != null) { if (isValueReference(validator)) { Class args[] = { FacesContext.class, UIComponent.class, Object.class }; MethodBinding vb = FacesContext.getCurrentInstance(). getApplication().createMethodBinding(validator, args); input.setValidator(vb); } else { throw new FacesException("Invalid Expression"); } } } }
And here is the same tag handler class converted to use the 1.2 APIs:
public class SimpleComponentTag extends UIComponentELTag { ValueExpression someProperty; MethodExpression validator; public void setSomeProperty(ValueExpression someProperty) { this.someProperty = someProperty; } public void setValidator(MethodExpression validator) { this.validator = validator; } ... protected void setProperties(UIComponent component) { super.setProperties(component); if (someProperty != null) { if (!someProperty.isLiteralText()) { component.setValueExpression("someProperty", someProperty); } else { component.setSomeProperty(Boolean.valueOf(someProperty.getExpressionString())); } } if(validator != null) { component.addValidator(new MethodExpressionValidator(validator)); } } }
Notice that the new
setProperties method does
not have to do the work
of getting the expression from the Application instance and
converting
a String to a value binding or method binding. Instead, the JSP
container manages
the ValueExpression and MethodExpression objects and
passes them to the
tag handler. Also, setProperties
does not have to check if the property
is enabled to accept a
value expression or method expression because this information
is already in the TLD,
which is shown in the previous section.
Making Changes to the Custom Component Classes
JavaServer Faces 1.1 and earlier versions used the ValueBinding
and
MethodBinding classes to
represent value expressions and method
expressions, respectively. These classes are still available for those
who want to continue using them.
If you want
your components to use the unified EL, however, you need to change all
instances of ValueBinding
to ValueExpression and
all instances of
MethodBinding to MethodExpression. The ValueExpression and
MethodExpression APIs are now part of the unified EL
specification. The following component method shows the 1.1
version of a value-binding enabled component property.
public boolean someProperty() {
if (this.someProperty) { return (this.someProperty); } ValueBinding vb = getValueBinding("someProperty"); if (vb != null) { Boolean value = (Boolean) vb.getValue(getFacesContext()); if (value != null) { return (value.booleanValue()); } else { return false; } } else { return (this.someProperty); } }
The following code shows this method converted to use the new APIs.
public boolean someProperty() {
if (this.someProperty) { return (this.someProperty); } ValueExpression ve = getValueExpression("someProperty"); if (ve != null) { Boolean value = (Boolean) ve.getValue(getFacesContext().getELContext()); if (value !- null) { return (value.booleanValue()); } else { return false; } } else { return (this.someProperty); } }
Backwards Compatibility Issue for JSP Applications
Prior to JSP 2.1, the #{}
syntax was not reserved. Therefore, there might exist JSP pages
based on earlier versions of JSP technology that use the #{ characters where they are
not allowed according to the JSP 2.1 specification. These pages,
when used in a JSP 2.1 application, will generate a translation
error. The following are the two cases in which the use of #{ will produce an error when
the page is translated by a JSP 2.1 container:
- If #{ is used in template text as a String literal
- If #{ is used as a String literal for a tag attribute value when
the tag's TLD specifies a jsp-version of 2.1 or greater
To correct these problems, you need to take one of the following
actions:
- Escape the #{
characters as follows:
some text \#{ some more text
and
<my:tag someAttribute="sometext\#{moretext" />
- Configure your application to allow the #{} characters as a String literal by adding the deferred-syntax-allowed-as-literal
subelement to the jsp-property-group
element and setting it to true:
<jsp-property-group>
<deferred-syntax-allowed-as-literal>true</deferred-syntax-allowed-as-literal>
</jsp-property-group>
- Configure the page to accept the #{ characters as String literals with the deferredSyntaxAllowedAsLiteral
attribute of the page
directive:
<%@page ... deferredSyntaxAllowedAsLiteral="true" %>
Now It's Your Turn
So, you've seen what the new unified expression language has to
offer:
a pluggable, extensible resolver machinery and a way to set data
and
invoke methods from the page in addition to all the powerful features
the JSP expression language already offered you. And the best part is
that it is available now for you to use. All you need to do is go to https://glassfish.dev.java.net/
to get started. If you need help, check out these forums:
We'll be seeing you on java.net!
|
|