问题
I want to use enum values in a <h:selectManyCheckbox>
. The checkboxes get populated correctly, however, when selecting some values and submitting them, their runtime type is String
, and not enum. My code:
<h:selectManyCheckbox value=\"#{userController.roles}\" layout=\"pageDirection\">
<f:selectItems value=\"#{userController.rolesSelectMany}\" />
</h:selectManyCheckbox>
UserController class (SecurityRole is an enum type):
public SelectItem[] getRolesSelectMany() {
SelectItem[] items = new SelectItem[SecurityRole.values().length];
int i = 0;
for (SecurityRole role : SecurityRole.values()) {
items[i++] = new SelectItem(role, role.toString());
}
return items;
}
public List<SecurityRole> getRoles() {
getCurrent().getRoles();
}
public void setRoles(List<SecurityRole> roles) {
getCurrent().setRoles(roles);
}
When JSF calls the setRoles method, it contains a list of type String, and not the enum type. Any ideas? Thanks!
回答1:
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.
回答2:
In some cases the List could just as well be an array SomeType[], and in this case no explicit converter is needed.
Generic erasure was a clever way of putting generics into the language without breaking the old stuff, but now we live forever with the consequences of that decision...
来源:https://stackoverflow.com/questions/3822058/use-enum-in-hselectmanycheckbox