4

I've been working with converters in my PrimeFaces SelectOneMenu objects. They work fine if I only tell which class the converter is referring to:

@FacesConverter(forClass = DescriptorVO.class)
public class DescriptorVOConverter implements Converter { ... }

This way, I don't have to explicitly tell the JSF component which converter should be used when it is populated with objects of class DescriptorVO.

However, I made a page that used a p:SelectManyCheckbox and I couldn't for the life of me know why it wasn't calling my converter. Then I gave it a name, like so:

@FacesConverter(forClass = RoleVO.class, value = "roleVOConverter")
public class RoleVOConverter implements Converter { ... }

and passed it as one of the component's properties

<p:selectManyCheckbox id="cbx_roles" required="true" converter="roleVOConverter"
    requiredMessage="At least one role must be selected."
    value="#{userView.selectedRoles}" layout="responsive">
    <f:selectItems value="#{userView.roles}" var="role"
        itemLabel="#{role.title}" itemValue="#{role}" />
</p:selectManyCheckbox>

and voi la, it started calling the converter correctly. This raised a question to me regarding when I should name my converters (through the value attribute) and when telling them which class the converter should be used with (with the forClass attribute) is enough. I never had to namy any converters when working with PrimeFaces, only for this particular SelectManyCheckbox component. Do different components have different necessities regarding converters or did I just get the concept of converters wrong?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555

1 Answers1

5

That can happen when the value of the UISelectMany component references a generic java.util.Collection type like List<Role> instead of an array like Role[]. This is specified in javadoc of UISelectMany (emphasis mine):

Obtain the Converter using the following algorithm:

  • If the component has an attached Converter, use it.

  • If not, look for a ValueExpression for value (if any). The ValueExpression must point to something that is:

    • An array of primitives (such as int[]). Look up the registered by-class Converter for this primitive type.
    • An array of objects (such as Integer[] or String[]). Look up the registered by-class Converter for the underlying element type.
    • A java.util.Collection. Do not convert the values.
  • If for any reason a Converter cannot be found, assume the type to be a String array.

The reason is, the generic type <Role> is lost during runtime and not directly determinable. It works when you're using MyFaces instead of Mojarra as it will in case of a java.util.Collection inspect the actual type by manually iterating over <f:selectItem(s)> and determine the Converter based on first non-null value.

I created spec issue 1422 on this to get this in JSF spec and improve Mojarra too.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • That's exactly what happens with me. I forgot to mention the `ClassCastException`, but that happened too: I was getting `String` representations of my objects inside my `ArrayList`. And you say this behavior is in the documentation of the `javax.faces` API and yet MyFaces has overriden it? – Douglas De Rizzo Meneghetti Jun 11 '16 at 09:57
  • That's correct. MyFaces often goes steps further to improve behavior where it "doesn't make sense". – BalusC Jun 11 '16 at 10:31