miércoles, 8 de julio de 2020

CRUD con RESTful API y JSON

Las aplicaciones de diversos sistemas en diversos casos no pueden acceder directamente a servidores de bases de datos por diversos motivos como las aplicaciones de Android, estas pueden tener sus bases de datos locales como SQLite, pero si desean interactuar con una base de datos externa como un MySQL requieren un puente que les permita ducha conexión. En este entorno aparecen los Servicios Web, que tienen muchas más aplicaciones y funcionalidades, que permitirán a las aplicaciones acceder a esas bases de datos y realizar operaciones CRUD.

Para crear un WebService se siguen los pasos comunes en su construcción (Clic aquí para ver como crear un WebService desde cero). El siguiente paso es crear la conexión a la base de datos.

Para poder ejecutar las operaciones de CRUD mediante un Servicio Web REST con java debemos definir una conexión mediante una clase Java (clic aquí para ver la conexión de Java a MySQL) o mediante JPA


Código

NOTA: En este ejemplo no se usará la unidad de persistencia (persistence.xml).

Teniendo como base a la tabla "Inquilinos"(clic aquí para ver el DTO) a la que le realizaremos un mantenimiento (CRUD) teniendo como DAO a "DaoInquilinos" y su correspondiente implementación (clic aquí para ver el DAO).Además, las pruebas a las funciones declaradas en el DAO (clic aquí para ver el DAO).

Se creará un punto de partida para el WebService llamado RestApi que tendrá el siguiente código

package apiRest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/service")
public class RestApi extends Application {

}

Luego debemos de crear las funciones REST que se emplearán para las diversas funcionalidades del CRUD, como vamos a emplear JSON se puede optar por usar alguna biblioteca para manipular este formato de intercambio, pero se puede realizar este mismo procedimiento sin agregar ninguna bilbioteca adicional a Jersey y JAX-RS 2.0. Tendríamos una clase llamada JSONRest

package apiRest.services;

import javax.ws.rs.Consumes;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/inquilino")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class JSONRest {
    /* Dentro se colocan los métodos 
    	-GET
        -POST
        -PUT
        -DELETE
    */    
}

SELECT

Tendremos dos casos, cuando recibimos todos los registros(inquilinosSEL) y cuando se recibe solo uno de los registros(inquilinosGET)

La clase JSONRest contiene el siguiente código

@GET
@Path("/{id}")
public Response inquilinosGET(@PathParam("id") int id) {
    DaoInquilinos dao = new DaoInquilinosImpl();
    Inquilinos inquilino=  dao.inquilinosGet(id);
    if (inquilino == null) {
        String msg = "Inquilino no encontrado con id: " + id;
        return Response.ok(msg, MediaType.TEXT_PLAIN).build();
    } else {
        return Response.ok(inquilino).build();
    }
}

@GET
public Response inquilinosSEL() {
    DaoInquilinos dao = new DaoInquilinosImpl();
    List<Inquilinos> lista = dao.inquilinosSel();
    if (lista == null) {
        String msg = "Sin usuarios";
        return Response.ok(msg, MediaType.TEXT_PLAIN).build();
    } else {
        GenericEntity<List<Inquilinos>> entity
                = new GenericEntity<List<Inquilinos>>(lista) {
        };
        return Response.ok(entity).build();
    }
}

Probando desde el navegador

  • http://localhost:8082/WebService/service/inquilinos/1
  • http://localhost:8082/WebService/service/inquilinos/

Nota: la dirección (localhost) y el puerto (8082) variarán dependiendo de la ubicación y puerto empleado por el servidor de Java EE.

En el caso de la función "inquilinoGET" que requiere un valor para hacer la búsqueda. Otra forma es usar la anotación que es @QueryParam en lugar de @PathParam y esa anotación a su vez tiene como parámetro el nombre que tendrá desde la petición, similar a:
http://localhost:8082/WebService/service/inquilino/?id=1

INSERT

Se emplea el método POST, indicado mediante una anotación, para enviar la información a insertar, en nuestro caso es la BD la que genera automáticamente el código

La clase JSONRest contiene el siguiente código

@POST
@Produces(MediaType.TEXT_PLAIN)
public Response inquilinosINS(Inquilinos inquilino) {
    DaoInquilinos dao = new DaoInquilinosImpl();
    String msg = dao.inquilinosIns(inquilino);
    return Response.ok(msg).build();
}

UPDATE

Se emplea el método PUT, indicado mediante una anotación, para enviar la información que se desea modificar.

La clase JSONRest contiene el siguiente código

@PUT
@Produces(MediaType.TEXT_PLAIN)
public Response inquilinosUPD(Inquilinos inquilino) {
    DaoInquilinos dao = new DaoInquilinosImpl();
    String msg = dao.inquilinosUpd(inquilino);
    return Response.ok(msg).build();
}

DELETE

Urilizando la anotación DELETE se envía los identificadores de los registros a eliminar separados por comas. También se puede relizar eliminaciones individuales

La clase JSONRest contiene el siguiente código

@DELETE
@Path("/{ids}")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public Response inquilinosDEL(@PathParam("ids") String ids) {
    List<Integer> list = Arrays.stream(ids.split(","))
            .map(Integer::parseInt)
            .collect(Collectors.toList());
    DaoInquilinos dao = new DaoInquilinosImpl();
    String msg = dao.inquilinosDel(list);
    return Response.ok(msg).build();
}

Código completo

package apiRest.services;

import dao.DaoInquilinos;
import dao.impl.DaoInquilinosImpl;
import dto.Inquilinos;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/inquilinos")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class JSONRest {

    @GET
    @Path("/{id}")
    public Response inquilinosGET(@PathParam("id") int id) {
        DaoInquilinos dao = new DaoInquilinosImpl();
        Inquilinos inquilino = dao.inquilinosGet(id);
        if (inquilino == null) {
            String msg = "Inquilino no encontrado con id: " + id;
            return Response.ok(msg, MediaType.TEXT_PLAIN).build();
        } else {
            return Response.ok(inquilino).build();
        }
    }

    @GET
    public Response inquilinosSEL() {
        DaoInquilinos dao = new DaoInquilinosImpl();
        List<Inquilinos> lista = dao.inquilinosSel();
        if (lista == null) {
            String msg = "Sin usuarios";
            return Response.ok(msg, MediaType.TEXT_PLAIN).build();
        } else {
            GenericEntity<List<Inquilinos>> entity
                    = new GenericEntity<List<Inquilinos>>(lista) {
            };
            return Response.ok(entity).build();
        }
    }

    @POST
    @Produces(MediaType.TEXT_PLAIN)
    public Response inquilinosINS(Inquilinos inquilino) {
        DaoInquilinos dao = new DaoInquilinosImpl();
        String msg = dao.inquilinosIns(inquilino);
        return Response.ok(msg).build();
    }

    @PUT
    @Produces(MediaType.TEXT_PLAIN)
    public Response inquilinosUPD(Inquilinos inquilino) {
        DaoInquilinos dao = new DaoInquilinosImpl();
        String msg = dao.inquilinosUpd(inquilino);
        return Response.ok(msg).build();
    }

    @DELETE
    @Path("/{ids}")
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.TEXT_PLAIN)
    public Response inquilinosDEL(@PathParam("ids") String ids) {
        List<Integer> list = Arrays.stream(ids.split(","))
                .map(Integer::parseInt)
                .collect(Collectors.toList());
        DaoInquilinos dao = new DaoInquilinosImpl();
        String msg = dao.inquilinosDel(list);
        return Response.ok(msg).build();
    }
}

Se puede apreciar en este caso que en la anotación @Produces y @Consumes se indica explícitamente que se trabajará con JSON, pero en las respuestas de las funciones el tipo de retorno no es String, sino "Response", esto permite formatear correctamente el contenido capturado por las funciones DAO y transformarlo a JSON sin emplear otra biblioteca.

RESTful API

Crear un Web Service

Bibliotecas requeridas

  • JAX-RS 2.0
  • Jersey 2.5.1


