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.

 

 

 

Microservicios. Una aproximación práctica con Spring.

Microservicios. Una aproximación básica.

Martin Fowler propone en su artículo “Microservices” una de las definiciones que más me gusta para describir qué son los microservicios:

La arquitectura basada en microservicios es una nueva manera de desarrollar aplicaciones como si fueran un conjunto de pequeños servicios, cada uno de ellos corriendo su propio proceso (servidor) y comunicándose entre si utilizando mecanismos de comunicación ligeros, normalmente protocolo HTTP.

Lo dice él y lo comparto yo, los microservicios son una nueva manera de diseñar aplicaciones que obliga a tener en cuenta el hecho de que cada funcionalidad es un servicio, cada uno con sus propios recursos, abandonando el consabido enfoque monolítico tradicional donde todas las funcionalidades forman parte de una único servicio.

Esta entrada abre una sucesión de artículos donde de manera básica quiero aproximarme al mundo de los microservicios utilizando diferentes aproximaciones; Spring Cloud, Play y vert.x.

Este primer artículo recorre el enfoque desde el punto de vista de Spring con Spring Cloud. Mi intención es implementar unos microservicios utilizando el ecosistema propuesto por Spring framework, a saber; Springfox, la implementación de Spring de la especificación Swagger para documentar la API REST, y Spring Cloud Netflix para ofrecer el microservicio y ponerlo a disposición de terceros a través del Service Registry de Netflix, Eureka.

El ejemplo.

El ejemplo elegido es una funcionalidad básica pero suficiente para permitir probar el desarrollo de microservicios a través de las soluciones elegidas, en esta entrada Spring Cloud.

La aplicación va a dar servicios a operaciones aritméticas ofreciendo dos microservicios, uno que realiza suma de dos operandos y el otro que realiza multiplicaciones de dos operandos.

De manera común a cada una de las pruebas voy a utilizar Apache Maven para manejar el ciclo de vida de cada componente que cree.

Spring Cloud y Spring Boot, dos facilitadores para el desarrollo.

Spring boot y Spring cloud son dos de las nuevas herramientas que Spring propone para facilitar el desarrollo de aplicaciones distribuidas basadas en microservicios.

Spring Boot es uno de los nuevos inventos de la familia Spring cuya pretensión es facilitar la vida a los desarrolladores simplificando todo el trabajo de configuración del propio framework. Para ello, Spring boot autoconfigura los componentes, automatizando la gestión de dependencias y el despliegue. Estas autoconfiguraciones son perfectas para los casos más comunes, pero en caso de necesitar algo más especial, las autoconfiguraciones pueden ser personalizadas utilizando anotaciones y/o ficheros de configuración.

Spring Cloud es otro de esos geniales inventos de la familia Spring que propone un ecosistema de herraminetas que nos ayudan a desplegar nuestros servicios en nubes. Soporta Cloud Foundry y Heroku de caja, así como otras nubes a través de conectores. Spring Cloud permitirá a las aplicaciones descubrir y compartir información/configuración de otras aplicaciones, y de sí misma, en tiempo de ejecución.

Servicios REST para la calculadora.

El primer paso para probar la tecnología de Spring es generar el servicio REST que va a proporcionar las operaciones aritméticas de suma y multiplicación de la calculadora.

Gracias a Spring Boot el desarrollo y pruebas de este API se hace muy rápidamente y con apenas esfuerzo.

El primer paso es crear un proyecto Java para contener el API REST. El proyecto se llama CalculadoraAritmeticaRestAPI.


4.0.0
com.tecnicomio.microservices.springfrwk
CalculadoraAritmeticaRestAPI
1.0-SNAPSHOT

org.springframework.boot
spring-boot-starter
1.5.10.RELEASE

org.springframework.boot
spring-boot-starter-web
1.5.10.RELEASE

Es importante fijarse que las únicas dependencias que he añadido han sido las referentes con spring boot. No he incluido ninguna dependencia referente al API REST. Ya se encargará Spring Boot de encontrar las dependencias necesarias para compilar y ejecutar correctamente el proyecto. Lo que si se ha añadido es el starter web para que Spring Boot tenga las dependencias necesarias para manejar aplicaciones web.

El siguiente paso es crear el servicio REST que proporciona las operaciones que se necesitan para la calculadora aritmética; sumar y multiplicar.

package com.tecnicomio.microservicios.spring;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/aritmetica")
public class CalculadoraAritmeticaRESTController {
public CalculadoraAritmeticaRESTController() {
}

@RequestMapping(
value = "/sumar",
method = RequestMethod.GET,
params = {"operando1", "operando2"}
)
public Integer sumar(@RequestParam("operando1") Integer operando1,
@RequestParam("operando2") Integer operando2) {
return operando1 + operando2;
}

@RequestMapping(
value = "/multiplicar",
method = RequestMethod.GET,
params = {"operando1", "operando2"}
)
public Integer multiplicar(@RequestParam("operando1") Integer operando1,
@RequestParam("operando2") Integer operando2) {
return operando1 * operando2;
}
}

No es el objetivo de esta entrada explicar cómo desarrollar servicios REST con Spring así que sólo comentaré que la principal anotación para convertir una clase Java en un API REST es @RestController. La otras anotaciones, leanse @RequestMapping y @RequestParam, sirven para configurar las funcionalidades que se exponen en el API. Ni que decir tiene que existen muchas más anotaciones y configuraciones, así que os dejo un link al tutorial de servicios REST de spring.io donde se explican bastante bien los conceptos:

Building a RESTful Web Service

Y finalmente, para que Spring Boot obre su magia se implementa una clase cuyo principal cometido es decirle a Spring que nos encontramos ante una aplicación web que contiene un servicio REST que debe publicarse para poder ser invocado.

package com.tecnicomio.microservicios.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = {"com.tecnicomio.microservicios.spring"})
public class CalculadoraAritmeticaServer {
public static void main(String[] args) {
SpringApplication.run(CalculadoraAritmeticaServer.class, args);
}
}

Podría haber incluido estas líneas directamente en el controller REST, pero he preferido por orden y limpieza crear esta mini clase como launcher.

Lo más curioso de Spring Boot es la inteligencia que incorpora, ya que al ejecutar estas 11 escasas líneas de código, Spring Boot sabe que se encuentra ante una aplicación web, con un servicio REST y arranca un Tomcat embebido para que se pueda acceder y ejecutar el servicio aritmético de calculadora.

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::       (v1.5.10.RELEASE)

