p:selectManyMenu и @FacesConverter(forClass = Clazz.class)

<p:selectManyMenu id="colourList"
                  var="color"
                  value="#{testBean.selectedColours}"
                  converter="#{colourConverter}"
                  showCheckbox="true"
                  required="true"
                  label="Colour"
                  style="overflow: auto; width: 317px; background-color: white; max-height: 200px;">

    <f:selectItems var="colour"
                   value="#{testBean.colours}"
                   itemLabel="#{colour.colourHex}"
                   itemValue="#{colour}"/>
    <p:column>
        <span style="display: inline-block; width: 275px; height: 20px; background-color:\##{color.colourHex}; border: 1px solid black;"
              title="Name: #{color.colourName} | Hex: #{color.colourHex}" />
    </p:column>
</p:selectManyMenu>

<p:commandButton value="Submit" actionListener="#{testBean.action}"/>

CSS остается без изменений, если кто-то захочет применить пример на практике. Он будет отображать три основных цвета (RGB) с флажками перед ними, как показано ниже.

введите описание изображения здесь

Управляемый боб:

@Named
@ViewScoped
public class TestBean implements Serializable {

    @Inject
    private DataStore dataStore;
    private List<Colour> colours; //Getter & setter.
    private List<Colour> selectedColours; //Getter & setter.
    private static final long serialVersionUID = 1L;

    public TestBean() {}

    @PostConstruct
    private void init() {
        colours = dataStore.getColours();
    }

    public void action() {
        for (Colour colour : selectedColours) {
            System.out.println("colourName : "
                    + colour.getColourName()
                    + " : colourHex : "
                    + colour.getColourHex());
        }
    }
}

Если converter="#{colourConverter}" атрибут удален из <p:selectManyMenu> то это вызывает java.lang.ClassCastException быть брошенным в action() метод, хотя конвертер украшен @FacesConverter(forClass = Colour.class),

java.lang.ClassCastException: java.lang.String cannot be cast to com.example.Colour

Похоже, что это проблема ластика универсального типа (параметр универсального типа List<Colour> удаляется во время выполнения, чтобы он превратился в нетипизированный List).

Colour[] затем должен работать, но action() сам метод не был вызван при попытке.

Какова точная причина, почему это требует явного упоминания конвертера?


Дополнительно:

Конвертер:

@Named
@ApplicationScoped
@FacesConverter(forClass = Colour.class)
public class ColourConverter implements Converter {

    @Inject
    private DataStore dataStore;

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

        try {
            long parsedValue = Long.parseLong(value);

            if (parsedValue <= 0) {
                throw new ConverterException("FacesMessage");
            }

            Colour entity = dataStore.findColourById(parsedValue);

            if (entity == null) {
                throw new ConverterException("FacesMessage");
            }
            return entity;
        } catch (NumberFormatException e) {
            throw new ConverterException("FacesMessage", e);
        }
    }

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

        if (!(value instanceof Colour)) {
            throw new ConverterException("Message");
        }

        Long id = ((Colour) value).getColourId();
        return id != null ? id.toString() : "";
    }
}

Бин области действия приложения, где List<Colour> поддерживается

@Named
@ApplicationScoped
public class DataStore {

    private List<Colour> colours;

    public DataStore() {}

    @PostConstruct
    private void init() {
        colours = new ArrayList<>();

        Colour colour = new Colour();
        colour.setColourId(1L);
        colour.setColourName("Red");
        colour.setColourHex("FF0000");
        colours.add(colour);

        colour = new Colour();
        colour.setColourId(3L);
        colour.setColourName("Green");
        colour.setColourHex("008000");
        colours.add(colour);

        colour = new Colour();
        colour.setColourId(2L);
        colour.setColourName("Blue");
        colour.setColourHex("0000FF");
        colours.add(colour);
    }

    public Colour findColourById(Long id) {
        for (Colour colour : colours) {
            if (colour.getColourId().equals(id)) {
                return colour;
            }
        }

        return null;
    }

    public List<Colour> getColours() {
        return colours;
    }
}

Класс доменной модели:

public class Colour implements Serializable {

    private Long colourId;
    private String colourName;
    private String colourHex;
    private static final long serialVersionUID = 1L;

    public Colour() {}

    public Long getColourId() {
        return colourId;
    }

    public void setColourId(Long colourId) {
        this.colourId = colourId;
    }

    public String getColourName() {
        return colourName;
    }

    public void setColourName(String colourName) {
        this.colourName = colourName;
    }

    public String getColourHex() {
        return colourHex;
    }

    public void setColourHex(String colourHex) {
        this.colourHex = colourHex;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 47 * hash + Objects.hashCode(getColourId());
        return hash;
    }

    @Override
    public boolean equals(Object that) {
        if (!(that instanceof Colour)) {
            return false;
        }

        return this == that || Objects.equals(getColourId(), ((Colour) that).getColourId());
    }

    @Override
    public String toString() {
        return String.format("%s[colourId=%d]", getClass().getCanonicalName(), getColourId());
    }
}

1 ответ

Решение

Эта проблема двоякая.

Первая проблема заключается в том, что EL не может определить тип значения модели, потому что информация общего типа теряется во время выполнения. В основном становится Object.class, Вам в основном нужно заменить List<Colour> от Colour[], На это подробно дан ответ в этом вопросе: UISelectMany в List вызывает java.lang.ClassCastException: java.lang.String не может быть приведен к T.

Вторая проблема заключается в том, что PrimeFaces InputRenderer есть ошибка, которая не учитывает типы массивов перед поиском конвертера. Номера строк ниже соответствуют 5.2

156    protected Converter findImplicitConverter(FacesContext context, UIComponent component) {
157        ValueExpression ve = component.getValueExpression("value");
158
159        if(ve != null) {
160            Class<?> valueType = ve.getType(context.getELContext());
161                
162            if(valueType != null)
163                return context.getApplication().createConverter(valueType);
164        }
165
166        return null;
167    }

В вашем конкретном случае valueType является Colour[].class вместо Colour.class, Это объясняет, почему он не может найти конвертер, связанный с Colour.class, Прежде чем создавать конвертер, он должен проверить, valueType является типом массива, и если это так, извлеките из него тип компонента.

if (valueType.isArray()) {
    valueType = valueType.getComponentType();
}

Лучше всего сообщить об этом как об ошибке ребятам из PrimeFaces, а также о том, что она работает нормально в стандартных компонентах, таких как <h:selectManyMenu>, Между тем, вам лучше всего просто явно зарегистрировать конвертер.

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