Minientrada

Generar un certificado autofirmado

A veces necesito que mis sitios web funcionen sobre un canal HTTP seguro y no quiero, por falta de presupuesto o por falta de necesidad, que una CA (entidad certificadora de confianza) me firme un certificado.

Para estos casos, y usando OpenSSL, genero un certificado auto-firmado.

El certificado auto-firmado no sirve para garantizar la identidad de mi servidor, pero al menos me provee de un canal de comunicación seguro.

Esto suele ser un caso de uso bastante habitual en entornos de pruebas y/o de desarrollo.

Prerrequisitos

Autofirmar el certificado.

[1er paso] Generar una solicitud de firma de certificado (CSR, Certificate Signing Request)

La solicitud de firma de certificado contiene la información del certificado junto con la clave pública, y es la información que se envía a las autoridades certificadoras (CAs) para  que la firmen y generen el certificado de confianza.

Para generar la solicitud de firma CSR de un website que se llama techstrategydev.midominio.net, se ejecuta el siguiente comando:

$> openssl req -new -newkey rsa:2048 -nodes -keyout techstrategydev.midominio.net.key -out techstrategydev.midominio.net.csr

Generating a 2048 bit RSA private key

.....+++

...........................+++

writing new private key to 'techstrategydev.midominio.net.key'

-----

You are about to be asked to enter information that will be incorporated

into your certificate request.

What you are about to enter is what is called a Distinguished Name or a DN.

There are quite a few fields but you can leave some blank

For some fields there will be a default value,

If you enter '.', the field will be left blank.

-----

Country Name (2 letter code) [AU]:ES

State or Province Name (full name) [Some-State]:Bizkaia

Locality Name (eg, city) []:Bilbao

Organization Name (eg, company) [Internet Widgits Pty Ltd]:EGV COMPANY

Organizational Unit Name (eg, section) []:ARQUITECTURA

Common Name (e.g. server FQDN or YOUR name) []:techstrategydev.midominio.net

Email Address []:mi.email@midominio.net

Please enter the following 'extra' attributes

to be sent with your certificate request

A challenge password []:xxxxxxxxxxxx

An optional company name []:

Esta ejecución debería generar dos ficheros:

  • El fichero *.key que es la clave privada que hay que guardar en un lugar seguro bajo siete candados. Este fichero está protegido por la contraseña que hemos introducido cuando nos ha pedido «A challenge password».
  • El fichero *.csr, que es la petición de firma de certificado que contiene la clave pública.

 

[2o paso (opcional)] Comprobar los datos introducidos en la solicitud de firma de certificado (CSR, Certificate Signing Request)

Se puede comprobar que la información que hemos introducido en la petición es correcta.

Se puede comprobar el «Subject» ejecutando el siguiente comando:

$> openssl req -subject -noout -in techstrategydev.midominio.net.csr

subject=/C=ES/ST=Bizkaia/L=Bilbao/O=EGV COMPANY/OU=ARQUITECTURA/CN=techstrategydev.midominio.net/emailAddress=mi.email@midominio.net

 

[3er paso] Auto-firmar la petición CSR y generar el certificado auto-firmado.

El fichero CSR es el que se envía a la entidad certificadora (CA) para que lo firme y nos devuelva el certificado firmado. En este caso, como ya hemos dicho, no vamos a enviarlo a ninguna CA si no que vamos a firmar nosotros la solicitud (CSR) usando la clave privada.

$> openssl x509 -req -days 1095 -in techstrategydev.midominio.net.csr -signkey techstrategydev.midominio.net.key -out techstrategydev.midominio.net.crt

El parámetro -days indica el número de días en los que el certificado es válido. En este caso, al indicar 1095 días estamos diciendo que el certificado tiene una validez de 3 años (365×3).

La ejecución de este comando nos genera un fichero *.crt que contiene el certificado auto-firmado.

[4o paso (opcional)] Generar el certificado en formato PFX

La extensión PFX se utiliza en los servidores de Windows para los archivos que contienen tanto los archivos de clave pública (el archivo *.crt con el certificado auto-firmado) y la clave privada que corresponde a ese certificado (generado por el servidor cuando hemos generado la CSR en el 1er paso).

Para obtener el fichero PFX se ejecuta el siguiente comando:

$> openssl pkcs12 -export -out techstrategydev.midominio.net.pfx -inkey techstrategydev.midominio.net.key -in techstrategydev.midominio.net.crt

Este comando nos solicitará una password para proteger la clave privada y tras esto nos genera un fichero *.pfx que ya podemos incluir en nuestro servidor windows, o como es mi caso en la nube de azure.

 

 

 

Minientrada

Montando un entorno de desarrollo para aplicaciones Angular 8

El manejo, desarrollo y configuración de los backend es el punto fuerte de mi carrera profesional, y debo reconocer que tengo un gran debe con el frontend.

Acabo de comenzar un proyecto en el que Angular 8 y PrimeNG van a ser mis grandes aliados para crear una interfaz de usuario web atractiva. Me ha llegado el momento, por tanto, de comenzar a escribir algunos posts relacionados con Javascript. El primero, como crear en mi MAC un entorno de desarrollo que me permita crear la web del futuro.

Requisitos.

Para desarrollar con Angular, es necesario tener:

  • Node.js – En versión 12.2.0 o superior.
  • Typescript – En versión 2.1 o superior.
  • Navegador – Recomendado utilizar Google Chrome, aunque Mozilla Firefox también funcionará bien.
  • Instalar Angular CLI
  • Instalar un IDE de desarrollo.

Instalar homebrew.

