miércoles, 9 de enero de 2013

Converters en JSF

Manejo de Converters en JSF

JSF es un potente framework para desarrollo de software web, el cual nos ofrece múltiples herramientas para simplificar nuestras labores como web developers. Una de estas herramientas son los convertidores (Converters) los cuales nos permiten manipular objetos Java en algunos componentes de nuestra aplicación (POJO por su sigla en inglés). Esto nos posibilita acceder a todos los atributos de nuestros objetos directamente desde los archivos xhtml o jsp. Para explicar mejor las ventajas, veamos el siguiente ejemplo (usando la librería PrimeFaces):

Usualmente, el manejo de un selectOneMenu sería el siguiente (sin Converter):


index.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p ="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Ejemplo de JSF Converter</title>
    </h:head>
    <h:body>
        <h:form>
            <h:outputLabel value="Seleccione una persona" for="somPersonas"/>
            <p:selectOneMenu id="somPersonas" value="#{converterExampleBean.valorSeleccionado}">
                <f:selectItems value="#{converterExampleBean.items}"/>
            </p:selectOneMenu>
            <p:commandButton value="Mostrar datos" process="@form" update="@form" actionListener="#{converterExampleBean.buscarPersona()}"/>
            <h:panelGrid columns="5" border="2">
                <h:outputText value="Identificación"/>
                <h:outputText value="Nombres"/>
                <h:outputText value="Apellidos"/>
                <h:outputText value="Dirección"/>
                <h:outputText value="Teléfono"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.identificacion}"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.nombres}"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.apellidos}"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.direccion}"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.telefono}"/>
            </h:panelGrid>            
        </h:form>
    </h:body>
</html>



Persona.java

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.siesoftware.converterexample;
/**
 *
 * @author Sergio
 */
public class Persona {
private String nombres,apellidos,identificacion,
direccion,telefono;
public Persona() {
}
public Persona(String nombres, String apellidos, String identificacion, String direccion, String telefono) {
this.nombres = nombres;
this.apellidos = apellidos;
this.identificacion = identificacion;
this.direccion = direccion;
this.telefono = telefono;
}
public String getNombres() {
return nombres;
}
public void setNombres(String nombres) {
this.nombres = nombres;
}
public String getApellidos() {
return apellidos;
}
public void setApellidos(String apellidos) {
this.apellidos = apellidos;
}
public String getIdentificacion() {
return identificacion;
}
public void setIdentificacion(String identificacion) {
this.identificacion = identificacion;
}
public String getDireccion() {
return direccion;
}
public void setDireccion(String direccion) {
this.direccion = direccion;
}
public String getTelefono() {
return telefono;
}
public void setTelefono(String telefono) {
this.telefono = telefono;
}
}

ConverterExampleBean.java


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.siesoftware.converterexample.GUI;
import com.siesoftware.converterexample.Persona;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.model.SelectItem;
/**
 *
 * @author Sergio
 */
@ManagedBean
@ViewScoped
public class ConverterExampleBean {
private String valorSeleccionado;
private List<SelectItem>items;
private HashMap hm;
private Persona personaSeleccionada;
/**
     * Creates a new instance of ConverterExampleBean
     */
public ConverterExampleBean() {
}
@PostConstruct
public void init(){
List<Persona>personas=generarPersonas();
hm=new HashMap();
for (int i = 0; i < personas.size(); i++) {
Persona persona=personas.get(i);
hm.put(persona.getIdentificacion(), persona);
}
crearItems(personas);
}
public List<Persona> generarPersonas(){
List<Persona>personaList=new ArrayList<Persona>();
String[]nombres={"Juan Carlos","Luis Daniel","María Cristina","Pedro","Ana"};
String[]apellidos={"Huertas Molina","Rodríguez Tamayo","Velandia Calderón","Guzmán Ospina","Jiménez Bernal"};
String[]ids={"28485996","889988-00990","8822398852","9283792834","112123192312"};
String[]direcciones={"Calle 56 No 32 45","Av américas No 6C 43","Carrera 43 No 32 19","Autopista Norte No 129 87","Av Circunvalar No 78 41"};
String[]telefonos={"3998998","2199495","4890098","5788992","6849596"};
for(int i=0;i<nombres.length;i++){
Persona p=new Persona(nombres[i],apellidos[i],ids[i], direcciones[i],telefonos[i]);
personaList.add(p);
}
return personaList;
}
public void crearItems(List<Persona>p){
items=new ArrayList<SelectItem>();
for (int i = 0; i < p.size(); i++) {
Persona persona = p.get(i);
items.add(new SelectItem(persona.getIdentificacion(), persona.getNombres()+" "+persona.getApellidos()));
}
}
public void buscarPersona(){
personaSeleccionada=(Persona)hm.get(valorSeleccionado);
}
public String getValorSeleccionado() {
return valorSeleccionado;
}
public void setValorSeleccionado(String valorSeleccionado) {
this.valorSeleccionado = valorSeleccionado;
}
public List<SelectItem> getItems() {
return items;
}
public void setItems(List<SelectItem> items) {
this.items = items;
}
public Persona getPersonaSeleccionada() {
return personaSeleccionada;
}
public void setPersonaSeleccionada(Persona personaSeleccionada) {
this.personaSeleccionada = personaSeleccionada;
}
}

