Значение параметра "Ошибка преобразования" для "нулевого конвертера". Зачем мне нужен конвертер в JSF?

У меня проблемы с пониманием того, как эффективно использовать выделение в JSF 2 с POJO/ сущностью. Например, я пытаюсь выбрать Warehouse сущность через раскрывающийся список:

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" />
</h:selectOneMenu>

И ниже управляемый боб:

@Named
@ViewScoped
public class Bean {

    private Warehouse selectedWarehouse;
    private List<SelectItem> availableWarehouses;

    // ...

    @PostConstruct
    public void init() {
        // ...

        availableWarehouses = new ArrayList<>();

        for (Warehouse warehouse : warehouseService.listAll()) {
            availableWarehouses.add(new SelectItem(warehouse, warehouse.getName()));
        }
    }

    // ...
}

Обратите внимание, что я использую весь Warehouse сущность как ценность SelectItem,

Когда я отправляю форму, это терпит неудачу со следующим сообщением лиц:

Значение параметра Ошибка конверсии com.example.Warehouse@cafebabe для нулевого конвертера.

Я надеялся, что JSF сможет просто установить правильный Warehouse возражать против моего управляемого боба, когда я обернуть его в SelectItem, Оборачивая мою сущность в SelectItem должен был пропустить создание Converter для моей сущности.

Я действительно должен использовать Converter всякий раз, когда я хочу использовать сущности в моем <h:selectOneMenu>? Для JSF должна быть возможность просто извлечь выбранный элемент из списка доступных элементов. Если мне действительно нужно использовать конвертер, каков практический способ сделать это? До сих пор я дошел до этого:

  1. Создать Converter реализация для субъекта.
  2. Переопределение getAsString(), Я думаю, что мне это не нужно, так как свойство label SelectItem будет использоваться для отображения метки выпадающего списка.
  3. Переопределение getAsObject(), Я думаю, что это будет использоваться, чтобы вернуть правильный SelectItem или объект в зависимости от типа выбранного поля, определенного в управляемом компоненте.

getAsObject() смущает меня Каков эффективный способ сделать это? Имея строковое значение, как я могу получить связанный объект сущности? Должен ли я запросить объект объекта из сервисного объекта на основе строкового значения и вернуть объект? Или, возможно, каким-то образом я могу получить доступ к списку сущностей, которые формируют элементы выбора, зациклить их, чтобы найти правильную сущность, и вернуть сущность?

Каков нормальный подход к этому?

3 ответа

Решение

Вступление

JSF генерирует HTML. HTML в терминах Java в основном один большой String, Чтобы представить объекты Java в HTML, они должны быть преобразованы в String, Кроме того, при отправке формы HTML отправленные значения обрабатываются как String в параметрах HTTP-запроса. Под прикрытием JSF извлекает их из HttpServletRequest#getParameter() который возвращается String,

Преобразовать между нестандартным объектом Java (т.е. не String, Number или же Boolean для которого EL имеет встроенные преобразования, или Date для которого JSF предоставляет встроенные <f:convertDateTime> тег), вы действительно должны предоставить Converter, SelectItem не имеет никакого специального назначения вообще. Это просто остаток от JSF 1.x, когда было невозможно поставить, например, List<Warehouse> прямо к <f:selectItems>, Он также не имеет особого отношения к этикеткам и конверсии.

getAsString ()

Вам необходимо реализовать getAsString() метод таким образом, чтобы желаемый объект Java был представлен в уникальном String представление, которое можно использовать в качестве параметра HTTP-запроса. Обычно здесь используется технический идентификатор (первичный ключ базы данных).

public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
    if (modelValue == null) {
        return "";
    }

    if (modelValue instanceof Warehouse) {
        return String.valueOf(((Warehouse) modelValue).getId());
    } else {
        throw new ConverterException(new FacesMessage(modelValue + " is not a valid Warehouse"));
    }
}

Обратите внимание, что возвращение пустой строки в случае нулевого / пустого значения модели является значительным и требуется для javadoc. См. Также Использование "Пожалуйста, выберите" f:selectItem с нулевым / пустым значением внутри ap: selectOneMenu.

getAsObject ()

Вам необходимо реализовать getAsObject() таким образом, что именно это String представление, как возвращено getAsString() может быть преобразован обратно в точно такой же объект Java, указанный как modelValue в getAsString(),

