Значение параметра Ошибка преобразования для «нулевого конвертера». Зачем мне нужен конвертер в JSF? - PullRequest
37 голосов
/ 19 января 2011

У меня проблемы с пониманием того, как эффективно использовать выделение в 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 'для' null Converter '.

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

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

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

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

Каков обычный подход к этому?

Ответы [ 2 ]

66 голосов
/ 19 января 2011

Введение

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 с нулевым / пустым значением внутри p: 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() и дорогими бизнес-операциями / операциями с базами данных. Некоторые конкретные примеры использования см. Также showcase .

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

<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;
3 голосов
/ 02 августа 2017

Пример универсального преобразователя 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>
...