Используйте enum в h: selectManyCheckbox - PullRequest
       51

Используйте enum в h: selectManyCheckbox

19 голосов
/ 29 сентября 2010

Я хочу использовать значения перечисления в <h:selectManyCheckbox>. Флажки заполняются правильно, однако при выборе и отправке некоторых значений их тип времени выполнения равен String, а не enum. Мой код:

<h:selectManyCheckbox value="#{userController.roles}" layout="pageDirection">
     <f:selectItems value="#{userController.rolesSelectMany}" />
</h:selectManyCheckbox>

Класс UserController (тип перечисления SecurityRole):

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);
}

Когда JSF вызывает метод setRoles, он содержит список типа String, а не тип enum. Есть идеи? Спасибо!

Ответы [ 2 ]

36 голосов
/ 29 сентября 2010

Эта проблема не имеет отношения к перечислениям. У вас будет такая же проблема с другими типами List, для которых JSF имеет встроенные конвертеры, например List<Integer>, List<Double>, и так далее.

Проблема в том, что EL работает во время выполнения, и что информация общего типа теряется во время выполнения. Таким образом, по сути, JSF / EL ничего не знает о параметризованном типе List и по умолчанию String, если явным Converter не указано иное. Теоретически, было бы возможно использовать противные рефлексы с помощью ParameterizedType#getActualTypeArguments(), но у разработчиков JSF / EL могут быть причины не делать этого.

Вам действительно нужно явно определить конвертер для этого. Поскольку JSF уже поставляется со встроенным EnumConverter (который не может использоваться автономно в данном конкретном случае, потому что вы должны указывать тип enum во время выполнения), вы можете просто расширить его следующим образом:

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>

Несколько более общим (и хакерским) решением было бы сохранение типа enum в качестве атрибута компонента.

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, кстати, не подходит из-за невозможности указать целевой тип enum (a Class<Enum>) со стороны просмотра. Библиотека утилит JSF OmniFaces предлагает именно этот конвертер из коробки .

Обратите внимание, что для обычного свойства Enum встроенного EnumConverter уже достаточно. JSF автоматически создаст его экземпляр с правильным целевым типом перечисления.

1 голос
/ 13 августа 2012

В некоторых случаях List также может быть массивом SomeType [] , и в этом случае явный преобразователь не требуется.

Стирание дженериков было умным способом введения дженериков в язык без разрушения старых вещей, но теперь мы вечно живем с последствиями этого решения ...

...