public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
    if (submittedValue == null || submittedValue.isEmpty()) {
        return null;
    }

    try {
        return warehouseService.find(Long.valueOf(submittedValue));
    } catch (NumberFormatException e) {
        throw new ConverterException(new FacesMessage(submittedValue + " is not a valid Warehouse ID"), e);
    }
}

Другими словами, вы должны быть технически способны вернуть возвращаемый объект как modelValue аргумент getAsString() а затем вернуть полученную строку как submittedValue аргумент getAsObject() в бесконечном цикле.

использование

Наконец, просто аннотируйте Converter с @FacesConverter чтобы подключить рассматриваемый тип объекта, JSF автоматически позаботится о преобразовании, когда Warehouse Тип когда-либо входит в картину:

@FacesConverter(forClass=Warehouse.class)

Это был "канонический" подход JSF. В конце концов, это не очень эффективно, так как он мог бы просто взять предмет из <f:selectItems>, Но самый важный момент Converter является то, что он возвращает уникальный String представление, так что объект Java может быть идентифицирован простым String подходит для передачи по HTTP и HTML.

Универсальный конвертер на основе toString()

Утилита JSF OmniFaces имеет SelectItemsConverter который работает на основе toString() результат сущности. Таким образом, вам не нужно возиться с getAsObject() и более дорогие операции с базами данных. Для некоторых конкретных примеров использования смотрите также витрину.

Чтобы использовать это, просто зарегистрируйте это как ниже:

<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">

И убедитесь, что toString() вашей Warehouse Сущность возвращает уникальное представление сущности. Например, вы можете напрямую вернуть идентификатор:

@Override
public String toString() {
    return String.valueOf(id);
}

Или что-то более читаемое / многоразовое:

@Override
public String toString() {
    return "Warehouse[id=" + id + "]";
}

Смотрите также:


Не имеет отношения к проблеме, поскольку в JSF 2.0 больше не требуется явно иметь List<SelectItem> как <f:selectItem> значение. Просто List<Warehouse> также будет достаточно.

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" var="warehouse"
        itemLabel="#{warehouse.name}" itemValue="#{warehouse}" />
</h:selectOneMenu>
private Warehouse selectedWarehouse;
private List<Warehouse> availableWarehouses;

Пример универсального конвертера JSF с ABaseEntity и идентификатором:

ABaseEntity.java

public abstract class ABaseEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    public abstract Long getIdentifier();
}

SelectItemToEntityConverter.java

@FacesConverter(value = "SelectItemToEntityConverter")
public class SelectItemToEntityConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext ctx, UIComponent comp, String value) {
        Object o = null;
        if (!(value == null || value.isEmpty())) {
            o = this.getSelectedItemAsEntity(comp, value);
        }
        return o;
    }

    @Override
    public String getAsString(FacesContext ctx, UIComponent comp, Object value) {
        String s = "";
        if (value != null) {
            s = ((ABaseEntity) value).getIdentifier().toString();
        }
        return s;
    }

    private ABaseEntity getSelectedItemAsEntity(UIComponent comp, String value) {
        ABaseEntity item = null;

        List<ABaseEntity> selectItems = null;
        for (UIComponent uic : comp.getChildren()) {
            if (uic instanceof UISelectItems) {
                Long itemId = Long.valueOf(value);
                selectItems = (List<ABaseEntity>) ((UISelectItems) uic).getValue();

                if (itemId != null && selectItems != null && !selectItems.isEmpty()) {
                    Predicate<ABaseEntity> predicate = i -> i.getIdentifier().equals(itemId);
                    item = selectItems.stream().filter(predicate).findFirst().orElse(null);
                }
            }
        }

        return item;
    }
}

И использование:

<p:selectOneMenu id="somItems" value="#{exampleBean.selectedItem}" converter="SelectItemToEntityConverter">
    <f:selectItem itemLabel="< select item >" itemValue="#{null}"/>
    <f:selectItems value="#{exampleBean.availableItems}" var="item" itemLabel="${item.identifier}" itemValue="#{item}"/>
</p:selectOneMenu>

Я добился этого, просто изменив тип «значения» в <h:selectOneMenu.. на String.

Другие вопросы по тегам