Para instalar los requisitos hay varias maneras de hacerlo, utilizando por ejemplo el instalador que te descargas del website correspondiente (p.e el instalador de node.js  https://nodejs.org/es/download/) o utilizando Homebrew, un gestor de paquetes macOS para instalar aquellos paquetes que no se instalan desde la tienda de Apple.

Utilizar Homebrew permite instalar, desinstalar y actualizar de manera automática los paquetes que se vayan necesitando.

Para instalar homebrew hay que ejecutar desde un terminal lo siguiente:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Para comprobar que brew se ha instalado correctamente puedes ejecutar el comando brew con la opción –version. El resultado debería ser la versión instalada.

$brew --version
Homebrew 2.1.6
Homebrew/homebrew-core (git revision ebe4; last commit 2019-06-16)

Homebrew habrá creado una estructura de directorios para manejar aquellas aplicaciones que se instalan utilizando la herramienta. Esto ordenará la instalación de esos paquetes y aplicaciones que no se instalan utilizando las herramientas de MAC.

Si ya tenías  instalado brew, acuérdate de actualizarlo:

$brew update

Instalar Node.js

Instalaremos nodejs utilizando homebrew.

Instalaremos nodejs utilizando directamente el instalador del website de nodejs. La página de descargas es https://nodejs.org/es/download/.

Al terminar la instalación habrá dejado instalado el ejecutable node y el gestor de paquetes npm en el directorio ejecutable de usuario /usr/local/bin. 

Para comprobar que nodejs está correctamente instalado puedes preguntar por las versiones tanto de node como de npm.

$ node -v
v10.16.0

$npm -version
6.9.0

Instalar Typescript

Para que angular funcione correctamente usaremos typescript 2.1 o superior. Para instalarlo usaremos el gestor de paquetes npm.

$ npm install -g typescript

Instalar Angular CLI

Angular CLI es la consola de cliente que se utiliza en angular para administrar la creación de aplicaciones.

$ npm install -g @angular/cli

Para probar que se ha instalado correctamente como siempre se puede consultar la versión instalada.

$ ng --version
     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/

    
Angular CLI: 8.0.3
Node: 10.16.0
OS: darwin x64
Angular: 
... 

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.800.3
@angular-devkit/core         8.0.3
@angular-devkit/schematics   8.0.3
@schematics/angular          8.0.3
@schematics/update           0.800.3
rxjs                         6.4.0

 

Instalar un IDE de desarrollo.

Existen tantos IDEs de desarrollo como estrellas en el firmamento. Quizás esto es una exageración, pero hay muchos. Mi recomendación es que instales uno con el que te sientas a gusto, y si no tienes preferencias, que instales Visual Studio Code de Microsoft. Gratis y continuamente mantenido.

Puedes descargarlo aquí.

 

Y de momento esto es todo. Ya tenemos instalado en nuestro ordenador lo necesario para poder desarrollar sitios web usando Angular.

Minientrada

Breve introducción a las expresiones lambda.

Expresiones lambda

Las expresiones lambda no son complejas de usar y entre otras ventajas permiten pasar comportamiento como valor, algo muy utilizado en Javascript, y abreviar líneas de código sin perder legibilidad en el código. Es por esto que Java las adoptó definitivamente en la JavaSE 8. Pero, para que las expresiones lambda sean verdaderamente útiles, hay que llegar a entenderlas. Hasta que eso ocurre pueden llegar a ser bastante confusas.

Si nos atenemos a la definición oficial de expresión lambda diríamos que son funciones anónimas que implementan una interfaz funcional, que así dicho suena muy técnico, pero si te lo dicen así sin más, puede dejarte algo frío. A mi es lo que me pasó, así que me puse manos a la obra para intentar aterrizar el concepto y desgranar la definición teórica haciéndola comprensible para mortales como yo. De ese germen surgió esta entrada.

Lo que me quedó claro de la definición es qué, para entender las expresiones lambda hay que entender qué es una función anónima y que es una interfaz funcional.

Función anónima

Las funciones anónimas permiten crear instancias de un objeto que implementa un interfaz en particular sin que para ello sea necesario desarrollar la clase que implementa esa interfaz explícitamente.

¡Toma ya!

Esto se ve claro con un ejemplo. Imaginemos una interfaz que ofrece métodos para una calculadora:

package com.tecnicomio.pruebas.funcionesanonimas;

public interface ICalculadora {

    Integer suma (Integer operando1, Integer operando2);

}

De manera tradicional tendríamos una clase que implementara la interfaz:

package com.tecnicomio.pruebas.funcionesanonimas;

public class Calculadora implements ICalculadora {

    @Override
    public Integer suma(Integer operando1, Integer operando2) {
        return operando1 + operando2;
    }
}

Posiblemente, utilizaríamos esta clase en otra parte del código de la siguiente manera:

[...]
       ICalculadora calculadora = new Calculadora();
       Integer operando1 = 10;
       Integer operando2 = 22;
       Integer resultado = calculadora.suma(operando1, operando2);
       System.out.println(operando1 + "+" + operando2 + "=" + resultado);
[...]

Ahora haré una aproximación con funciones anónimas. La interfaz se mantiene, pero la clase desaparece.

El código resultado sería:

[...]

       Integer operando1 = 10;
       Integer operando2 = 22;

       System.out.println(operando1 + "+" + operando2 + "=" +
                new ICalculadora() {
                    @Override
                    public Integer suma(Integer operando1, Integer operando2) {
                        return operando1 + operando2;
                    }
                }.suma(operando1, operando2));
[...]

Repasando qué hemos conseguido con la función anónima, llegamos a las siguientes conclusiones:

  • Nos hemos ahorrado la definición y creación de la clase, que en el caso de clases que se usan en un único sitio, puede ser un gran ahorro. Así evitamos contaminar el proyecto con esas clases que no se reutilizan en otras partes.
  • Cuando la implementación es corta, como en el caso de este ejemplo, en el que el código se encuentre directamente en el lugar donde se usa, puede hacer que éste sea más entendible (aunque tal vez menos legible).
  • Permite seguir manejando correctamente las variables locales y miembros de la clase sin tener que definir un constructor para poder recibirlas y usarlas.

En este ejemplo, es probable que el ahorro de crear la clase, versus la disminución de legibilidad, haga que no merezca suficientemente la pena, pero pongamos un ejemplo donde verdaderamente las funciones anónimas dan la talla. Imaginemos una ventana con 15 botones y sus respectivos 15 event listeners cada vez que el usuario hace click en ellos. En este caso, habría que crear 15 clases donde cada una implementa el interfaz ActionListener. Clases que solo se usan en el punto del código donde se implementa el formulario. En este caso, usar funciones anónimas es mucho más rentable.

Interfaz Funcional

Un interfaz funcional es aquel interfaz que tiene únicamente un método y este método es abstracto, es decir un método sin implementar. Las interfaces funcionales fueron agregadas a partir de la versión JavaSE 8 y vienen de la mano de las expresiones lambda.

A continuación dos ejemplos de interfaz funcional. El primero es un ejemplo al uso, con un único método abstracto.

package com.tecnicomio.interfazfuncional;

public interface IInterfazFuncionalSimple {
  public String saludo(String nombre);
}

El segundo es un ejemplo un poco más rebuscado pero igualmente válido; un único método abstracto y varios métodos default.

package com.tecnicomio.interfazfuncional;

public interface IInterfazFuncionalRebuscada {
  public String saludo(String nombre);

  public default String holaMundo() {
    Return "Hola mundo.";
  }
}

Para asegurarnos que la interfaz cumple con las reglas de las interfaces funcionales podríamos anotar la interfaz con @FunctionalInterface. En este caso si introdujéramos más de un método abstracto (sin implementación) el compilador nos daría el error: «Multiple non overriding abstract methods found in interface com.tecnicomio.pruebas.interfazfuncional.InterfazFuncionalAnotada». En caso de no introducir ningún método abstracto, nos daría el error: «No Target method found».

package com.tecnicomio.interfazfuncional;

@FunctionalInterface
public interface IInterfazFuncionalAnotada {
  public String saludo(String nombre);

  public default String holaMundo() {
    Return "Hola mundo.";
  }
}

En el ejemplo de la calculadora, la interfaz ICalculadora es un claro ejemplo de Interfaz Funcional, sin anotar.

Expresiones lambda

Y ahora que ya han quedado un poco más claros los conceptos «funciones anónimas» e «interfaces funcionales» volvamos a la definición de expresión lambda: las expresiones lambda son funciones anónimas que implementan una interfaz funcional.

Por tanto, podríamos decir que son una evolución de las funciones anónimas que pretenden simplificar aún más el código, pero que para conseguir esta simplificación, obligan a que la funcionalidad de la expresión lambda implemente una interfaz funcional.

Sintaxis de las expresiones lambda

La sintaxis de las expresiones lambda es:

	
(Parametros) -> { cuerpo expresión lambda }

Teniendo en cuenta qué:

  • El operador lambda (->) separa la declaración de parámetros del cuerpo de la función.
  • Parámetros
    • Cuando se tiene un solo parámetro pueden omitirse los paréntesis.
    • Cuando no se tienen parámetros, o cuando se tienen dos o más, sí es necesario su uso.
  • Cuerpo de la expresión lambda
    • Cuando el cuerpo de la expresión lambda tiene una única línea pueden omitirse las llaves y no se necesita especificar la cláusula return en el caso de que se devuelva valor.

Ejemplos de expresiones lambda pueden ser:

  • Expresión lambda con un único parámetro y una única línea: num -> num+10
  • Expresión lambda sin parámetros y una única línea: () -> System.out.println(«Hola mundo»)
  • Expresión lambda con dos parámetros y una única línea: (int operando1, int operando2) -> operando1 * operando2
  • Expresión lambda con múltiples parámetros y varias líneas de función: (String nombre) -> {String retorno=»Hola «; retorno=retorno.concat(nombre); return retorno;}

La calculadora con expresiones lambda

Y como lo mejor para entender algo es verlo con un ejemplo, apliquemos las expresiones lambda al ejemplo de la calculadora que podría quedar así:

        ICalculadora calculadora = (Integer operando1, Integer operando2)->(operando1+operando2);

        Integer operando1 = 10;
        Integer operando2 = 22;

        System.out.println(operando1 + "+" + operando2 + "=" + calculadora.suma(operando1,operando2));

O si queremos ahorrar aún más líneas de código sin perder legibilidad podría quedar de esta otra forma:

    System.out.println("En una línea:10+22=" + ((ICalculadora)(Integer operando1, Integer operando2)->(operando1+operando2)).suma(10,22));

Conclusiones

Como se puede ver en el ejemplo de la calculadora utilizar expresiones lambda tienen claros y algunos obscuros.

Para mí los claros son:

  • Al hacer uso de funciones anónimas sin necesidad de crear clases anónimas se crea código más claro y conciso.
  • Acercan Java a la programación funcional muy utilizado en lenguajes de script, como Javascript, donde las funciones juegan un papel protagonista. Esto permite poder pasar funciones como valores de variables, valores de retorno o parámetros de otras funciones, es decir, gracias a las expresiones lambda se puede pasar comportamiento como valor.
  • Al usarlas en combinación con la API Stream se pueden realizar operaciones de tipo filtro/mapeo sobre colecciones de datos de forma secuencial o paralela siendo la implementación transparente al desarrollador.
  • Logran un código más compacto y fácil de leer.
  • Reducen la escritura de código.

El oscuro para mí es claro:

  • Hay que entenderlas para sacarles el máximo partido. Y entenderlas es cambiar la manera de pensar del javero de toda la vida.

Links

Minientrada

GIT – Detrás de un proxy

GIT – Detrás de un proxy.

En todas las empresas para las que he trabajado in-situ siempre me he encontrado un proxy para que los trabajadores accedemos a internet de manera controlada y de manera productiva. Desgraciadamente, los proxies a veces dan quebraderos de cabeza cuando las aplicaciones que usamos necesitan de una conexión con la red de redes para funcionar correctamente. GIT es una de esas aplicaciones que, por ejemplo, cuando queremos consolidar nuestro código fuente en la nube, necesita de una conexión a internet para funcionar correctamente, y si hay un proxy de por medio, necesitaremos de una configuración extra para conseguirlo.

La herramienta para configurar GIT

Antes de nada, una pequeña pincelada de la herramienta que GIT proporciona para obtener y establecer variables de configuración que controlan el funcionamiento de GIT: git config.

Se puede elegir que estas configuraciones se almacenen en tres lugares distintos, dependiendo del nivel de alcance que queremos que tengan:

  • /etc/gitconfig (en windows /mingw64/etc/gitconfig): Contiene configuraciones para todos los usuarios del sistema y para todos los repositorios. Para ello hay que pasar el comando –system a git config.
  • /.gitconfig: Contiene configuraciones específicas del usuario para todos los repositorios. Para ello hay que pasar el comando –global a git config.
  • /.git/config/config: Contiene configuraciones propias de cada repositorio. En este caso, no es necesario pasar comando alguno, pero hay que indicar el repositorio sobre el que se quiere aplicar la configuración.

Cada nivel sobreescribe los valores del nivel anterior, por lo que los valores particulares del repositorio tienen preferencia frente a los del usuario y estos, a su vez, tienen preferencia frente a los de sistema.

Configurar el uso de proxy en GIT.

git config --global http.proxy  http://proxy.user:proxy.pass@proxy.name_or_ip:proxy.port

Donde:

  • proxy.user es el usuario que tiene permiso para acceder al proxy.
  • proxy.pass es la password de dicho usuario
  • proxy.name_or_ip es el nombre DNS o la dirección IP de la máquina que hace de proxy de internet.
  • proxy.port es el puerto por el que se accede a comunicar con el proxy de internet.
  • –global,  le indica a git que la configuración es propia del usuario y sirve para todos sus repositorios.

Consultar el proxy configurado en git

git config --global --get http.proxy

Borrar el proxy configurado en git.

git config --global --unset http.proxy

Conclusión

En este caso la solución es sencilla y funciona correctamente si seguimos los pasos indicados. Espero, como siempre, que esta información, aunque fácil de encontrar en la documentación oficial de GIT, sea de utilidad.

Minientrada

GIT – Aproximación básica al control de fuentes.

En esta entrada voy a hablar de GIT de una manera muy básica, conceptos, funcionamiento y uso práctico desde un punto de vista útil del día a día.

GIT vs Subversion

Para alguien como yo que vengo de utilizar primero CVS y después Subversion, el funcionamiento de GIT puede resultar a priori familiar, sobre todo debido a que los comandos parecen similares a los de Subversion, pero esta familiaridad puede inducir a errores a la hora de funcionar. Por tanto, es importante entender el funcionamiento de GIT para no cometer errores basados en la experiencia con otras herramientas.

La principal diferencia entre SVN y GIT es la manera de modelar el control de los ficheros fuentes. Subversion almacena la versión inicial de los datos que queremos controlar y posteriormente va almacenando los cambios realizados sobre ellos, esta información sobre los cambios se conocen como deltas. GIT sin embargo, almacena instantaneas de cada momento concreto, y para resultar óptimo si un fichero no ha cambiado se utiliza la versión guardada en la instantánea anterior.

Estructura de un respositorio GIT

Un repositorio GIT se almacena en local, no en un servidor, y la información que se almacena está compuesta por tres árboles conceptuales: el directorio de trabajo que contiene los archivos, el Index que actúa como zona intermedia y el árbol HEAD que apunta al último commit realizado.

Bondades de GIT

La mayoría de las operaciones de GIT solo necesitan archivos y recursos locales lo que proporciona más sensación de rapidez. Por ejemplo, para consultar la historia del proyecto, GIT no tiene que conectar con un servidor remoto y esperar su respuesta, si no que obtiene la información de  la base de datos local. Para obtener las diferencias de un fichero con la versión de hace un mes, buscará el fichero en local y calcularás las diferencias sin tener que ir a una ubicación externa. Esto permite operar con GIT sin tener conexión efectiva con el exterior.

Descargar GIT

GIT se puede descargar desde la sección de descargas de su sitio web oficial. Existen binarios para Windows (32 y 64bits) , Mac OS X, Linux y Solaris.

Uso práctico

Creación de un repositorio nuevo

Para poner bajo el control de GIT un directorio con código fuente hay que generar un repositorio sobre él. Esto se realiza situándose en el directorio y ejecutando el siguiente comando:

git init

Este comando convierte automáticamente el directorio elegido en el (master) y crea dentro un subdirectorio .git que contiene toda la información que GIT necesita para funcionar correctamente.

Aunque se ha creado el repositorio, todavía ninguno de los fuentes están todavía bajo el control de GIT. Para ello hay que añadir los ficheros que se desean controlar al repositorio.

Conectar con un repositorio existente.

Hay ocasiones en las que se desea descargar el código fuente de un repositorio ya existente (de un master).

Para ello hay que situarse en el directorio donde se quiere tener el código fuente descargado. Para ello se ejecuta el siguiente comando:

git clone username@host:/path/to/repository

Este comando descarga el código a la carpeta desde la que se ha lanzado su ejecución. Si el (master) se encuentra en la máquina local se puede lanzar el comando omitiendo username@host:.

Si te pasa como a mí me pasa a menudo, que primero desarrollo y luego cuando me parece que la cosa merece la pena decido ponerla a buen recaudo bajo un control de fuentes, te darás cuenta que GIT, a diferencia de otros, no permite realizar la operación de clonado sobre un directorio existente (y lleno de fuentes). En este caso, los pasos a dar son los siguientes:

  • Realizar el clone en un directorio temporal
git clone username@host:/path/to/repository temp
  • Copiar el fichero .git al directorio donde queríamos clonar el repositorio originalmente.
mv temp/.git code/.git
  • Borrar el directorio temporal.
rm -rf temp

Añadir ficheros nuevos a un repositorio.

Para añadir ficheros de código fuente al control de GIT se realiza en dos pasos: añadir (add) y consolidar (commit).

Añadir el fichero le dice a GIT que ficheros tiene que tener en cuenta a la hora de realizar el control. Para ello se lanza el siguiente comando:

git add <filename>

Si se quieren añadir todos los ficheros de un directorio se lanza el siguiente comando:

git add .

Si se quieren añadir todos los ficheros con una extensión concreta se lanza el comando:

git add *.<extensión>

Una vez indicado qué ficheros hay que añadir al control de GIT, hay que consolidarlos.

Eliminar ficheros de un repositorio.

La eliminación de ficheros de código fuente al control de GIT también se realiza en dos pasos: eliminar (remove) y consolidar (commit).

El comando para eliminar un fichero es el siguiente:

git rm <filename>

Si lo que se quiere borrar es una carpeta con su contenido habrá que indicarle a git que el borrado es recursivo:

git rm -r <path/to/remove>

Una vez indicados los ficheros a borrar hay que consolidar este cambio en GIT.

Consolidar los cambios en el repositorio local.

Tanto si se ha añadido, como si se ha modificado o eliminado ficheros, hay que consolidar estas operaciones en el repositorio. Hasta que no se lleve a cabo la consolidación, los cambios solo estarán disponibles en nuestro área de trabajo.

Para consolidar los cambios se lanza el comando:

git commit -m "<mensaje de la consolidación>"

Consolidar los cambios en el repositorio remoto.

Una vez consolidado los cambios en el repositorio local, tal vez deseemos consolidar estas operaciones en el repositorio master. Hasta que no se lleve a cabo la consolidación, los cambios solo estarán disponibles en nuestro repositorio GIT local.

Para consolidar los cambios se lanza el comando:

git push

Generar un TAG de versión a partir de una situación concreta en el repositorio.

Una vez que se ha terminado de realizar el desarrollo y se ha decidido que el software está listo para ir a producción, se puede etiquetar el HEAD con el nombre que se estime oportuno.

Este proceso de etiquetado en GIT se conoce como TAG (también en SVN) y se lleva a cabo con el comando:

git tag <nombre-tag> <id. commit>

El nombre del TAG normalmente suele ser un número de versión, por ejemplo, 1.0, 1.0.1, 5.2.4. El identificador del commit se puede obtener con el comando git log y es un alfanumérico único que identifica el repositorio con una instantánea concreta.

Links

Documentación oficial
PDF resumen con los comandos básicos de GIT.

Minientrada

Internacionalizando una aplicación web basada en JSF 2.x

En esta entrada voy a realizar una aplicación muy básica con JSF 2.x y voy a internacionalizarla, de forma que se pueda ejecutar en dos lenguajes diferentes: español e inglés.

Es conveniente si tenemos claro que la aplicación debe aceptar varios idiomas que la internacionalización la apliquemos en la fase más temprana posible. Incorporar la internacionlización cuando la aplicación ya está muy avanzada suele ser bastante más costoso que hacerlo en etapas tempranas por varios motivos, pero principalmente las diferencias que hay entre los diferentes idiomas en cuanto a decir lo mismo con más o menos palabras pueden afectar al diseño de las páginas.

Requisitos para desarrollar el ejemplo.

Para que se pueda reproducir el ejemplo y esté operativo al 100% comento qué entorno he utilizado para hacerlo funcionar.

IDE desarrollo: Eclipse 3.6 (Neon).
Máquina virtual Java: JDK 1.8.0_65.
Servidor de aplicaciones: Apache Tomcat 8.5
Ciclo de vida: Maven 3.x
Tecnología: JSF 2.2 (Java EE 7)

Recomiendo la lectura de la entrada JSF 2.x Hola Mundo pues en ella comento todos los aspectos de configuración que en esta entrada voy a dar por supuestos, como la elección de librerías, configuración de servlets, etcétera.

Módulo web de aplicación.

Este ejemplo es tan sencillo que va a contar con un único módulo; el módulo web. El módulo web genera un WAR que se despliega directamente en Apache Tomcat.

Estructura

2016-09-09_14-18-13

Fichero: pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>es.egv.jee6.jsf2</groupId>
 <artifactId>i18nBasico</artifactId>
 <version>1.0.0</version>
 <packaging>war</packaging>

 <dependencies>
   <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-api</artifactId>
     <version>2.2.11</version>
   </dependency>

   <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-impl</artifactId>
     <version>2.2.11</version>
   </dependency>

   <dependency>
     <groupId>javax.el</groupId>
     <artifactId>javax.el-api</artifactId>
     <version>3.0.1-b04</version>
   </dependency>

   <dependency>
     <groupId>javax.servlet.jsp.jstl</groupId>
     <artifactId>jstl-api</artifactId>
     <version>1.2</version>
   </dependency>
 </dependencies>

</project>

Fichero: WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>BasicoI18N</display-name>
  <welcome-file-list>
    <welcome-file>i18nwelcome.xhtml</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
 
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
  </servlet-mapping>
  
  <context-param>
    <param-name>javax.faces.CONFIG_FILES</param-name>
    <param-value>WEB-INF/faces-config.xml</param-value>
  </context-param>

  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
  
</web-app>

Destacar la inclusión de un parámetro de configuración que indica donde se encuentra el fichero de configuración de JSF. En este ejemplo vamos a incluir este fichero de configuración ya que va a ser necesario indicar los ficheros donde se encuentran las traducciones del idioma y el idioma por defecto de la aplicación.

Fichero: WEB-INF/faces-config.xml

<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_2.xsd"
    version="2.2">
    <application>
        <locale-config>
            <default-locale>es</default-locale>
        </locale-config>
        <resource-bundle>
            <base-name>i18n.i18nBasico</base-name>
            <var>i18n</var>
        </resource-bundle>
    </application>
</faces-config>

El campo <locale-config> sirve para definir las configuraciones relativas a la situación geográfica, entre ellas las localizaciones permitidas <supported-locale> o la localización por defecto <default-locale>. La localización es imprescindible para internacionalizar la aplicación.

El campo <resource-bundle> es la piedra angular de la internacionalización, ya que en él se indica donde va a encontrar la aplicación los ficheros que contienen las etiquetas en sus respectivos idiomas <base-name> y el nombre de la variable que se va a utilizar para hacer referencias a ellas <var>. Así podremos encontrar en las páginas dinámicas código como el siguiente donde se hace referencia a la etiqueta i18nwelcome.saludo del grupo de recursos identificado por el nombre de variable i18n:

<h:outputText value="#{i18n['i18nwelcome.saludo']}" />

El fichero faces-config.xml añade un montón de posibilidades de configuración más, pero se escapan al ámbito de esta entrada. Pueden consultarse en el siguiente link.

Los ficheros de recursos para los idiomas.

Estos ficheros contienen las traducciones a diferentes idiomas de las etiquetas de la aplicación. El nombre es el indicado en el campo <base-name> al que se le concatena el lenguaje de la variable Locale. Por ejemplo, para contener las etiquetas en inglés de la aplicación, el fichero se llama i18nBasico_en.properties.

Fichero: i18n/i18nBasico_en.properties

i18nwelcome.titulo = JSF 2.x internacionalization example.
i18nwelcome.saludo = Internacionalization with JSF 2.x. 
i18nwelcome.etiquetaIdioma = Language
i18nwelcome.descripcion-metodo1 = Combo Box Method
i18nwelcome.descripcion-metodo2 = Image Method

Fichero: i18n/i18nBasico_es.properties

i18nwelcome.titulo = Ejemplo de internacionalización con JSF 2.x.
i18nwelcome.saludo = Internacionalización con JSF 2.x. 
i18nwelcome.etiquetaIdioma = Idioma
i18nwelcome.descripcion-metodo1 = Método basado en una lista seleccionable. 
i18nwelcome.descripcion-metodo2 = Método basado en imágenes.

Se puede ver que el nombre de las etiquetas es el mismo en ambos ficheros, pero el valor de las mismas está traducido al idioma que se indica en el nombre del fichero.

El Managed Bean que maneja la lógica de presentación del idioma.

Fichero: es.egv.jee6.jsf2.i18n.IdiomaBean.java

package es.egv.jee6.jsf2.i18n;

import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;

@ManagedBean(name="idiomaBean")
@SessionScoped
public class IdiomaBean implements Serializable {


    private static final long serialVersionUID = 1L;

    private String codigoIdioma;
    
    private Map<String,Object> listaIdiomas;
    
    public IdiomaBean() {
        super();
        this.codigoIdioma = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
        this.listaIdiomas = new LinkedHashMap<String,Object>();
        this.listaIdiomas.put("Español", new Locale("es"));
        this.listaIdiomas.put("Inglés", new Locale("en"));
    }

    public String getCodigoIdioma() {
        return codigoIdioma;
    }
    
    public void setCodigoIdioma(String codigoIdioma) {
        this.codigoIdioma = codigoIdioma;
    }
    
    public Map<String, Object> getListaIdiomas() {
        return listaIdiomas;
    }
    
    public void doCambioIdiomaConLista(ValueChangeEvent e)
    {

        String newCodigoIdioma = e.getNewValue().toString();
        System.out.println("newCodigoIdioma=" + newCodigoIdioma);
        System.out.println("idiomaBean=" + this.toString());

        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : listaIdiomas.entrySet()) 
        {

               if(entry.getValue().toString().equals(newCodigoIdioma))
               {
                   System.out.println("Asignando nueva locale al contexto de Faces.");
                   FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
               }
        }
    }
    
    public void doCambioIdiomaConImagen(String nuevoIdioma)
    {

        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : listaIdiomas.entrySet()) 
        {

               if(entry.getValue().toString().equals(nuevoIdioma))
               {
                   System.out.println("Pinchado en imagen " + nuevoIdioma + ". Asignando nueva locale al contexto de Faces");
                   this.codigoIdioma = nuevoIdioma;
                   FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
               }
        }
    }    
   
    @Override
    public String toString() {
        return "IdiomaBean [codigoIdioma=" + codigoIdioma + ", listaIdiomas=" + listaIdiomas + "]";
    }     
}

