I am trying to write a JSF custom component that dynamically chooses creates and renders an existing composite component. So far everthing is working fine except for passing attributes to the composite.
This is my custom component class (error handling etc. stripped for better reading):
@FacesComponent(createTag = true)
public class ChooseEZComp extends UIComponentBase {
@Override
public void encodeBegin(FacesContext context) throws IOException {
Object value = getAttributes().get("value");
String ezCompName = value.getClass().getSimpleName().toLowerCase();
// ezCompName is something like "freelink" or "treenode"
Map<String, Object> params = new HashMap<>();
params.put("node", value);
// log.debug(params.get("node").getClass().getName()) -> yields correct class name
ViewDeclarationLanguage viewDeclarationLanguage = context
.getApplication()
.getViewHandler()
.getViewDeclarationLanguage(context, context.getViewRoot().getViewId());
UIComponent component = viewDeclarationLanguage
.createComponent(context,
"http://xmlns.jcp.org/jsf/composite/ezcomp",
ezCompName,
params);
component.encodeAll(context);
}
}
A composite component (I have several of them), that gets choosen and rendered by this class:
<cc:interface>
<cc:attribute name="node" required="true"/>
</cc:interface>
<cc:implementation>
<h:outputText value="The class of node is: #{cc.attrs.node.class.name}"/>
</cc:implementation>
This is how I use the tag in my JSF page:
<test:chooseEZComp value="#{treeView.selectedNode.data}"/>
So "value" is always guaranteed not of type java.lang.String (it's some JPA @Entity).
But nevertheless the result output in the JSF page is always:
The class of node is: java.lang.String
Where am I wrong? Isn't it possible to pass something other than String as parameter to an composite?
I am runnind wildfly-8.2.0-final with Java EE 7 (and Primefaces 5 but which is not used here)
Any hints welcome!
Edit: of course I also tried to force the type of the attribute in the cc:interface
<cc:interface>
<cc:attribute name="node" required="true" type="some.package.type"/>
</cc:interface>
But this consequently resulted in a IllegalArgument Exception:
IllegalArgumentException: Cannot convert ... of type class java.lang.String to class
Turns out I misunderstood the API ... Map<String, Object> in the signature made me think I can pass an object. But the Javadoc is more precise about this:
So you do not pass the value into createComponent but rather the expression that was used to calculate the value for the specified attribute or property name:
Funny side read: to find a solution I debugged through the jsf-imp-2.2.8-jbossorg and stumbled upon the code to create the component. Basically what it does is:
At least after finding this it's clear why you need to pass in the value expression rather than the value itself.