Nested JSF Composite Components leading to a Stack Overflow exception
Asked Answered
A

1

11

The problem

When I attempt to nest a Composite Component within itself, with some logic to end the infinite recursion I receive a stack overflow exception. My understanding is that <c:xxx> tags run at view build time so I was not expecting to have an infinite view build as I presume has been the case.

This is the composite component simpleNestable.xhtml

<!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:composite="http://java.sun.com/jsf/composite"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:em="http://xmlns.jcp.org/jsf/composite/emcomp"

  xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">

    <h:head>
        <title>This content will not be displayed</title>
    </h:head>
    <h:body>
        <composite:interface>
            <composite:attribute name="depth" required="true" type="java.lang.Integer"/>
        </composite:interface>

        <composite:implementation>
            <c:if test="#{cc.attrs.depth lt 3}">
                 #{cc.attrs.depth}
                 #{cc.attrs.depth+1}
                 <em:simpleNestable depth="#{cc.attrs.depth+1}" /> 

            </c:if>

        </composite:implementation>
    </h:body>
</html>

This is how it's used

<h:head>
    <title>Facelet Title</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <h:outputStylesheet name="./css/default.css"/>
    <h:outputStylesheet name="./css/cssLayout.css"/>
</h:head>
<h:body>        
     <emcomp:simpleNestable depth="1"/>

</h:body>

The Stack Overflow Exception

java.lang.StackOverflowError
    at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:109)
    at javax.faces.component.UIComponentBase$AttributesMap.get(UIComponentBase.java:2407)
    at com.sun.faces.el.CompositeComponentAttributesELResolver$ExpressionEvalMap.get(CompositeComponentAttributesELResolver.java:393)
    at javax.el.MapELResolver.getValue(MapELResolver.java:199)
    at com.sun.faces.el.DemuxCompositeELResolver._getValue(DemuxCompositeELResolver.java:176)
    at com.sun.faces.el.DemuxCompositeELResolver.getValue(DemuxCompositeELResolver.java:203)
    at com.sun.el.parser.AstValue.getValue(AstValue.java:140)
    at com.sun.el.parser.AstValue.getValue(AstValue.java:204)
    at com.sun.el.parser.AstPlus.getValue(AstPlus.java:60)
    at com.sun.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:226)
    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:2407)
    at com.sun.faces.el.CompositeComponentAttributesELResolver$ExpressionEvalMap.get(CompositeComponentAttributesELResolver.java:393)
    at javax.el.MapELResolver.getValue(MapELResolver.java:199)
    at com.sun.faces.el.DemuxCompositeELResolver._getValue(DemuxCompositeELResolver.java:176)
    at com.sun.faces.el.DemuxCompositeELResolver.getValue(DemuxCompositeELResolver.java:203)
    at com.sun.el.parser.AstValue.getValue(AstValue.java:140)
    at com.sun.el.parser.AstValue.getValue(AstValue.java:204)
    at com.sun.el.parser.AstPlus.getValue(AstPlus.java:60)

The Question

How can I nest composite components (or similar) within themselves (to a non predefined depth) without receiving a stack overflow exception

Why I want this

I have arbitrarily nested data that I want to represent within a nested collapsibleSubTable from RichFaces, alternatives to my approach are very welcome

Aircondition answered 1/5, 2015 at 12:43 Comment(0)
M
8

The problem was in the context of the #{cc} and the statefulness of the composite attribute. The #{cc} in any attribute of the nested composite references itself instead of the parent. The attribute being stateful means that the #{cc} was re-evaluated in every child which in turn ultimately references itself instead of the parent. Hence the stack overflow. It's evaluating the depth of itself in an infinite loop.

I tricked the statefulness of the attribute by making it stateless using a backing component as below which immediately evaluates it and assigns it as a component property:

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

    private Integer depth;

    @Override
    public void setValueExpression(String name, ValueExpression binding) {
        if ("depth".equals(name)) {
            setDepth((Integer) binding.getValue(getFacesContext().getELContext()));
        }
        else {
            super.setValueExpression(name, binding);
        }
    }

    public Integer getDepth() {
        return depth;
    }

    public void setDepth(Integer depth) {
        this.depth = depth;
    }

}

Which is to be declared in interface's componentType as below:

<cc:interface componentType="treeComposite">
    <cc:attribute name="depth" type="java.lang.Integer" />
</cc:interface>

And, in the implementation you should in the test reference the stateless property and in the nested composite reference the one of the parent (because #{cc} in the attribute of the nested composite references the nested composite itself):

<cc:implementation>
    <br />We're at depth #{cc.depth}.
    <c:if test="#{cc.depth gt 0}">
        <my:tree depth="#{cc.parent.depth - 1}" />
    </c:if>
</cc:implementation>

I only changed the meaning of "depth" here to be the other way round so that it's just declarative from the client on without the need to edit it in the implementation. So, in the client you have to say depth="#{3}" if you want 3 nested children:

<my:tree depth="#{3}" />

Note the importance of it being an EL expression rather than a literal. Otherwise setValueExpression() in the backing component won't be called.

Marybellemarybeth answered 1/5, 2015 at 13:46 Comment(5)
With this it's claiming that"The following attribute(s) are required, but no values have been supplied for them: depth." Could I see the whole of your <cc:interface> to see how you're defining the attribute depth?Aircondition
Also just to check:are your cc and my: xmlns:cc="http://xmlns.jcp.org/jsf/composite and xmlns:my="http://xmlns.jcp.org/jsf/composite/emcompAircondition
Interface has just the depth attribute exactly like you had. The namespaces are indeed like that. I just canonicalized them (to prevent future readers having the same problem to think that it's the "advocated" style .. "But BalusC used it like that!" etc :/ ).Marybellemarybeth
@Marybellemarybeth Your blog post balusc.omnifaces.org/2016/02/… deals with this topic as well and in more depth, right? If I'm not mistaken, you could add a link above.Counseloratlaw
@Marybellemarybeth - any chance of this becoming not an issue in future versions of JSF?Ensepulcher

© 2022 - 2024 — McMap. All rights reserved.