Valor de configuración de error de conversión para ‘convertidor nulo’ – ¿Por qué necesito un convertidor en JSF?

Tengo problemas para entender cómo usar la selección en JSF 2 con POJO / entidad de manera efectiva. Por ejemplo, estoy intentando seleccionar una entidad de Warehouse través del menú desplegable a continuación:

     

Y el frijol administrado a continuación:

 @Named @ViewScoped public class Bean { private Warehouse selectedWarehouse; private List availableWarehouses; // ... @PostConstruct public void init() { // ... availableWarehouses = new ArrayList(); for (Warehouse warehouse : warehouseService.listAll()) { availableWarehouses.add(new SelectItem(warehouse, warehouse.getName())); } } // ... } 

Tenga en cuenta que utilizo toda la entidad de Warehouse como el valor de SelectItem .

Cuando envío el formulario, esto falla con el siguiente mensaje de caras:

Error de conversión que establece el valor ‘com.example.Warehouse@cafebabe’ para ‘convertidor nulo’.

Esperaba que JSF pudiera simplemente establecer el objeto de Warehouse correcto para mi bean administrado cuando lo SelectItem en un SelectItem . Envolver mi entidad dentro de SelectItem estaba destinado a omitir la creación de un Converter para mi entidad.

¿Realmente tengo que usar un Converter siempre que quiera hacer uso de entidades en mi ? Debería ser posible para JSF solo extraer el elemento seleccionado de la lista de elementos disponibles. Si realmente tengo que usar un convertidor, ¿cuál es la forma práctica de hacerlo? Hasta ahora llegué a esto:

  1. Crea una implementación de Converter para la entidad.
  2. getAsString() el getAsString() . Creo que no es necesario, ya que la propiedad de etiqueta de SelectItem se utilizará para mostrar la etiqueta de la opción desplegable.
  3. Sobreescribiendo el getAsObject() . Creo que esto se utilizará para devolver el SelectItem o entidad correcto, según el tipo del campo seleccionado definido en el bean gestionado.

El getAsObject() me confunde. ¿Cuál es la manera eficiente de hacer esto? Teniendo el valor de la cadena, ¿cómo obtengo el objeto de la entidad asociada? ¿Debería consultar el objeto de entidad desde el objeto de servicio en función del valor de cadena y devolver la entidad? ¿O quizás de alguna manera puedo acceder a la lista de las entidades que forman los elementos de selección, recorrerlas en bucle para encontrar la entidad correcta y devolver la entidad?

¿Cuál es el enfoque normal de esto?

Introducción

JSF genera HTML. HTML está en términos de Java básicamente una String grande. Para representar objetos Java en HTML, deben convertirse a String . Además, cuando se envía un formulario HTML, los valores enviados se tratan como String en los parámetros de solicitud de HTTP. Bajo las cubiertas, JSF los extrae de HttpServletRequest#getParameter() que devuelve String .

Para convertir un objeto Java no estándar (es decir, no una String , Number o Boolean para el cual EL tiene conversiones integradas, o Date para la cual JSF proporciona la etiqueta ), realmente debe suministrar un Converter personalizado. SelectItem no tiene ningún propósito especial. Es solo un remanente de JSF 1.x cuando no fue posible suministrar, por ejemplo, List directamente a . Tampoco tiene un tratamiento especial en cuanto a tags y conversión.

getAsString ()

getAsString() implementar el método getAsString() de tal manera que el objeto Java deseado se represente en una representación de String única que se pueda utilizar como parámetro de solicitud HTTP. Normalmente, la ID técnica (la clave principal de la base de datos) se usa aquí.

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

Tenga en cuenta que devolver una cadena vacía en el caso de un valor de modelo nulo / vacío es significativo y requerido por el javadoc . Consulte también Uso de “Seleccione” f: seleccione Elemento con valor nulo / vacío dentro de ap ap: selectOneMenu .

getAsObject ()

getAsObject() implementar getAsObject() de tal manera que exactamente String representación de String que devuelve getAsString() se pueda convertir de nuevo exactamente en el mismo objeto Java especificado como modelValue en 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 otras palabras, debe ser técnicamente capaz de devolver el objeto devuelto como argumento modelValue de getAsString() y luego volver a pasar la cadena obtenida como argumento de getAsObject() en un bucle infinito.

Uso

Finalmente, solo anote el Converter con @FacesConverter para enganchar el tipo de objeto en cuestión, JSF se encargará automáticamente de la conversión cuando el tipo de Warehouse entre en escena:

 @FacesConverter(forClass=Warehouse.class) 

Ese fue el enfoque “canónico” de JSF. Después de todo, no es muy efectivo ya que también podría haber agarrado el artículo de . Pero el punto más importante de un Converter es que devuelve una representación de String única , de modo que el objeto Java podría identificarse mediante una String simple adecuada para pasar en HTTP y HTML.

Convertidor genérico basado en toString ()

La biblioteca de utilidades JSF OmniFaces tiene un SelectItemsConverter que funciona en función del resultado toString() de la entidad. De esta forma ya no es necesario getAsObject() con getAsObject() y costosas operaciones de negocio / base de datos. Para algunos ejemplos de uso concreto, vea también el escaparate .

Para usarlo, simplemente regístrelo de la siguiente manera:

  

Y asegúrese de que toString() de su entidad de Warehouse devuelve una representación única de la entidad. Por ejemplo, puede devolver directamente la ID:

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

O algo más legible / reutilizable:

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

Ver también:

  • Cómo llenar las opciones de h: selectOneMenu de la base de datos?
  • Conversor de entidad JSF genérico : para que no tenga que escribir un convertidor para cada entidad.
  • Usar enums en JSF selectitems – enums necesita ser tratado de manera un poco diferente
  • Cómo inyectar @EJB, @PersistenceContext, @Inject, @Autowired, etc. en @FacesConverter?

No relacionado con el problema, ya que JSF 2.0 ya no se requiere explícitamente tener un valor List como . Solo una List también sería suficiente.

     
 private Warehouse selectedWarehouse; private List availableWarehouses; 

Ejemplo de convertidor genérico JSF con ABaseEntity e identificador:

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 selectItems = null; for (UIComponent uic : comp.getChildren()) { if (uic instanceof UISelectItems) { Long itemId = Long.valueOf(value); selectItems = (List) ((UISelectItems) uic).getValue(); if (itemId != null && selectItems != null && !selectItems.isEmpty()) { Predicate predicate = i -> i.getIdentifier().equals(itemId); item = selectItems.stream().filter(predicate).findFirst().orElse(null); } } } return item; } } 

Y el uso: