Blog sobre desarrollo en Java/Jakarta EE, VueJS, DevOps y más..

lunes, 26 de octubre de 2020

Primefaces: Implementación de Lazy Loading

 

Primefaces dispone de un amplio conjunto de componentes que podemos utilizar en nuestros proyectos, uno de ellos es DataTable que se utiliza para mostrar datos en un formato tabular. Este componente, pone a disposición de los programadores funcionalidades muy interesantes tales como: paginación, búsqueda, filtrado, selección, ordenamiento, entre otras.

Para ello debemos implementar la consulta (query) utilizando Criteria API, dentro del EJB correspondiente, ya que ella nos permite construir una query en tiempo de ejecución.

Nuestra base de datos cuenta con una sola tabla llamada persona, cuya estructura es la siguiente:

Luego mediante JPA debemos mapear esta tabla a una clase java llamada Persona.java

@Entity
@Table(name = "persona")
public class Persona implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private Integer id;
  @Column(name = "nombre_apellido")
  private String nombreApellido;
  @Column(name = "email")
  private String email;

  // Constructor

 // Setters/Getters

}

En la interface local PersonaFacadeLocal.java, debemos definir dos métodos, el primero de ellos llamado findAll, cuenta con los siguientes parámetros:

  • start - Indica la posición inicial del cojunto de datos
  • size -  Indica la cantidad de registros a mostrar
  • sortField - Campo por el que se desea ordenar la colección
  • sortOrder- Tipo de ordenamiento (ASC | DESC)
  • filters - Criterios de búsqueda

y el segundo llamado count con un solo parámetro de tipo Map, que sera el encargado de retornar la cantidad de registros encontrados en base a los filtros aplicados. 

PersonaFacadeLocal.java

List<Persona> findAll(int start, int size, String sortField, SortOrder sortOrder, Map<String, FilterMeta> filters);
 
int count(Map<String, FilterMeta> filters);

Luego se debe realizar la implementación de ambos métodos dentro del session bean PersonaFacade como se observa a continuación:

PersonaFacade.java

El método findAll comienza con la interfaces CriteriaBuilder y CriteriaQuery, ya que ellas nos permitiran construir una query dinámica de manera segura sin ser víctimas de la inyección SQL, ya que se valida su contrucción y los parámetros que recibe.

CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Persona> criteriaQuery = criteriaBuilder.createQuery(Persona.class);
Root<Persona> root = criteriaQuery.from(Persona.class);
CriteriaQuery<Persona> select = criteriaQuery.select(root);
List<Predicate> listaPredicados = new ArrayList<>();
List<Persona> listaPersonas = new ArrayList<>(); 

En esta sección verificamos si existe o no algún criterio de ordenamiento y su tipo.

      if (sortField != null) {
        criteriaQuery.orderBy(sortOrder == SortOrder.ASCENDING
                ? criteriaBuilder.asc(root.get(sortField))
                : criteriaBuilder.desc(root.get(sortField)));
      }

Luego, verificamos si el usuario especificó algun criterio de búsqueda para filtrar los resultados. Recordemos que la tabla persona cuenta con los campos id, nombre_apellido, email, por lo que el usuario puede aplicar cualquiera de estos 3 criterios. En el caso de haya alguno se lo añade a una lista de predicados, caso contrario se ignoran.

      if (filters != null && filters.size() > 0) {

        // Capturmos los filtros
        Object id = filters.get("id").getFilterValue();
        String email = (String) filters.get("email").getFilterValue();
        String nombreApellido = (String) filters.get("nombreApellido").getFilterValue();

        // Filtro id
        if (id != null) {
          Predicate predicado = criteriaBuilder.equal(root.get("id"), id);
          listaPredicados.add(predicado);
        }

        // Filtro nombre y apellido
        if (nombreApellido != null) {
          Predicate predicado = criteriaBuilder.like(criteriaBuilder.lower(root.get("nombreApellido")), "%" + nombreApellido.toLowerCase() + "%");
          listaPredicados.add(predicado);
        }

        // Más filtros...

        // Añadimos predicados al criterio de búsqueda
        if (listaPredicados.size() > 0) {
          listaPredicados.forEach(x -> {
            criteriaQuery.where(listaPredicados.toArray(new Predicate[listaPredicados.size()]));
          });
        }
      } 

Es importante destacar que la búsqueda por id debe retornar una sola fila, y en los restantes casos el usuario puede ir escribiendo letra por letra y el filtro debe ir encontrando las coincidencias en "tiempo real", por lo que podría retornar más de un resultado o ninguno.

En el CDI Controller llamado por ejemplo PersonaDataController.java y con un scope de tipo vista, debemos realizar las siguientes tareas:

  • Inyectar una referencia a la interface PersonaFacadeLocal para poder utilizar sus métodos:
  @EJB
  PersonaFacadeLocal personaEJB;
  • Definir una variable llamada listaPersonas cuyo tipo de dato es LazyDataModel especialidada en Persona, con sus respectivos métodos set y get.
   private LazyDataModel<Persona> listaPersonas; 
  • Implementar los métodos init e iniciar acompañados de la anotación @PostConstruct, para que al momento en que el usuario acceda a la vista el listado este disponible.
  @PostConstruct
  public void init() {
    iniciar();
  }

private void iniciar() {
    listaPersonas = new LazyDataModel<Persona>() {
      @Override
      public List<Persona> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, FilterMeta> filterBy) {
        List<Persona> listaDePersonas = personaEJB.findAll(first, pageSize, sortField, sortOrder, filterBy);
        listaPersonas.setRowCount(personaEJB.count(filterBy));
        refreshTableState();
        return listaDePersonas;
      }

      @Override
      public Object getRowKey(Persona object) {
        return object.getId();
      }

      @Override
      public Persona getRowData(String rowKey) {
        Persona persona = null;
        try {
          persona = personaEJB.find(Integer.parseInt(rowKey));
        } catch (NumberFormatException e) {
          System.out.println("ocurrio un error : " + e.getLocalizedMessage());
        }
        return persona;
      }
    };
  }

El método iniciar() es muy interesante, ya que en su interior se instancia el objeto listaPersonas, y en su interior se sobreescriben los métodos load(), getRowKey(), getRowData().

El método load(), es el encargado de acceder a la base de datos para obtener el listado mediante el método findAll() y a su vez "setea" la cantidad de registros obtenidos mediante setRowCount. 

El metodo  getRowKey(), se encarga de retornar el campo que identifica de manera univoca a cada registro en el listado. 

Por su parte, el metodo getRowData(), tiene como objetivo buscar un objeto por su clave primaria y retornarlo al usuario.

Estos dos últimos método solo son necesarios en caso de que se desee implementar la selección de registros en el componente DataTable.

Finalmente en la vista (archivo index.xhtml), implementamos el componente DataTable de la siguiente manera:

  <p:dataTable id="tblPersonas"
                       value="#{personaDataController.listaPersonas}"
                       var="p"
                       paginator="true"
                       paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
                       currentPageReportTemplate="{startRecord}-{endRecord} de {totalRecords} registros"
                       rowsPerPageTemplate="10,25,50,100"
                       rows="10"
                       rowKey="#{p.id}"
                       selectionMode="single"
                       selection="#{personaDataController.personaSeleccionada}"
                       lazy="true"
                       emptyMessage="Su búsqueda no arrojó resultados."
                       >
            <f:facet name="header">
              Listado de Clientes
            </f:facet>
            <p:column headerText="Cod."
                      field="id"
                      filterBy="#{p.id}"
                      sortable="true"
                      sortBy="#{p.id}"
                      style="text-align: center;"
                      >
              <h:outputText value="#{p.id}"/>
            </p:column>
            <p:column headerText="Nombre y Apellido"
                      field="nombreApellido"
                      filterBy="#{p.nombreApellido}"
                      sortable="true"
                      sortBy="#{p.nombreApellido}">
              <h:outputText value="#{p.nombreApellido}"/>
            </p:column>
            <p:column headerText="Email"
                      field="email"
                      filterBy="#{p.email}"
                      sortable="true"
                      sortBy="#{p.email}">
              <h:outputText value="#{p.email}"/>
            </p:column>
          </p:dataTable>
 

