Search code examples
jsfmojarrajsf-2.3

How to use jsf in "namespaced mode"


In a website we want to integrate some snippets provided by jsf-applications, think of a dashboard-app or a "portal-light". While analyzing the requirements we came across a blog-post by Arjan Tjims on jsf 2.3 new features, where he mentioned a new "namespaced mode":

In namespaced mode, which is specifically intended for Portlets but can be used in other environments as well, the partial response is given an id that's taken to be the "naming container id". All predefined postback parameter names (such as "javax.faces.ViewState", "javax.faces.ClientWindow", "javax.faces.RenderKitId", etc) are prefixed with this and the naming separator (default ":"). e.g. javax.faces.ViewState" becomes "myname:javax.faces.ViewState". Namespaced mode is activated when the UIViewRoot instance implements the NamingContainer interface.

Our application might be a usecase for that "namespaced mode", so we want to give it a try.

We built a MyUIViewRoot where we implemented NamingContainer and wrapped the original UIViewRoot-instance. We registered a MyViewHandler in faces-config.xml which handles the wrapping of the ViewRoot. For testing we used a simple counter-app with two <h:form>-elements (seems to be important).

We find that "namespace mode" seems to be activated, eg the javax.faces.ViewState indeed is prepended by some namespace and becomes j_id1:javax.faces.ViewState:0. But both actions do not work any more - the postback request does not restore the View any more but creates a new one. So with our simple approach we are missing something (btw, removing only the implements NamingContainer from MyUIViewRoot the counter-app works fine again).

  • Is there some documentation, a howto or a working example out there that we have overlooked?
  • How to activate "namespace mode" correctly? What have we missed to make the postback work again?
  • How can we make MyUIViewRoot to prepend the ViewState with myNamespace?

The application is running in a payara-5 application server.

Our index.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
  xmlns:h="http://xmlns.jcp.org/jsf/html"
  xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head/>
<h:body>
  <h:form id="counterForm">
    <h:panelGrid columns="2">
      <h:outputLabel value="Counter" />
      <h:outputText value="#{counterUiController.counter}" />
    </h:panelGrid>
    <h:commandButton value="inc" action="#{counterUiController.incAction}">
      <f:ajax execute="@form" render="@form" />
    </h:commandButton>
  </h:form>
  <h:form id="resetForm">
    <h:commandButton value="reset" action="#{counterUiController.resetAction}">
      <f:ajax execute="@form" render=":counterForm" />
    </h:commandButton>
  </h:form>
</h:body>
</html>

The CounterUiController:

@Named
@ViewScoped
public class CounterUiController implements Serializable {

    private int counter;

    public int getCounter() {
        return counter;
    }

    public void incAction() {
        counter++;
    }

    public void resetAction() {
        counter=0;
    }
}

Our UIViewRoot-Implementation:

public class MyUIViewRoot extends UIViewRoot implements NamingContainer, FacesWrapper<UIViewRoot> {

    private static final Logger LOG = Logger.getLogger(MyUIViewRoot.class.getName());

    private UIViewRoot wrapped;

    public MyUIViewRoot(UIViewRoot wrapped) {
        this.wrapped = wrapped;
        LOG.log(Level.INFO, "new instance created: {0}", this);
    }

    @Override
    public UIViewRoot getWrapped() {
        return wrapped;
    }

    @Override
    public String createUniqueId() {
        return wrapped==null ? null : wrapped.createUniqueId();
    }

    @Override
    public void setId(String id) {
        if( wrapped!=null ) {
            wrapped.setId(id);
        }
    }
    // all other methodes delegated to `wrapped` directly
}

Our ViewHandler:

public class MyViewHandler extends ViewHandlerWrapper {

    private static final Logger LOG = Logger.getLogger(MyViewHandler.class.getName());

    public MyViewHandler(ViewHandler wrapped) {
        super(wrapped);
    }

    @Override
    public UIViewRoot createView(FacesContext context, String viewId) {
        UIViewRoot retval = super.createView(context, viewId);
        retval = wrapIfNeeded(retval);
        LOG.log(Level.INFO, "view created: {0}", retval);
        return retval;
    }

    @Override
    public UIViewRoot restoreView(FacesContext context, String viewId) {
        UIViewRoot retval =  super.restoreView(context, viewId);
        retval = wrapIfNeeded(retval);
        LOG.log(Level.INFO, "view restored: {0}", retval);
        return retval;
    }

    private UIViewRoot wrapIfNeeded(UIViewRoot root) {
        if (root != null && !(root instanceof MyUIViewRoot)) {
            LOG.log(Level.INFO, "view wrapped: {0}, {1}", new Object[] { root, root.getId() });
            return new MyUIViewRoot(root);
        } else {
            return root;
        }
    }
}

Solution

  • You need to replace the UIViewRoot not to wrap it.

    public class NamespacedView extends UIViewRoot implements NamingContainer {
        //
    }
    

    And then in faces-config.xml.

    <component>
        <component-type>javax.faces.ViewRoot</component-type>
        <component-class>com.example.NamespacedView</component-class>
    </component>
    

    That's basically all. See also the Mojarra IT on this.