Estructura del proyecto



Persona.java
package entity;

import java.time.LocalDate;

public class Persona {

    private String dni;
    private String name;
    private LocalDate birthday;
    private Float weight;

    public Persona() {
    }

    public String getDni() {
        return dni;
    }

    public void setDni(String dni) {
        this.dni = dni;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public void setBirthday(LocalDate birthday) {
        this.birthday = birthday;
    }

    public Float getWeight() {
        return weight;
    }

    public void setWeight(Float weight) {
        this.weight = weight;
    }    
}

RestApi.java
package apiRest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/service")
public class RestApi extends Application {

}

TextRest.java
package apiRest.services;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/texto")
@Produces(MediaType.TEXT_PLAIN)
public class TextRest {

    @GET
    @Path("/prueba")
    public String dataGet() {
        return "Probando un Web Service";
    }
}

JsonRest.java
package apiRest.services;

import entity.Persona;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
public class JsonRest {

    @GET
    @Path("/objeto")
    public Response getObject() {
        Persona p = new Persona();
        p.setDni("96584422");
        p.setName("Paul");
        p.setBirthday(LocalDate.of(1993, 5, 17));
        p.setWeight(62.7f);
        return Response.ok(p).build();
    }
    
    @GET
    @Path("/lista")
    public Response getList() {
        Persona p1 = new Persona();
        p1.setDni("10084422");
        p1.setName("Milagros");
        p1.setBirthday(LocalDate.of(1993, 2, 7));
        p1.setWeight(52.7f);
        Persona p2 = new Persona();
        p2.setDni("00084422");
        p2.setName("Yadira");
        p2.setBirthday(LocalDate.of(1992, 10, 1));
        p2.setWeight(57f);
        Persona p3 = new Persona();
        p3.setDni("41557422");
        p3.setName("Alejandra");
        p3.setBirthday(LocalDate.of(1990, 1, 21));
        p3.setWeight(49.9f);
        List<Persona> list = new ArrayList<>();
        list.add(p1);
        list.add(p2);
        list.add(p3);
        GenericEntity<List<Persona>> ge = new GenericEntity<List<Persona>>(list){};
        return Response.ok(ge).build();
    }
}

Si colocamos http://localhost:8082/RestApi/service/texto/prueba en el navegador obtendremos la respuesta:

  • Probando un Web Service


Para consumir los Web Services que devuelven Json se debe invocar a sus URI en los clientes REST que serían:
  • http://localhost:8082/RestApi/service/json/lista
  • http://localhost:8082/RestApi/service/json/objeto


Nota: la dirección (localhost) y el puerto (8082) variarán dependiendo de la ubicación y puerto empleado por el servidor de Java EE.

jueves, 2 de julio de 2020

Java Server Faces - Facelets Tags

Detrás del manejo de las páginas XHTML que componen la vista existe todo un mecanismo de optimización y administración. En JSF a las páginas que componen la vista se les suele llamar archivos *xhtml, pero en realidad en el desarrollo web las páginas XHTML tienen otro significado en su lugar se les debe de llamar "Facelets" estos mediante las instrucciones del desarrollados en XML y tras pasar por el Framework Web y otras instancias generará el archivo HTML deseado y que visualiza el cliente.

Los Facelets proporcionan etiquetas para crear fácilmente composiciones de plantillas basadas en un único archivo de plantilla, similar a la tecnología de .NET de páginas maestras. La finalidad de este mecanismo es la de reducir la duplicación de código para diversas secciones del sitio web que se repiten en todas las páginas web, como:

