This problem is not specifically related to enums. You would have the same problem with other List
types for which JSF has builtin converters, e.g. List<Integer>
, List<Double>
, etcetera.
The problem is that EL operates runtime and that generic type information is lost during runtime. So in essence, JSF/EL doesn't know anything about the parameterized type of the List
and defaults to String
unless otherwise specified by an explicit Converter
. In theory, it would have been possible using nasty reflection hacks with help of ParameterizedType#getActualTypeArguments()
, but the JSF/EL developers may have their reasons for not doing this.
You really need to explicitly define a converter for this. Since JSF already ships with a builtin EnumConverter
(which isn't useable standalone in this particular case because you have to specify the enum type during runtime), you could just extend it as follows:
package com.example;
import javax.faces.convert.EnumConverter;
import javax.faces.convert.FacesConverter;
@FacesConverter(value="securityRoleConverter")
public class SecurityRoleConverter extends EnumConverter {
public SecurityRoleConverter() {
super(SecurityRole.class);
}
}
And use it as follows:
<h:selectManyCheckbox value="#{userController.roles}" converter="securityRoleConverter">
<f:selectItems value="#{userController.rolesSelectMany}" />
</h:selectManyCheckbox>
or
<h:selectManyCheckbox value="#{userController.roles}">
<f:converter converterId="securityRoleConverter" />
<f:selectItems value="#{userController.rolesSelectMany}" />
</h:selectManyCheckbox>
A bit more generic (and hacky) solution would be to storing the enum type as component attribute.
package com.example;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
@FacesConverter(value="genericEnumConverter")
public class GenericEnumConverter implements Converter {
private static final String ATTRIBUTE_ENUM_TYPE = "GenericEnumConverter.enumType";
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value instanceof Enum) {
component.getAttributes().put(ATTRIBUTE_ENUM_TYPE, value.getClass());
return ((Enum<?>) value).name();
} else {
throw new ConverterException(new FacesMessage("Value is not an enum: " + value.getClass()));
}
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Class<Enum> enumType = (Class<Enum>) component.getAttributes().get(ATTRIBUTE_ENUM_TYPE);
try {
return Enum.valueOf(enumType, value);
} catch (IllegalArgumentException e) {
throw new ConverterException(new FacesMessage("Value is not an enum of type: " + enumType));
}
}
}
It's useable on all kinds of List<Enum>
using converter ID genericEnumConverter
. For List<Double>
, List<Integer>
, etc one would have used the builtin converters javax.faces.Double
, javax.faces.Integer
and so on. The builtin Enum converter is by the way unsuitable due to the inability to specify the target enum type (a Class<Enum>
) from the view side on. The JSF utility library OmniFaces offers exactly this converter out the box.
Note that for a normal Enum
property, the builtin EnumConverter
already suffices. JSF will instantiate it automagically with the right target enum type.