sábado, 12 de abril de 2014

JSF 2.1 Composite Components (LatencyIndicator)

Introducción

Actualmente me encuentro migrando un proyecto desarrollado en AngularJS a Java Server Faces (JSF) + Primefaces. Aún estoy aprendiendo las particularidades de ambos frameworks y debo decir que la curva de aprendizaje es bastante satisfactoria en lo que a la relación tiempo de aprendizaje / conocimientos aplicables se refiere.

JSF ofrece la posibilidad de crear componentes personalizados reutilizables conocidos como Composite Components. Los composite components son componentes web que se modelan una vez y ya están listos para ser utilizados en distintos proyectos de manera extremadamente sencilla. Me pareció una característica muy interesante desde un punto de vista de la reutilización del software y me decidí a hacer un ejemplo muy sencillo pero que creo que ilustra bien esta característica de JSF.

El componente LatencyIndicator

En este artículo vamos a desarrollar un componente sencillo que representa el estado de latencia de una conexión apoyándonos en composite components.

Para desarrollar un componente lo mas importante es determinar la manera en que se va a comportar. En principio nuestro indicador tendrá tres estados, baja latencia (representado como un piloto verde), latencia media (ámbar) y latencia alta (rojo).

Para comenzar a definir un componente debemos incluir el espacio de nombres de composite components al documento .xhtml del mismo.


LatencyIndicator.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:composite="http://java.sun.com/jsf/composite"
      xmlns:h="http://java.sun.com/jsf/html">

    <!-- INTERFACE -->
    <composite:interface>
        <composite:attribute name="latencia" type="java.lang.Integer" required="true"/>
        <composite:attribute name="umbralMedio" type="java.lang.Integer" default="75"/>
        <composite:attribute name="umbralAlto" type="java.lang.Integer" default="150"/>
    </composite:interface>

    <!-- IMPLEMENTATION -->
    <composite:implementation>
        <h:panelGrid columns="2" border="0" styleClass="latencia-grid">
            <h:graphicImage value="#{resource['images:rojo.png']}" rendered="#{cc.attrs.latencia ge cc.attrs.umbralAlto}"/>
            <h:graphicImage value="#{resource['images:ambar.png']}" rendered="#{(cc.attrs.latencia lt cc.attrs.umbralAlto) and (cc.attrs.latencia ge cc.attrs.umbralMedio)}"/>            
            <h:graphicImage value="#{resource['images:verde.png']}" rendered="#{cc.attrs.latencia lt cc.attrs.umbralMedio}"/>
            <h:outputText value="#{cc.attrs.latencia} ms."/>
        </h:panelGrid>
    </composite:implementation>
</html>
Simplemente se ha declarado que va a constar de tres atributos. El valor de la latencia y los umbrales medio y alto de latencia por defecto inicializados a 75 y 150 milisegundos. El comportamiento del componente consta de tres imágenes que se renderizan de forma condicional en función del rango de latencia en que se encuenta la conexión. ¿Podría haberse empleado la estructura c:choose de JSTL. Si, pero mezclar JSF y JSTL no es conveniente ya que se procesan en etapas distintas en el ciclo de renderizado del documento JSF y esto deriva en algunos problemas y comportamientos, a priori, no esperados como por ejemplo al incluir el componente en un iterable (datatable).


Ejemplo de uso del componente

Como ejemplo incluiré el componente en una tabla que representará distintos servidores y las latencias que hay con ellos.

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:miscomponentes="http://java.sun.com/jsf/composite/ezcomp"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:p="http://primefaces.org/ui">
    <h:head>
        <title>Facelet Title</title>
        <style>
            .ui-widget, ui-widget ui-widget {
                font-size: 10pt;
            }
            
            .latencia-grid tr, .latencia-grid tr td {
                border: none;
            }
        </style>
    </h:head>
    <h:body>
        <h:form id="miForm">
            <h:commandButton id="boton" value="Generar Latencias">
                <f:ajax render="@form" event="click" listener="#{latencyBean.generarLatencias()}"/>
            </h:commandButton>
            <p:dataTable value="#{latencyBean.servidores}" var="servidor" style="width: 100px;">
                <p:column headerText="Latencia">
                    <miscomponentes:LatencyIndicator latencia="#{servidor.latencia}"/>
                </p:column>
                <p:column headerText="Nombre">
                    <h:outputText value="#{servidor.nombre}"/>
                </p:column>
                <p:column headerText="IP">
                    <h:outputText value="#{servidor.ip}"/>
                </p:column>
            </p:dataTable>
        </h:form>
    </h:body>
</html>
Servidor.java

package entidades;

public class Servidor {
    private String nombre;
    private String ip;
    private Integer latencia;

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public Integer getLatencia() {
        return latencia;
    }

    public void setLatencia(Integer latencia) {
        this.latencia = latencia;
    }
}

LatencyBean.java

package beans;

import entidades.Servidor;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class LatencyBean {
    private List<Servidor> servidores;
    
    public LatencyBean()
    {
        generarServidores();
    }

    private void generarServidores()
    {
        Random rand = new Random();
        servidores = new ArrayList<Servidor>();
        
        for (int i = 0; i < 10; i++)
        {
            Servidor servidor = new Servidor();
            servidor.setIp("192.168.0." + i);
            servidor.setNombre("Servidor " + i);
            servidor.setLatencia(rand.nextInt(150 + 50));
            servidores.add(servidor);
        }
    }

    public final void generarLatencias() {
        Random rand = new Random();
        for (Servidor s : servidores)
        {
            s.setLatencia(rand.nextInt(150 + 50));
        }
    }

    public List<Servidor> getServidores() {
        return servidores;
    }

    public void setServidores(List<Servidor> servidores) {
        this.servidores = servidores;
    }
}

Resultado