My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

sâmbătă, 7 februarie 2015

JSF 2.2 - Quick Overview of Request Parameters and JSF Decoding

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));
         }
     } 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

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors