2010-09-29 21 views
16

Quiero usar los valores enum en un <h:selectManyCheckbox>. Las casillas de verificación se llenan correctamente, sin embargo, al seleccionar algunos valores y enviarlos, su tipo de tiempo de ejecución es String, y no enum. Mi código:Usar enumeración en h: selectManyCheckbox

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

clase UserController (SecurityRole es un tipo enum):

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

Cuando JSF llama al método setRoles, que contiene una lista de tipo String, y no el tipo de enumeración. ¿Algunas ideas? ¡Gracias!

Respuesta

35

Este problema no está específicamente relacionado con enumeraciones. Tendría el mismo problema con otros tipos de List para los cuales JSF tiene convertidores integrados, p. List<Integer>, List<Double>, etcétera.

El problema es que EL opera el tiempo de ejecución y que la información de tipo genérico se pierde durante el tiempo de ejecución. Así que, en esencia, JSF/EL no sabe nada sobre el tipo parametrizado de List y el valor predeterminado es String a menos que se especifique lo contrario en un Converter explícito. En teoría, habría sido posible usar hacks de reflexión desagradables con la ayuda de ParameterizedType#getActualTypeArguments(), pero los desarrolladores de JSF/EL pueden tener sus razones para no hacerlo.

Realmente necesita definir explícitamente un convertidor para esto. Desde JSF ya envía con una orden interna del EnumConverter (que no es independiente utilizable en este caso particular, ya que tiene que especificar el tipo de enumeración en tiempo de ejecución), sólo podría extenderse de la siguiente manera:

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

} 

y utilizarlo como siguiente:

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

o

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

Un poco más genérico (y hacky) solución sería almacenar el tipo enum como atributo de componente.

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

} 

Es utilizable en todo tipo de List<Enum> utilizando convertidor de Identificación genericEnumConverter. Para List<Double>, List<Integer>, etc. uno habría usado los convertidores integrados javax.faces.Double, javax.faces.Integer y así sucesivamente. El convertidor Enum integrado no es adecuado por la incapacidad de especificar el tipo de enumeración objetivo (a Class<Enum>) desde el lado de la vista. La biblioteca de utilidades JSF OmniFaces ofrece exactamente este convertidor out the box.

Tenga en cuenta que para una propiedad Enum normal, el EnumConverter incorporado ya es suficiente. JSF lo instanciará automágicamente con el tipo de enum objetivo correcto.

+1

No diría que es un hack de reflexión desagradable. los marcos hacen eso, más tipo de información, más felicidad. Los chicos de JSF probablemente se sienten abrumados por la complejidad de su creación, no tienen tiempo para esto. – irreputable

+0

+1 por la nueva palabra que aprendí: automágicamente :) – lamostreta

+0

@BalusC, gracias por otra respuesta más informativa y útil. Si puedo ser tan audaz, ¿puedo preguntar por qué eligió extender 'EnumConverter' en lugar de delegar en una instancia de la misma? – Nick

1

En algunos casos la Listapodría muy bien ser una matriz SomeType [], y en este caso no es necesario ningún convertidor explícita.

El borrado genérico fue una manera ingeniosa de poner los genéricos en el lenguaje sin romper las cosas viejas, pero ahora vivimos para siempre con las consecuencias de esa decisión ...