En este ejemplo, creamos un listado de personas con algunos atributos, luego, para simular una base de datos se ha empleado un HashMap en donde se guarda este listado y se coloca como llave la identificación de la persona y finalmente, hacemos un método que, de acuerdo al valor seleccionado en el selectOneMenu, busque la persona por su número de identificación y nos traiga los datos (tal como se haría con una base de datos). El resultado es el siguiente:


Incialmente, se muestra el selectOneMenu

Despliegue del listado de personas

Selección de la persona

Muestra de datos

Como se puede apreciar en este ejemplo, se ha asignado un método de búsqueda al botón, de este modo, obtenemos los datos de la persona mediante su número de identificación.

Ahora, vamos a realizar el mismo proceso empleando un Converter. Pero primero debemos tener claro cómo funciona dicho Converter: 
Converter, es una interfaz java, la cual tiene dos métodos: getAsObject y getAsString. De este modo, cuando en un componente se emplea un Converter, JSF buscará el convertidor declarado y ejecutará estos métodos para obtener los objetos que se incluyen en el componente; generalmente estos converters se emplean en componentes de selección tales como selectOneMenu, selectOneCheckbox, selectOneRadio, entre otros. Así entonces, al seleccionar uno de los elementos del componente de selección, obtendremos no uno de sus atributos (como el id, en este caso, el cual se asigna mediante el atributo value) sino el objeto como tal. En síntesis, el proceso que se lleva a cabo al asignar un convertidor a un componente es encapsular y desencapsular el objeto mediente los mencionados métodos. En el siguiente código se documenta este proceso:

index.xhtml






<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p ="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Ejemplo de JSF Converter</title>
    </h:head>
    <h:body>
        <h:form>
            <h:outputLabel value="Seleccione una persona" for="somPersonas"/>
            <!--Primer cambio: el valor del selectOneMenu ya no es un String, sino  un objeto, además se 
            indica que se va a emplear un converter-->
            <p:selectOneMenu id="somPersonas" value="#{converterExampleBean.personaSeleccionada}" converter="personaConverter">
                <!--Segundo cambio: El arreglo para los ítems ya no es de tipo SelectItem sino de objetos,
                así se debe declarar un nombre de variable en el atributo "var", se debe indicar
                la etiqueta que queremos que aparezca en la interfaz y se asigna el objeto como valor del ítem-->
                <f:selectItems value="#{sessionBean.personaList}" var="pers" itemLabel="#{pers.nombres} #{pers.apellidos}"
                               itemValue="#{pers}"/>
            </p:selectOneMenu>
            <!--Tercer cambio: El botón ya no debe ejecutar la búsqueda-->
            <p:commandButton value="Mostrar datos" process="@form" update="@form"/>
            <h:panelGrid columns="5" border="2">
                <h:outputText value="Identificación"/>
                <h:outputText value="Nombres"/>
                <h:outputText value="Apellidos"/>
                <h:outputText value="Dirección"/>
                <h:outputText value="Teléfono"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.identificacion}"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.nombres}"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.apellidos}"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.direccion}"/>
                <h:outputText value="#{converterExampleBean.personaSeleccionada.telefono}"/>
            </h:panelGrid>            
        </h:form>
    </h:body>
</html>



ConverterExampleBean.java


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.siesoftware.converterexample.GUI;

import com.siesoftware.converterexample.Persona;
import com.siesoftware.converterexample.SessionBean;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;

/**
 *
 * @author Sergio
 */
@ManagedBean
@ViewScoped
public class ConverterExampleBean {
    
    /**
     * Como es necesario obtener el listado de personas en el converter, el
     * manejo de este listado se ha trasladado a un bean de scope session
     */
    @ManagedProperty(value="#{sessionBean}")
    private SessionBean sessionBean;
    
    private Persona personaSeleccionada;
    
    

    /**
     * Creates a new instance of ConverterExampleBean
     */
    public ConverterExampleBean() {
    }
    
    @PostConstruct
    public void init(){
    }
    
    

    public Persona getPersonaSeleccionada() {
        return personaSeleccionada;
    }

    public void setPersonaSeleccionada(Persona personaSeleccionada) {
        this.personaSeleccionada = personaSeleccionada;
    }