Defino un managed bean específico para manejar el idioma. Será el encargado de conservar el idioma seleccionado por el usuario, una lista de idiomas seleccionables, y las diferentes acciones que se llevan a cabo cuando se realizan acciones relacionadas con el idioma.

@ManagedBean(name="idiomaBean")
@SessionScoped
public class IdiomaBean implements Serializable {

Este ManagedBean lo defino en el ámbito de la sesión @SessionScoped. De esta manera, el idioma queda almacenado en todas las navegaciones que realice el usuario hasta que cierre el navegador, o la sesión.

    private String codigoIdioma;
    private Map<String,Object> listaIdiomas;

Se definen la variable codigoIdioma para contener el idioma seleccionado por el usuario y la variable listaIdiomas para contener los idiomas que van a mostrarse al usuario para proceder a la traducción de la aplicación.Se incluyen también sus getters y sus setters.

    public IdiomaBean() {
        super();
        this.codigoIdioma = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
        this.listaIdiomas = new LinkedHashMap<String,Object>();
        this.listaIdiomas.put("Español", new Locale("es"));
        this.listaIdiomas.put("Inglés", new Locale("en"));
    }

Se incluye un constructor para rellenar el código del idioma con el idioma configurado por JSF en el arranque, que será el idioma que se ha configurado en el <default-locale> del fichero faces-config.xml, y rellenar la lista de idiomas con los idiomas válidos para la aplicación.