2018-03-15 12:37:48.853  INFO 121776 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : Starting CalculadoraAritmeticaServer on devstation.localdomain with PID 121776 (/srv/java/src/pruebassimples/microservices/springfrwk/SumaAritmetica/target/classes started by developer in /srv/java/src/pruebassimples/microservices/springfrwk/SumaAritmetica)
2018-03-15 12:37:48.859  INFO 121776 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : No active profile set, falling back to default profiles: default
2018-03-15 12:37:48.975  INFO 121776 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@782859e: startup date [Thu Mar 15 12:37:48 CET 2018]; root of context hierarchy
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils$1 (file:/home/developer/.m2/repository/org/springframework/spring-core/4.3.14.RELEASE/spring-core-4.3.14.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
2018-03-15 12:37:50.549  INFO 121776 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2018-03-15 12:37:50.563  INFO 121776 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-03-15 12:37:50.564  INFO 121776 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.27
2018-03-15 12:37:50.646  INFO 121776 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-03-15 12:37:50.646  INFO 121776 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1685 ms
2018-03-15 12:37:50.734  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2018-03-15 12:37:50.737  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-03-15 12:37:50.737  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-03-15 12:37:50.737  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-03-15 12:37:50.737  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-03-15 12:37:51.154  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@782859e: startup date [Thu Mar 15 12:37:48 CET 2018]; root of context hierarchy
2018-03-15 12:37:51.218  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aritmetica/multiplicar],methods=[GET],params=[operando1 && operando2]}" onto public java.lang.Integer com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController.multiplicar(java.lang.Integer,java.lang.Integer)
2018-03-15 12:37:51.219  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aritmetica/sumar],methods=[GET],params=[operando1 && operando2]}" onto public java.lang.Integer com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController.sumar(java.lang.Integer,java.lang.Integer)
2018-03-15 12:37:51.221  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-03-15 12:37:51.221  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-03-15 12:37:51.244  INFO 121776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 12:37:51.244  INFO 121776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 12:37:51.274  INFO 121776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 12:37:51.423  INFO 121776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-03-15 12:37:51.537  INFO 121776 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-03-15 12:37:51.542  INFO 121776 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : Started CalculadoraAritmeticaServer in 13.353 seconds (JVM running for 14.096)

Así que con un pom.xml y dos clases java muy simples tenemos el servicio REST publicado y accesible.

Ejecutando el Servicio REST con Postman

Documentando el API.

Para poder ejecutar los servicios expuestos debemos conocer la URL para invocarlo, los métodos que ofrece, los parámetros con que se llama a cada método y la información con la que se rellena.

Una manera de recibir esta información podría ser, por ejemplo, vía email, pero una mejor manera de recibirlo es tener un portal donde se pueda acceder a un listado de todos los servicios REST publicados y su información de uso. Este portal que concentra la información sobre APIs, es el objeto de la especificación Swagger.

La especificación swagger define un interfaz estándar para las APIs RESTful, que no se casa con ningún lenguaje de programación concreto, qué permite tanto a humanos como a máquinas descubrir y comprender las capacidades del servicio sin necesitad de acceder, ni al código fuente del mismo, ni a su documentación, ni a través de la inspección del comportamiento del servicio a través de la red. Cuando esta interfaz se define correctamente, los posibles consumidores del API pueden entender e interactuar correctamente con el servicio.

Springfox es la implementación de la especificación swagger que propone Spring. Para añadirla en nuestro servicio aritmético tenemos que seguir varios pasos.

El primero, añadir en el pom.xml la dependencia necesaria para utilizar Springfox.


io.springfox
springfox-swagger2
2.8.0

io.springfox
springfox-swagger-ui
2.8.0

El segundo es crear una clase de configuración de swagger.

package com.tecnicomio.microservicios.spring.com.tecnicomio.microservicios.spring.swagger;

import com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@ComponentScan(basePackageClasses = {CalculadoraAritmeticaRESTController.class})
public class SwaggerConfiguration {

@Bean
public Docket calculadoraAritmeticaApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("api-calculadora-aritmetica")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/aritmetica.*"))
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Calculadora aritmética API Rest")
.description("API RESTful para la calculadora aritmética.")
.termsOfServiceUrl("http://en.wikipedia.org/wiki/Terms_of_service")
.contact(new Contact("Eduardo González de Viñaspre", "http://tecnicomio.com", "eduardo.gonzalezdevinaspre@gmail.com"))
.license("Apache License Version 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version("2.0")
.build();
}
}

Importante la anotación @EnableSwagger2 que indica que hay que activar swagger. El método calculadoraAritmeticaApi indica el formato de la documentación y su nombre, y el método apiInfo que indica información básica sobre la documentación que nos vamos a encontrar en el portal publicado.

Y finalmente documentar el controlador REST del API así como los POJOs que usemos para definir los parámetros de entrada y de salida de los diferentes métodos. En este caso como no hay POJOs, solo documento el controlador REST.

package com.tecnicomio.microservicios.spring;

import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.*;

import javax.ws.rs.core.MediaType;

@RestController
@RequestMapping("/aritmetica")
@Api(value = "/aritmetica",
description = "Proporciona las operaciones necesarias para implementar una calculadora aritmética",
produces = "text/plain")
public class CalculadoraAritmeticaRESTController {
public CalculadoraAritmeticaRESTController() {
}

@RequestMapping(
value = "/sumar",
method = RequestMethod.GET,
params = {"operando1", "operando2"},
produces = MediaType.TEXT_PLAIN
)
@ApiOperation(value="Suma aritmética", notes = "Realiza la suma de dos operandos")
@ApiResponses( {
@ApiResponse(code = 200, message = "Devuelve el resultado de la suma.")
})
public Integer sumar(@RequestParam(value = "operando1",defaultValue = "0") @ApiParam(value = "operando 1 de la suma", defaultValue = "0", required = true, example = "5") Integer operando1,
@RequestParam(value = "operando2", defaultValue = "0") @ApiParam(value = "operando 2 de la suma", defaultValue = "0", required = true, example = "15") Integer operando2) {
return operando1 + operando2;
}

@RequestMapping(
value = "/multiplicar",
method = RequestMethod.GET,
params = {"operando1", "operando2"}
)
@ApiOperation(value="Multiplicación aritmética", notes = "Realiza la multiplicación de dos operandos")
@ApiResponses( {
@ApiResponse(code = 200, message = "Devuelve el resultado de la multiplicación.")
})
public Integer multiplicar(@RequestParam(value = "operando1", defaultValue = "1") @ApiParam(value = "operando 1 de la multiplicación", defaultValue = "1", required = true, example = "5") Integer operando1,
@RequestParam(value = "operando2", defaultValue = "1") @ApiParam(value = "operando 2 de la multiplicación", defaultValue = "1", required = true, example = "10") Integer operando2) {
return operando1 * operando2;
}
}

Las anotaciones que obran la magia son:

  • @Api – Define un contenedor de documentación.
  • @ApiOperation – Define la documentación de una operación dentro del servicio.
  • @ApiResponses y @ApiResponse – Documenta las respuestas del método del servicio.
  • @ApiParam- Documenta los parámetros de los parámetros de los métodos.

Gracias a Spring Boot no se necesita mucho más. Ahora ya se puede acceder a la URL de swagger: http://localhost:8080/swagger-ui.html# y si todo ha salido bien veremos la página que swagger ofrece con la documentación del servicio REST.

Documentación swagger para el proyecto de calculadora aritmética

