2010-09-29 13 views
16

Je souhaite utiliser les valeurs enum dans un <h:selectManyCheckbox>. Les cases à cocher sont remplies correctement, cependant, lorsque vous sélectionnez des valeurs et que vous les soumettez, leur type d'exécution est String et non enum. Mon code:Utiliser enum dans h: selectManyCheckbox

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

classe UserController (SecurityRole est un type 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); 
} 

Lorsque JSF appelle la méthode setRoles, il contient une liste de type String, et non le type enum. Des idées? Merci!

Répondre

35

Ce problème n'est pas spécifiquement lié à enums. Vous auriez le même problème avec d'autres types List pour lesquels JSF a des convertisseurs intégrés, par ex. List<Integer>, List<Double>, etc.

Le problème est que EL exploite l'exécution et que les informations de type générique sont perdues pendant l'exécution. Donc, en substance, JSF/EL ne sait rien sur le type paramétré du List et vaut String par défaut sauf indication contraire par un Converter explicite. En théorie, il aurait été possible d'utiliser des hacks de réflexion désagréables avec l'aide de ParameterizedType#getActualTypeArguments(), mais les développeurs JSF/EL peuvent avoir leurs raisons pour ne pas le faire.

Vous avez vraiment besoin de définir explicitement un convertisseur pour cela. Depuis JSF déjà livré avec une commande interne EnumConverter (qui n'est pas autonome utilisable dans ce cas particulier parce que vous devez spécifier le type ENUM lors de l'exécution), vous pouvez simplement l'étendre comme suit:

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

} 

et l'utiliser comme suit:

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

ou

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

Un peu plus générique (et hacky) solution serait de stocker le type enum comme attribut de composant.

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

} 

Il est utilisable sur toutes sortes de List<Enum> en utilisant ID convertisseur genericEnumConverter. Pour List<Double>, List<Integer>, etc. on aurait utilisé les convertisseurs intégrés javax.faces.Double, javax.faces.Integer et ainsi de suite. Le convertisseur Enum intégré est d'ailleurs inadapté en raison de l'impossibilité de spécifier le type enum cible (un Class<Enum>) du côté de la vue. La bibliothèque utilitaire JSF OmniFaces offre exactement ce convertisseur out the box. Notez que pour une propriété Enum normale, le EnumConverter intégré suffit déjà. JSF va l'instancier automagiquement avec le bon type enum cible.

+1

Je ne dirais pas que c'est un mauvais reflet. cadres font cela, plus d'informations de type, plus de bonheur. Les gars de JSF sont probablement dépassés par la complexité de leur création, ils n'ont pas le temps pour ça. – irreputable

+0

+1 pour le nouveau mot que j'ai appris: automagically :) – lamostreta

+0

@BalusC, je vous remercie pour une autre réponse informative et utile. Si je peux être si gras, puis-je demander pourquoi vous avez choisi d'étendre 'EnumConverter' au lieu de déléguer à une instance de celui-ci? – Nick

1

Dans certains cas, la liste pourrait tout aussi bien être un tableau UnType [], et dans ce cas, aucun convertisseur explicite est nécessaire.

L'effacement générique était une façon astucieuse de mettre les génériques dans la langue sans casser les vieilles choses, mais maintenant nous vivons à jamais avec les conséquences de cette décision ...