   public void doCambioIdiomaConLista(ValueChangeEvent e)
    {

        String newCodigoIdioma = e.getNewValue().toString();
        System.out.println("newCodigoIdioma=" + newCodigoIdioma);
        System.out.println("idiomaBean=" + this.toString());

        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : listaIdiomas.entrySet()) 
        {

               if(entry.getValue().toString().equals(newCodigoIdioma))
               {
                   System.out.println("Asignando nueva locale al contexto de Faces.");
                   FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
               }
        }
    }

Se añade un método para manejar el cambio del idioma cuando se seleccione un idioma de la lista.

<h:selectOneMenu value="#{idiomaBean.codigoIdioma}" onchange="submit()"
                valueChangeListener="#{idiomaBean.doCambioIdiomaConLista}">

Y, se añade un método que se ejecutará cuando se seleccione un cambio de idioma pinchando en la imágen del mismo.

    public void doCambioIdiomaConImagen(String nuevoIdioma)
    {

        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : listaIdiomas.entrySet()) 
        {

               if(entry.getValue().toString().equals(nuevoIdioma))
               {
                   System.out.println("Pinchado en imagen " + nuevoIdioma + ". Asignando nueva locale al contexto de Faces");
                   this.codigoIdioma = nuevoIdioma;
                   FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
               }
        }
    }
