2010-09-29 10 views
16

Desidero utilizzare i valori enum in un <h:selectManyCheckbox>. Le caselle di controllo vengono popolate correttamente, tuttavia, quando si selezionano alcuni valori e vengono inviati, il loro tipo di runtime è String e non enum. Il mio codice:Utilizzare enum in h: selectManyCheckbox

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

classe UserController (SecurityRole è 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); 
} 

Quando JSF chiama il metodo setRoles, contiene un elenco di tipo String, e non il tipo enum. Qualche idea? Grazie!

risposta

35

Questo problema non è specificamente correlato alle enumerazioni. Si avrebbe lo stesso problema con altri tipi di List per i quali JSF ha convertitori incorporati, ad es. List<Integer>, List<Double>, ecc.

Il problema è che EL gestisce il runtime e che le informazioni di tipo generico vengono perse durante il runtime. Quindi, in sostanza, JSF/EL non sa nulla del tipo parametrizzato di List e di default su String se non diversamente specificato da un esplicito Converter. In teoria, sarebbe stato possibile utilizzare gli hacker di riflessione con l'aiuto di ParameterizedType#getActualTypeArguments(), ma gli sviluppatori JSF/EL potrebbero avere le loro ragioni per non farlo.

È davvero necessario definire un convertitore per questo. Dal momento che JSF già fornito con un builtin EnumConverter (che non è standalone utilizzabile in questo caso particolare perché si deve specificare il tipo enum durante il runtime), si può solo estendere come segue:

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

} 

e usarlo come segue:

<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 po 'più generico (e hacky) soluzione sarebbe quella di memorizzare il tipo enum come attributo 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)); 
     } 
    } 

} 

E 'utilizzabile su tutti i tipi di List<Enum> utilizzando convertitore ID genericEnumConverter. Per List<Double>, List<Integer>, ecc. Si sarebbero utilizzati i convertitori incorporati javax.faces.Double, javax.faces.Integer e così via. Il convertitore Enum incorporato non è adatto a causa dell'incapacità di specificare il tipo di enumerazione target (a Class<Enum>) dal lato della vista. La libreria di utilità JSF OmniFaces offre esattamente questo convertitore out the box.

Si noti che per una normale proprietà Enum, il EnumConverter integrato è già sufficiente. JSF lo istanzia automaticamente con il giusto tipo di enumerazione target.

+1

Non direi che è un brutto riflesso. le strutture lo fanno, più informazioni sul tipo, più felicità. I ragazzi della JSF sono probabilmente sopraffatti dalla complessità della loro creazione, non hanno tempo per questo. – irreputable

+0

+1 per la nuova parola che ho imparato: automagicamente :) – lamostreta

+0

@BalusC, grazie per l'ennesima risposta informativa e utile. Se posso essere così audace, posso chiederti perché hai scelto di estendere 'EnumConverter' invece di delegare a un'istanza di esso? – Nick

1

In alcuni casi l'elencopotrebbe altrettanto bene essere un array SomeType [], e in questo caso non è necessario alcun convertitore esplicito.

La cancellazione generica era un modo intelligente di mettere i generici nella lingua senza rompere le vecchie cose, ma ora viviamo per sempre con le conseguenze di quella decisione ...

Problemi correlati