在 p:dataTable 过滤器中显式使用隐式 JSF 转换器 javax.faces.convert.EnumConverter

Using the implicit JSF converter javax.faces.convert.EnumConverter explicitly in p:dataTable filters

枚举:

public enum OrderStatus {

    New("New"),
    Paid("Paid"),
    Shipped("Shipped"),
    Completed("Completed");

    private final String label;

    private OrderStatus(String label) {
        this.label = label;
    }

    public String getLabel() {
        return label;
    }
}

使用 enum 输入 <p:dataTable> 过滤器,如下所示。

<p:dataTable var="row"
             value="#{testBacking}"
             lazy="true"
             rows="10"
             widgetVar="dataTableUIWidget">

    <p:column id="id" headerText="Id">
        <h:outputText value="#{row.orderId}"/>
    </p:column>

    <p:column headerText="Order Status" filterBy="#{row.orderStatus}">
        <f:facet name="filter">
            <p:selectOneMenu onchange="PF('dataTableUIWidget').filter();">
                <f:selectItem itemLabel="Select" itemValue=""/>

                <f:selectItems var="orderStatus"
                               value="#{enumBean.orderStatus}" 
                               itemLabel="#{orderStatus.label}"/>
            </p:selectOneMenu>
        </f:facet>

        <h:outputText value="#{row.orderStatus}"/>
    </p:column>
</p:dataTable>

row.orderStatus 是上述 enum 在其关联的 JPA 实体中的一种类型。

<p:selectOneMenu> 关联的过滤器需要明确指定 javax.faces.convert.EnumConverter,因为它的值未绑定到支持 bean 属性(否则适当的转换器会适当地播放它的角色基于关联的支持 bean 中 属性 的类型隐含地自行发挥作用。

我希望提到转换器如下

<f:converter converterId="javax.faces.Enum"/>

应该像其他隐式转换器一样工作。

但是,这会导致问题(当如上所述指定 <f:converter> 时)。

Severe: JSF1006: Cannot instantiate converter of type javax.faces.Enum

这个转换器有什么问题?我正在寻找一个可行的解决方案。

使用 PrimeFaces 5.2 最终版本/Mojarra 2.2.12。


补充:

converter 指定 javax.faces.EnumconverterId

public class EnumConverter implements Converter, PartialStateHolder {

    public static final String CONVERTER_ID = "javax.faces.Enum";
    public static final String ENUM_ID = "javax.faces.converter.EnumConverter.ENUM";
    public static final String ENUM_NO_CLASS_ID = "javax.faces.converter.EnumConverter.ENUM_NO_CLASS";
    private Class<? extends Enum> targetClass;
    private boolean isTransient;
    private boolean initialState;

    public EnumConverter() {}
}

因此,这应该通过将 javax.faces.Enum 指定为 <f:converter>convertId 作为其他隐式 JSF 转换器来工作。

上述测试用例中使用的 bean(完全可选以供同行评审):

@Named
@ViewScoped
public class TestBacking extends LazyDataModel<OrderTable> implements Serializable {

    @Inject
    private OrderService orderService;
    private static final long serialVersionUID = 1L;

    public TestBacking() {}

    @Override
    public List<OrderTable> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {

        if (MapUtils.isNotEmpty(filters)) { // Debugging purpose only.
            System.out.println("Enum filter : " + (filters.get("orderStatus") instanceof OrderStatus));
            System.out.println("Filter value : " + filters.get("orderStatus"));
        }

        int rowCount = MapUtils.isNotEmpty(filters) ? orderService.rowCount(filters).intValue() : orderService.rowCount().intValue();
        setRowCount(rowCount);

        return orderService.getList(first, pageSize, null, filters);
    }
}

load() 方法中的 stdout 语句显示以下输出。

Enum filter : false
Filter value : New

(filters.get("orderStatus") instanceof OrderStatus returns false 很明显,因为过滤器组件 <p:selectOneMenu> 没有通过转换器。它只是 returns String 没有转换)。

EnumConverter 是一个特殊的转换器,只能用 Class<E> 作为参数构造(最终设置为 targetClass)。没有它,转换器将无法工作。不幸的是,它并不是在所有枚举中都可用(即它实际上不是 "generic enum converter")。 在那里你假设 implicit/automatic 枚举转换,它实际上是由 EL 强制转换完成的,而不是由 JSF EnumConverter 完成的。只要目标类型可解析为枚举,EL 确实对枚举具有通用支持。

要显式使用 JSF 枚举转换器,您基本上需要像下面那样扩展 EnumConverter 以将目标枚举传递给 c'tor(无需覆盖 getAsString/Object() 方法):

@FacesConverter(value="orderStatusConverter")
public class OrderStatusConverter extends EnumConverter {

    public OrderStatusConverter() {
        super(OrderStatus.class);
    }

}

然后引用它:

<f:converter converterId="orderStatusConverter" />

不要忘记将 <f:selectItem itemValue=""> 更改为 itemValue="#{null}",否则您将在 java.lang.String 上获得 ClassCastException

如果您碰巧使用了 OmniFaces, you can also use its generic enum converter

<f:converter converterId="omnifaces.GenericEnumConverter" />