web-dev-qa-db-fra.com

Erreur de conversion lors de la définition de la valeur pour 'Convertisseur nul' - Pourquoi ai-je besoin d'un convertisseur en JSF?

J'ai des problèmes pour comprendre comment utiliser efficacement la sélection dans JSF 2 avec POJO/entité. Par exemple, j'essaie de sélectionner une entité Warehouse via la liste déroulante ci-dessous:

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" />
</h:selectOneMenu>

Et le bean géré ci-dessous:

@Named
@ViewScoped
public class Bean {

    private Warehouse selectedWarehouse;
    private List<SelectItem> availableWarehouses;

    // ...

    @PostConstruct
    public void init() {
        // ...

        availableWarehouses = new ArrayList<>();

        for (Warehouse warehouse : warehouseService.listAll()) {
            availableWarehouses.add(new SelectItem(warehouse, warehouse.getName()));
        }
    }

    // ...
}

Notez que j'utilise l'entité Warehouse entière comme valeur de SelectItem.

Lorsque je soumets le formulaire, cela échoue avec le message de visages suivant:

Erreur de conversion en définissant la valeur "com.example.Warehouse@cafebabe" pour "Convertisseur nul".

J'espérais que JSF pourrait simplement définir l'objet Warehouse correct sur mon bean géré lorsque je l'enveloppe dans un SelectItem. Envelopper mon entité dans le SelectItem était censé ignorer la création d'un Converter pour mon entité.

Dois-je vraiment utiliser un Converter chaque fois que je veux utiliser des entités dans mon <h:selectOneMenu>? Il devrait être possible pour JSF d'extraire simplement l'élément sélectionné de la liste des éléments disponibles. Si je dois vraiment utiliser un convertisseur, quelle est la manière pratique de le faire? Jusqu'à présent, je suis arrivé à ceci:

  1. Créez une implémentation Converter pour l'entité.
  2. Substitution de la getAsString(). Je pense que je n'en ai pas besoin car la propriété label du SelectItem sera utilisée pour afficher le libellé de l'option déroulante.
  3. Substitution de la getAsObject(). Je pense que cela sera utilisé pour retourner la bonne SelectItem ou l'entité en fonction du type du champ sélectionné défini dans le bean géré.

La getAsObject() me confond. Quelle est la manière efficace de procéder? Ayant la valeur de chaîne, comment puis-je obtenir l'objet entité associé? Dois-je interroger l'objet entité à partir de l'objet service en fonction de la valeur de chaîne et renvoyer l'entité? Ou peut-être que je peux accéder à la liste des entités qui forment les éléments de sélection, les boucler pour trouver l'entité correcte et renvoyer l'entité?

Quelle est l'approche normale de cela?

37
bertie

Introduction

JSF génère du HTML. Le HTML est en Java termes essentiellement un grand String. Pour représenter Java objets en HTML, ils doivent être convertis en String. De plus, lorsqu'un formulaire HTML est soumis, les valeurs soumises sont traitées comme String dans les paramètres de requête HTTP. Sous les couvertures, JSF les extrait du HttpServletRequest#getParameter() qui renvoie String.

Pour effectuer la conversion entre un objet Java non standard (c'est-à-dire pas un String, Number ou Boolean pour lequel EL a intégré des conversions, ou Date pour laquelle JSF fournit une balise intégrée <f:convertDateTime> ), vous devez vraiment fournir un --- Converter personnalisé. Le SelectItem n'a aucun but particulier. C'est juste un reste de JSF 1.x quand il n'était pas possible de fournir par exemple List<Warehouse> Directement vers <f:selectItems>. Il n'a pas non plus de traitement spécial quant aux étiquettes et à la conversion.

getAsString ()

Vous devez implémenter la méthode getAsString() de telle manière que l'objet Java souhaité soit représenté dans un unique String représentation qui peut être utilisée comme paramètre de requête HTTP. Normalement, l'ID technique (la clé primaire de la base de données) est utilisé ici.

public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
    if (modelValue == null) {
        return "";
    }

    if (modelValue instanceof Warehouse) {
        return String.valueOf(((Warehouse) modelValue).getId());
    } else {
        throw new ConverterException(new FacesMessage(modelValue + " is not a valid Warehouse"));
    }
}

Notez que le retour d'une chaîne vide en cas de valeur de modèle nulle/vide est significatif et requis par javadoc . Voir aussi tilisation d'un "Veuillez sélectionner" f: selectItem avec une valeur nulle/vide dans un p: selectOneMen .

getAsObject ()