<h:commandLink action="#{idiomaBean.doCambioIdiomaConImagen('en')}">

Destacar que se ha incluido la asignación del idioma seleccionado a la variable codigoIdioma del bean para que quede guardada la sección.

this.codigoIdioma = nuevoIdioma;

Páginas web dinámicas.

El ejemplo consta de tres páginas web dinámicas: i18nwelcome.xhtml que es la página básica de bienvenida, i18nidiomametodo1.xhtml que es la página con el manejo de idioma basado en una lista seleccionable, e i18nidiomametodo2.xhtml que es la página con el manejo del idioma basado en imágenes.

2016-09-09_15-17-48

Fichero: i18nwelcome.xhtml

<!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:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">

<h:body>
    <h1>#{i18n['i18nwelcome.titulo']}</h1>
    <h:form>
        <h2>
            <h:outputText value="#{i18n['i18nwelcome.saludo']}" />
        </h2>        
        <ui:include src="i18nidiomametodo1.xhtml" />        
        <ui:include src="i18nidiomametodo2.xhtml" />
    </h:form>
</h:body>
</html>

Esta página muestra dos etiquetas a internacionalizar; i18nwelcome.titulo e i18nwelcome.saludo. Para mostrar correctamente el idioma se utiliza expresiones EL que son interpretadas por JSF en tiempo de ejecución.

