Nested JSF Composite Components leading to a Stack Overflow exception
Solution 1:
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.