This post is
just a quick overview of request parameters and decoding from the JSF point of
view. Basically, request parameters are parameters attached to a GET/POST
request. In a GET request, they appear in the query string of the request (they
are visible in the browser address bar), and they are known as request query
parameters. Usually, in JSF, they are attached in a GET requests with <f:param>
nested in tags, like <h:link> or <h:button>. The
request parameters in POST requests are not visible in browser address bar,
and, in JSF, they usually represents values of components (e.g. <h:inputText>), but they can be also explicitly submitted by nesting <f:param> in tags as <h:commandLink> or <h:commandButton>. Programmatically
speaking, request parameters are available in all JSF phases via ExternalContext
methods:
·
getRequestParameterMap() - "Return an immutable Map whose keys are the set of request
parameters names included in the current request, and whose values (of type String) are the first (or only) value for each
parameter name returned by the underlying request. The returned Map must implement the entire contract for
an unmodifiable map as described in the JavaDocs for java.util.Map." - source: documentation
·
getRequestParameterValuesMap() - "Return an immutable Map whose keys are the set of request
parameters names included in the current request, and whose values (of type String[]) are all of the values for each parameter
name returned by the underlying request. The returned Map must implement the entire contract for an
unmodifiable map as described in the JavaDocs for java.util.Map." - source: documentation
·
getRequestParameterNames() - "Return an Iterator over the names of all request
parameters included in the current request."
- source: documentation
Now, let's
see an example. First we write a simple GET request via <h:link> and <f:param>:
<h:link>
<f:param name="name"
value="Rafael"/>
<f:param name="surname"
value="Nadal"/>
<f:param name="age"
value="27"/>
<f:param name="title"
value="US Open"/>
<f:param name="titles"
value="Australian Open"/>
<f:param name="titles"
value="Roland Garros"/>
<f:param name="titles" value="Wimbledon"/>
<f:param name="titles"
value="US Open"/>
Save
</h:link>
The GET request
will look like this:
http://localhost:8080/MyApp/faces/index.xhtml?name=Rafael&surname=Nadal&age=27&title=US+Open&titles=Australian+Open&titles=Roland+Garros&titles=Wimbledon&titles=US+Open
And, if we
check the above methods, we will obtain:
-------------getRequestParameterMap------------------
{name=Rafael,
surname=Nadal, age=27, title=US Open, titles=Australian Open}
-----------------------------------------------------
----------getRequestParameterValuesMap---------------
parameter
name: name values: [Rafael]
parameter
name: surname values: [Nadal]
parameter
name: age values: [27]
parameter
name: title values: [US Open]
parameter
name: titles values: [Australian Open,
Roland Garros, Wimbledon, US Open]
-----------------------------------------------------
--------------getRequestParameterNames---------------
name
surname
age
title
titles
-----------------------------------------------------
Or, you can
simply check this Firebug screenshot:
Note
When multiple <f:param>
with the same name
are used in <h:link>
(or, <h:button>)
all of them are found in request parameters and, programmatically, you see them
via getRequestParameterValuesMap().
Further,
let's have a snippet that generates a POST request on submit:
<h:form
id="myFormId">
<h:inputText id="nameId"
value="#{playersBean.name}"/>
<h:inputText id="surnameId"
value="#{playersBean.surname}"/>
<h:inputText id="ageId"
value="#{playersBean.age}"/>
<h:selectOneMenu id="selectId"
value="#{playersBean.title}">
<f:selectItem itemValue="Roland
Garros" itemLabel="Roland Garros" />
<f:selectItem itemValue="US
Open" itemLabel="US Open" />
<f:selectItem itemValue="Australian
Open" itemLabel="Australian Open" />
<f:selectItem
itemValue="Wimbledon" itemLabel="Wimbledon" />
</h:selectOneMenu>
<h:commandButton id="btnId"
value="Save" action="#{playersBean.save()}">
<f:param name="titles"
value="Australian Open"/>
<f:param name="titles"
value="Roland Garros"/>
<f:param name="titles"
value="Wimbledon"/>
<f:param name="titles"
value="US Open"/>
</h:commandButton>
</h:form>
The POST
request will be:
http://localhost:8080/MyApp/faces/index.xhtml
Now, the
request parameters are "hidden", they don't appear in request URL.
But, the above methods will reveal the below output:
----------------getRequestParameterMap---------------
{myFormId=myFormId,
myFormId:nameId=Rafael, myFormId:surnameId=Nadal, myFormId:ageId=27,
myFormId:selectId=US Open, javax.faces.ViewState=5793039802010346474:1784016248394526440,
myFormId:btnId=myFormId:btnId, titles=US Open}
-----------------------------------------------------
----------getRequestParameterValuesMap---------------
parameter
name: myFormId values: [myFormId]
parameter
name: myFormId:nameId values: [Rafael]
parameter
name: myFormId:surnameId values: [Nadal]
parameter
name: myFormId:ageId values: [27]
parameter
name: myFormId:selectId values: [US
Open]
parameter
name: javax.faces.ViewState values:
[5793039802010346474:1784016248394526440]
parameter
name: myFormId:btnId values:
[myFormId:btnId]
parameter
name: titles values: [US Open]
-----------------------------------------------------
--------------getRequestParameterNames---------------
myFormId
myFormId:nameId
myFormId:surnameId
myFormId:ageId
myFormId:selectId
javax.faces.ViewState
myFormId:btnId
titles
-----------------------------------------------------
The POST
request parameters can be seen in the below figure also:
Note
When multiple <f:param>
with the same name are used in <h:commandButton> (or, <h:commandLink>)
only the last one is found in request parameters.
The request
parameters are very useful for JSF especially in Apply Request Values phase. In
this phase, JSF inspects request parameters and extracts the request values for
each component in the component tree. By calling the setSubmittedValue(),
the request values are stored locally in components.
Per example,
let's see how the request query parameters are used to set the values of the
view parameters. Simple code follows:
<h:head>
<f:metadata>
<f:viewParam name="name"/>
<f:viewParam name="surname"/>
<f:viewParam name="title"/>
</f:metadata>
</h:head>
<h:body>
Player: #{name} #{surname}
Title: #{title}
<br/>
<h:link>
<f:param name="name" value="Rafael"/>
<f:param name="surname"
value="Nadal"/>
<f:param name="age"
value="27"/>
<f:param name="title"
value="US Open"/>
<f:param name="titles"
value="Australian Open"/>
<f:param name="titles"
value="Roland Garros"/>
<f:param name="titles"
value="Wimbledon"/>
<f:param name="titles"
value="US Open"/>
Save
</h:link>
...
</h:body>
URL:
http://localhost:8080/MyApp/faces/index.xhtml?name=Rafael&surname=Nadal&age=27&title=US+Open&titles=Australian+Open&titles=Roland+Garros&titles=Wimbledon&titles=US+Open
When the Save
link is clicked, the highlighted <f:param> will supply the values for
the view parameters via the query string. But, what is happening behind the
scene ? Well, behind the <f:viewParam> we have the UIViewParameter
component, which extends UIInput.
This class overrides the UIInput.decode() method as below:
// Mojarra
2.2.9 source code - UIViewParameter
...
enum
PropertyKeys {
name,
submittedValue
}
...
public String getName() {
return (String)
getStateHelper().eval(PropertyKeys.name);
}
public void setName(String name)
{
getStateHelper().put(PropertyKeys.name, name);
}
...
@Override
public Object getSubmittedValue()
{
return
getStateHelper().get(PropertyKeys.submittedValue);
}
@Override
public void
setSubmittedValue(Object submittedValue) {
getStateHelper().put(PropertyKeys.submittedValue,
submittedValue);
}
...
@Override
public void
decode(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
String paramValue =
context.getExternalContext().getRequestParameterMap().get(getName());
// submitted value will stay as previous value
(null on initial request) if a parameter is absent
if (paramValue != null) {
setSubmittedValue(paramValue);
}
rawValue = (String) getSubmittedValue();
setValid(true);
}
...
So, when the
decode()
method is called, it inspect the request parameter map for the value of the
view parameter. Practically, JSF obtain the value of the name
attribute (via getName())
and check the request parameters map for a key equal to this value. This
request value is stored locally via setSubmittedValue() method. Actually, it
is stored in state, and this is why view parameters are stateful. In
subsequence requests, if another request value cannot be obtained from the
request parameters map, the getSubmittedValue() method will return from
state the old value.
The
situation is not the same for UIInput components. First, the decode()
method of UIInput
will call its super implementation, and will finally reach the UIComponentBase.decode():
// Mojarra
2.2.9 source code - UIComponentBase
...
public void
decode(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
String rendererType = getRendererType();
if (rendererType != null) {
Renderer renderer =
this.getRenderer(context);
if (renderer != null) {
renderer.decode(context, this);
}
else {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Can't get Renderer
for type " + rendererType);
}
}
}
}
...
So, JSF
calls the decode()
method of the UIInput
renderer, which in Mojarra is com.sun.faces.renderkit.html_basic.TextRenderer.
But, the decode()
method is found in a super-class of it, named com.sun.faces.renderkit.html_basic.HtmlBasicRenderer:
// Mojarra
2.2.9 source code - HtmlBasicRenderer
...
@Override
public void
decode(FacesContext context, UIComponent component) {
rendererParamsNotNull(context, component);
if (!shouldDecode(component)) {
return;
}
String clientId = decodeBehaviors(context,
component);
if (!(component instanceof UIInput)) {
//
decode needs to be invoked only for components that are
//
instances or subclasses of UIInput.
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"No decoding necessary since the
component {0} is not an instance or a sub class of UIInput", component.getId());
}
return;
}
if (clientId == null) {
clientId = component.getClientId(context);
}
assert(clientId != null);
Map<String, String> requestMap =
context.getExternalContext().getRequestParameterMap();
// Don't overwrite the value unless you have
to!
String newValue = requestMap.get(clientId);
if (newValue != null) {
setSubmittedValue(component, newValue);
if
(logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "new value
after decoding {0}", newValue);
}
}
}
...
So, the value
of an UIInput
is obtained via the clientId, which
is key in the request parameter map, as you saw earlier.
For components
that support multiple values, as UISelectMany, JSF uses the getRequestParameterValuesMap(),
instead of getRequestParameterMap().
Here it is the decode()
for UISelectOne
and UISelectMany
- this overrides the above decode() of UIInput:
// Mojarra
2.2.9 source code - MenuRenderer
...
@Override
public void
decode(FacesContext context, UIComponent component) {
rendererParamsNotNull(context, component);
if (!shouldDecode(component)) {
return;
}
String clientId = decodeBehaviors(context,
component);
if (clientId == null) {
clientId = component.getClientId(context);
}
assert(clientId != null);
// currently we assume the model type to be of
type string or
// convertible to string and localized by the
application.
if (component instanceof UISelectMany) {
Map<String, String[]> requestParameterValuesMap =
context.getExternalContext().getRequestParameterValuesMap();
if
(requestParameterValuesMap.containsKey(clientId)) {
String newValues[] =
requestParameterValuesMap.get(clientId);
setSubmittedValue(component,
newValues);
if (logger.isLoggable(Level.FINE)) {
logger.fine("submitted values
for UISelectMany component " +
component.getId() + " after decoding " + Arrays.toString(newValues));
component.getId() + " after decoding " + Arrays.toString(newValues));
}
} else {
// Use the empty array, not null, to
distinguish
// between an deselected UISelectMany
and a disabled one
setSubmittedValue(component, new
String[0]);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Set empty array
for UISelectMany component " + component.getId() + " after decoding
");
}
}
} else {
// this is a UISelectOne
Map<String, String>
requestParameterMap = context.getExternalContext().getRequestParameterMap();
if
(requestParameterMap.containsKey(clientId)) {
String newValue =
requestParameterMap.get(clientId);
setSubmittedValue(component,
newValue);
if (logger.isLoggable(Level.FINE)) {
logger.fine("submitted value for
UISelectOne component " + component.getId() + " after decoding "
+ newValue);
}
} else {
// there is no value, but this is different
from a null value.
setSubmittedValue(component,
RIConstants.NO_VALUE);
}
}
}
...
In post
"JSF 2.2 Multiple File Upload with HTML5 and AJAX" post, you can see how to alter the decode() method of FileRenderer
for implementing multiple file upload. In OmniFaces 2 source code, you can see
how to override the decode() method in ComponentIdParam component,
for extracting request values from query string.
So, request
parameters and decode()
are strongly related, and is important to know how they work in order to alter
their implementation.
Niciun comentariu :
Trimiteți un comentariu