Vous devez implémenter getAsObject() de telle sorte que exactement cette représentation String telle que renvoyée par getAsString() peut être reconverti en exactement le même objet Java spécifié comme modelValue dans getAsString().

public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
    if (submittedValue == null || submittedValue.isEmpty()) {
        return null;
    }

    try {
        return warehouseService.find(Long.valueOf(submittedValue));
    } catch (NumberFormatException e) {
        throw new ConverterException(new FacesMessage(submittedValue + " is not a valid Warehouse ID"), e);
    }
}

En d'autres termes, vous devez être techniquement en mesure de renvoyer l'objet renvoyé en tant qu'argument modelValue de getAsString(), puis de renvoyer la chaîne obtenue en tant qu'argument submittedValue de getAsObject() dans une boucle infinie.

Usage

Enfin juste annoter le Converter avec @FacesConverter pour accrocher le type d'objet en question, JSF se chargera alors automatiquement de la conversion lorsque Warehouse le type entre dans l'image:

@FacesConverter(forClass=Warehouse.class)

C'était l'approche JSF "canonique". Ce n'est après tout pas très efficace car il aurait pu aussi juste récupérer l'objet du <f:selectItems>. Mais le point le plus important d'une Converter est qu'elle renvoie une représentation unique String, de sorte que la Java l'objet pourrait être identifié par un simple String adapté pour passer autour de HTTP et HTML.

Convertisseur générique basé sur toString ()

La bibliothèque d'utilitaires JSF OmniFaces a un SelectItemsConverter qui fonctionne en fonction du résultat toString() de l'entité. De cette façon, vous n'avez plus besoin de manipuler getAsObject() et les opérations commerciales/de base de données coûteuses. Pour quelques exemples d'utilisation concrète, voir aussi showcase .

Pour l'utiliser, enregistrez-le comme ci-dessous:

<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">

Et assurez-vous que la toString() de votre entité Warehouse renvoie une représentation unique de l'entité. Vous pouvez par exemple renvoyer directement l'ID:

@Override
public String toString() {
    return String.valueOf(id);
}

Ou quelque chose de plus lisible/réutilisable:

@Override
public String toString() {
    return "Warehouse[id=" + id + "]";
}

Voir également:


Unrelated au problème, car depuis JSF 2.0, il n'est plus explicitement requis d'avoir une valeur List<SelectItem> Comme valeur <f:selectItem>. Un simple List<Warehouse> Suffirait également.

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" var="warehouse"
        itemLabel="#{warehouse.name}" itemValue="#{warehouse}" />
</h:selectOneMenu>
private Warehouse selectedWarehouse;
private List<Warehouse> availableWarehouses;
66
BalusC

Exemple de convertisseur générique JSF avec ABaseEntity et identifiant:

ABaseEntity.Java

public abstract class ABaseEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    public abstract Long getIdentifier();
}

SelectItemToEntityConverter.Java

@FacesConverter(value = "SelectItemToEntityConverter")
public class SelectItemToEntityConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext ctx, UIComponent comp, String value) {
        Object o = null;
        if (!(value == null || value.isEmpty())) {
            o = this.getSelectedItemAsEntity(comp, value);
        }
        return o;
    }

    @Override
    public String getAsString(FacesContext ctx, UIComponent comp, Object value) {
        String s = "";
        if (value != null) {
            s = ((ABaseEntity) value).getIdentifier().toString();
        }
        return s;
    }

    private ABaseEntity getSelectedItemAsEntity(UIComponent comp, String value) {
        ABaseEntity item = null;

        List<ABaseEntity> selectItems = null;
        for (UIComponent uic : comp.getChildren()) {
            if (uic instanceof UISelectItems) {
                Long itemId = Long.valueOf(value);
                selectItems = (List<ABaseEntity>) ((UISelectItems) uic).getValue();

                if (itemId != null && selectItems != null && !selectItems.isEmpty()) {
                    Predicate<ABaseEntity> predicate = i -> i.getIdentifier().equals(itemId);
                    item = selectItems.stream().filter(predicate).findFirst().orElse(null);
                }
            }
        }

        return item;
    }
}

Et utilisation:

<p:selectOneMenu id="somItems" value="#{exampleBean.selectedItem}" converter="SelectItemToEntityConverter">
    <f:selectItem itemLabel="< select item >" itemValue="#{null}"/>
    <f:selectItems value="#{exampleBean.availableItems}" var="item" itemLabel="${item.identifier}" itemValue="#{item}"/>
</p:selectOneMenu>
3
proxymo