JSF 2 facelets <f:metadata/> in template and page
Asked Answered
M

2

9

I have the following template (masterLayout.xhtml):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets">
    <f:view contentType="text/html">
        <ui:insert name="metadata"/>
        <h:head>
            <title><ui:insert name="windowTitle"/> | MySite</title>
        </h:head>

        <h:body>
            <div id="container">
                <div id="header">
                    <ui:insert name="header">
                        <ui:include src="/WEB-INF/templates/header.xhtml"/>
                    </ui:insert>
                </div>
                <div id="content">
                    <ui:insert name="content"/>
                </div>
                <div id="footer">
                    <ui:insert name="footer">
                        <ui:include src="/WEB-INF/templates/footer.xhtml"/>
                    </ui:insert>
                </div>
            </div>
        </h:body>
    </f:view>
</html>

and page that uses it (search.xhtml):

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title></title>
    </h:head>
    <h:body>
        <ui:composition template="/WEB-INF/templates/masterLayout.xhtml">
            <ui:define name="metadata">
                <f:metadata>
                    <f:viewParam name="address" value="#{searchBean.address}"/>
                    <f:event type="preRenderView" listener="#{userSessionBean.preRenderViewCookieLogin(e)}"/>
                    <f:event type="preRenderView" listener="#{searchBean.preRenderView(e)}"/>
                </f:metadata>
            </ui:define>
            <ui:define name="windowTitle">#{searchBean.address}</ui:define>

            <ui:define name="content">

                <!-- Content goes here -->

            </ui:define>
        </ui:composition>
    </h:body>
</html>

The problem is that I want to place the call to userSessionBean.preRenderViewCookieLogin(e) in the template because there are many other pages. This method checks that the user is logged in (according to session state) and, if not, checks that a cookie is available which can be used to log the user in and, if so (and if valid), logs the user in automatically. The system works in the code above, but when I try to push this into the template, my view parameters are no longer being set.

Here's the modified version of the above, with userSessionBean.preRenderViewCookieLogin(e) pushed up to the template.

masterLayout.xhtml:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets">
    <f:view contentType="text/html">
        <f:metadata>
            <f:event type="preRenderView" listener="#{userSessionBean.preRenderViewCookieLogin(e)}"/>
            <ui:insert name="metadata"/>
        </f:metadata>
        <h:head>
            <title><ui:insert name="windowTitle"/> | MySite</title>
        </h:head>

        <h:body>
            <div id="container">
                <div id="header">
                    <ui:insert name="header">
                        <ui:include src="/WEB-INF/templates/header.xhtml"/>
                    </ui:insert>
                </div>
                <div id="content">
                    <ui:insert name="content"/>
                </div>
                <div id="footer">
                    <ui:insert name="footer">
                        <ui:include src="/WEB-INF/templates/footer.xhtml"/>
                    </ui:insert>
                </div>
            </div>
        </h:body>
    </f:view>
</html>

search.xhtml

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title></title>
    </h:head>
    <h:body>
        <ui:composition template="/WEB-INF/templates/masterLayout.xhtml">
            <ui:define name="metadata">
                <f:viewParam name="address" value="#{searchBean.address}"/>
                <f:event type="preRenderView" listener="#{searchBean.preRenderView(e)}"/>
            </ui:define>
            <ui:define name="windowTitle">#{searchBean.address}</ui:define>

            <ui:define name="content">

                <!-- Content goes here -->

            </ui:define>
        </ui:composition>
    </h:body>
</html>

Notice that I've moved the <f:metadata/> tag to the template. That alone is the problem as removing userSessionBean.preRenderViewCookieLogin(e) makes no difference. I also tried a variation on the code that worked, which was to just move userSessionBean.preRenderViewCookieLogin(e) into the template which means it can't be inside the <f:metadata/> tag. In this case, that method executed after all the view parameters had been set and searchBean.preRenderView(e) called. I want userSessionBean.preRenderViewCookieLogin(e) to be called before any page's preRenderView(e) is called, not after. And just for fun I tried putting an <f:metadata/> around userSessionBean.preRenderViewCookieLogin(e), which called this method, but didn't set the view parameters.

