Valeur de paramètre D'erreur de Conversion pour 'convertisseur null' - Pourquoi ai-je besoin d'un convertisseur dans JSF?

j'ai des problèmes à comprendre comment utiliser la sélection dans JSF 2 avec POJO/entity efficacement. 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 dessous de managed bean:

@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 la totalité de Warehouse comme valeur de SelectItem .

lorsque je soumets le formulaire, cela ne fonctionne pas avec le message suivant:

Erreur de Conversion valeur de réglage de la 'com.exemple.Warehouse@cafebabe 'Pour' null Converter'.

j'espérais que JSF pourrait simplement régler le bon Warehouse objet à mon haricot géré quand je l'envelopper dans un SelectItem . Envelopper mon entité dans le SelectItem était destiné à sauter 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 JSF être possible de les extraire l'élément sélectionné de la liste des éléments disponibles. Si je dois vraiment utiliser un convertisseur, Quelle est la façon pratique de le faire? Jusqu'à présent, je suis arrivé à ceci:

  1. créer une mise en œuvre Converter pour l'entité.
  2. substitution de la getAsString() . Je pense que je n'en ai pas besoin puisque la propriété label du SelectItem sera utilisée pour afficher l'étiquette de l'option dropdown.
  3. surpassant le getAsObject() . Je pense que cela sera utilisé pour retourner le bon SelectItem ou entité selon le type du champ sélectionné défini dans le haricot géré.

Le getAsObject() me confond. Quelle est la manière efficace de faire cela? Avec la valeur string, comment obtenir l'objet entity associé? Dois-je interroger l'objet entité à partir de l'objet de service en fonction de la valeur de la chaîne et retourner 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 retourner l'entité?

Quelle est l'approche normale de ceci?

32
demandé sur BalusC 2011-01-19 14:17:17

2 réponses

Introduction

JSF génère HTML. HTML est en langage Java essentiellement un grand String . Pour représenter des objets Java 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 convertir entre a objet Java non standard (c'est-à-dire pas un String , Number ou Boolean pour lequel EL a conversions, ou Date pour lequel JSF fournit une étiquette <f:convertDateTime> ), vous devez vraiment fournir un Converter . Le SelectItem n'a aucun but particulier. C'est juste un résidu de JSF 1.x lorsqu'il n'était pas possible de fournir par exemple List<Warehouse> directement à <f:selectItems> . Il a également aucun traitement spécial en ce qui concerne les étiquettes et la conversion.

getAsString ()

vous devez implémenter la méthode getAsString() de telle sorte que l'objet Java désiré soit représenté dans une représentation unique String 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 retourner une chaîne vide dans le cas d'un modèle nul/vide, la valeur est significative et requise par le javadoc . Voir aussi en utilisant un" s'il vous plaît sélectionner "f:selectItem with null/empty value inside a p:selectOneMenu .

getAsObject ()

Vous avez besoin de mettre en œuvre getAsObject() de telle façon que exactement que String une représentation retourné par getAsString() peut être converti en exactement le même objet Java spécifié en 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 capable de passer de nouveau l'objet retourné comme modelValue argument getAsString() et ensuite passer à la chaîne comme submittedValue argument getAsObject() dans une boucle infinie.

Utilisation

enfin il suffit d'Annoter le Converter avec @FacesConverter pour accrocher sur le type d'objet en question, JSF se chargera alors automatiquement de la conversion lorsque le type Warehouse entrera dans l'image:

@FacesConverter(forClass=Warehouse.class)

C'était l'approche" canonique " du JSF. Après tout, ce n'est pas très efficace car il aurait pu en effet tout simplement avoir saisi l'article du <f:selectItems> . Mais le point le plus important d'un Converter est qu'elle renvoie un uniques Représentation String , de sorte que L'objet Java puisse être identifié par un simple String approprié pour passer en HTTP et HTML.

convertisseur Générique basé sur toString ()

JSF utility library OmniFaces a une SelectItemsConverter qui travaille sur des toString() résultat de l'entité. De cette façon, vous n'avez pas besoin de jouer avec getAsObject() et coûteux business/base de données les opérations de plus. Pour des exemples d'utilisation concrète, voir aussi la vitrine .

pour l'utiliser, il suffit de l'enregistrer comme suit:

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

et assurez-vous que le toString() de votre Warehouse entité retourne une représentation unique de l'entité. Vous pouvez par exemple retourner 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 aussi:


N'est pas lié au problème, puisque JSF 2.0 Il n'est plus explicitement requis d'avoir une valeur List<SelectItem> comme <f:selectItem> . Un List<Warehouse> suffirait aussi.

<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;
59
répondu BalusC 2017-05-23 12:34:45

exemple de convertisseur Générique JSF avec ABaseEntity et Identificateur:

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
répondu proxymo 2017-08-02 16:49:30