#{i18n['i18nwelcome.titulo']}

Esta expresión indica que se va a recoger del bundle i18n la etiqueta i18nwelcome.titulo.

Por claridad, los dos métodos que se van a utilizar para la selección del idioma se han realizado en dos páginas dinámicas independientes que se incluyen en la página básica mediante la sentencia <ui:include src=»»/>.

<ui:include src="i18nidiomametodo1.xhtml" />

Fichero: i18nidiomametodo1.xhtml

<ui:composition 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
        <h:outputText value="#{i18n['i18nwelcome.descripcion-metodo1']}."/>
        <h:panelGrid columns="2">
            <h:outputText value="#{i18n['i18nwelcome.etiquetaIdioma']}:" />
            <h:selectOneMenu value="#{idiomaBean.codigoIdioma}" onchange="submit()"
                valueChangeListener="#{idiomaBean.doCambioIdiomaConLista}">
                <f:selectItems value="#{idiomaBean.listaIdiomas}" />
            </h:selectOneMenu>
        </h:panelGrid>
</ui:composition>

Estos ficheros como se han diseñado para ser incluidos en un fichero padre y no para llamarse directamente se crean como elementos <ui:composition/>.

El valor seleccionado en la lista seleccionable es el codigoIdioma almacenado en el Managed Bean.

<h:selectOneMenu value="#{idiomaBean.codigoIdioma}"

La lista se carga con los valores almacenados en la listaIdiomas del Managed Bean.

<f:selectItems value="#{idiomaBean.listaIdiomas}" />

Cuando se cambia el elemento seleccionado se lanza el método doCambioIdiomaConLista.

onchange="submit()"
                valueChangeListener="#{idiomaBean.doCambioIdiomaConLista}"

Fichero: i18nidiomametodo2.xhtml

<ui:composition 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
        <h:outputText value="#{i18n['i18nwelcome.descripcion-metodo2']}."/>
        <h:panelGrid columns="1">
            <h:commandLink action="#{idiomaBean.doCambioIdiomaConImagen('en')}">
                <h:graphicImage library="images" name="bandera-en.png" rendered="#{idiomaBean.codigoIdioma == 'es'}"/>
            </h:commandLink>
            <h:commandLink action="#{idiomaBean.doCambioIdiomaConImagen('es')}">
                <h:graphicImage library="images" name="bandera-es.png" rendered="#{idiomaBean.codigoIdioma == 'en'}"/>
            </h:commandLink>
        </h:panelGrid>
</ui:composition>

Destacar que se muestra el botón con la bandera del idioma que no está seleccionado, para así poder cambiarlo. Para ello se utiliza el atributo rendered y expresiones EL.

rendered="#{idiomaBean.codigoIdioma == 'es'}"

Demo

El pase de diapositivas requiere JavaScript.

Link

Configurar Aplicaciones JSF
Documentación oficial JSF 2.x

 

Minientrada

Firefox, el proxy y el continuo error 407 (Proxy Authentication Required).

En el último proyecto en el que estoy recientemente involucrado he definido un entorno de trabajo virtualizado con máquinas basadas en Windows 10 que no se incluyen en el dominio y por tanto no realizan autenticación en él; la autenticación se realiza mediante usuarios locales. Sin embargo, para salir a internet se utiliza un proxy, en el que sí es necesario autenticarse a través del dominio windows de la organización.

A priori, sobre el papel esta configuración es bastante común y no da problemas. Para abrir una URL de internet desde el navegador, inicialmente se intenta hacer login con el usuario local, al no ser usuario de dominio y no poder autenticarse en el dominio, el navegador muestra una ventana para introducir el usuario y la password correctas. Si con ese usuario y password la autenticación es correcta en el dominio, automáticamente se navega a la url exterior solicitada.

En las máquinas virtuales de los desarrolladores, con Internet Explorer no hay ningún tipo de problema, se introduce el usuario, la password y la salida a internet es automática. Con Microsoft Edge el funcionamiento es correcto también, pero con Firefox (cualquier versión, aunque concretamente estamos utilizando Firefox Developer Edition 50.0a2) empiezan los problemas ya que este navegador se bucla solicitando continuamente el usuario y la password que el proxy necesita para realizar la autenticación.

2016-09-08_16-16-55

Solución

Este problema que es bastante engorroso, porque anula el funcionamiento de Firefox y lo elimina como navegador, se soluciona cambiando varias configuraciones internas de Mozilla Firefox.

Para acceder a dichas configuraciones hay que teclear la url about:config.

Es probable que si no hemos accedido antes a ésta URL, nos pida una confirmación para mostrar la página, pues modificar las configuraciones avanzadas puede ser potencialmente peligroso para el navegador.

2016-09-08_15-47-08

En este caso como estamos seguros de lo que vamos a configurar pinchamos en Aceptamos el riesgo.

Las configuraciones que hay que cambiar son:

  • signon.autologin.proxy –> TRUE
  • network.automatic-ntlm-auth.allow-proxies –> FALSE
  • network.auth.use-sspi –> FALSE

Estas configuraciones pueden ser encontradas mediante el campo de Busqueda. El cambio en los campos booleanos se realiza haciendo doble click sobre ellos.

2016-09-08_16-08-28

Completadas las configuraciones, se cierra y vuelve a abrir el navegador, se teclea la url de internet a la que se quiere navegar, se rellena el usuario y la password (una única vez), et voila, debería mostrarse el contenido web de la URL.

Links

Explicaciones de las diferentes configuraciones avanzadas de Firefox.

Minientrada

Publicar aplicaciones en Tomcat mediante MAVEN.

La mayoría de las veces que desarrollo aplicaciones web para Tomcat necesito hacer pruebas rápidamente para comprobar que lo que estoy haciendo funciona correctamente y se ve como debe.

Para conseguir una velocidad óptima en estas pruebas, existe la opción de crear un Servidor en Eclipse y publicar la aplicación, así cada vez que haga un cambio en el código fuente, este se compila y despliega automáticamente en el servidor, pudiendo pasar rápidamente a la fase de pruebas.

Este método está muy bien y puede que sea la manera más rápida de probar, de hecho, suelo utilizarla mucho en las fases iniciales de los proyectos cuando todo es desarrollo nuevo y nada es mantenimiento. Desafortunadamente esta opción tiene también sus contras. La mayoría de las veces, los entornos de integración, de calidad o pre-productivos no tienen instaladas herramientas de desarrollo como Eclipse, incluso ni si quiera tienen instalados gestores de ventanas para así aligerar el consumo de RAM y de CPU.

Por esto, una de mis alternativas favoritas y dado que la mayor parte de las veces utilizo MAVEN para la gestión del ciclo de vida de las aplicaciones Java, es utilizar el plugin Maven de Tomcat.

Esta entrada no es más que un resúmen práctico para incluir este plugin en los desarrollos Java.

Requisitos para seguir el ejemplo.

Para reproducir el ejemplo y tenerlo operativo al 100% he utilizado las siguientes herramientas para hacerlo funcionar.

