Monday, February 8, 2016

Recursive tree of composite components

While trying to create kind of tree structure (menu, discussion, comments, etc) with Facelets and composite components, you'll probably ever have attempted to recursively nest a composite component in itself like the below <my:tree node="#{bean.tree}"> composite component example.

<cc:interface>
    <cc:attribute name="node" type="com.example.SomeTreeModel" required="true" />
</cc:interface>
<cc:implementation>
    <c:if test="#{not empty cc.attrs.node.children}">
        <ul>
            <c:forEach items="#{cc.attrs.node.children}" var="node">
                <li>
                    #{node.data}
                    <my:tree node="#{node}" />
                </li>
            </c:forEach>
        </ul>
    </c:if>
</cc:implementation>

And then you was surprised to see a stack overflow error (line numbers match Mojarra 2.2.12) — not surprisingly, this was also ever asked on Stack Overflow:

java.lang.StackOverflowError
    at java.lang.Exception.(Exception.java:84)
    at java.lang.RuntimeException.(RuntimeException.java:80)
    at javax.el.ELException.(ELException.java:66)
    at com.sun.faces.facelets.el.VariableMapperWrapper.resolveVariable(VariableMapperWrapper.java:107)
    at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler$1.resolveVariable(CompositeComponentTagHandler.java:377)
    at com.sun.faces.facelets.el.VariableMapperWrapper.resolveVariable(VariableMapperWrapper.java:103)
    at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler$1.resolveVariable(CompositeComponentTagHandler.java:377)
    at com.sun.faces.facelets.el.VariableMapperWrapper.resolveVariable(VariableMapperWrapper.java:103)
    at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler$1.resolveVariable(CompositeComponentTagHandler.java:377)
    at com.sun.faces.facelets.el.VariableMapperWrapper.resolveVariable(VariableMapperWrapper.java:103)
    [repeat]

What's happening here? Which Facelets EL variable exactly is being resolved on itself in an infinite loop? We have already used JSTL <c:forEach> instead of <ui:repeat> to avoid an infinite component tree during view build time (as the <ui:repeat> only runs during view render time not during view build time, see also JSTL in JSF2 Facelets… makes sense?), so that can't be the cause.

Upon inspection (just putting a debug breakpoint in VariableMapperWrapper) it appears to be the EL variable #{cc}.

What turns out, when you pass #{node} to the nested composite, then you're technically basically passing #{cc.attrs.node.children[index]} to it. The nested composite is in turn thus interpreting its #{cc.attrs.node} as #{cc.attrs[cc.attrs.node.children[index]]}. But ... in the context of the nested composite, the #{cc} is referring the nested composite itself! So, the #{cc.attrs.node} actually refers to the nested composite's own node. This results in a never ending loop.

We ultimately actually want it to be #{cc.attrs[cc.parent.attrs.node.children[index]]} where the #{cc.parent} just refers the parent component via UIComponent#getParent(). So, let's try it.

<cc:interface>
    <cc:attribute name="node" type="com.example.SomeTreeModel" required="true" />
</cc:interface>
<cc:implementation>
    <c:if test="#{not empty cc.attrs.node.children}">
        <ul>
            <c:forEach items="#{cc.attrs.node.children}" var="node" varStatus="loop">
                <li>
                    #{node.data}
                    <my:tree node="#{cc.parent.attrs.node.children[loop.index]}" />
                </li>
            </c:forEach>
        </ul>
    </c:if>
</cc:implementation>

It still yields a stack overflow error! It's only different from the above. Top lines of stack trace may vary depending on remaining stack size, but the repeating part is below (line numbers match Mojarra 2.2.12, Tomcat 8.0.30 and Weld 2.3.0):

    [repeat]
    at com.sun.faces.el.CompositeComponentAttributesELResolver$ExpressionEvalMap.get(CompositeComponentAttributesELResolver.java:393)
    at javax.el.MapELResolver.getValue(MapELResolver.java:65)
    at com.sun.faces.el.DemuxCompositeELResolver._getValue(DemuxCompositeELResolver.java:176)
    at com.sun.faces.el.DemuxCompositeELResolver.getValue(DemuxCompositeELResolver.java:203)
    at org.apache.el.parser.AstValue.getValue(AstValue.java:169)
    at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:184)
    at org.jboss.weld.el.WeldValueExpression.getValue(WeldValueExpression.java:50)
    at com.sun.faces.facelets.el.ContextualCompositeValueExpression.getValue(ContextualCompositeValueExpression.java:158)
    at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:109)
    at javax.faces.component.UIComponentBase$AttributesMap.get(UIComponentBase.java:2427)
    at com.sun.faces.el.CompositeComponentAttributesELResolver$ExpressionEvalMap.get(CompositeComponentAttributesELResolver.java:393)
    [repeat]

What's happening here? Which composite component attribute exactly is being evaluated on itself in an infinite loop?

What turns out, the attributes are stored as deferred value expressions, not as immediately evaluated values. We all already kind of knew it (the #{} versus ${} in good old JSP), but it still easily slips our mind. When #{cc.parent.attrs.node} is about to be evaluated, the parent gives back the sole EL value expression #{cc.attrs.node} as a ValueExpression object to the child and then the child is evaluating it in its own context. However, as you have read above, the #{cc} refers to the current composite, not the intented parent. This results again in an infinite loop.

We ultimately want the composite to internally immediately evaluate the #{cc.attrs.node} and store it somewhere as a request based instance variable instead of storing only the ValueExpression object in the component state. A backing component is a good candidate. There you can hook on UIComponent#setValueExpression() and then caputure and immediately evaluate the node attribute. So let's try it.

@FacesComponent("treeComposite")
public class TreeComposite extends UINamingContainer {

    private SomeTreeModel node;

    @Override
    public void setValueExpression(String name, ValueExpression expression) {
        if ("node".equals(name)) {
            setNode((SomeTreeModel) expression.getValue(getFacesContext().getELContext()));
        }
        else {
            super.setValueExpression(name, expression);
        }
    }

    public SomeTreeModel getNode() {
        return node;
    }

    public void setNode(SomeTreeModel node) {
        this.node = node;
    }

}

Note: for SomeTreeModel you could use org.omnifaces.model.tree.TreeModel as also used by <o:tree>.

Declare the above backing component as componentType of the composite interface and then replace #{cc.attrs.node} over all place by #{cc.node}.

<cc:interface componentType="treeComposite">
    <cc:attribute name="node" type="com.example.SomeTreeModel" required="true" />
</cc:interface>
<cc:implementation>
    <c:if test="#{not empty cc.node.children}">
        <ul>
            <c:forEach items="#{cc.node.children}" var="node" varStatus="loop">
                <li>
                    #{node.data}
                    <my:tree node="#{cc.parent.node.children[loop.index]}" />
                </li>
            </c:forEach>
        </ul>
    </c:if>
</cc:implementation>

Now it finally works! :)

By the way, my hand is free again! Cast was removed February 1st. The rightmost fingers are still weak, and I'm still using only 2-3 fingers to type, and I have to pause my hand more often, but hey, there is progress as compared to last month.

1 comment:

aziza said...

Hi
thanks for your helps
actually i'm working about jsf2.1 and sesion scope and implements serializable
but i have a probleme the variable cant get intialized when i call a bean for next time how can i resolve that

thanks