Para que esta implementación funcione, es muy importante que el valor del atributo lazy sea true (lazy=true), y que se haya definido el rowKey.

De este modo, nuestro componente DataTable divide el cojunto de registros en varias paginas cargando en la memoria del navegador solo 10 filas y permite que el usuario pueda ordenar de manera ascendente o descendente los registros y  filtrarlos de acuerdo a su criterio de búsqueda en tiempo de ejecución.

Seguramente haya otras formas de implementación, pero hasta el momento, a mi me funcionó de maravillas y no experimente ningún tipo de retraso en las operaciones  en una base de datos poblada con una cantidad enorme de registros.

Las sugerencias y/o comentarios siempre son bienvenidos, asi que estaré muy agradecido si alguien puede aportar su feedback. Al fin de cuentas, al conocimiento lo hacemos entre todos.

El proyecto completo se encuentra disponible en: https://github.com/Francisco-Castillo/primefaces-lazy-loading.git

Compartir:

martes, 20 de octubre de 2020

Tareas en Jenkins: Compilar proyecto Spring con Maven


En esta publicación veremos como crear la primer tarea en Jenkins, la cual consistirá en descargar el código fuente de un proyecto Spring desde GitHub, y compilarlo con Maven.

Para ello es necesario tener configurado Git, JDK, y Maven. En esta publicación se explica como hacerlo.

Crear tarea

1. Hacer clic en el menú "Nueva Tarea"


2. Establecer un nombre para la tarea, seleccionar la opción "Crear un proyecto de estilo libre", y por último hacer clic en el botón "OK"


3. En la pestaña "General", se deben realizar las siguientes acciones:
  • Colocar una descripción de la tarea
  • Tildar casilla "GitHub Project"
  • Especificar la dirección URL del proyecto


4. En la pestaña "Configurar el origen del código fuente", se deben realizar las siguiente acciones:
  • Seleccionar el radiobutton de "Git"
  • Especificar la dirección URL del repositorio
  • Agregar las credenciales (nombre de usuario y contraseña) para poder acceder al repositorio
  • Especificar la rama con la que se desea trabajar. Por defecto es master. 
 

5. En la pestaña "Disparadores de ejecuciones":
  • Seleccionar "Ejecutar Periodicamente"
  • En la opción "Programador", a modo de ejemplo,  le diremos a Jenkins que consulte el repositorio cada 5 minutos.

6. En la pestaña "Ejecutar", añadir un nuevo paso y seleccionar la opción "Ejecutar tareas Maven de nivel superior".

 
7. Para configurar la tarea maven, se debe seleccionar su versión (la que esta instalada en el equipo), y especificar "clean package" como se observa en la siguiente imagen:
 

Con estos pasos ya tendremos configurada nuestra primer tarea en Jenkins. Para observar su ejecución, nos ubicaremos en el Dashboard, y tras esperar unos minutos comenzará el trabajo.
 
Si hacemos clic en el nombre de la tarea, accederemos a sus detalles. En la siguiente imagen, se puede observar en el panel "Historia de tareas", que la misma se ejecutó dos veces exitosamente (lo sabemos por que el color de los círculos es azul, de haber sido rojos nos indicaría que hubo un error ), y se encuentra activa una nueva ejecución (la número 3).
 

 
Si hacemos clic en #3, veremos mas detalles relacionadas a este número de ejecución:
 