  • El encabezado
  • El menú de navegación
  • El pie de página.

El archivo de plantilla maestra debe representar un diseño de página web completo con todas las secciones de todo el sitio y usar etiquetas <ui: insert=""> para representar los lugares donde se pueden insertar las secciones específicas de la página.


Creando la estructura

Para ello , se creará un archivo plantilla, y archivos satélite que contengan el código de la cabecera y pie de página. Además, de diversas páginas para comprobar la navegación. La estructura debe ser la siguiente:


El código de layout.xhtml es:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:head>
        <title>#{title}</title>
    </h:head>
    <h:body>
        <header>
            <ui:include src="/WEB-INF/includes/layout/header.xhtml" />
        </header>
        <main>
            <ui:insert name="content" />
        </main>
        <footer>
            <ui:include src="/WEB-INF/includes/layout/footer.xhtml" />
        </footer>
    </h:body>
</html>
</code>


El código de header.xhtml es:
<ui:composition
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <a href="#{request.contextPath}/">        
        INICIO
    </a>
    <nav>
        <ul>
            <li><h:link outcome="/about" value="About" /></li>
            <li><h:link outcome="/help" value="Help" /></li>
            <li><h:link outcome="/contact" value="Contact" /></li>
        </ul>
    </nav>
    <hr/>
</ui:composition>


El código de footer.xhtml es:
<ui:composition
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <hr/>
    <nav>
        <ul>
            <li>Términos de servicio</li>
            <li>Política de privacidad</li>
            <li>Política de cookies</li>
        </ul>
    </nav>
    <small>
        © JavaJhon
    </small>
</ui:composition>


El código de index.xhtml es:
<ui:composition template="/WEB-INF/template/layout.xhtml"
                xmlns="http://www.w3.org/1999/xhtml"
                xmlns:h="http://xmlns.jcp.org/jsf/html"
                xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <ui:param name="title" value="Inicio" />
    <ui:define name="content">
        <h1>Pantalla de inicio</h1>
        <p>Lorem ipsum dolor sit amet.</p>
    </ui:define>
</ui:composition>


Tendremos una interacción como se aprecia en la imagen



Fuentes:

  • Schotlz, B. y Tijms, A. (2018). The Definitive Guide to JSF in Java EE 8:Building Web Applications with JavaServer Faces. United States of America: Apress.


miércoles, 1 de julio de 2020

Java Server Faces Resource Bundles

Un "resource bundle" en JAVA es un archivo o colección de ellos del tipo *.properties que contiene datos específicos de la localidad. Esto permite "internacionalizar" un aplicativo de forma que la configuración regional se mantiene de forma independiente a la codificación. Estos emplean una organización similar a los HashMap con clave y valor. Así podemos tener una clave "saludo" con valor "hola" en un archivo, en otro la misma clave con valor "hello" y así sucesivamente, pero estos archivos para relacionarse deben tener algo en común y es el uso del mismo nombre con la terminación "_" seguida de dos letras del código de lenguaje ISO 639-1-Alpha-2 antes de la extensión del archivo. así tendríamos los diversos archivos:
  • dictionary.properties, este sería el archivo por defecto
  • dictionary_es.properties, este sería específicamente para el español

Especificar un país se haría agregando otro "_" seguido de dos letras del código de países ISO 3166-1-Alpha-2 antes de la extensión del archivo
  • dictionary_pt_BR.properties, este sería para el portugués de Brasil.
  • dictionary_en_UK.properties, este sería para el inglés de Reino Unido.

NOTA: Lo ideal es que los nombres de los archivos y las claves se encuentren en un solo idioma y por recomendación el inglés. Aún cuando la aplicación no sea usada por personas que hablen ese idioma.


Creando un diccionario para español e inglés

Dentro de los paquetes del código fuente se creará un paquete con el nombre "bundles", dentro de este se crearán documentos con la extensión *.properties que serán:
  • text.properties
  • text_es.properties

text.properties text_es.properties
title=JSF with resource bundles
greeting=Hello my friend
button=Go to the app
title=JSF con paquetes de recursos
greeting=Hola amigo mío
button=Ve a la aplicación

Dependiendo del IDE empleado se visualizará y editará de diversas formas. Por ejemplo en Netbeans se puede apreciar ambos a la vez, mientras en otros se ven y editan por separado.




Llamar a un recurso desde una página de JSF

Estos recursos se comportan de la misma forma como una colección del tipo MAP, por lo que se les llamará de la misma forma con la siguiente sintaxis #{variable['identificador']}, pero antes de ello se debe definir la forma como se hará referencia a estos recursos. Existen dos formas, una es la llamada local mediante una etiqueta de prefijo "f" y la otra desde el archivo de configuración "faces-config.xml"


Llamado local a un recurso

Supongamos el siguiente archivo *.xhtml

<!DOCTYPE html>
<html lang="#{view.locale.toLanguageTag()}"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <f:loadBundle basename="bundles.text" var="texto" />
        <title>#{texto['title']}</title>
    </h:head>
    <h:body>
        <h2>#{texto['greeting']}</h2>
        <h:button value="#{texto['button']}"/>
    </h:body>
</html>

Observemos la línea 2 #{view.locale.toLanguageTag()}, esta permite que se establezca el lenguaje como el mismo del navegador, además se puede emplear para conocer cuál es el que se tiene.

La línea 7 es la etiqueta que hace el llamado al recurso, para ello dispone de dos atributos que son:

