Use enum in h:selectManyCheckbox

别说谁变了你拦得住时间么 提交于 2019-11-26 05:54:00

问题


I want to use enum values in a <h:selectManyCheckbox>. The checkboxes get populated correctly, however, when selecting some values and submitting them, their runtime type is String, and not enum. My code:

<h:selectManyCheckbox value=\"#{userController.roles}\" layout=\"pageDirection\">
     <f:selectItems value=\"#{userController.rolesSelectMany}\" />
</h:selectManyCheckbox>

UserController class (SecurityRole is an enum type):

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

When JSF calls the setRoles method, it contains a list of type String, and not the enum type. Any ideas? Thanks!


回答1:


This problem is not specifically related to enums. You would have the same problem with other List types for which JSF has builtin converters, e.g. List<Integer>, List<Double>, etcetera.

The problem is that EL operates runtime and that generic type information is lost during runtime. So in essence, JSF/EL doesn't know anything about the parameterized type of the List and defaults to String unless otherwise specified by an explicit Converter. In theory, it would have been possible using nasty reflection hacks with help of ParameterizedType#getActualTypeArguments(), but the JSF/EL developers may have their reasons for not doing this.

You really need to explicitly define a converter for this. Since JSF already ships with a builtin EnumConverter (which isn't useable standalone in this particular case because you have to specify the enum type during runtime), you could just extend it as follows:

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

}

And use it as follows:

<h:selectManyCheckbox value="#{userController.roles}" converter="securityRoleConverter">
    <f:selectItems value="#{userController.rolesSelectMany}" />
</h:selectManyCheckbox>

or

<h:selectManyCheckbox value="#{userController.roles}">
    <f:converter converterId="securityRoleConverter" />
    <f:selectItems value="#{userController.rolesSelectMany}" />
</h:selectManyCheckbox>

A bit more generic (and hacky) solution would be to storing the enum type as component attribute.

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

}

It's useable on all kinds of List<Enum> using converter ID genericEnumConverter. For List<Double>, List<Integer>, etc one would have used the builtin converters javax.faces.Double, javax.faces.Integer and so on. The builtin Enum converter is by the way unsuitable due to the inability to specify the target enum type (a Class<Enum>) from the view side on. The JSF utility library OmniFaces offers exactly this converter out the box.

Note that for a normal Enum property, the builtin EnumConverter already suffices. JSF will instantiate it automagically with the right target enum type.




回答2:


In some cases the List could just as well be an array SomeType[], and in this case no explicit converter is needed.

Generic erasure was a clever way of putting generics into the language without breaking the old stuff, but now we live forever with the consequences of that decision...



来源:https://stackoverflow.com/questions/3822058/use-enum-in-hselectmanycheckbox

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!