En "Console output" podemos ver el resultado que la consola arroja.
 

De esta manera finaliza la construcción de nuestra "primer tarea con Jenkins". En próximas publicaciones veremos como se pude notificar via mail al equipo de desarrollo e caso de que la compilación falle, como incorporar JUnit, SonarQube, desplegar en un servidor TomCat y/o Payara, crear pipelines y mucho más.

Compartir:

Jenkins: Configurar JDK, Maven y Git

 


Para que Jenkins pueda trabajar con proyectos Java y el sistema de versión de Git, descargando, compilando y testeando el código fuente, se debe especificar en que directorio se encuentran instalados de cada uno de los elementos. Para ello, haremos clic en el menú "Administrar Jenkins"

 

A continuación, seleccionamos la opción "Global Tool Configuration"

 
Luego, debemos desplazarnos hasta encontrar las secciones (JDK, Git y Maven), y allí especificar sus rutas de instalación. A continuación, se observan unas capturas a modo de ejemplo, usted debe reemplazar las rutas por las que correspondan en su caso.

Configurar Git

Configurar Maven 

Configurar JDK


Para finalizar, se debe hacer clic en el boton "Apply" y luego en "Save"
Compartir:

miércoles, 14 de octubre de 2020

Instalación de Jenkins en Ubuntu 18.04


Prerequisito: tener instalado el kit de desarrollo de java, una opción interesante es AdoptOpenJDK
 
Jenkins es una herramienta open source escrita en Java que permite implementar la integración continua automatizando todas aquellas tareas repetitivas. Además, cuenta con una amplia gama de plugins que nos permiten extender su funcionalidad y se encuentra disponible para múltiples plataformas tales como: CentOS/Fedora, Debian/Ubuntu, Windows, macOS, y un paquete genérico .war entre otras.

Instalación

Lo primero que debemos hacer es ingresar en el sitio web de Jenkins y acceder al menú "Descargas" haciendo clic en el siguiente enlace: https://www.jenkins.io/download/


Seleccionaremos la versión LTS 2.249.2 para Ubuntu/Debian, y al hacer clic sobre ella accederemos a las siguientes instrucciones:

1. Abrir una terminal e ingresar la siguiente linea:

wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -

2. Editar el archivo /etc/apt/source.list y agregar el siguiente repositorio:

deb https://pkg.jenkins.io/debian-stable binary/ 

3. Actualizar paquetes

sudo apt-get update

4. Instalar Jenkins

sudo apt-get install jenkins 

5. Iniciar servidor

sudo systemctl start jenkins

Con esto ya tendremos jenkins instalado y corriendo en el puerto 8080 por defecto, para verificarlo podemos acceder a http://localhost:8080

Configuración

Como primer medida, Jenkins nos solicitará que ingresemos una contraseña para su desbloqueo, la misma se encuentra en /var/lib/jenkins/secrets/initialAdminPassword

Abrimos una terminal, ingresamos el siguiente comando, y copiamos el valor retornado y lo pegamos en la caja "Administrator password". Luego presionamos el botón "Continue".

sudo cat /var/lib/jenkins/secrets/initialAdminPassword 


Seleccionamos la opción "Install suggested plugins" y automáticamente comenzara la instalación de los plugins. Finalizado este proceso, se nos solicitara la creación de una cuenta de usuario mediante el siguiente formulario:

 

Presionamos el botón "Save and continue", y posteriormente debemos especificar la url en la que "escuchará" jenkis. En este ejemplo, se cambio el puerto 8080 por 8089, ya que las aplicaciones web que corren en los servidores Glassfish y Payara ocupan el puerto 8080, entonces para evitar conflictos mejor cambiar.


 Por último, presionamos el botón "Save and Finish".
Para que el cambio de puerto funcione, debemos editar el archivo /etc/default/jenkins de la siguiente manera:

sudo nano /etc/default/jenkins 


 Guardamos los cambios y listo.

Compartir:

lunes, 12 de octubre de 2020

Auditoría de código fuente con SonarQube

 

Siempre es conveniente realizar un análisis de nuestro código fuente para conocer su calidad, y en caso de ser necesario, mejorarla.

En este artículo, veremos como auditar el código de un proyecto REST creado con spring-data-rest y maven.

Lo primero que se debe hacer es instalar SonarQube y ejecutar el servidor, este enlace puede ayudarte

Una vez iniciado el servidor, debemos abrir una terminal y ubicarnos en la raíz del proyecto que queremos analizar. Allí debemos ejecutar el siguiente comando:

 mvn clean install sonar:sonar -Dsonar.host.url=http://localhost:9000 -Dsonar.analysis.mode=publish


 Finalizado el análisis, podremos acceder al reporte ingresando http://localhost:9000 en nuestro navegador web.


Si hacemos clic en el nombre del proyecto, en este caso  "rest", accederemos a su detalle:


Podemos observar que la "calidad" de nuestro código fue aprobada (Quality Gate - Passed), también vemos que no tenemos  bugs (0 Bugs), no hay vulnerabilidades, pero si hay código que "huele mal" (3 Code Smells), y que solucionarlo podria llevarnos unos 14 minutos aproximadamente. Si hacemos clic en el número 3 que se encuentra sobre "Code Smells", veremos su detalle:


En este caso, el código "oloroso" o que "huele mal", o con el que podríamos llegar a tener problemas en un futuro son dos lineas que contienen imports que no se utilizan y un caso de test incompleto. 

El análisis que realizamos en esta ocasión es manual, ya que nosotros introducimos el comando maven necesario para ello, lo ideal seria que el mismo se realice de manera automática al momento de compilar el proyecto. En una próxima entrega, veremos como integrarlo con Jenkins.

El proyecto utilizado en este artículo esta disponible en el siguiente  repositorio

Compartir:

lunes, 5 de octubre de 2020

Descarga y configuración de SonarQube en Ubuntu 18.04

 

SonarQube es una herramienta escrita en Java que nos permite auditar el código fuente de nuestros proyectos, permitiendo identificar bugs, Vulnerabilities, y Code Smells.

Para poder utilizarla, debemos descargarla desde su sitio oficial: 

https://www.sonarqube.org/

Finalizada la descarga, debemos descomprimir el archivo .zip y editar el archivo llamado wrapper.conf que se encuentra en el interior del directorio config para establecer la ruta en donde se encuentra el binario de Java:

wrapper.java.command=/path/to/my/jdk/bin/java

Iniciar SonarQube 

Ingresar en el directorio bin/linux-x86-64 y ejecutar el comando ./sonar.sh console. Por defecto, SonarQube escucha en el puerto 9000, por lo que si accedemos a la url http://localhost:9000 nos encontraremos con su interfaz.


 En una próxima entrada, veremos como auditar el código fuente de un proyecto Java.

Compartir:

sábado, 19 de septiembre de 2020

Documentar proyecto JAX-RS con Swagger-Core y Swagger UI

 


link del proyecto: https://github.com/Francisco-Castillo/countries

Agregar dependencia en pom.xml

Para poder documentar nuestros proyectos API REST desarollados con JAX-RS, debemos utilizar la herramienta swagger-core. Para ello, es necesario incluir sus dependencias en el archivo pom.xml:

Agregar recursos en clase JAXRSConfig.java

Una vez agregada la dependencia, debemos incluir en la clase que se encarga de publicar el servicio REST, que en este caso se llama JAXRSConfig (podría ser cualquier otro nombre), los recursos que necesita swagger para poder iniciar:

  • resources.add(io.swagger.jaxrs.listing.ApiListingResource.class)
  • resources.add(io.swagger.jaxrs.listing.SwaggerSerializers.class)

