web-dev-qa-db-ja.com

h:selectManyCheckboxで列挙型を使用します

<h:selectManyCheckbox>で列挙値を使用したい。チェックボックスは正しく入力されますが、一部の値を選択して送信すると、実行時型はStringであり、列挙型ではありません。私のコード:

<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型のリストが含まれます。何か案は?ありがとう!

20
Theo

この問題は、特に列挙型とは関係ありません。 JSFにコンバーターが組み込まれている他のListタイプでも、同じ問題が発生します。 _List<Integer>_、_List<Double>_など。

問題は、ELが実行時に動作し、実行中にジェネリック型の情報が失われることです。したがって、本質的に、JSF/ELはListのパラメーター化された型について何も知らず、明示的なStringで特に指定されていない限り、デフォルトでConverterになります。理論的には、 ParameterizedType#getActualTypeArguments() の助けを借りて厄介なリフレクションハックを使用することは可能でしたが、JSF/EL開発者にはこれを行わない理由があるかもしれません。

このためのコンバーターを明示的に定義する必要があります。 JSFにはすでに組み込みの EnumConverter が付属しているため(実行時に列挙型を指定する必要があるため、この特定のケースではスタンドアロンでは使用できません)、次のように拡張できます。 :

_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>
_

もう少し一般的な(そしてハッキーな)解決策は、列挙型をコンポーネント属性として格納することです。

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

}
_

コンバーターIDgenericEnumConverterを使用して、あらゆる種類の_List<Enum>_で使用できます。 _List<Double>_、_List<Integer>_などの場合、組み込みのコンバーター_javax.faces.Double_、_javax.faces.Integer_などを使用します。組み込みの列挙型コンバーターは、ビュー側からターゲット列挙型(_Class<Enum>_)を指定できないため、ちなみに不適切です。 JSFユーティリティライブラリ OmniFaces はまさにこのコンバータを提供します 箱から出して

通常のEnumプロパティの場合、組み込みのEnumConverterですでに十分であることに注意してください。 JSFは、適切なターゲット列挙型を使用して自動的にインスタンス化します。

36
BalusC

場合によっては、Listも配列SomeType []である可能性があり、この場合、明示的なコンバーターは必要ありません。

ジェネリック消去は、古いものを壊すことなくジェネリックを言語に組み込む賢い方法でしたが、今ではその決定の結果で永遠に生きています...

1
Mike Hanafey