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:
- créer une mise en œuvre
Converter
pour l'entité. - substitution de la
getAsString()
. Je pense que je n'en ai pas besoin puisque la propriété label duSelectItem
sera utilisée pour afficher l'étiquette de l'option dropdown. - surpassant le
getAsObject()
. Je pense que cela sera utilisé pour retourner le bonSelectItem
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?
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:
- Comment remplir les options de H:selectOneMenu à partir de la base de données?
- convertisseur Générique d'entité JSF - de sorte que vous n'avez pas besoin d'écrire un convertisseur pour chaque entité.
- utilisation d'énums dans les sélections JSF - les énums doivent être traités un peu autrement
- comment injecter @EJB, @PersistenceContext, @Inject, @Autowired, etc dans @FacesConverter?
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;
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>