2010-09-29 7 views
16

Ich möchte Enum-Werte in einem <h:selectManyCheckbox> verwenden. Die Kontrollkästchen werden korrekt ausgefüllt. Wenn Sie jedoch einige Werte auswählen und übergeben, ist der Laufzeittyp String und nicht enum. Mein Code:Verwenden Sie enum in h: selectManyCheckbox

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

Usercontroller-Klasse (Security ist ein Aufzählungstyp):

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

Wenn JSF die setRoles Methode aufruft, enthält es eine Liste vom Typ String, und nicht der Aufzählungstyp. Irgendwelche Ideen? Vielen Dank!

Antwort

35

Dieses Problem bezieht sich nicht speziell auf Enums. Sie würden das gleiche Problem mit anderen List Typen haben, für die JSF eingebaute Konverter, z.B. List<Integer>, List<Double>, und so weiter.

Das Problem ist, dass EL Runtime betreibt und dass generische Informationen während der Laufzeit verloren gehen. Im Wesentlichen weiß JSF/EL also nichts über den parametrisierten Typ der List und die Standardwerte String, sofern nicht explizit durch Converter anders angegeben. Theoretisch wäre es mit Hilfe von ParameterizedType#getActualTypeArguments() möglich gewesen, widerliche Reflection-Hacks zu verwenden, aber die JSF/EL-Entwickler könnten ihre Gründe dafür haben, dies nicht zu tun.

Sie müssen wirklich explizit einen Konverter dafür definieren. Da JSF bereits Schiffe mit einer eingebauten EnumConverter (die nicht nutzbare Standalone in diesem besonderen Fall ist, weil Sie die Aufzählungstyp während der Laufzeit angeben müssen), könnten Sie erweitern es wie folgt:

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

} 

Und verwenden Sie es als folgt:

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

oder

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

Ein bisschen mehr Generika (und Hacky) Lösung wäre, den Enum-Typ als Komponentenattribut zu speichern.

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 ist einsetzbar auf allen Arten von List<Enum> Konverter ID mit genericEnumConverter. Für List<Double>, List<Integer> usw. hätte man die eingebauten Konverter javax.faces.Double, javax.faces.Integer und so weiter benutzt. Der eingebaute Enum-Konverter ist übrigens ungeeignet, da der Ziel-Enum-Typ (a Class<Enum>) von der Viewseite aus nicht spezifiziert werden kann. Die JSF-Dienstprogrammbibliothek OmniFaces bietet genau diesen Konverter out the box.

Beachten Sie, dass für eine normale Enum Eigenschaft der eingebaute EnumConverter bereits ausreicht. JSF wird es automatisch mit dem richtigen Zielaufzählungstyp instanziieren.

+1

Ich würde nicht sagen, das ist eine böse Reflexion hack. Frameworks tun das, mehr Typ Info, mehr Glück. JSF Jungs sind wahrscheinlich überwältigt von der Komplexität ihrer Kreation, sie haben keine Zeit dafür. – irreputable

+0

+1 für das neue Wort, das ich gelernt habe: automatisch :) – lamostreta

+0

@BalusC, danke für eine weitere informative und hilfreiche Antwort. Wenn ich so fett bin, darf ich fragen, warum Sie sich entschieden haben, 'EnumConverter' zu erweitern, anstatt an eine Instanz davon zu delegieren? – Nick

1

In einigen Fällen sind die Liste könnte genausogut ein Array Sometype sein [], und in diesem Fall keine expliziten Wandler benötigt wird.

Generische Löschung war eine clevere Art, Generika in die Sprache zu bringen, ohne die alten Sachen zu brechen, aber jetzt leben wir für immer mit den Konsequenzen dieser Entscheidung ...

Verwandte Themen