So, I would like to know:

  1. Why is this happening and is there a way to fix it?
  2. Is there a better way to ensure the same method is called for each page before anything else?

Edit: I just tried something else - a phase event: <f:view contentType="text/html" beforePhase="#{userSessionBean.beforePhase(e)}"> This is in masterLayout.xhtml. It is not being called at all; not for any phase.

Edit: Removed the e (damn you NetBeans!): <f:view contentType="text/html" beforePhase="#{userSessionBean.beforePhase}"> This is only called before the render response phase, which of course means it's called after the preRenderView event is raised.

Muckraker answered 27/9, 2011 at 18:42 Comment(0)
R
14

Why is this happening and is there a way to fix it?

From the <f:metadata> tag documentation (emphasis of 2nd paragraph is mine):

Declare the metadata facet for this view. This must be a child of the <f:view>. This tag must reside within the top level XHTML file for the given viewId, or in a template client, but not in a template. The implementation must insure that the direct child of the facet is a UIPanel, even if there is only one child of the facet. The implementation must set the id of the UIPanel to be the value of the UIViewRoot.METADATA_FACET_NAME symbolic constant.

So, it really has to go in the top view, not in the template.


Is there a better way to ensure the same method is called for each page before anything else?

In your particular case, store the logged-in user as a property of a session scoped managed bean instead of a cookie and use a filter on the appropriate URL pattern to check it. Session scoped managed beans are in the filter available as HttpSession attributes. Homegrown cookies are unnecessary as you're basically reinventing the HttpSession here. Unless when you want a "remember me" facility, but this should not be solved this way. Do it in a filter as well.

See also:

Rezzani answered 27/9, 2011 at 19:13 Comment(4)
Thanks @BalusC. I am actually trying to implement "Remember me" functionality. The method I want to be called each time first checks that the user is logged in, according to the state of a session-scoped managed bean. This is something I have already had working for a while. I just starting building the "Remember me" functionality so that users won't have to login again after their session expires. Is there just not a general purpose way to call the same method for every page before that page's preRenderView?Muckraker
Remove that (e). There's no such thing in the scope.Rezzani
Done. See results. I ended up using a phase listener. The phase listener looks up userSessionBean (session scoped) then calls the method on that bean that does the cookie login. I used and up-voted some code of yours here to get the bean by name in the phase listener. I'll post the solution here shortly.Muckraker
I didn't go the filter option because I thought it was overkill. Since I'm managing all the authentication within JSF, it didn't make sense to handle some of it outside of JSF when a simple phase listener would do the trick. I really appreciate your answer. Ah heck, I'll give you this one because you actually did answer the first part and pointed me in the right direction to solve the second part.Muckraker
M
0

I got this working using a PhaseListener instead.

public class CookieLoginPhaseListener implements PhaseListener
{
    @Override
    public void beforePhase(PhaseEvent event)
    {
    }

    @Override
    public void afterPhase(PhaseEvent event)
    {
        UserSessionBean userSessionBean = Util.lookupCdiBean("userSessionBean");
        userSessionBean.handleAuthCookie();
    }

    @Override
    public PhaseId getPhaseId()
    {
        return PhaseId.RESTORE_VIEW;
    }
}

and the little bit of bean lookup magic is here:

public static <T> T lookupCdiBean(String name)
{
    return (T) FacesContext.getCurrentInstance().getApplication().evaluateExpressionGet(FacesContext.getCurrentInstance(), "#{" + name + "}", Object.class);
}

Thanks to @BalusC for this: JSF - get managed bean by name. Since I'm using CDI, I can't use the more declarative @ManagedProperty option. Not a big deal though.

Muckraker answered 28/9, 2011 at 10:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.