  • basename, este tiene como valor la ubicación del recurso dentro del proyecto.
  • var, este indica el "alias" que empleará dentro de la página xhtml


Así en las líneas 8, 11 y 12 se aprecia como se llama a los recursos como si se tratara de una colección del tipo MAP. En un navegador que tiene al español como predefinido la salida sería en el cuerpo de la página el encabezado "Hola amigo mío" seguido del botón con el texto "Ve a la aplicación"


Llamada al recurso desde faces-config

Para poder realizar el llamado desde cualquier página sin usar "f:loadBundle" se debe agregar la referencia en el archivo faces-config.xml de la siguiente forma
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
              http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd"
              version="2.2">
    <application>
        <locale-config>
            <default-locale>en</default-locale>
            <supported-locale>es</supported-locale>
        </locale-config> 
        <resource-bundle>
            <base-name>bundles.text</base-name>
            <var>texto</var>
        </resource-bundle>
    </application>
</faces-config>


Ahí indicamos el lenguaje por defecto (en) y los lenguajes soportados, en este caso el español, luego se indica la ubicación y variable que se empleará.

Se debe tener algunas consideraciones como no usar como variable el que emplee un Bean, ya que se puede causar conflicto y resultados no deseados.

viernes, 26 de junio de 2020

Solución al problema de conexión MySQL con NetBeans Apache

Al realizar conexiones de MySQL o MariaBD mediante algunos IDE, específicamente los NetBeans Apache podemos toparnos con el siguiente error, o cuando se emplea las versiones más actuales del Driver de conexión de MySQL:


Cannot establish a connection to jdbc:mysql://localhost:3306/test using com.mysql.cj.jdbc.Driver (The server time zone value 'Hora est. Pacífico, Sudamér' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specifc time zone value if you want to utilize time zone support.)


Una de las soluciones es modificar la URL de la siguiente manera:
   
   jdbc:mysql://localhost:3307/test?useTimezone=true&serverTimezone=UTC
   
De esta manera al intentar la conexión se obtendrá el resultado esperado.


Pero no podemos conectarnos al Servidor MySQL, ya que solo podemos hacer esto para las bases de datos que contiene y obtenemos el siguiente error al intentar conectarnos.



La solución para no tener que modificar nuestras URL y poder conectarnos al Servidor MySQL desde Servicios de NetBeans es ejecutar la siguiente sentencia en el servidor MySQL:

   -- Usando nomenclatura UTC, por ejemplo '-5:00'
   SET GLOBAL time_zone ='-5:00'
   -- El valor varía conforme la zona horaria donde te encuentres
 



Así podremos conectarnos sin necesidad de tener que modificar cada URL.

viernes, 19 de junio de 2020

BootsFaces

Es un Framework de front-end para JSF que tiene como base a Bootstrap 3 y jQuery-UI que permite crear páginas con Bootstrap de manera simplificada al convertir el código ingresado en HTML con el estilo definido.


Emplear BootsFaces

Existen dos manera de utilizar este Framework, la primera es mediante descarga que genera un archivo *.jar y se debe agregar al proyecto.



La otra forma es emplear un repositorio como Maven o Gradle y agregar las siguientes líneas en los archivos de construcción.
Maven Gradle
<dependency>
    <groupId>net.bootsfaces</groupId>
    <artifactId>bootsfaces</artifactId>
    <version>1.4.2</version>
    <scope>compile</scope>
</dependency>
  
compile 'net.bootsfaces:bootsfaces:1.4.2'
 


Implementar en un XHTML

Se debe agregar el siguiente atributo-valor xmlns:b="http://bootsfaces.net/ui" que como se aprecia emplea el prefijo "b".

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
   xmlns:h="http://xmlns.jcp.org/jsf/html"
   xmlns:b="http://bootsfaces.net/ui">
<h:head>
   <title>Prueba 05 - BootsFaces</title>
</h:head>
<h:body>
   <!--Código de la página-->
</h:body>
</html>


Ejemplo de aplicación

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
 xmlns:h="http://xmlns.jcp.org/jsf/html"
 xmlns:b="http://bootsfaces.net/ui">
<h:head>
 <title>Prueba 05 - BootsFaces</title>
</h:head>
<h:body>
 <b:navBar brand="INICIO" brandHref="index.html" inverse="true"
  fixed="top">
  <b:navbarLinks>
   <b:navLink value="JSF" href="#"></b:navLink>
   <b:navLink value="Bootsfaces" href="#"></b:navLink>
   <b:dropMenu value="Pruebas">
    <b:navLink value="Prueba 1" href="#"></b:navLink>
    <b:navLink value="Prueba 2" href="#"></b:navLink>
    <b:navLink value="Prueba 3" href="#"></b:navLink>
    <b:navLink></b:navLink>
    <b:navLink header="Otros"></b:navLink>
    <b:navLink value="Resultados" href="#"></b:navLink>
   </b:dropMenu>
  </b:navbarLinks>
 </b:navBar>
 <b:jumbotron>
  <b:container>
   <h1>Encabezado</h1>
   <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
    do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
    enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
    ut aliquip ex ea commodo consequat. Duis aute irure dolor in
    reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
    culpa qui officia deserunt mollit anim id est laborum.</p>
   <p>
    <a href="https://javajhon.blogspot.com/" class="btn btn-lg btn-info">
     Blog sobre Java </a>
   </p>
  </b:container>
 </b:jumbotron>
 <b:container>
  <b:row styleClass="show-grid">
   <b:column span="4">
    <h2>Botones b:commandbutton</h2><br/>
    <h:form>
    <b:commandButton action="j" value="primary" look="primary"/><br/><br/>
    <b:commandButton action="j" value="success" look="success"/><br/><br/>
    <b:commandButton action="j" value="danger" look="danger"/><br/><br/>
    <b:commandButton action="j" value="info" look="info"/><br/><br/>
    <b:commandButton action="j" value="warning" look="warning"/><br/><br/>
    <b:commandButton action="j" value="default" look="default"/><br/><br/>
    </h:form>       
   </b:column>
   <b:column span="4">
    <h2>Botones class btn-*</h2><br/>
    <a class="btn btn-lg btn-primary">primary</a><br/><br/>
    <a class="btn btn-lg btn-success">success</a><br/><br/>
    <a class="btn btn-lg btn-danger">danger</a><br/><br/>
    <a class="btn btn-lg btn-info">info</a><br/><br/>
    <a class="btn btn-lg btn-warning">warning</a><br/><br/>
    <a class="btn btn-lg btn-default">default</a>    
   </b:column>
   <b:column span="4">    
    <h2>Etiquetas</h2>
    <b:dropButton value="DropButton" look="success">
     <b:navLink value="Prueba 1" href="#"></b:navLink>
     <b:navLink value="Prueba 2" href="#"></b:navLink>
     <b:navLink value="Prueba 3" href="#"></b:navLink>
     <b:navLink></b:navLink>
     <b:navLink header="Otros"></b:navLink>
     <b:navLink value="Resultados" href="#"></b:navLink>
    </b:dropButton>
    <b:panelGrid ></b:panelGrid>
    <b:panel></b:panel>
   </b:column>
  </b:row>
  <hr />
  <footer>
   <p>© JavaJhon 2020</p>
  </footer>
 </b:container>
</h:body>
</html>

Fuentes:

  • BootsFaces. (2018). BootsFaces Showcase. Recuperado de: https://showcase.bootsfaces.net/

Java Server Faces - DataTable

Las tablas de HTML en una aplicación web con Java Server Faces se construye mediante la etiqueta h:dataTable en los Facelets.

Los datos que se pueden mostrar mediante un h:dataTable son:
  • Arrays
  • Instancias del Java Colletion Framework
  • Instancias de java.sql.ResultSet
  • Instancias de javax.servlet.jsp.jstl.sql.Result
  • Instancias de javax.faces.model.DataModel

Supongamos el siguiente bean llamado "LibroBean":
package proyectoJSF.model;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class LibroBean {

 private Integer id = 105;
 private String nombre = "JSF EL";
 private AutorBean autor = new AutorBean();
 private String[] idiomas = { "Español", "Inglés", "italiano" };
 private List<String> paises;
 private Map<String, LocalDate> ediciones;

 public LibroBean() {
  cargarPaises();
  cargarEdiciones();
 }

 public String[] getIdiomas() {
  return idiomas;
 }

 public List<String> getPaises() {
  return paises;
 }

 public Map<String, LocalDate> getEdiciones() {
  return ediciones;
 }

 private void cargarPaises() {
  paises = new ArrayList<>();
  paises.add("Perú");
  paises.add("Canadá");
  paises.add("Japón");
 }

 private void cargarEdiciones() {
  ediciones = new TreeMap<>();
  ediciones.put("Primera", LocalDate.of(2012, 12, 30));
  ediciones.put("Segunda", LocalDate.of(2015, 2, 12));
  ediciones.put("Tercera", LocalDate.of(2020, 6, 7));
 }

 public Integer getId() {
  return id;
 }

 public void setId(Integer id) {
  this.id = id;
 }

 public String getNombre() {
  return nombre;
 }

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

 public AutorBean getAutor() {
  return autor;
 }

 public void setAutor(AutorBean autor) {
  this.autor = autor;
 }

}


Se aprecia que contiene diversas estructuras de datos, vamos a mostrar la estructura MAP mediante un h:dataTable de la siguiente manera:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
 xmlns:h="http://xmlns.jcp.org/jsf/html"
 xmlns:f="http://java.sun.com/jsf/core">
<h:head>
 <title>Prueba 04 - DataTable</title>
</h:head>
<h:body>
 <h1>Ediciones de los libros</h1>
 <h:dataTable value="#{libroBean.ediciones}" var="edicion" border="1">
  <h:column>
   <f:facet name="header">
    Edición
   </f:facet>
   <h:outputText value="#{edicion.key}" />
  </h:column>
  <h:column>
   <f:facet name="header">
    Fecha de publicación (aaaa/mm/dd)
   </f:facet>
   <h:outputText value="#{edicion.value}" />
  </h:column>
 </h:dataTable>
</h:body>
</html>


Tenemos las siguientes etiqeutas y sus atributos:
  • h:dataTable, se convierte posteriormente en una tabla HTML
    • value, es el atributo cuyo valor es el que se itera dentro de la tabla
    • var, es el nombre de la variable que usaremos dentro de la estructura
    • border, similar a la propiedad "border" de una tabla HTML
  • h:column, en contraste con HTML no es necesario definir las filas, se hacen directamente las columnas. Dentro tendremos:
    • f:facet name="header", dentro de esta etiqueta con este "name" se coloca el texto que irá en dicha columna.
    • h:outputText value="#{edicion.key}", tendremos una etiqueta de texto que mostrará la clave de cada elemento del MAP
    • h:outputText value="#{edicion.value}", tendremos una etiqueta de texto que mostrará el valor de cada elemento del MAP

El resultado visual sería:
Edición Fecha de publicación (aaaa/mm/dd)
Primera 2012-12-30
Segunda 2015-02-12
Tercera 2020-06-07