Agregar clase Bootstrap.java

Como la documentación que generemos sera visualizada mediante una interfaz gráfica, debemos crear un servlet para poder configurar swagger. El objeto "swagger" establece mediante su método "basePath" la ruta o el "punto de entrada" a nuestra api, que en este caso es "countries/api". Usted debe reemplazar esto por los valores que correspondan en su caso.

Agregar anotaciones

Una vez agregada la dependencia, debemos incluir las anotaciones que nos permitan describir los recursos, las operaciones, parámetros, respuestas y modelos de nuestra API. 

Algunas de las anotaciones que se suelen utilizar son las siguientes:

  • @Api
  • @ApiOperation
  • @ApiResponses
  • @ApiResponse
  • @ApiParam
  • @ApiImplicitParams
  • @ApiImplicitParam

@Api

Aplica a nivel de clase y se utiliza para denotar que dicha clase es un recurso para Swagger. A continuación se observa como aplicarla:


 


@ApiOperation

Esta anotación aplica a nivel de método, y se utiliza para incluir una explicación acerca de cual es la tarea que realiza dicho método.

@ApiResponses y @ApiResponse

Estas anotaciones se utilizan para describir cuales son las posibles respuestas que el metido podría producir. Por ejemplo, en una operación de inserción de productos puede ocurrir que el producto se inserte correctamente y en ese caso se debería producir una respuesta HTTP con un código de estado 201 y y el mensaje "Producto insertado correctamente", o bien podría ocurrir que el producto ya se encuentre en la base de datos, por lo que se podría retornar una respuesta incluyendo el código HTTP 209 y el mensaje de "Producto existente", entre otras. En el caso de retornar una sola respuesta se debe utilizar la anotación @ApiResponse, en caso de ser mas de una, usar @ApiResponse y en su interior incluir cada respuesta usando @ApiResponse.

@ApiParam

Se utiliza para describir los parámetros que espera recibir un método indicando su nombre, su tipo dato, si es requerido o no, entre otros. Esta anotación se debe incluir por cada parámetro que el método contenga.

A continuación se observa un ejemplo que incluye todas las anotaciones vistas hasta el momento:


@ApiImplicitParams y @ApiImplicitParam 

 Estas anotaciones se utilizan para indicar que un metodo puede recibir o no una serie de parametros. Generalmente se las suele utilizar en métodos que permiten realizar búsquedas. Al igual que en el caso de las respuestas, si el método espera recibir un solo parametro se debe utilizar @ApiImplicitParam, caso contrario utilizar @ApiImplicitParams, como se observa a continuacion:


Para conocer mas sobre las anotaciones, usted puede ingresar al siguiente enlace: https://github.com/swagger-api/swagger-core/wiki/Annotations-1.5.X

Agregar Swagger UI

El último paso, consiste en agregar la interfaz grafica de swagger, la cual esta disponible en el siguiente enlace: https://github.com/swagger-api/swagger-ui

Una vez clonada o descargada, se debe ingresar en la carpeta "diste" y copiar todos los dentro del directorio "Web Pages" de nuestro proyecto, quedando de la siguiente manera:

Una vez agregados los archivos, debemos editar "index.html", para colocar la ruta completa en donde se encuentra el archivo llamado "swagger.json" que es el que contiene la documentacion de nuestra API. Por defecto, la variable llamada "url", contiene un string hardcodeado a una url de prueba hacia swagger, el cual debe ser reemplazado por la dirección de nuestro proyecto que en este caso es: http://localhost:8080/countries/api/swagger.json, o bien obtenerlo de manera dinamica mediante la variable "apiUrl"


Luego debemos limpiar, construir y ejecutar el proyecto, como resultado obtendremos la visualización del archivo index.html publicado en: http://localhost:8080/countries



Compartir:

Acerca de mí

Mi foto
Capital, Santiago del Estero, Argentina