    public SessionBean getSessionBean() {
        return sessionBean;
    }

    public void setSessionBean(SessionBean sessionBean) {
        this.sessionBean = sessionBean;
    }
    
}

SessionBean.java
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.siesoftware.converterexample;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

/**
 *
 * @author Sergio
 * Este bean se ha creado para manipular el listado de personas tanto en el bean
 * inherente a la vista como en el converter
 */
@ManagedBean
@SessionScoped
public class SessionBean {
    
    private HashMap hm;
    private List<Persona>personaList;

    /**
     * Creates a new instance of SessionBean
     */
    public SessionBean() {
    }
    
    @PostConstruct
    public void init(){
        generarPersonas();
        hm=new HashMap();
        for (int i = 0; i < personaList.size(); i++) {
            Persona persona = personaList.get(i);
            hm.put(persona.getIdentificacion(), persona);
        }
    }
    
    public void generarPersonas(){
        personaList=new ArrayList<Persona>();
        String[]nombres={"Juan Carlos","Luis Daniel","María Cristina","Pedro","Ana"};
        String[]apellidos={"Huertas Molina","Rodríguez Tamayo","Velandia Calderón","Guzmán Ospina","Jiménez Bernal"};
        String[]ids={"28485996","889988-00990","8822398852","9283792834","112123192312"};
        String[]direcciones={"Calle 56 No 32 45","Av américas No 6C 43","Carrera 43 No 32 19","Autopista Norte No 129 87","Av Circunvalar No 78 41"};
        String[]telefonos={"3998998","2199495","4890098","5788992","6849596"};
        for(int i=0;i<nombres.length;i++){
            Persona p=new Persona(nombres[i],apellidos[i],ids[i], direcciones[i],telefonos[i]);
            personaList.add(p);
        }
    }

    public HashMap getHm() {
        return hm;
    }

    public void setHm(HashMap hm) {
        this.hm = hm;
    }

    public List<Persona> getPersonaList() {
        return personaList;
    }

    public void setPersonaList(List<Persona> personaList) {
        this.personaList = personaList;
    }
}



PersonaConverter.java


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.siesoftware.converterexample;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;

/**
 *
 * @author Sergio
 */

/**Se debe anotar que esta clase corresponde a un converter, así mismo
 * se debe asignar un nombre a este converter mediante el atributo
 * value en la anotación
 **/
@FacesConverter(value="personaConverter")
public class PersonaConverter implements Converter{
    
    /**
     * Se trae el SessionBean para manipular el listado de personal, 
     * es importante recordar que este bean posee ahora el HashMap que
     * simula una tabla de base de datos
     */
    private SessionBean sessionBean;

    /**
     * Este método se encarga de retornar el objeto seleccionado. Dado que en el método 
     * de encapsulamiento (getAsString) habíamos definido el ID como representación, 
     * mediante este ID se busca el objeto y se retorna.
     * @param context El contexto de la aplicación
     * @param component El componente que llama a este método
     * @param value La representación que dimos al objeto en el método getAsString
     * @return El objeto seleccionado en el componente
     */
    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if(value==null||value.equals("")){
            return null;
        }
        sessionBean=(SessionBean)context.getELContext().getELResolver().getValue(context.getELContext(), null, "sessionBean");
        Persona p=(Persona)sessionBean.getHm().get(value);
        return p;
    }
    
    /**
     * Este método se ejecuta en el momento que se cargan los ítems en el componente, 
     * podríamos interpretarlo como el encapsulamiento del objeto;
     * de este modo, se debe retornar una representación del objeto en forma de String, 
     * en nuestro caso lo haremos mediante el ID. Así, por cada uno de los ítems del 
     * componente tendremos su respectivo número de identificación.
     * @param context El contexto de la aplicación
     * @param component El componente que llama a este método
     * @param value El ítem que estamos enviando; de este modo, este método se 
     * ejecuta tantas veces como número de ítems del arreglo
     * @return La representación en forma de String que queremos asignar al objeto
     */
    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if(value==null){
            return "";
        }
        Persona p=(Persona)value;
        return p.getIdentificacion();
    }
}

De esta manera, obtendremos el mismo resultado, delegando la búsqueda al converter y posibilitando obtener todos los atributos del objeto seleccionado; esto es especialmente útil en casos en los cuales deseamos acceder a múltiples atributos desde la interfaz, así como, por ejemplo, personalizar el contenido de componentes de primefaces.

Espero que esta entrada sea de mucha ayuda y si existe alguna duda, con gusto estaré atento a los comentarios. Así mismo, adjunto un enlace donde se podrá descargar el proyecto en netbeans con maven. ¡un saludo!

Descarga del proyecto en netbeans