IDE desarrollo: Eclipse 3.6 (Neon).
Máquina virtual Java: JDK 1.8.0_65.
Servidor de aplicaciones: Apache Tomcat 8.5
Ciclo de vida: Maven 3.x (en mi caso la 3.3.9).

Es necesario haberse descargado el servidor de aplicaciones (contenedor JSP/Servlets en este caso) y poder acceder a la URL http://<dirección ip>:<puerto>/manager. También es necesaria una aplicación web a la que añadiremos todo lo necesario para probar el despliegue automático en dicho servidor.

Configurar tomcat para poder hacer despliegues vía script.

Para subir al servidor de Tomcat una aplicación, el plugin de maven utiliza por defecto la interfaz de administración http://servidor:puerto/manager/text. Para poder utilizar esta interfaz, el usuario de tomcat debe contar con el rol manager-script, si no la publicación no funcionará correctamente.

Para ello, en el fichero $CATALINA_BASE/conf/tomcat_users.xml hay que asignar este rol a un usuario existente, o crear un usuario para este tipo de despliegues con este rol.

Fichero: $CATALINA_BASE/conf/tomcat_users.xml

[...]
<user username="eduardo" password="xxxxxxx" roles="manager-gui,manager-script"/>
[...]

En este caso, el usuario eduardo tiene permisos para subir aplicaciones desde la interfaz web (manager-gui) y desde el plugin de Maven (manager-script).

Añadir el plugin de tomcat a la aplicación.

Añadir el plugin de tomcat en la aplicación se hace en el fichero pom.xml.

Fichero: pom.xml

    [...]
    <build>
        <plugins>
            [...]
            <!-- Tomcat plugin -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <url>http://localhost:8080/manager/text</url>
                    <username>username</username>
                    <password>password</password>
                </configuration>
            </plugin>
            [...]
        </plugins>
    </build>

El plugin tomcat7-maven-plugin pese al nombre funciona correctamente con Tomcat7, Tomcat8 y Tomcat85. No lo he probado con Tomcat9 debido a que todavía está en su versión Milestone en el momento de crear esta entrada.

La url para desplegar en Tomcat mediante script es http://servidor:puerto/manager/text.

El username y el password son el usuario y la contraseña de un usuario con el rol manager-script.

Por defecto, el plugin publica la aplicación con el nombre como contexto de aplicación. Se puede cambiar este contexto en la configuración del plugin añadiendo la entrada <path>/nombre-contexto</path>. Por ejemplo, imaginemos que se quiere diferenciar las publicaciones hechas en el entorno de integración de las hechas en el entorno de desarrollo, se añade <path>/xxxxx-integracion</path>; donde xxxxxx puede ser el nombre de la aplicación. De esta forma  ya podríamos acceder a la aplicación desde este nuevo contexto: http://localhost:8080/xxxxxx-integracion/.

Adicionalmente, el contenido de <path> podría incluirse en un fichero de propiedades para diferentes perfiles de maven y así conseguir que el pom.xml sea el mismo en todos los entornos y solo cambie la manera de invocarlo. Pero estas configuraciones más avanzadas de MAVEN no son el objeto de esta entrada y las dejaremos para otro momento.

Ejecutar el despliegue en Tomcat utilizando Maven.

Una vez que todo está configurado correctamente, es el momento de ejecutar el despliegue utilizando Maven.

Para ejecutarlo se lanza el siguiente comando:

   mvn org.apache.tomcat.maven:tomcat7-maven-plugin:2.2:redeploy   

La respuesta sería algo así:

[INFO]
[INFO] <<< tomcat7-maven-plugin:2.2:redeploy (default-cli) < package @ xxxxxx<<<
[INFO]
[INFO] --- tomcat7-maven-plugin:2.2:redeploy (default-cli) @ xxxxxx---
[INFO] Deploying war to http://localhost:8080/xxxxxx
Uploading: http://localhost:8080/manager/text/deploy?path=%2Fxxxxxx&update=true
Uploaded: http://localhost:8080/manager/text/deploy?path=%2Fxxxxxx&update=true (3118 KB at 1734.6 KB/sec)

[INFO] tomcatManager status code:200, ReasonPhrase:
[INFO] OK - Desplegada aplicación en trayectoria de contexto /xxxxxxx
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.679 s
[INFO] Finished at: 2016-09-02T12:36:59+02:00
[INFO] Final Memory: 15M/201M
[INFO] ------------------------------------------------------------------------

 Links

Página oficial del plugin de maven para Tomcat 7

 

Minientrada

JSF 2.x Hola Mundo

En esta entrada voy a explicaros como hacer una aplicación muy básica con JSF 2.x, un Hola Mundo. JSF (o Java Server Faces) es la tecnología estándar de Java para simplificar la interfaz de usuario de aplicaciones web.

La versión 2.0 de JSF fue lanzada en Agosto de 2009 y corresponde con Java EE 6. La versión 2.2 de JSF fue lanzada en Abril de 2013 y corresponde con Java EE 7.

Para probar la aplicación voy a publicarla en un contenedor de servlets Apache Tomcat 8.5. Al no ser un servidor de aplicaciones homologado con Java EE habrá que incluir algunas librerías que no serían necesarias si la publicación se hiciera en un contenedor de aplicaciones homologado.

Al tener que incluir las librerías en la aplicación Java voy a utilizar la versión 2.2 de JSF. Hay que tener en cuenta que si se va a publicar en un servidor de aplicaciones existente la versión de JSF que incorporan depende de la Java EE homologada que implementen; Java EE 5 – JSF 1.2, Java EE 6 – JSF 2.0, Java EE 7 – JSF 2.2.

Entorno utilizado para desarrollar el ejemplo.

Para que se pueda reproducir el ejemplo y esté operativo al 100% comento qué entorno he utilizado para hacerlo funcionar.

IDE desarrollo: Eclipse 3.6 (Neon).
Máquina virtual Java: JDK 1.8.0_65.
Servidor de aplicaciones: Apache Tomcat 8.5
Ciclo de vida: Maven 3.x
Tecnología: JSF 2.2 (Java EE 7)

Módulo web de aplicación.

Este ejemplo es tan sencillo que va a contar con un único módulo; el módulo web. El módulo web genera un WAR que se despliega directamente en Apache Tomcat.

Fichero: pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>es.egv.jee6.jsf2</groupId>
 <artifactId>BasicoJSF2</artifactId>
 <version>1.0.0</version>
 <packaging>war</packaging>

 <dependencies>
   <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-api</artifactId>
     <version>2.2.11</version>
   </dependency>

   <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-impl</artifactId>
     <version>2.2.11</version>
   </dependency>

   <dependency>
     <groupId>javax.el</groupId>
     <artifactId>javax.el-api</artifactId>
     <version>3.0.1-b04</version>
   </dependency>

   <dependency>
     <groupId>javax.servlet.jsp.jstl</groupId>
     <artifactId>jstl-api</artifactId>
     <version>1.2</version>
   </dependency>
 </dependencies>

</project>

En este caso, al desplegarse la aplicación en un contenedor de Servlets y no en un servidor de aplicaciones se incluyen como dependencias las librerías: jsf-api, jsf-impl (mojarra), javax.el-api y jstl-api. Si hubieramos desplegado esta aplicación en un servidor de aplicaciones homologado con la Java EE 6 o superiores, solo deberíamos incluir como dependencia la librería jsf-api, ya que el resto serían proporcionadas por el contenedor. En ese caso tener en cuenta también que la versión Java EE 6 incluye la versión 2.0 de JSF.

