Ответ 1
Эта проблема не связана конкретно с перечислениями. У вас будет такая же проблема с другими типами List
, для которых JSF имеет встроенные преобразователи, например. List<Integer>
, List<Double>
и т.д.
Проблема заключается в том, что EL работает во время выполнения и что общая информация типа теряется во время выполнения. Таким образом, по сути, JSF/EL ничего не знает о параметризованном типе List
и по умолчанию имеет значение String
, если иное не указано явным Converter
. Теоретически, было бы возможно использовать неприятные рефлексивные хаки с помощью ParameterizedType#getActualTypeArguments()
, но разработчики JSF/EL могут иметь свои причины не делая это.
Вам действительно нужно явно определить конвертер для этого. Поскольку JSF уже поставляется со встроенным EnumConverter
(который не используется автономно в данном конкретном случае, потому что вы должны указать тип перечисления во время выполнения), вы можете просто расширить его следующим образом:
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);
}
}
И используйте его следующим образом:
<h:selectManyCheckbox value="#{userController.roles}" converter="securityRoleConverter">
<f:selectItems value="#{userController.rolesSelectMany}" />
</h:selectManyCheckbox>
или
<h:selectManyCheckbox value="#{userController.roles}">
<f:converter converterId="securityRoleConverter" />
<f:selectItems value="#{userController.rolesSelectMany}" />
</h:selectManyCheckbox>
Несколько более общее (и взломанное) решение будет состоять в том, чтобы сохранить тип перечисления в качестве атрибута компонента.
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));
}
}
}
Он используется для всех типов List<Enum>
с использованием идентификатора конвертера genericEnumConverter
. Для List<Double>
, List<Integer>
и т.д. Вы бы использовали встроенные преобразователи javax.faces.Double
, javax.faces.Integer
и так далее. Встроенный конвертер Enum, кстати, непригоден из-за невозможности указать целевой тип перечисления (a Class<Enum>
) со стороны представления. Библиотека утилиты JSF OmniFaces предлагает именно этот конвертер из окна.
Обратите внимание, что для обычного свойства Enum
уже существует встроенный EnumConverter
. JSF автоматически создает экземпляр этого файла с правильным типом перечисления цели.