Si extendemos una operación, además de ver la documentación, podemos incluso llegar a probarla:

Formulario Swagger para probar métodos.

Arrancando una herramienta de Service Discovery: Eureka.

Al final la tecnología de microservicios está formada por servicios simples como el servicio de aritmética que acabamos de publicar, pero que además quedan envueltos por una ecosistema que aporta una serie de ventajas que facilitan el mantenimiento de estos servicios, por ejemplo, el registro y localización de los mismos, el balanceo de carga, la tolerancia a fallos, entre otros.

El registro de los servicios para su posterior localización y uso es importantísimo en cuanto tenemos un proyecto medio-grande entre manos. Basta con imaginar tener 100 microservicios dando servicio a un sitio web para darnos cuenta que tenemos que tener algún tipo de inventario con información sobre ellos. En el ecosistema de los microservicios la pieza que facilita este registro es el Service Discovery.

A día de hoy existen varios Service Discovery, algunos de los más importantes son:

  • Zookeeper de Apache Foundation
  • Eureka de Netflix
  • Consul

El siguiente paso por tanto es registrar el servicio aritmético en un Service Discovery, y para ello voy a utilizar Eureka, el Service Discovery que utiliza Netflix, una de las empresas que sirve más peticiones a lo largo y ancho de internet.

Lo primero que voy a hacer es crear un nuevo proyecto EurekaApplication para contener todo lo relativo a la ejecución de Eureka. El pom.xml del nuevo proyecto es:


4.0.0
com.tecnicomio.microservices.springfrwk
EurekaServer
1.0-SNAPSHOT

org.springframework.cloud
spring-cloud-starter-eureka-server
1.4.3.RELEASE

org.springframework.boot
spring-boot-starter
1.5.10.RELEASE

javax.xml.bind
jaxb-api
2.3.0

org.springframework.cloud
spring-cloud-starter-parent
Brixton.SR7
pom
import

¡¡Cuidado!! He tenido que añadir las dependencias de jaxb-api para que el Tomcat que Spring Boot incluye arranque correctamente. Esto es debido a que en la JDK 9 tras implementar los Java Modules, algunas librerías ya no se incluyen como si se hacía en la JDK 6/7/8. Uno de esos paquetes que no se incluye es java.xml.bind que contiene todo el funcionamiento de JAXB.

A continuación añado una clase para arrancar el servidor de Eureka.

package com.tecnicomio.microservicios.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

La anotación aquí es @EnableEurekaServer que le dice a Spring Boot que se quiere arrancar el Service Discovery de Eureka.

Y finalmente, para que configurar brevemente el servidor Eureka, se añade el fichero application.yml al directorio resources del proyecto.

server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false

En el fichero de configuración estamos indicando que el servidor de Eureka se arranque en el puerto 8761 (el puerto por defecto de los servidores de Eureka), que el cliente no se registre a si mismo ya que vamos a registrar el servicio aritmético.

Así que finalmente, con estos pasos, ya deberíamos tener el servidor Eureka arrancado.

Eureka Server Up and running

Con esto hemos conseguido que el servicio se autoregistre en Eureka, que le notifique cuando está disponible, cómo se le puede invocar, los metadatos que se pueden utilizar y mucha más información.

Descubriendo el servicio aritmético en Eureka.

Recapitulando, llegados a este punto hemos creado un servicio REST que ofrece funcionalidad para una calculadora aritmética, y hemos arrancado una instancia de Eureka, una herramienta para el descubrimiento de servicios.

Desafortunadamente, cuando miramos la consola de Eureka todavía no se ve registrado el servicio aritmético. Esto es debido a que hay que indicarle al servicio que implemente el Service Registry pattern de Eureka para convertirle en un posible cliente.

El primer paso es añadir las depedencias de Eureka al proyecto CalculadoraAritmeticaRestAPI:


org.springframework.cloud
spring-cloud-starter-eureka-server
1.4.3.RELEASE

El segundo indicarle al servicio aritmético que es un cliente de Eureka. Para ello añadimos la anotación @EnableEurekaClient a la clase servidor.

