1

I have a problem with a JSF project.

  • GlassFish Server 3.1.2
  • Mojarra 2.1.6

I'm trying to show a table containing the request header fields. Therefor I've written this managed bean:

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

@ManagedBean
@RequestScoped
public class RequestHeader extends LinkedHashMap<String, List<String>> {
    private List<String> keys;

    @PostConstruct
    public void init() {
        final HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
        keys = Collections.list(request.getHeaderNames());
        for (final String key : keys) {
            final List<String> value = Collections.list(request.getHeaders(key));
            final List<String> oldValue = get(key);
            if (oldValue == null) {
                put(key, value);
            } else {
                oldValue.addAll(value);
            }
        }
    }

    public List<String> keys() {
        return keys;
    }
}

This is my JSF page:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:c="http://java.sun.com/jsp/jstl/core">
    <h:head>
        <title>HTTP request headers</title>
    </h:head>
    <h:body>
        <h:dataTable value="#{requestHeader.keys()}" var="k" border="1">
            <f:facet name="header">HTTP request headers</f:facet>
            <h:column>
                <f:facet name="header">Key</f:facet>
                <h:outputText value="#{k}" />
            </h:column>
            <h:column>
                <f:facet name="header">Value</f:facet>
                <!-- This forEach seems to be ignored. -->
                <c:forEach items="#{requestHeader[k]}" var="v">
                    <h:outputText value="#{v}" /><br />
                </c:forEach>
            </h:column>
        </h:dataTable>
    </h:body>
</html>

Instead of having the values in the second column of the table, the cells are empty:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>HTTP request headers</title>
    </head>
    <body>
        <table border="1">
            <thead>
                <tr><th colspan="2" scope="colgroup">HTTP request headers</th></tr>
                <tr>
                    <th scope="col">Key</th>
                    <th scope="col">Value</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>user-agent</td>
                    <td></td>
                </tr>
                <tr>
                    <td>host</td>
                    <td></td>
                </tr>
                <tr>
                    <td>accept</td>
                    <td></td>
                </tr>
                <tr>
                    <td>accept-language</td>
                    <td></td>
                </tr>
                <tr>
                    <td>accept-encoding</td>
                    <td></td>
                </tr>
                <tr>
                    <td>cache-control</td>
                    <td></td>
                </tr>
                <tr>
                    <td>connection</td>
                    <td></td>
                </tr>
            </tbody>
        </table>
    </body>
</html>

I did several tests. <h:outputText value="#{requestHeader[k]}" /> and c:forEach on other lists will work.

Why won't it work this way?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
xehpuk
  • 7,814
  • 3
  • 30
  • 54

2 Answers2

6

Taghandlers like JSTL tags runs during view build time, while the UI components like JSF <h:xxx> tags runs during view render time. So they doesn't run in sync as you'd expect from the coding. In your code, at the moment the <c:forEach> runs, the <h:dataTable> hasn't run at all and thus its var attribute is not been set and thus #{k} is not available when the <c:forEach> runs and thus it retrieves an empty/non-existent collection.

You need an UI component instead if you want a nested iteration inside another UI component. One of them is Facelets <ui:repeat>.

<ui:repeat value="#{requestHeader[k]}" var="v">
    <h:outputText value="#{v}" /><br />
</ui:repeat>

In case you're still on JSF 1.x, use Tomahawk's <t:dataList> instead, or simply another <h:dataTable>.

See also:


Unrelated to the concrete problem: you don't need a backing bean here at all. All request headers are already as a Map available by the implicit EL object #{header}.

All with all, your approach can be simplified as follows, without any backing bean:

<h:dataTable value="#{header.keySet().toArray()}" var="headerName" border="1">
    <f:facet name="header">HTTP request headers</f:facet>
    <h:column>
        <f:facet name="header">Name</f:facet>
        <h:outputText value="#{headerName}" />
    </h:column>
    <h:column>
        <f:facet name="header">Value</f:facet>
        <ui:repeat value="#{header[headerName]}" var="headerValue">
            <h:outputText value="#{headerValue}" /><br />
        </ui:repeat>
    </h:column>
</h:dataTable>

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Oh, a typical JSF beginner's mistake. This leads to a few new questions: 1) You mention _view build time_ and _view render time_. I cannot find these terms in the JSF lifecycle. Do they correspond to the _Restore View Phase_ and the _Render Response Phase_? Now unrelated: 2.a) Since the `header` EL object is a `Map`, I won't need `` at all, right? 2.b) In the `HttpServletRequest`, one header field name is mapped to multiple values (while `header` maps to **one** value). When will there be multiple values? It seems like the last header field with same name will "win". – xehpuk Aug 20 '12 at 21:56
  • Oops, `#{header.keySet()}` will blow everything up: `java.lang.IllegalAccessException: Class javax.el.BeanELResolver can not access a member of class java.util.Collections$UnmodifiableMap with modifiers "public"`. So I guess this won't work because `header` is a _private_ static class `java.util.Collections$UnmodifiableMap` and thus you can't call methods on it? – xehpuk Aug 20 '12 at 22:25
  • That's apparently servletcontainer specific. It works for me on Tomcat 7. Anyway, you have at least gotten the explanation and answer for your initial problem. – BalusC Aug 20 '12 at 22:31
1

Try to use ui:repeat tag instead of c:forEach.See this link for why ui:repeat use instead of c:forEach.

Community
  • 1
  • 1
Sai Ye Yan Naing Aye
  • 6,622
  • 12
  • 47
  • 65