Fichero: web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>BasicoJSF2</display-name>
  <welcome-file-list>
    <welcome-file>holamundo.xhtml</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
 
    <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
  </servlet-mapping>

  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
</web-app>

Destacar dos cosas importantes en el fichero de configuración del módulo web:

  • Al cargarse el módulo web en el servidor se ejecuta el servlet que JSF incorpora para interpretar sus páginas (javax.faces.webapp.FacesServlet).
  • Mientras se esta desarrollando la aplicación es conveniente informar el parámetro JSF javax.faces.PROJECT_STAGE con el valor Development. De esta forma el servidor proporciona mucha información de DEBUG que de otra manera no estaría disponible. Cuando se quiera poner la aplicación en producción se sustituye ese valor por el de Production.

JSF 2.x Managed Bean

Los managed bean son clases java que se registran en el framework de JSF, lo que permite que puedan ser consumidos desde las páginas dinámicas. Desde la versión 2.0 de JSF este registro se puede llevar a cabo mediante la anotación @javax.faces.bean.ManagedBean.

Los managed bean contienen métodos get y set para poder guardar y recoger datos de la sesión y métodos de lógica de negocio, aunque si la aplicación es compleja la lógica de negocio recomiendo que recaiga en componentes EJB.

Fichero: HolaMundoMBean.java

package es.egv.jee6.jsf2.mbean;

import java.io.Serializable;
import java.text.SimpleDateFormat;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean(name="holaMundoMBean")
@SessionScoped
public class HolaMundoMBean implements Serializable {
    private static final long serialVersionUID = -239729940657225276L;
    
    private String name;
    
    public String getName() {
        return name;
    }

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

    public String getCurrentTime() {
         
        return new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new java.util.Date().getTime()); // Older version, SimpleDateFormat is not thread safe
    }
    
    
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("HolaMundoMBean [name=");
        builder.append(name);
        builder.append("]");
        return builder.toString();
    }    
    
}

JSF 2.x Páginas dinámicas

El formato recomendado al desarrollar páginas dinámicas en JSF es el formato XHTML (Extensible HTML) .

Para el ejemplo de hola mundo voy a realizar dos páginas dinámicas; holamundo.xhtml y holamundobienvenida.xhtml. Desde la página inicial se viaja a la de bienvenida llevándose la información del nombre para que la bienvenida sea personalizada.

Fichero: holamundo.xhtml

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!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:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html">
<h:head>
    <title>Aplicación JSF 2.x</title>
</h:head>
<h:body bgcolor="white">
    <h2>Ejemplo. Hola Mundo en JSF 2.x</h2>
    <h:form>
        <h3>Fecha y hora: <h:outputLabel> #{holaMundoMBean.currentTime}</h:outputLabel></h3>
        <h3>A continuacion escribe tu nombre en el siguiente campo:</h3>
        <h:inputText value="#{holaMundoMBean.name}"/>
        <h:commandButton value="Pulsa" action="holamundobienvenida"/>
    </h:form>
</h:body>
</html>

Fichero: holamundobienvenida.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">
 
    <h:head>
        <title>Aplicación JSF 2.x</title>
    </h:head>
    <h:body bgcolor="cyan">
        <h2>Ejemplo. Hola Mundo en JSF 2.x</h2>
        <h3>Hola #{holaMundoMBean.name}, bienvenido al ejemplo Hola Mundo con JSF 2.x.</h3>
        <h3>Fecha y hora: #{holaMundoMBean.currentTime} </h3>
        <h3>Hasta Luego.</h3>
        <h:form>
            <h:commandButton value="Volver" action="holamundo"/>
        </h:form>
    </h:body>
</html>

Hay que fijarse en las siguientes peculiaridades:

  • En JSF 1.x es obligatorio declarar una «navigation rule» en el fichero faces-config.xml para indicarle a la aplicación a qué página hay que ir cuando se pulsa un botón. Sin embargo, en JSF 2.x para facilitar la navegación se puede poner el nombre de la página directamente en el atributo action del botón. En aplicaciones pequeñas esta ventaja es un buen recurso. En aplicaciones grandes es recomendable seguir utilizando el fichero faces-config.xml para definir las reglas de navegación.
  • El campo #{…} indica una expresión dinámica de JSF que se interpreta en tiempo de ejecución. En la página holamundo.xhtml la expresión  #{holaMundoMBean.name} en el componente inputText indica que cuando se hace submit del formulario, JSF encuentra el Managed Bean holaMundoMBean, y en la propiedad name recoge el valor del componente mediante el método set correspondiente. Cuando en la página holamundobienvenida.xhtml se ejecute la expresión #{holaMundoMBean.name}, esta accede a la propiedad correspondiente del Managed Bean y recupera el valor incluido en la anterior página.

Resultado final.

A continuación se pueden ver los pantallazos con la aplicación funcionando.

En la primera página se teclea el nombre y se pulsa el botón de submit del formulario.

2016-08-29_14-45-14

Una vez tecleado y cuando se pulsa el botón se muestra la página de bienvenida.

2016-08-29_14-46-20

Si se pincha el botón Volver se regresa a la página holamundo.xhtml.

Links

Java Server Faces en la Wikipedia

Java EE Historial de versiones

Extensible HTML

Minientrada

Tomcat 8.5 en Eclipse Neon 4.6.0 (Build id: 20160613-1800)

No existe Runtime Enviroment para Tomcat 8.5 en Eclipse Neon.

Me descargué el otro día la nueva versión de Eclipse para comenzar mis nuevos proyectos basados en JEE 6 con un entorno de desarrollo puesto al día. Adicionalmente descargué también la última versión de Apache Tomcat 8.5 para probar los trabajos.

Mi sopresa surgió cuando al enlazar la versión de Eclipse con la Server Runtime de Apache Tomcat 8.5, encuentro que puedo seleccionar la versión 8.0 y la 9.0, pero no la susodicha.

2016-08-25_10-57-12

Al seleccionar cualquiera de las otras dos 8.0 o 9.0 obtengo el error «The Apache Tomcat installation at this directory is version 8.5.4. A Tomcat 8.0 (9.0 si selecciono la 9) installation is expected«.

Buscando en la base de datos Bugzilla de Eclipse encuentro el bug 494936. En el se comenta  de forma resumida que Eclipse Neon todavía no está preparado para Apache Tomcat 8.5 y que está previsto para futuras versiones.

Una solución temporal.

Para salir del paso, en el propio caso abierto de bugzilla, Levan Kekelidze proporciona una nueva versión del plugin de tomcat que permite añadir Tomcat 8.5 como si fuera la versión 9.0 del server runtime.

La versión de este plugin está publicado por Levan como attachment y se puede descargar directamente desde este enlace.

Se reemplaza el fichero org.eclipse.jst.server.tomcat.core_1.1.800.v201602282129.jar que se encuentra en <ECLIPSE_NEON_INSTALLATION_DIR>\plugins por el fichero descargado en el enlace previo.

Et voila!!! Ya podemos agregar una instancia de Apache Tomcat 8.5 como si fuera de tipo Apache Tomcat 9.0.

2016-08-25_11-13-11

Mientras sale el parche que permita agregar de manera nativa la Server Runtime Enviroment para Apache Tomcat 8.5 se puede continuar trabajando de manera temporal con esta solución. Con esta «solución», todo funciona correctamente, se puede arrancar, parar, depurar y desplegar aplicaciones desde Eclipse al servidor.

Espero que esta entrada os sea de utilidad.