import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@RestController
@RequestMapping("/aritmetica")
public class CalculadoraAritmeticaRESTController {

Y por último, configurar el cliente Eureka mediante un fichero application.yml donde indiquemos el nombre del micro-servicio, la URL y otras posibles configuraciones.

spring:
application:
name: calculadora-aritmetica-eureka-client
server:
port: 0
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
instance:
preferIpAddress: true

Hecho esto, se arranca el servicio CalculadoraAritmetica y automáticamente debería registrarse en el servidor Eureka indicado en el fichero YAML de configuración.

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::        (v1.3.8.RELEASE)

2018-03-15 15:25:41.087  INFO 7659 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : No active profile set, falling back to default profiles: default
2018-03-15 15:25:41.105  INFO 7659 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5942ee04: startup date [Thu Mar 15 15:25:41 CET 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@43b9fd5
2018-03-15 15:25:42.095  WARN 7659 --- [           main] o.s.c.a.ConfigurationClassPostProcessor  : Cannot enhance @Configuration bean definition 'refreshScope' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.
2018-03-15 15:25:42.259  INFO 7659 --- [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=357c0c59-b5b5-38af-babc-0373b7d67e30
2018-03-15 15:25:42.275  INFO 7659 --- [           main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-03-15 15:25:42.328  INFO 7659 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [class org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$defd02e1] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-03-15 15:25:42.335  INFO 7659 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration' of type [class org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration$$EnhancerBySpringCGLIB$$885c04d7] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-03-15 15:25:42.710  INFO 7659 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 0 (http)
2018-03-15 15:25:42.728  INFO 7659 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2018-03-15 15:25:42.729  INFO 7659 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.37
2018-03-15 15:25:42.850  INFO 7659 --- [ost-startStop-1] org.apache.catalina.loader.WebappLoader  : Unknown loader jdk.internal.loader.ClassLoaders$AppClassLoader@1b9e1916 class jdk.internal.loader.ClassLoaders$AppClassLoader
2018-03-15 15:25:42.868  INFO 7659 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-03-15 15:25:42.868  INFO 7659 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1764 ms
2018-03-15 15:25:43.278  INFO 7659 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2018-03-15 15:25:43.282  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'metricsFilter' to: [/*]
2018-03-15 15:25:43.282  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-03-15 15:25:43.282  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-03-15 15:25:43.282  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-03-15 15:25:43.283  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'requestContextFilter' to: [/*]
2018-03-15 15:25:43.283  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'webRequestLoggingFilter' to: [/*]
2018-03-15 15:25:43.283  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'applicationContextIdFilter' to: [/*]
2018-03-15 15:25:44.388  INFO 7659 --- [           main] o.s.cloud.commons.util.InetUtils         : Cannot determine local hostname
2018-03-15 15:25:44.737  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5942ee04: startup date [Thu Mar 15 15:25:41 CET 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@43b9fd5
2018-03-15 15:25:44.866  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aritmetica/sumar],methods=[GET],params=[operando1 && operando2]}" onto public java.lang.Integer com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController.sumar(java.lang.Integer,java.lang.Integer)
2018-03-15 15:25:44.867  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aritmetica/multiplicar],methods=[GET],params=[operando1 && operando2]}" onto public java.lang.Integer com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController.multiplicar(java.lang.Integer,java.lang.Integer)
2018-03-15 15:25:44.869  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-03-15 15:25:44.869  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-03-15 15:25:44.897  INFO 7659 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 15:25:44.897  INFO 7659 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 15:25:44.941  INFO 7659 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 15:25:45.415  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/pause || /pause.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
2018-03-15 15:25:45.416  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.417  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.value(java.util.Map)
2018-03-15 15:25:45.417  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env/reset],methods=[POST]}" onto public java.util.Map org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.reset()
2018-03-15 15:25:45.420  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/trace || /trace.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.421  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/dump || /dump.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.422  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/mappings || /mappings.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.423  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/restart || /restart.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.context.restart.RestartMvcEndpoint.invoke()
2018-03-15 15:25:45.424  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/features || /features.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.424  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2018-03-15 15:25:45.425  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env || /env.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.425  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/configprops || /configprops.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.426  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/archaius || /archaius.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.427  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/health || /health.json],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(java.security.Principal)
2018-03-15 15:25:45.427  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/info || /info.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.428  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/refresh || /refresh.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
2018-03-15 15:25:45.429  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2018-03-15 15:25:45.429  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.430  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/resume || /resume.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
2018-03-15 15:25:45.431  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.554  INFO 7659 --- [           main] o.s.ui.freemarker.SpringTemplateLoader   : SpringTemplateLoader for FreeMarker: using resource loader [org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5942ee04: startup date [Thu Mar 15 15:25:41 CET 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@43b9fd5] and template loader path [classpath:/templates/]
2018-03-15 15:25:45.555  INFO 7659 --- [           main] o.s.w.s.v.f.FreeMarkerConfigurer         : ClassTemplateLoader for Spring macros added to FreeMarker configuration
2018-03-15 15:25:45.634  WARN 7659 --- [           main] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2018-03-15 15:25:45.634  INFO 7659 --- [           main] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2018-03-15 15:25:45.642  WARN 7659 --- [           main] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2018-03-15 15:25:45.642  INFO 7659 --- [           main] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2018-03-15 15:25:45.689  WARN 7659 --- [           main] arterDeprecationWarningAutoConfiguration : spring-cloud-starter-eureka-server is deprecated as of Spring Cloud Netflix 1.4.0, please migrate to spring-cloud-starter-netflix-eureka-server
2018-03-15 15:25:45.768  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-03-15 15:25:45.784  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'refreshScope' has been autodetected for JMX exposure
2018-03-15 15:25:45.784  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'environmentManager' has been autodetected for JMX exposure
2018-03-15 15:25:45.786  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'configurationPropertiesRebinder' has been autodetected for JMX exposure
2018-03-15 15:25:45.787  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'refreshEndpoint' has been autodetected for JMX exposure
2018-03-15 15:25:45.788  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'restartEndpoint' has been autodetected for JMX exposure
2018-03-15 15:25:45.794  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'environmentManager': registering with JMX server as MBean [org.springframework.cloud.context.environment:name=environmentManager,type=EnvironmentManager]
2018-03-15 15:25:45.816  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'restartEndpoint': registering with JMX server as MBean [org.springframework.cloud.context.restart:name=restartEndpoint,type=RestartEndpoint]
2018-03-15 15:25:45.827  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'refreshScope': registering with JMX server as MBean [org.springframework.cloud.context.scope.refresh:name=refreshScope,type=RefreshScope]
2018-03-15 15:25:45.832  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'configurationPropertiesRebinder': registering with JMX server as MBean [org.springframework.cloud.context.properties:name=configurationPropertiesRebinder,context=5942ee04,type=ConfigurationPropertiesRebinder]
2018-03-15 15:25:45.848  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'refreshEndpoint': registering with JMX server as MBean [org.springframework.cloud.endpoint:name=refreshEndpoint,type=RefreshEndpoint]
2018-03-15 15:25:46.013  INFO 7659 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2018-03-15 15:25:46.158  INFO 7659 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 42809 (http)
2018-03-15 15:25:46.158  INFO 7659 --- [           main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 42809
2018-03-15 15:25:46.168  INFO 7659 --- [           main] o.s.c.n.eureka.InstanceInfoFactory       : Setting initial instance status as: STARTING
2018-03-15 15:25:46.741  INFO 7659 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2018-03-15 15:25:46.743  INFO 7659 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2018-03-15 15:25:46.877  INFO 7659 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2018-03-15 15:25:46.878  INFO 7659 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2018-03-15 15:25:47.112  INFO 7659 --- [           main] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Disable delta property : false
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Single vip registry refresh property : null
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Force full registry fetch : false
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Application is null : false
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Registered Applications size is zero : true
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Application version is -1: true
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Getting all instance registry info from the eureka server
2018-03-15 15:25:47.657  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : The response status is 200
2018-03-15 15:25:47.658  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Starting heartbeat executor: renew interval is: 30
2018-03-15 15:25:47.667  INFO 7659 --- [           main] c.n.discovery.InstanceInfoReplicator     : InstanceInfoReplicator onDemand update allowed rate per min is 4
2018-03-15 15:25:47.670  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Discovery Client initialized at timestamp 1521123947669 with initial instances count: 0
2018-03-15 15:25:47.696  INFO 7659 --- [           main] c.n.e.EurekaDiscoveryClientConfiguration : Registering application calculadora-aritmetica-eureka-client with eureka with status UP
2018-03-15 15:25:47.697  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Saw local status change event StatusChangeEvent [timestamp=1521123947697, current=UP, previous=STARTING]
2018-03-15 15:25:47.715  INFO 7659 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : Started CalculadoraAritmeticaServer in 11.699 seconds (JVM running for 12.527)
2018-03-15 15:25:47.716  INFO 7659 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_CALCULADORA-ARITMETICA-EUREKA-CLIENT/localhost:calculadora-aritmetica-eureka-client:0: registering service...
2018-03-15 15:25:47.867  INFO 7659 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_CALCULADORA-ARITMETICA-EUREKA-CLIENT/localhost:calculadora-aritmetica-eureka-client:0 - registration status: 204
2018-03-15 15:26:14.100  INFO 7659 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-03-15 15:26:14.100  INFO 7659 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-03-15 15:26:14.129  INFO 7659 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 29 ms
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Disable delta property : false
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Single vip registry refresh property : null
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Force full registry fetch : false
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Application is null : false
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Registered Applications size is zero : true
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Application version is -1: false
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Getting all instance registry info from the eureka server
2018-03-15 15:26:17.709  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : The response status is 200
Eureka Client Published at Eureka Server

Estableciendo un Gateway API para dar acceso a los servicios

Llegados a este punto tenemos:

  • Un servicio REST para operaciones aritméticas
  • El servicios REST de operaciones aritméticas documentado con Swagger, y está documentación accesible a través de una URL
  • Eureka, el Registry Discovery de NETFLIX arrancado
  • El servicio REST de operaciones aritméticas capaz de ser detectado por el Registry Discovery.

La ultima pieza que voy a incluir en este ecosistema de microservicios es el Gateway que concentra las llamadas de los clientes. La verdad es que el ejemplo que estoy utilizando se queda un poco corto para entender porque es necesario un gateway, total, solo tenemos un servicio publicado en una máquina al que podemos acceder desde una única URL.

Pero, ¿qué pasaría si tuviera 10 servicios cada uno con su URL propia? Tendría 10 urls, cada una diferente y cada una con su documentación swagger. El API Gateway viene al rescate y ofrece un único punto de acceso, una URL de invocación homogénea y una única documentación swagger.

El API Gateway en el que voy a integrar mi API de operaciones aritméticas va a ser ZUUL, el API Gateway de Netflix.

Links

Swagger2
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 – 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

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.

 

Extraer un XSD a partir de un XML existente

A veces me he encontrado en la tesitura de tener un XML maravilloso con toda la información que necesito y querer explotar su información mediante Java.

Si utilizamos JAXB, el estándar de Java para el tratamiento XML-JAVA, veremos que el compilador xjc, incluido en la JDK (a partir de la versión 1.6.0_3), necesita un XSD para generar los objetos java correspondientes.

Por tanto, el primer paso, y el motivo de esta entrada, es tratar las diferentes maneras que hay de convertir el XML en un XSD.

Voy a utilizar para la generación del XSD un XML de ejemplo sencillo, pero suficiente, para ver las diferencias entre las herramientas presentadas.

<?xml version="1.0" encoding="UTF-8"?>
<servers>
	<server id="svnserver.com">
		<ip>192.168.1.1</ip>
		<osuser>souser</osuser>
		<ospass>sopass</ospass>
		<repositories>
			<repository name="a53">
				<url>http://svnserver.com/svn/a53</url>
				<svnuser>user</svnuser>
				<svnpass>pass</svnpass>
			</repository>
			<repository name="i07">
				<url>http://svnserver.com/svn/i07</url>
				<svnuser>user</svnuser>
				<svnpass>pass</svnpass>
			</repository>
		</repositories>
	</server>
	<server id="svnserver2.com">
		<ip>192.168.1.2</ip>
		<osuser>souser</osuser>
		<ospass>sopass</ospass>
		<repositories>
			<repository name="a53_backup">
				<url>http://svnserver.com/svn/a53_backup</url>
				<svnuser>user</svnuser>
				<svnpass>pass</svnpass>
			</repository>
		</repositories>
	</server>
</servers>

Trang.jar (http://www.thaiopensource.com/relaxng/trang.html)

Este programa no se actualiza desde 2008, pero tampoco es preocupante porque tampoco ha cambiado la especificación W3C de los XML Schemas. Se puede descargar directamente desde su página web y es muy sencillo de utilizar.

usage: java -jar trang.jar 
		[-I rng|rnc|dtd|xml] 
		[-O rng|rnc|dtd|xsd] 
		[-i input-param] 
		[-o output-param] 
		inputFileOrUri ... outputFile

Partiendo del XML de ejemplo propuesto, la respuesta de trang ha sido la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="servers">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="server"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="server">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="ip"/>
        <xs:element ref="osuser"/>
        <xs:element ref="ospass"/>
        <xs:element ref="repositories"/>
      </xs:sequence>
      <xs:attribute name="id" use="required" type="xs:NCName"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="ip" type="xs:NMTOKEN"/>
  <xs:element name="osuser" type="xs:NCName"/>
  <xs:element name="ospass" type="xs:NCName"/>
  <xs:element name="repositories">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="repository"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="repository">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="url"/>
        <xs:element ref="svnuser"/>
        <xs:element ref="svnpass"/>
      </xs:sequence>
      <xs:attribute name="name" use="required" type="xs:NCName"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="url" type="xs:anyURI"/>
  <xs:element name="svnuser" type="xs:NCName"/>
  <xs:element name="svnpass" type="xs:NCName"/>
</xs:schema>

El XSD generado es un XSD básico, sencillo y útil. Lo único que no termina de convencerme es la tipificación xs:NCName (Non Colonized Name) que le ha dado a las cadenas de texto. No es una tipificación incorrecta, pero limita el uso de qualified names (nombres con namespace) en el contenido del texto.

inst2xsd (http://xmlbeans.apache.org/index.html)

La herramienta inst2xsd viene incluida en el software XMLBEANS de Apache Foundation que se puede descargar directamente desde su página web. Esta herramienta es más completa que Trang, aunque sin ser difícil también es más compleja de utilizar.

Usage: inst2xsd [opts] [instance.xml]*
Options include:
    -design [rd|ss|vb] - XMLSchema design type
             rd  - Russian Doll Design - local elements and local types
             ss  - Salami Slice Design - global elements and local types
             vb  - Venetian Blind Design (default) - local elements and global complex types
    -simple-content-types [smart|string] - Simple content types detection (leaftext). Smart is the default
    -enumerations [never|NUMBER] - Use enumerations. Default value is 10.
    -outDir [dir] - Directory for output files. Default is '.'
    -outPrefix [file_name_prefix] - Prefix for output file names. Default is 'schema'
    -validate - Validates input instances agaist generated schemas.
    -verbose - print more informational messages
    -license - print license information
    -help - help imformation

Para generar el siguiente XML he utilizado la opción -enumerations never para evitar que los valores que se incluyen en el ejemplo se convierten en enumeraciones en el XSD final.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="servers" type="serversType"/>
  <xs:complexType name="repositoryType">
    <xs:sequence>
      <xs:element type="xs:anyURI" name="url"/>
      <xs:element type="xs:string" name="svnuser"/>
      <xs:element type="xs:string" name="svnpass"/>
    </xs:sequence>
    <xs:attribute type="xs:string" name="name" use="optional"/>
  </xs:complexType>
  <xs:complexType name="repositoriesType">
    <xs:sequence>
      <xs:element type="repositoryType" name="repository" maxOccurs="unbounded" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="serverType">
    <xs:sequence>
      <xs:element type="xs:string" name="ip"/>
      <xs:element type="xs:string" name="osuser"/>
      <xs:element type="xs:string" name="ospass"/>
      <xs:element type="repositoriesType" name="repositories"/>
    </xs:sequence>
    <xs:attribute type="xs:string" name="id" use="optional"/>
  </xs:complexType>
  <xs:complexType name="serversType">
    <xs:sequence>
      <xs:element type="serverType" name="server" maxOccurs="unbounded" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

El XSD generado con esta herramienta es aún más simple que el generado con Trang. Los objetos que contienen texto los ha tipificado como xs:string que puede contener cualquier tipo de texto.

Conclusión

Existen más herramientas en el mercado para obtener ficheros XML Schema a partir de ficheros XML. Algunas son de pago, otras son más complejas, otras tienen interfaz gráfico, pero las dos herramientas descritas en esta entrada son suficientes para obtener XSD de calidad de forma fácil y gratuita.

Si bien me ha gustado más la generación de la herramienta inst2xsd por la generación menos restrictiva de las cadenas de texto, ambas herramientas han generado XSD aptos para utilizar con JAXB y poder generar las clases Java necesarias para tratar la información del XML.

Links

Xml Schema W3C
NCName

Buenas prácticas JAXWS. Utilizando RequestWrapper.

Desde la aparición de la arquitectura SOA (Service-Oriented Architecture) y la posterior llegada de SCA (Service Component Architecture), la utilización de servicios web se ha multiplicado en los desarrollos software de las empresas.

En el mundillo java surgieron diferentes tecnologías para facilitar la creación de los servicios web (Axis1, Axis2, CXF, etcétera), pero a raíz de la llegada de la implementación de la especificación JSR 224 – JAXWS a partir de la JDK 1.5, es cuando la creación de servicios web se ha hecho más inter-operable y gracias a la utilización de anotaciones se ha facilitado enormemente.

En esta entrada me centro en la anotación JAXWS @javax.xml.ws.RequestWrapper, como usarla y la utilidad que tiene.

Un caso de uso.

Cuando se realiza un servicio web con JAX-WS se crea una clase java con anotaciones JSR 181 (@WebService, @WebMethod, etcétera) que describen el contrato WSDL que luego utilizan los clientes para invocar al servicio.

Para explicar esta entrada, voy a utilizar el siguiente ejemplo de servicio web básico, compuesto por un método que recibe una serie de parámetros.

package com.egv.webservices.jaxws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

@WebService ( name="WebServiceEjemplo", 
              targetNamespace="http://egv.com/webservices/jaxws")
@SOAPBinding(  style=SOAPBinding.Style.DOCUMENT, 
               use=SOAPBinding.Use.LITERAL, 
               parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
public class WebServiceEjemplo {
	
	@WebMethod(operationName="metodoEjemplo")
	public void metodoEjemplo(  @WebParam(name="codigo1")String codigo1,
                                @WebParam(name="descripcion1")String descripcion1,
                                @WebParam(name="codigo2")Integer codigo2,
                                @WebParam(name="descripcion2")String descripcion2) {
		System.out.println("codigo1=" + codigo1);
		System.out.println("descripcion1=" + codigo1);
		System.out.println("codigo2=" + codigo2);
		System.out.println("descripcion2=" + descripcion2);
	}

}

Esta clase java genera un método metodoEjemplo en el servicio web con las siguiente Request de tipo SOAP:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jax="http://egv.com/webservices/jaxws">
   <soapenv:Header/>
   <soapenv:Body>
      <jax:metodoEjemplo>
         <!--Optional:-->
         <codigo1>?</codigo1>
         <!--Optional:-->
         <descripcion1>?</descripcion1>
         <!--Optional:-->
         <codigo2>?</codigo2>
         <!--Optional:-->
         <descripcion2>?</descripcion2>
      </jax:metodoEjemplo>
   </soapenv:Body>
</soapenv:Envelope>

Hasta aquí el caso más básico de creación de un servicio web. Pero, qué ocurre si queremos complicarlo haciendo que los parámetros codigo1 y codigo2 se requieran de forma obligatoria.

Para conseguir esta obligatoriedad podemos optar por dos tipos de soluciones: una aproximación a posteriori o una aproximación a priori.

La aproximación a posteriori

La solución a posteriori se introduce una vez que el servicio web se ha invocado y se consigue añadiendo en el código una comprobación de parámetros mediante condicionales. Por ejemplo:

if (codigo1 == null || codigo2 == null) {
throw new ParametrosObligatoriosException();
}

Esta comprobación es funcionalmente correcta pero no es óptima. El motivo es que para realizar la comprobación de parámetros se hace una llamada al servicio web, la ejecución del método y el envío de la respuesta.

La aproximación a priori

La solución a priori se basa en el uso conjunto de la anotación javax.xml.ws.RequestWrapper de JAXWS combinado con el uso de las anotaciones JSR 222 – JAXB. Esto puede sonar complejo, e inicialmente nos va a obligar a codificar más líneas de código, sin embargo, la flexibilidad que proporciona a la hora de jugar con los parámetros, tipo, obligatoriedad, etcétera, es mucho más óptima.

El primer paso para llevar a cabo esta solución es hacer un wrapper para envolver la request del método. Este wrapper debe contener los campos declarados como parámetros del método con la anotación WebParam. Cada campo deber ir anotado con anotaciones JAXB que indican las características del campo concreto.

package com.egv.webservices.jaxws;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(propOrder = { "codigo1", "descripcion1", "codigo2", "descripcion2" })
@XmlAccessorType(XmlAccessType.FIELD)
public class WebServiceEjemploWrapperRequest {
	
	@XmlElement(name="codigo1", required=true)
	public String codigo1;

	@XmlElement(name="descripcion1")
	public String descripcion1;

	@XmlElement(name="codigo2", required=true)
	public Integer codigo2;

	@XmlElement(name="descripcion2")
	public String descripcion2;

	[…] //Getter y Setter para cada variable.

La anotación @XmlElement indica el nombre de la variable y en las variables codigo1 y codigo2 además incluye el indicador de obligatoriedad required=true.

El segundo paso es anotar el método del servicio web en su clase con la anotación RequestWrapper

	@WebMethod(operationName="metodoEjemplo")
	@RequestWrapper(localName = "metodoEjemploRequestWrapper", 
    className = "com.egv.webservices.jaxws.WebServiceEjemploWrapperRequest")
	public void metodoEjemplo(  @WebParam(name="codigo1")String codigo1,
                                @WebParam(name="descripcion1")String descripcion1,
                                @WebParam(name="codigo2")Integer codigo2,
                                @WebParam(name="descripcion2")String descripcion2) {

El resultado final podemos observarlo en la request SOAP generada:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jax="http://egv.com/webservices/jaxws">
   <soapenv:Header/>
   <soapenv:Body>
      <jax:metodoEjemploRequestWrapper>
         <codigo1>?</codigo1>
         <!--Optional:-->
         <descripcion1>?</descripcion1>
         <codigo2>?</codigo2>
         <!--Optional:-->
         <descripcion2>?</descripcion2>
      </jax:metodoEjemploRequestWrapper>
   </soapenv:Body>
</soapenv:Envelope>

Los campos codigo1 y codigo2 han dejado de ser opcionales y ahora han pasado a ser obligatorios. Sin embargo, si ejecutamos desde un cliente y dejamos sin informar alguno de los campos obligatorios, el servicio web se ejecuta y da respuesta.

El método se ejecuta pese a no rellenar los campos obligatorios

La explicación a este supuesto mal funcionamiento es que la validación del esquema del servicio web consume bastantes recursos afectando al rendimiento y por tanto no se activa por defecto. Si se quiere activar la validación del esquema bastará con añadir la anotación com.sun.xml.ws.developer.SchemaValidation a nivel de servicio web.

@WebService (name="WebServiceEjemplo", targetNamespace="http://egv.com/webservices/jaxws")
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT, 
			use=SOAPBinding.Use.LITERAL, 
			parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
@SchemaValidation
public class WebServiceEjemplo {
[…]

Una vez añadida la anotación, al invocar el servicio web sin rellenar el campo codigo1, la respuesta es:

   <S:Body>
      <S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
         <faultcode>S:Server</faultcode>
         <faultstring>com.sun.istack.XMLStreamException2: org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: Invalid content was found starting with element 'descripcion1'. One of '{codigo1}' is expected.</faultstring>
         <detail>
            <ns2:exception class="javax.xml.ws.WebServiceException" note="To disable this feature, set com.sun.xml.ws.fault.SOAPFaultBuilder.disableCaptureStackTrace system property to false" xmlns:ns2="http://jax-ws.dev.java.net/">
               <message>com.sun.istack.XMLStreamException2: org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: Invalid content was found starting with element 'descripcion1'. One of '{codigo1}' is expected.</message>
               <ns2:stackTrace>
                  <ns2:frame class="com.sun.xml.ws.util.pipe.AbstractSchemaValidationTube" file="AbstractSchemaValidationTube.java" line="242" method="doProcess"/>
[…]

La validación del esquema hace también comprobaciones de tipos de datos, de forma que si en el campo Integer introducimos ‘abc’ se lanzará una excepción.

<message>com.sun.istack.XMLStreamException2: org.xml.sax.SAXParseException: cvc-datatype-valid.1.2.1: 'abc' is not a valid value for 'integer'.</message>

Ideas a evitar

Se podría idear una solución intermedia sin utilizar la anotación RequestWrapper. Esta solución intermedia pasaría por incluir los campos de la request en un objeto wrapper, como el objeto WebServiceEjemploWrapperRequest del ejemplo, y hacer que este objeto sea el único parámetro del método.

	@WebMethod(operationName="metodoMalEjemplo")
	public void metodoMalEjemplo(	@WebParam(name="objetoComplejo")WebServiceEjemploWrapperRequest objetoComplejo) {
		System.out.println("codigo1=" + objetoComplejo.getCodigo1());
		System.out.println("descripcion1=" + objetoComplejo.getDescripcion1());
		System.out.println("codigo2=" + objetoComplejo.getCodigo2());
		System.out.println("descripcion2=" + objetoComplejo.getDescripcion2());
	}

Esta programación dará como resultado la siguiente petición SOAP:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jax="http://egv.com/webservices/jaxws">
   <soapenv:Header/>
   <soapenv:Body>
      <jax:metodoMalEjemplo>
         <!--Optional:-->
         <objetoComplejo>
            <codigo1>?</codigo1>
            <!--Optional:-->
            <descripcion1>?</descripcion1>
            <codigo2>?</codigo2>
            <!--Optional:-->
            <descripcion2>?</descripcion2>
         </objetoComplejo>
      </jax:metodoMalEjemplo>
   </soapenv:Body>
</soapenv:Envelope>	

El funcionamiento de los clientes que llaman al método con la request configurada de esta manera es similar a la configuración con RequestWrapper. Sin embargo, añadir el encapsulado adicional <objetoComplejo> no es una buena idea ya que cuanto más simple permanezca el modelo más compatible será éste.

Conclusión

La utilización de la anotación RequestWrapper permite utilizar JAXB para configurar las características de cada campo que participa en la request de un método de un servicio web.

Esta anotación permite generar una request lo más simple posible lo que, entre otras cosas, hará que los métodos sean completamente compatibles con sistemas de cacheo que normalmente no funcionan bien con objetos complejos.

Links

Publicar servicios web JAX-WS 2.1 en Tomcat 6

La llegada de la crisis se está dejando ver en todos los ámbitos y la informática no es una excepción. Hasta hace no mucho las empresas no daban excesiva importancia al coste de sus servidores y se pagaban las licencias de software religiosamente. Pero, la crisis ha dado un vuelco a esta situación. El gasto se controla más y allí donde antes se utilizaba un servidor de pago para dar servicio, ahora se busca sustituirlo por versiones de código abierto y gratuitas.

Concretamente, en el cliente para el que trabajo actualmente se ha decido empezar a utilizar Apache Tomcat para aquellas aplicaciones que no necesitan de la pila Enterprise de Java.

Esta entrada recoge cómo publicar servicios web JAX-WS en un Apache Tomcat 6.x.

Requisitos

Antes de comenzar con el código puro y duro voy a hacer un pequeño repaso del entorno necesario para seguir la solución propuesta en la entrada.

La máquina virtual Java es la Jrockit-jdk1.6.0_45-R28.2.7-4.1.0. Utilizo esta versión porque en los entornos productivos es esta máquina virtual la que se instala. El ejemplo puede funcionar igualmente con la HotSpot 1.6. La versión en concreto que estoy utilizando contiene la versión JAX-WS RI 2.1.6. Si quieres averiguar que versión de JAX-WS incorpora tu máquina virtual basta con ejecutar el siguiente comando:

${java-home}/bin/wsimport -version

El servidor Tomcat elegido para las pruebas es la versión 6.0.37. No utilizo la versión 7 de Tomcat porque los entornos productivos están configurados con la versión Tomcat 6. Esta versión admite las especificaciones Servlet/2.5, JSP/2.1 y requiere la versión 1.5 o versiones superiores de la máquina virtual Java.

Instalación básica de Tomcat.

El primer paso es realizar una instalación básica de Tomcat.

El servidor se puede descargar de la página oficial de Apache Tomcat. La instalación básica es muy sencilla, basta con descomprimir el fichero descargado en un directorio que a partir de ahora voy a llamar ${TOMCAT_HOME}.

Antes de arrancar el servidor hay que configurar el usuario administrador para poder acceder a la aplicación de despliegue de Tomcat. Los usuarios de Tomcat se configuran en el fichero ${TOMCAT_HOME}/conf/tomcat-users.xml. En este fichero se añaden las siguientes líneas:

<role rolename="manager"/>
<role rolename="admin"/>
<user username="admin" password="admin" roles="admin,manager"/>

No tendría que decirlo, pero por si acaso, ni que decir tiene que el nombre del usuario administrador así como su password no tiene porque coincidir con el ejemplo, queda a elección de la persona que instala el servidor crear los roles y usuarios que más le convengan.

Para el ejemplo básico creo el rol manager, el rol admin y el usuario admin que estará compuesto por estos dos roles.

Una vez modificado este fichero se puede poner en marcha el servidor. Para arrancar y parar, el propio servidor proporciona dos scripts que facilitan enormemente la tarea. El script de arranque es ${TOMCAT_HOME}/bin/startup.bat (en entornos Windows) o ${TOMCAT_HOME}/bin/startup.sh (en entornos unix). Para parar el servidor el script es ${TOMCAT_HOME}/bin/shutdown.bat (Windows) y ${TOMCAT_HOME}/bin/shutdown.sh (Unix).

Creación de la aplicación de ejemplo con servicios web JAXWS.

La aplicación de ejemplo se crea utilizando Maven 3.x para facilitar el manejo del ciclo de vida en general y de las dependencias en particular.

Desde Eclipse se crea un nuevo Maven Project a partir del arquetipo org.apache.maven.archetypes – maven-archetype-webapp en su versión RELEASE. Esto proporciona la base de una aplicación web dinámica. La estructura se parece a lo que se ve en la siguiente captura:

Arquitectura creada por Maven para una aplicación web.

Para facilitar el desarrollo, publicación y pruebas de la aplicación se incluye en el fichero pom.xml que ha generado el arquetipo la configuración de dos plugins; maven-war-plugin y maven-clean-plugin.

El plugin maven-war-plugin configura el empaquetado WAR de la aplicación con ciertas características:

  • Se va a publicar la aplicación directamente en el directorio de despliegues por defecto de Tomcat. Esto se hace añadiendo el tag <webappDirectory> en las configuración del plugin y permite hacer pruebas inmediatas de la aplicación sin necesidad de estar publicándola constantemente desde el Deployer.
  • Se va a dejar una versión empaquetada en un fichero WAR en el directorio dist de la aplicación. Esto se consigue con el tag <outputDirectory>. La idea es poder tener un WAR independiente para poder enviar la aplicación a otros entornos de forma fácil.
<build>
    […]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>
          <webappDirectory>E:\srv\dominio\aplic\dominio_tomcat\apache-tomcat-6.0.37\webapps\ExampleWS</webappDirectory>
          <outputDirectory>./dist</outputDirectory>
          <warName>ExampleWS-${version}</warName>
        </configuration>
      </plugin>
    </plugins>
	 […]
</build>

El plugin maven-clean-plugin limpia los compilados de la aplicación. Como se ha añadido la generación del fichero war en un directorio específico, se configura este plugin para que lo elimine cuando se ejecute un mvn clean.

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-clean-plugin</artifactId>
    <version>2.5</version>
    <configuration>
      <filesets>
         <fileset>
          <directory>./dist</directory>
          <followSymlinks>false</followSymlinks>
          <useDefaultExcludes>true</useDefaultExcludes>
          <includes>
            <include>*.war</include>
          </includes>
        </fileset>
      </filesets>
    </configuration>
  </plugin>

Adicionalmente, se añaden las dependencias necesarias para que la aplicación entienda y publique los servicios web JAXWS:

  • jaxws-rt v2.1.6
  <dependencies>
  […]
    <dependency>
      <groupId>>com.sun.xml.ws</groupId>
      <artifactId>jaxws-rt</artifactId>
      <version>2.1.7</version>
    </dependency>
  […]
  </dependencies>

Creación el servicio web de ejemplo.

Desde que la JDK 1.5 incorporó las anotaciones y JAX-WS hizo uso de ellas, crear un servicio web de ejemplo se ha convertido en una tarea sencilla.

package com.egv.webservices.jaxws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

@WebService (name="WebServiceUno", targetNamespace="http://egv.com/webservices/jaxws")
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT, 
			use=SOAPBinding.Use.LITERAL, 
			parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
public class WebServiceUno {
	
	@WebMethod(operationName="HolaMundo")
	public void HolaMundo(@WebParam(name="nombre")String nombre) {
		System.out.println("Hola mundo! Te saluda " + nombre);
	}

}  

Este servicio web se llama WebServiceUno y tiene un único método que se llama HolaMundo que recibe un nombre como parámetro y que una vez ejecutado escribe en consola Hola mundo! te saluda ${nombre}. Un servicio web muy simple pero perfecto para probar la publicación de WS JAXWS en Tomcat 6.

Configuración de la aplicación para publicar el servicio web en Tomcat.

Para publicar los servicios web en tomcat es necesario que la aplicación publique un servlet específico de JAXWS en el arranque. Este servlet se encuentra en el paquete com.sun.xml.ws.transport.http.servlet.WSServlet y viaja con la implementación de JAXWS que se ha descargado con la dependencia oportuna. En el fichero web.xml de la aplicación se incluye el siguiente código:

<web-app>
  	[…]
    <servlet>
        <servlet-name>WebServiceUno</servlet-name>
        <servlet-class>
        	com.sun.xml.ws.transport.http.servlet.WSServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>WebServiceUno</servlet-name>
        <url-pattern>/wsUno</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>120</session-timeout>
    </session-config>
	[…]  
</web-app>

El servlet WSServlet ejecuta los servicios web que se incluyan en la aplicación. Pero, para que Tomcat sepa que servicios web tiene que publicar hay que incluir el fichero sun-jaxws.xml. Este fichero contiene la configuración de los endpoints de los servicios web.

<?xml version="1.0" encoding="UTF-8"?>
<endpoints
  xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
  version="2.0">
  <endpoint
      name="WebServiceUno"
      implementation="com.egv.webservices.jaxws.WebServiceUno"
      url-pattern="/wsUno"/>
</endpoints>

Tomcat entiende este fichero gracias al listener com.sun.xml.ws.transport.http.servlet.WSServletContextListener específico de JAXWS. Este listener es el encargado de parsear el fichero y crear un HttpAdapter por cada endpoint definido. El listener hay que incluirlo en la aplicación añadiendo las siguientes líneas en el fichero web.xml:

<web-app>
[…]
<listener>
        <listener-class>
                com.sun.xml.ws.transport.http.servlet.WSServletContextListener
        </listener-class>
    </listener> 
[…]
</web-app>

El war generado presenta la siguiente estructura:

Estructura del fichero War.

Desplegar y probar.

Una vez que todo está creado, se ejecuta mvm install.

Si la compilación es correcta, ésta ejecución debería dejar en el directorio de despliegue de tomcat la aplicación correctamente publicada. En el deployer de tomcat se verá la aplicación correctamente publicada.

Aplicación correctamente desplegada.

Accediendo a la URI del servicio web se ve el servicio web publicado.

Web Service correctamente desplegado.

Probando la aplicación a través de SOAPUi se comprueba el funcionamiento correcto.

Ejecución desde SOAPUi

En la consola del servidor encontramos la traza escrita.

Consola de salida del servidor.

Conclusión

Como se ha visto a lo largo de la entrada, no es especialmente difícil publicar servicios web JAXWS en Apache Tomcat. La ventaja es que Tomcat cuenta con una licencia de código abierto ahorrando a las empresas bastantes euros en costosas licencias de servidores de aplicación.

Por tanto, si la aplicación que se desea desarrollar no tiene exigencias Enterprise y va a tener una carga normal de trabajo, ésta configuración se puede utilizar sin problemas.