Aprende a consumir servicios REST vía HTTPS leyendo precios de criptomonedas


Una de las tareas más comunes de un desarrollador es consumir servicios REST vía HTTPS, en este post se explicará lo siguiente:

  • Generar un certificado HTTPS
  • Instalar el certificado en la máquina virtual
  • Crear un cliente REST
  • Hacer una petición para obtener el valor de criptomonedas

Paso 1 Analizar el api a consumir

En este ejemplo leeremos información sobre criptomonedas, para hacerlo utilizaremos el api REST de Bitso (Empresa dedicada a la compra y venta de bitcoins, ripple, litecoin, entre otras criptomonedas). Para más información sobre los endpoints disponibles ver el siguiente link Documentación Bitso. En este ejemplo se consumirá un servicio para obtener el precio del bitcoin y ripple para esto se utilizará el endpoint ticker con los siguientes parámetros:

Es importante mencionar que los precios mostrados en este sitio son en pesos mexicanos.

Paso 2 Configurar el projecto

Una vez que entendemos el api que se va a consumir que conocemos los endpoints que se ejecutarán, el siguiente paso será crear un proyecto, para esto se creará un proyecto Maven e incluiremos las siguientes dependencias:

<dependencies>
	<dependency>
		<groupId>org.apache.storm</groupId>
		<artifactId>storm-core</artifactId>
		<version>1.0.1</version>
	</dependency>
	<dependency>
		<groupId>org.glassfish.jersey.core</groupId>
		<artifactId>jersey-client</artifactId>
		<version>2.17</version>
	</dependency>
	<dependency>
		<groupId>org.glassfish.jersey.media</groupId>
		<artifactId>jersey-media-json-jackson</artifactId>
		<version>2.17</version>
	</dependency>
</dependencies>

Y el siguiente plugin de Maven:

<build>
	<plugins>
		<plugin>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.2</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
	</plugins>
</build>

Con estas dependencias y este plugin tendremos la versión de Java 8 y las dependencias necesarias para trabajar con Jersey client (El api que se utilizará para consumir servicios).

Paso 3 Creando modelo de la aplicación

Un paso importante al crear un cliente REST es des serializar la respuesta a objetos java, para esto debemos crear una representación del JSON en clases Java.

Json:

{
	success: true,
	payload: {
		high: "15.13",
		last: "14.69",
		created_at: "2017-12-18T14:13:36+00:00",
		book: "xrp_mxn",
		volume: "1700513.79486861",
		vwap: "14.14839779",
		low: "14.07",
		ask: "14.69",
		bid: "14.50"
	}
}

Clases Java:

PayLoad.java


import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * @author raidentrance
 *
 */
public class Payload {

	@JsonProperty("high")
	private double high;

	@JsonProperty("last")
	private double last;

	@JsonProperty("created_at")
	private String createdAt;

	@JsonProperty("book")
	private String book;

	@JsonProperty("volume")
	private Double volume;

	@JsonProperty("vwap")
	private Double vwap;

	@JsonProperty("low")
	private Double low;

	@JsonProperty("ask")
	private Double ask;

	@JsonProperty("bid")
	private Double bid;

	public double getHigh() {
		return high;
	}

	public void setHigh(double high) {
		this.high = high;
	}

	public double getLast() {
		return last;
	}

	public void setLast(double last) {
		this.last = last;
	}

	public String getCreatedAt() {
		return createdAt;
	}

	public void setCreatedAt(String createdAt) {
		this.createdAt = createdAt;
	}

	public String getBook() {
		return book;
	}

	public void setBook(String book) {
		this.book = book;
	}

	public Double getVolume() {
		return volume;
	}

	public void setVolume(Double volume) {
		this.volume = volume;
	}

	public Double getVwap() {
		return vwap;
	}

	public void setVwap(Double vwap) {
		this.vwap = vwap;
	}

	public Double getLow() {
		return low;
	}

	public void setLow(Double low) {
		this.low = low;
	}

	public Double getAsk() {
		return ask;
	}

	public void setAsk(Double ask) {
		this.ask = ask;
	}

	public Double getBid() {
		return bid;
	}

	public void setBid(Double bid) {
		this.bid = bid;
	}

	@Override
	public String toString() {
		return "Payload [high=" + high + ", last=" + last + ", createdAt=" + createdAt + ", book=" + book + ", volume="
				+ volume + ", vwap=" + vwap + ", low=" + low + ", ask=" + ask + ", bid=" + bid + "]";
	}

}

CoinPrice.java


/**
 * @author raidentrance
 *
 */
public class CoinPrice {
	private boolean success;
	private Payload payload;

	public boolean isSuccess() {
		return success;
	}

	public void setSuccess(boolean success) {
		this.success = success;
	}

	public Payload getPayload() {
		return payload;
	}

	public void setPayload(Payload payload) {
		this.payload = payload;
	}

	@Override
	public String toString() {
		return "RipplePrice [success=" + success + ", payload=" + payload + "]";
	}

}

Paso 4 Creando el cliente REST

El siguiente paso es empezar a escribir código, lo primero que escribiremos será un AbstractClient, que servirá para dar soporte para escribir multiples clientes REST:

AbstractClient.java


import java.util.logging.Logger;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;

/**
 * @author raidentrance
 *
 */
public class AbstractClient {
	private String url;
	private String contextPath;

	private static final Logger log = Logger.getLogger(AbstractClient.class.getName());

	public AbstractClient(String url, String contextPath) {
		this.url = url;
		this.contextPath = contextPath;
	}

	protected WebTarget createClient(String path) {
		String assembledPath = assembleEndpoint(path);
		Client client = ClientBuilder.newClient();
		WebTarget target = client.target(assembledPath);
		return target;
	}

	private String assembleEndpoint(String path) {
		String endpoint = url.concat(contextPath).concat(path);
		log.info(String.format("Calling endpoint %s", endpoint));
		return endpoint;
	}
}

La clase AbstractClient será la clase base que se utilizará para todos los clientes que creemos, cuenta con dos métodos principales:

  • assembleEndpoint(String path) : Construye la url a ejecutar
  • WebTarget createClient(String path) : Crea el cliente HTTP para invocar la petición REST, en caso de requerir autenticar la petición este es el lugar para hacerlo.

ApplicationEndpoint.java


/**
 * @author raidentrance
 *
 */
public class ApplicationEndpoint {
	private static String TICKER = "/ticker";

	public static String getCoinPrice(String coin) {
		return TICKER.concat(String.format("?book=%s", coin));
	}
}

La clase ApplicationEndpoint se utiliza para definir los endpoints a ejecutar en la aplicación.

ServiceException.java


/**
 * @author raidentrance
 *
 */
public class ServiceException extends Exception {
	private Integer httpStatusCode;

	private static final long serialVersionUID = -1873750263916403862L;

	public ServiceException(String message, Integer httpStatusCode) {
		super(message);
		this.httpStatusCode = httpStatusCode;
	}

	public Integer getHttpStatusCode() {
		return httpStatusCode;
	}

	public void setHttpStatusCode(Integer httpStatusCode) {
		this.httpStatusCode = httpStatusCode;
	}
}

La clase ServiceException se utilizará para propagar los errores en caso de que existan.
BitsoClient


import java.util.logging.Logger;

import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import com.raidentrance.client.endpoints.ApplicationEndpoint;
import com.raidentrance.client.error.ServiceException;
import com.raidentrance.client.model.CoinPrice;

/**
 * @author raidentrance
 *
 */
public class BitsoClient extends AbstractClient {
	private static final Logger log = Logger.getLogger(BitsoClient.class.getName());

	public BitsoClient(String url, String contextPath) {
		super(url, contextPath);
	}

	public CoinPrice getRipplePrice() throws ServiceException {
		log.info("Getting ripple price");
		WebTarget client = createClient(ApplicationEndpoint.getCoinPrice("xrp_mxn"));
		Response response = client.request(MediaType.APPLICATION_JSON).get();
		log.info("Status " + response.getStatus());
		CoinPrice result = null;
		Integer status = response.getStatus();
		if (Status.OK.getStatusCode() == status) {
			result = response.readEntity(CoinPrice.class);
		} else {
			throw new ServiceException(response.readEntity(String.class), status);
		}
		return result;
	}

	public CoinPrice getBitcoinPrice() throws ServiceException {
		log.info("Getting ripple price");
		WebTarget client = createClient(ApplicationEndpoint.getCoinPrice("btc_mxn"));
		Response response = client.request(MediaType.APPLICATION_JSON).get();
		log.info("Status " + response.getStatus());
		CoinPrice result = null;
		Integer status = response.getStatus();
		if (Status.OK.getStatusCode() == status) {
			result = response.readEntity(CoinPrice.class);
		} else {
			throw new ServiceException(response.readEntity(String.class), status);
		}
		return result;
	}

}

Ahora es tiempo de crear el cliente REST en este caso será la clase BitsoClient como se puede ver recibe los siguientes parámetros en el constructor:

  • url : Representa la url a consumir
  • contextPath : Representa la url base de los servicios

También cuenta con 2 métodos:

  • getRipplePrice() : Como su nombre lo indica devuelve un objeto con el precio actual de la criptomoneda ripple.
  • getBitcoinPrice() : Como su nombre lo indica devuelve un objeto con el precio actual de la criptomoneda bitcoin.

TestClient.java


import com.raidentrance.client.error.ServiceException;
import com.raidentrance.client.model.CoinPrice;

/**
 * @author raidentrance
 *
 */
public class TestClient {
	public static void main(String[] args) throws ServiceException {
		BitsoClient client = new BitsoClient("https://api.bitso.com/", "v3");
		CoinPrice ripplePrice = client.getRipplePrice();
		CoinPrice bitcoin = client.getBitcoinPrice();
		System.out.println(String.format("Ripple price %s",ripplePrice.toString()));
		System.out.println(String.format("Bitcoin price %s",bitcoin.toString()));
	}
}

La clase TestClient se utilizará para mostrar en consola los valores obtenidos por las API’s REST.

Ejecutando la aplicación

Si ejecutamos la aplicación y no contamos con el certificado para ejecutar la petición recibiremos el siguiente error:

SunCertPathBuilderException: unable to find valid certification path to requested target

Así que utilizaremos una clase llamada InstallCert.java creada por el equipo de sun que puedes encontrar aquí, ejecutarla pasando como parámetro la url de la cual deseas extraer el certificado con el siguiente comando :

java InstallCert api.bitso.com

El cuál mostrará la siguiente salida:

Opening connection to api.bitso.com:443...
Starting SSL handshake...

No errors, certificate is already trusted

Server sent 3 certificate(s):

 1 Subject CN=ssl511101.cloudflaressl.com, OU=PositiveSSL Multi-Domain, OU=Domain Control Validated
   Issuer  CN=COMODO ECC Domain Validation Secure Server CA 2, O=COMODO CA Limited, L=Salford, ST=Greater Manchester, C=GB
   sha1    11 bd 1f 03 0a b4 07 8c d3 f1 49 16 f2 97 0a 9f 97 07 55 cd
   md5     33 1e 1d 7f 95 09 de 0b 0b b8 31 9d f2 48 85 b8

 2 Subject CN=COMODO ECC Domain Validation Secure Server CA 2, O=COMODO CA Limited, L=Salford, ST=Greater Manchester, C=GB
   Issuer  CN=COMODO ECC Certification Authority, O=COMODO CA Limited, L=Salford, ST=Greater Manchester, C=GB
   sha1    75 cf d9 bc 5c ef a1 04 ec c1 08 2d 77 e6 33 92 cc ba 52 91
   md5     5e 0e 41 9b 20 ea 57 54 77 f1 1b 52 e2 c8 18 e0

 3 Subject CN=COMODO ECC Certification Authority, O=COMODO CA Limited, L=Salford, ST=Greater Manchester, C=GB
   Issuer  CN=AddTrust External CA Root, OU=AddTrust External TTP Network, O=AddTrust AB, C=SE
   sha1    ae 22 3c bf 20 19 1b 40 d7 ff b4 ea 57 01 b6 5f dc 68 a1 ca
   md5     c7 90 a5 6c 69 cb af 0b f3 f3 0a 40 d0 a2 ae cc

<strong>Enter certificate to add to trusted keystore or 'q' to quit: [1]</strong>

Oprimiremos la tecla de 1 y enter.

Ejecutaremos de nuevo el comando, seleccionaremos 1 de nuevo y notaremos que la salida es ahora la siguiente:

Added certificate to keystore 'jssecacerts' using alias 'api.bitso.com-1'

Este comando generará un archivo llamado jssecacerts, el último paso será copiar ese archivo al directorio $JAVA_HOME\jre\lib\security y listo, tu aplicación podrá hacer peticiones https a el dominio de bitso.

El último paso será ejecutar nuestra aplicación para validar que todo funciona bien y generará la siguiente salida:

dic 18, 2017 10:37:04 AM com.raidentrance.client.BitsoClient getRipplePrice
INFORMACIÓN: Getting ripple price
dic 18, 2017 10:37:04 AM com.raidentrance.client.AbstractClient assembleEndpoint
INFORMACIÓN: Calling endpoint https://api.bitso.com/v3/ticker?book=xrp_mxn
dic 18, 2017 10:37:05 AM com.raidentrance.client.BitsoClient getRipplePrice
INFORMACIÓN: Status 200
dic 18, 2017 10:37:05 AM com.raidentrance.client.BitsoClient getBitcoinPrice
INFORMACIÓN: Getting ripple price
dic 18, 2017 10:37:05 AM com.raidentrance.client.AbstractClient assembleEndpoint
INFORMACIÓN: Calling endpoint https://api.bitso.com/v3/ticker?book=btc_mxn
dic 18, 2017 10:37:06 AM com.raidentrance.client.BitsoClient getBitcoinPrice
INFORMACIÓN: Status 200
<strong>Ripple price RipplePrice [success=true, payload=Payload [high=15.13, last=14.67, createdAt=2017-12-18T16:37:02+00:00, book=xrp_mxn, volume=1908408.5657127, vwap=14.20595744, low=14.07, ask=14.64, bid=14.52]]
Bitcoin price RipplePrice [success=true, payload=Payload [high=382000.0, last=374994.16, createdAt=2017-12-18T16:37:03+00:00, book=btc_mxn, volume=301.0574943, vwap=373301.92134721, low=362505.0, ask=375800.0, bid=374994.16]]</strong>

Como se puede ver se imprimieron los precios de las criptomonedas de forma exitosa, ya dependerá de ti la aplicación que crees con esto.

Conclusión

En este post se tocaron temas importantes como:

  • Configurar un proyecto para hacer peticiones REST con Jersey
  • Crear un patrón de diseño para la construcción de clientes REST
  • Ejecutar peticiones REST vía HTTPS
  • Obtener el valor en pesos de las criptomonedas Bitcoin y Ripple

Si te gusta el contenido y quieres enterarte cuando realicemos un post nuevo síguenos en nuestras redes sociales https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Support multiple profiles with Spring boot


One of the most common problems while we are developing applications is to have just one code and execute it in different environments. In this post we will explain how to support multiple environments using Spring boot step by step, to do it we will start with the project created in the post Spring boot + Jersey.

First steps

Before to continue learning how to configure the Spring profiles, we have to understand the following points:

  • For each environment we will have a profile, therefore if we have the environments Development, QA and Production we will have 3 profiles in our application.
  • In Spring boot the profiles are defined on runtime it means that you don’t need to compile your code by using an specific flag.
  • There are many ways to define a profile in Spring boot, in this post we will explain 2 by using properties and yaml files.

Defining profiles by using properties files

To configure the Spring profiles we have to execute the following steps:

1 Add a properties file per profile

The first step will be to create a properties file in the /src/main/resources folder for each environment that we will support by using the following structure:

application-${profile}.properties

${profile} Represents the environment, for this example we will create the following 3 files:

  • application-dev.properties
com.raidentrance.app.name=Spring boot dev
  • application-qa.properties
com.raidentrance.app.name=Spring boot qa
  • application-prod.properties
com.raidentrance.app.name=Spring boot prod

2 Create a new service to show the value of the property

Once we create the property named com.raidentrance.app.name the next step will be present its value in a web service, to do it we will use the following service:

/**
 *
 */
package com.raidentrance.resource;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author maagapi
 *
 */
@Component
@Path("/configs")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class ProjectConfigurationResource {

	@Value("${com.raidentrance.app.name}")
	private String appName;

	private static final Logger log = LoggerFactory.getLogger(UserResource.class);

	@GET
	@Path("/appName")
	public Response getAppName() {
		log.info("Getting project configuration");
		return Response.ok(appName).build();
	}
}

The previous service will show in the response of the endpoint GET /configs/appName the value defined in our properties file

3 Register the service in Jersey

We have to remember that if we create a new service we need to include it in the JerseyConfig class.

/**
 *
 */
package com.raidentrance.config;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import com.raidentrance.resource.ProjectConfigurationResource;
import com.raidentrance.resource.UserResource;

/**
 * @author raidentrance
 *
 */
@Component
public class JerseyConfig extends ResourceConfig {
	public JerseyConfig() {
		register(UserResource.class);
		register(ProjectConfigurationResource.class);
	}
}

The UserResource was created in the previous example and it is not necessary for this example.

4 Testing everything together

To test it is necessary to compile our application by using the command mvn clean install as you can see it is not necessary to include the profile by compiling, what it is going to be different is the execution, to execute our application we will execute the command:

mvn -Dspring.profiles.active=qa spring-boot:run

As you can see the previous command will use the configurations defined in the qa profile.

If we execute the endpoint GET /configs/appName we will get the following result:

Spring boot qa

Defining profiles by using a yaml file

Once we know how to define the profiles by using multiple .properties file the next step will be to understand how to do it by suing yaml files.

To support multiple profiles by using yaml files we only need to modify the step number 1.

1 Define the configurations

When we create the configurations in a yaml file we don’t need to create multiple files per environment, we only need to create one, lets see the structure:

spring:
    profiles:
        active: dev
---
spring:
    profiles: dev
com:
    raidentrance:
      app:
        name: Spring boot dev
---
spring:
    profiles: qa
com:
    raidentrance:
      app:
        name: Spring boot qa
---
spring:
    profiles: prod
com:
    raidentrance:
      app:
        name: Spring boot prod

 As you can observe now you can define in the file the default profile and the properties for all the environments.

You can find the code in the following url https://github.com/raidentrance/spring-boot-example/tree/part7-profiles.

Don’t forget to follow us in our social networks in https://twitter.com/geeks_mx and in https://www.facebook.com/geeksJavaMexico/.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring boot + Profiles (Soporta múltiples entornos)


Uno de los problemas más comunes cuando se desarrollan aplicaciones en cualquier lenguaje es utilizar un solo código que este se ejecute en múltiples entornos (dev, qa y prod). En todos los entornos se utilizarán las mismas configuraciones, lo que cambiará de uno a otro serán los valores asignados a las mismas.

Para soportar esto Spring boot provee perfiles que son diferentes a los perfiles normales de Maven, para saber más sobre ellos re recomendamos el post Perfiles Maven en Español !.

Primeros pasos

Antes de adentrarnos a cómo configurar los perfiles primero existen algunos conceptos que debemos entender:

  • Un perfil se asignará a un entorno de tal modo que si tenemos los entornos, Desarrollo, QA y Producción se deberán crear 3 perfiles.
  • A diferencia de los perfiles normales de Maven, en Spring boot los perfiles se utilizarán en tiempo de ejecución, de este modo no es necesario compilar los proyectos con la bandera -P.
  • Existen diferentes formas de definir un perfil en Maven, en este post se explicarán las 2 principales utilizando archivos properties y utilizando archivos yaml.
  • Para estos ejemplos se tomará como base el proyecto creado en el post Spring Boot + REST Jersey Parte 1.

Definiendo perfiles utilizando archivos properties

Para configurar los perfiles maven utilizando archivos properties es necesario hacer lo siguiente.

1. Agregar un archivo properties por cada perfil que se desea soportar

El primer paso será crear un archivo properties en el folder src/main/resources por cada uno de los entornos a soportar siguiendo la siguiente estructura:

application-${profile}.properties

${profile} Será el nombre de nuestro entorno. Para este ejemplo se crearán los siguientes 3 archivos:

  • application-dev.properties
 com.raidentrance.app.name=Spring boot dev
  • application-qa.properties
 com.raidentrance.app.name=Spring boot qa
  • application-prod.properties
 com.raidentrance.app.name=Spring boot prod

2. Crear un nuevo servicio para mostrar el valor de la propiedad

Una vez definida la nueva propiedad llamada com.raidentrance.app.name el siguiente paso será exponerla en un servicio. Para esto se creará el siguiente servicio:

/**
*
*/
package com.raidentrance.resource;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* @author maagapi
*
*/
@Component
@Path("/configs")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class ProjectConfigurationResource {

@Value("${com.raidentrance.app.name}")
private String appName;

private static final Logger log = LoggerFactory.getLogger(UserResource.class);

@GET
@Path("/appName")
public Response getAppName() {
log.info("Getting project configuration");
return Response.ok(appName).build();
}
}

El servicio anterior mostrará en la respuesta del endpoint GET /configs/appName el nombre definido en nuestro archivo de configuración.

3. Registrar el servicio en Jersey

Recordemos que siempre que se crea un servicio nuevo en Jersey lo debemos agregar en nuestra clase JerseyConfig como se muestra a continuación:

/**
*
*/
package com.raidentrance.config;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import com.raidentrance.resource.ProjectConfigurationResource;
import com.raidentrance.resource.UserResource;

/**
* @author raidentrance
*
*/
@Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(UserResource.class);
register(ProjectConfigurationResource.class);
}
}

Recordemos que el servicio UserResource fue el que ya existía en el ejemplo y no es necesario para la explicación de perfiles.

4. Probado todo junto

Una vez que ya se configuró todo lo anterior lo único que resta es probarlo, para esto solo es necesario compilar nuestra aplicación como siempre con un simple mvn clean install, lo único que será diferente será al momento de ejecutarlo, para esto se utilizará el siguiente comando:

mvn -Dspring.profiles.active=qa spring-boot:run

Como se puede ver en el comando se ejecutará la aplicación utilizando las configuraciones definidas en el perfil de qa.

Si ejecutamos el endpoint GET /configs/appName la salida será la siguiente:

Spring boot qa

Definiendo perfiles utilizando archivos yaml

Una vez que ya sabemos como definir los perfiles utilizando multiples archivos .properties, el siguiente paso será entender como hacerlo utilizando archivos yaml.

Paso 1 Definir las configuraciones

A diferencia de la configuración utilizando archivos properties, para definir multiples perfiles utilizando archivos yaml solo es necesario crear un solo archivo para todos los perfiles de la aplicación veamos el ejemplo mencionado anteriormente pero utilizando archivos yaml.

spring:
    profiles:
        active: dev
---
spring:
    profiles: dev
com:
    raidentrance:
      app:
        name: Spring boot dev
---
spring:
    profiles: qa
com:
    raidentrance:
      app:
        name: Spring boot qa
---
spring:
    profiles: prod
com:
    raidentrance:
      app:
        name: Spring boot prod

Como se puede observar a diferencia de la definición con archivos properties es posible definir en un solo archivo yaml el perfil por defecto a utilizar y las propiedades para los diferentes entornos.

Los demás componentes del código serán iguales para ambas formas.

Puedes encontrar el código completo en el siguiente enlace https://github.com/raidentrance/spring-boot-example/tree/part7-profiles .

Si te gusta el contenido o tienes preguntas síguenos en nuestras redes sociales: https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/.

 

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring Boot + REST Jersey (Adding Spring HATEOAS and MapStruct) Part 3


In the previous examples we explained the configuration of Spring Boot + REST Jersey Part 1 and Spring Boot + REST Jersey (Adding Spring data) Part 2. Now we are going to take the previous examples as base to show how HATEOAS works by using Spring Boot + Jersey.

Step 1: Configuration

Spring boot has starter dependencies that are very useful to add new modules to our application, in this example we will add the spring-hateoas dependency as follows:

<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
</dependency>

Step 2: Adding entities

In the previous example we learned how to use Spring data to get information from a database and how to use Spring Jersey to expose this information in a web service. In this example we created the entity User, now we are going to create a new entity named Role.

/**
 *
 */
package com.raidentrance.entities;

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * @author raidentrance
 *
 */
@Entity
@Table(name = "ROLE")
public class Role implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID_ROLE")
    private Integer idRole;

    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 45)
    @Column(name = "NAME")
    private String name;

    @Size(max = 100)
    @Column(name = "DESCRIPTION")
    private String description;

    private static final long serialVersionUID = 3428234636660051311L;

    ........
}

And we are going to modify the entity User.

/**
 *
 */
package com.raidentrance.entities;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

/**
 * @author raidentrance
 *
 */
@Entity
@Table(name ="USER")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USER_ID")
    private Integer idUser;

    @Column(name = "USERNAME")
    private String username;

    @Column(name = "PASSWORD")
    private String password;

    @JoinColumn(name = "ROLE_ID", referencedColumnName = "ID_ROLE")
    @ManyToOne(optional = false)
    private Role role;
.....
}

Step 3: Updating the sql scripts

As we created a new entity we are going to need a new table to store the information, in the previous example we created 2 files schema.sql and init.sql, they contain the scripts to create the tables and to fill the data.

schema.sql

CREATE TABLE ROLE(
ID_ROLE INTEGER PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(45) NOT NULL ,
DESCRIPTION VARCHAR(100) NULL
);

CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL,
ROLE_ID INTEGER NOT NULL,
    FOREIGN KEY (ROLE_ID)
    REFERENCES ROLE (ID_ROLE)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION);

init.sql

INSERT INTO ROLE (NAME,DESCRIPTION)VALUES("ADMIN","Administrator");
INSERT INTO ROLE (NAME,DESCRIPTION)VALUES("USER","Normal user");

INSERT INTO USER (USERNAME,PASSWORD,ROLE_ID)VALUES('raidentrance','superSecret',1);
INSERT INTO USER (USERNAME,PASSWORD,ROLE_ID)VALUES('john','smith',1);
INSERT INTO USER (USERNAME,PASSWORD,ROLE_ID)VALUES('juan','hola123',2);

HATEOAS is the abbreviation of Hypermedia as the Engine of Application state and it will allow to navigate across all the REST resources without any documentation, in this way each of the resources will return a link to its resource.

Step 4: Creating DTO’s with HATEOAS support

Now we have to create Dto’s to support the links generated by HATEOAS by extending of the class ResourceSupport.

/**
 *
 */
package com.raidentrance.dto;

import java.io.Serializable;
import org.springframework.hateoas.ResourceSupport;

/**
 * @author raidentrance
 *
 */
public class RoleDto extends ResourceSupport implements Serializable {

    private Long idRole;

    private String name;

    private String description;

    ........

}
/**
 *
 */
package com.raidentrance.dto;

import java.io.Serializable;
import org.springframework.hateoas.ResourceSupport;
import com.raidentrance.entities.Role;

/**
 * @author raidentrance
 *
 */
public class UserDto extends ResourceSupport implements Serializable {

    private Long idUser;

    private String username;

    private String password;

    private RoleDto role;

..........
}

As you can see the Dto’s extends from the class ResourceSupport, it allows to have support to include links to the resources.

Step 5: Creating an Abstract Assembler

The next step is create an abstract assembler, this will be useful to generate the links to the resources.

/**
 *
 */
package com.raidentrance.assembler;

import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.jaxrs.JaxRsLinkBuilder;
import org.springframework.hateoas.mvc.ResourceAssemblerSupport;

import jersey.repackaged.com.google.common.base.Preconditions;

/**
 * @author raidentrance
 *
 */
public abstract class JaxRsResourceAssemblerSupport<T, D extends ResourceSupport>
        extends ResourceAssemblerSupport<T, D> {
    private final Class<?> controllerClass;

    public JaxRsResourceAssemblerSupport(Class<?> controllerClass, Class<D> resourceType) {

        super(controllerClass, resourceType);
        this.controllerClass = controllerClass;
    }

    @Override
    protected D createResourceWithId(Object id, T entity, Object... parameters) {
        Preconditions.checkNotNull(entity);
        Preconditions.checkNotNull(id);
        D instance = instantiateResource(entity);
        instance.add(JaxRsLinkBuilder.linkTo(controllerClass).slash(id).withSelfRel());
        return instance;
    }
}

Step 6: Creating a mapper

Now we are going to create a mapper to transform from JPA entities to Dto’s, in order to do it we will use MapStruct.

/**
 *
 */
package com.raidentrance.mapper;

import org.mapstruct.Mapper;
import com.raidentrance.dto.RoleDto;
import com.raidentrance.dto.UserDto;
import com.raidentrance.entities.Role;
import com.raidentrance.entities.User;

/**
 * @author raidentrance
 *
 */

@Mapper
public interface UserMapper {
    UserDto userEntityToUser(User entity);

    User userToUserEntity(UserDto dto);

    RoleDto roleEntityToRole(Role entity);

    Role roleToRoleEntity(RoleDto role);
}

Step 7: Adding endpoints

The first step will be create the resource for “users” and “roles”, in the following steps we will explain how to implement each method.
UserResource.java

/**
 *
 */
package com.raidentrance.resource;

import java.util.ArrayList;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.raidentrance.entities.User;
import com.raidentrance.repositories.UserRepository;
import jersey.repackaged.com.google.common.collect.Lists;

/**
 * @author raidentrance
 *
 */

@Component
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

    ......
}

RoleResource.java

/**
 *
 */
package com.raidentrance.resource;

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

/**
 * @author raidentrance
 *
 */
@Component
@Path("/roles")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class RoleResource {
......
}

Step 8: Registering the endpoints

Like in the previous examples we need to register each resource in Jersey.

/**
 *
 */
package com.raidentrance.config;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import com.raidentrance.resource.RoleResource;
import com.raidentrance.resource.UserResource;

/**
 * @author raidentrance
 *
 */
@Component
public class JerseyConfig extends ResourceConfig {
    public JerseyConfig() {
         register(UserResource.class);
         register(RoleResource.class);
    }
}

Step 9: Creating assemblers

Now we have to create the assemblers, these components will be responsible to transform from Entities to Dto’s and to include the links generated by HATEOS.

RoleAssembler.java

/**
 *
 */
package com.raidentrance.assembler;

import org.mapstruct.factory.Mappers;
import org.springframework.stereotype.Component;
import com.raidentrance.dto.RoleDto;
import com.raidentrance.entities.Role;
import com.raidentrance.mapper.UserMapper;
import com.raidentrance.resource.RoleResource;

/**
 * @author raidentrance
 *
 */
@Component
public class RoleAssembler extends JaxRsResourceAssemblerSupport<Role, RoleDto> {

    private UserMapper mapper = Mappers.getMapper(UserMapper.class);

    public RoleAssembler() {
        super(RoleResource.class, RoleDto.class);
    }

    @Override
    public RoleDto toResource(Role entity) {
        RoleDto role = createResourceWithId(entity.getIdRole(), entity);
        RoleDto result = mapper.roleEntityToRole(entity);
        result.add(role.getLinks());
        return result;
    }
}

UserAssembler.java

/**
 *
 */
package com.raidentrance.assembler;

import org.mapstruct.factory.Mappers;
import org.springframework.beans.factory.annotation.Autowired;
import com.raidentrance.dto.RoleDto;
import com.raidentrance.dto.UserDto;
import com.raidentrance.entities.User;
import com.raidentrance.mapper.UserMapper;
import com.raidentrance.resource.UserResource;

/**
 * @author raidentrance
 *
 */
@Component
public class UserAssembler extends JaxRsResourceAssemblerSupport<User, UserDto> {
    @Autowired
    private RoleAssembler assembler;

    private UserMapper mapper = Mappers.getMapper(UserMapper.class);

    public UserAssembler() {
        super(UserResource.class, UserDto.class);
    }

    @Override
    public UserDto toResource(User entity) {
        UserDto resource = createResourceWithId(entity.getIdUser(), entity);
        UserDto result = mapper.userEntityToUser(entity);
        RoleDto role = assembler.toResource(entity.getRole());
        result.add(resource.getLinks());
        result.setRole(role);
        return result;
    }
}

Step 10: Adding the repository to the entity Role

As we are using Spring data, it is necessary to create the repository of the entity Role in order to be able to get the data from the database.

/**
 *
 */
package com.raidentrance.repositories;

import org.springframework.data.repository.CrudRepository;

import com.raidentrance.entities.Role;

/**
 * @author raidentrance
 *
 */
public interface RoleRepository extends CrudRepository<Role, Integer>{

}

Step 11: Completing the endpoints

Now we are going to add the missing code to the endpoints that we have created.

UserResource.java

/**
 *
 */
package com.raidentrance.resource;

import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.raidentrance.assembler.UserAssembler;
import com.raidentrance.entities.User;
import com.raidentrance.repositories.UserRepository;
import jersey.repackaged.com.google.common.collect.Lists;

/**
 * @author raidentrance
 *
 */

@Component
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

    @Autowired
    private UserAssembler userAssembler;

    @Autowired
    private UserRepository userRepository;

    @GET
    public Response getUsers() {
        List<User> users = Lists.newArrayList(userRepository.findAll());
        return Response.ok(userAssembler.toResources(users)).build();
    }

    @GET
    @Path("/{idUser}")
    public Response getById(@PathParam("idUser") Integer idUser) {
        User requested = userRepository.findOne(idUser);
        return Response.ok(userAssembler.toResource(requested)).build();
    }

}

RoleResource.java

/**
 *
 */
package com.raidentrance.resource;

import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.raidentrance.assembler.RoleAssembler;
import com.raidentrance.entities.Role;
import com.raidentrance.repositories.RoleRepository;
import jersey.repackaged.com.google.common.collect.Lists;

/**
 * @author raidentrance
 *
 */
@Component
@Path("/roles")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class RoleResource {

    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private RoleAssembler assembler;

    @GET
    public Response getRoles() {
        List<Role> role = Lists.newArrayList(roleRepository.findAll());
        return Response.ok(assembler.toResources(role)).build();
    }

    @GET
    @Path("/{idRole}")
    public Response getById(@PathParam("idRole") Integer idRole) {
        Role requested = roleRepository.findOne(idRole);
        return Response.ok(assembler.toResource(requested)).build();
    }
}

Step 12: Compile the application

As we are using MapStruct we need to generate the Mappers, in order to do it we have to execute the command mvn generate-sources, once the mappers are created we are ready to compile the application by using the command mvn clean install.

Step 13: Testing all together

The last step is to execute the class SprinBootSampleApplication and access to the following urls:

Open the url http://localhost:8080/users

[
{
idUser: 1,
username: "raidentrance",
password: "superSecret",
role: {
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
},
links: [
{
rel: "self",
href: "http://localhost:8080/users/1"
}
]
},
{
idUser: 2,
username: "john",
password: "smith",
role: {
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
},
links: [
{
rel: "self",
href: "http://localhost:8080/users/2"
}
]
},
{
idUser: 3,
username: "juan",
password: "hola123",
role: {
idRole: 2,
name: "USER",
description: "Normal user",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/2"
}
]
},
links: [
{
rel: "self",
href: "http://localhost:8080/users/3"
}
]
}
]

http://localhost:8080/users/1

{
idUser: 1,
username: "raidentrance",
password: "superSecret",
role: {
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
},
links: [
{
rel: "self",
href: "http://localhost:8080/users/1"
}
]
}

http://localhost:8080/roles

[
{
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
},
{
idRole: 2,
name: "USER",
description: "Normal user",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/2"
}
]
}
]

http://localhost:8080/roles/1

{
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
}

If you want to learn more about web services we recommend the following books:

You can find the complete code in the following link https://github.com/raidentrance/spring-boot-example/tree/part3-adding-hateoas

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring Boot + REST Jersey Part 1


In this post we will explain in a simple way the basic configuration of Spring Boot with Jersey, this is the first in a series of posts that will be explaining more advanced topics regarding the previously mentioned technologies.

1.- Configuration:

Configuring Spring Boot is a very simple task, you just need to inherit from the spring boot base project, include the dependencies of the spring modules and configure the plugins to be used in the project.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.0.RELEASE</version>
</parent>

When we inherit from the spring boot parent project, we are inheriting all the versions defined for each component so you don’t need specify the versions in each dependency.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jersey</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
    </dependency>
</dependencies>

As you can see, you only need specify the dependencies but not the versions, they are all specified in the spring-boot-starter-parent.

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

2.- Creating a main class:

Once we have completed our maven settings the next step is to create a main class for our application, this class will be responsible for the initialisation of the container and the project; the default container for the application is Jetty but as it is indicated by the dependencies in our pom.xml file, we will be using Tomcat.

/**
 *
 */
package com.raidentrance;

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

/**
 * @author raidentrance
 *
 */
@SpringBootApplication
public class SprinBootSampleApplication  {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(SprinBootSampleApplication.class, args);
    }

}

One of the most common questions at this point is “Why are we using a main method if we are creating a web application?” The answer is quite simple, the application is not going to generate a war file but a fat jar, it is known as fat jar because besides the application itself, it includes an embedded container such as a Jetty or Tomcat one. In future posts we will explain how to use this in a production environment.

3.- Writing the REST web service.

To create a REST web service with spring you just have to use the annotations from the Jersey project and mark the classes with @Component to add it to the Spring context.

/**
 *
 */
package com.raidentrance.resource;

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

import org.springframework.stereotype.Component;

/**
 * @author raidentrance
 *
 */

@Component
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

    @GET
    public String getUsers() {
     return "{\"username\":\"raidentrance\"}";
    }
}

The following lines are intended to describe the purpose of each annotation used in the present example:

  • @Component: Used to include the class UserResource in the Spring Context.
  • @Path(“users”): Used to define an endpoint where the service is going to be accessed, it is possible to put another annotation at method level to specify more complex endpoints.
  • @Produces(MediaType.APPLICATION_JSON) / @Consumes(MediaType.APPLICATION_JSON): These annotations specify that the endpoints registered on that class will consume and produce JSON files, for this reason the method getUsers() returns a JSON formatted string, it is possible to put these annotations in a method level to modify its behaviour.

4.- Registering the service:

Once we have the service and spring boot has been configured, the missing step is to register the service in Jersey, for this you have to create a class where all the services are going to be registered.

/**
 *
 */
package com.raidentrance.config;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import com.raidentrance.resource.UserResource;

/**
 * @author raidentrance
 *
 */
@Component
public class JerseyConfig extends ResourceConfig {
    public JerseyConfig() {
         register(UserResource.class);
    }
}

This class shouldn’t be invoked from anywhere in the application, just by being registered using the @Component annotation and inheriting from ResourceConfig Spring will know which configuration to use to register the services.

5.- Executing and testing.

To test the application, the only thing we need to do is to execute  SpringBootSampleApplication class as a desktop application, or using its jar file with the command:

 java -jar target/jar-name.jar

Output:

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

2016-09-02 11:23:28.005 INFO 20231 --- [ restartedMain] c.r.SprinBootSampleApplication : Starting SprinBootSampleApplication on m-C02RV1WXG8WP.local with PID 20231 (/Users/maagapi/Documents/Github/spring-boot-example/target/classes started by maagapi in /Users/maagapi/Documents/Github/spring-boot-example)
2016-09-02 11:23:28.008 INFO 20231 --- [ restartedMain] c.r.SprinBootSampleApplication : No active profile set, falling back to default profiles: default
2016-09-02 11:23:28.064 INFO 20231 --- [ restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@53294542: startup date [Fri Sep 02 11:23:28 CDT 2016]; root of context hierarchy
2016-09-02 11:23:28.646 INFO 20231 --- [ restartedMain] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2016-09-02 11:23:29.000 INFO 20231 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2016-09-02 11:23:29.012 INFO 20231 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service Tomcat
2016-09-02 11:23:29.013 INFO 20231 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.4
2016-09-02 11:23:29.075 INFO 20231 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2016-09-02 11:23:29.075 INFO 20231 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1013 ms
2016-09-02 11:23:29.260 INFO 20231 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2016-09-02 11:23:29.261 INFO 20231 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2016-09-02 11:23:29.261 INFO 20231 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'com.raidentrance.config.JerseyConfig' to [/*]
2016-09-02 11:23:29.441 INFO 20231 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2016-09-02 11:23:29.469 INFO 20231 --- [ restartedMain] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2016-09-02 11:23:29.510 INFO 20231 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-09-02 11:23:29.513 INFO 20231 --- [ restartedMain] c.r.SprinBootSampleApplication : Started SprinBootSampleApplication in 2.206 seconds (JVM running for 2.542)

To test the code you should access http://localhost:8080/users in your web browser.

In the following sections we will explain how to add logs, configure spring security, spring data, error handling and deployments to production environments.

If you want to learn more about web services we recommend the following books:

You can find the complete code for this project in the following link:

https://github.com/raidentrance/spring-boot-example

Author: Alejandro Agapito Bautista

Twitter: @raidentrance

Contact: raidentrance@gmail.com

Translated by: Oscar Camacho – melchior01

Contact: adamfirstangel@hotmail.com

Web services REST con Dropwizard en Español !


Introducción

Dropwizard es un framework ligero para el desarrollo de aplicaciones que esta creciendo mucho en los últimos meses, es la competencia directa de frameworks como Spring boot, cuenta con soporte para configuraciones, métricas, manejo de logs y herramientas de producción nuevas y estables en el mercado de una forma fácil de utilizar, bien documentada y robusta. Es importante mencionar que Dropwizard a diferencia de muchos frameworks esta enfocada al 100% al desarrollo de aplicaciones REST.

Arquitectura

Dropwizard contiene los siguientes frameworks como parte de su arquitectura base:

  • Jetty: Las aplicaciones desarrolladas con Dropwizard contienen un contenedor Jetty embebido, lo que significa que las aplicaciones desarrolladas generarán lo que se conoce como un Fat Jar(Jar gordo) llamado así porque incluye entre sus dependencias el contenedor.
  • Jersey: Con el fin de desarrollar aplicaciones REST Dropwizard utiliza Jersey (Como implementación de JAX-RS) .
  • Jackson:Jackson se ha convertido en el estándar para serializar y desserializar de objetos JSON a objetos Java.
  • Metrics:La biblioteca de Metrics permite generar métricas sobre componentes críticos de la aplicación.
  • Otras herramientas: A demás de las mencionadas anteriormente, Dropwizard cuenta con soporte para trabajar con otros API’s como:
    • Guava
    • Logback y Slf4j
    • Hibernate
    • Apache HttpClient
    • JDBI
    • Liquibase
    • Freemaker
    • JodaTime

Configuración

Con el fin de configurar Dropwizard se debe agregar la siguiente dependencia al proyecto:

<properties>
    <dropwizard.version>1.0.2</dropwizard.version>
</properties>
<dependencies>
    <dependency>
        <groupId>io.dropwizard</groupId>
        <artifactId>dropwizard-core</artifactId>
        <version>${dropwizard.version}</version>
    </dependency>
</dependencies>

A demás de los siguientes plugins :

<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-shade-plugin</artifactId>
			<version>2.3</version>
			<configuration>
				<createDependencyReducedPom>true</createDependencyReducedPom>
				<filters>
					<filter>
						<artifact>*:*</artifact>
						<excludes>
							<exclude>META-INF/*.SF</exclude>
							<exclude>META-INF/*.DSA</exclude>
							<exclude>META-INF/*.RSA</exclude>
						</excludes>
					</filter>
				</filters>
			</configuration>
			<executions>
				<execution>
					<phase>package</phase>
					<goals>
						<goal>shade</goal>
					</goals>
					<configuration>
						<transformers>
							<transformer 								implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
							<transformer 								implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
								<mainClass>com.raidentrance.DropwizardApplication</mainClass>
							</transformer>
						</transformers>
					</configuration>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

Creando clase de Configuración de Dropwizard

En Dropwizard se trabajará con un archivo de configuración yml, el cuál contendrá información sobre puertos a utilizar, configuraciones de base de datos, manejo de logs, configuraciones propias, etc. La forma en la que se cargan esas configuraciones a la aplicación es a través del uso de una clase de configuración que toma las configuraciones de un archivo yml, para este ejemplo se utilizará el siguiente archivo application.yml:

appName: raidentrance
server:
  applicationConnectors:
    - type: http
      port: 8080

Como se puede observar se cuentan con diferentes propiedades para ver la lista completa de propiedades disponibles hacer click aqui. Adicionalmente se agregó la propiedad appName, esta propiedad es un ejemplo que muestra como agregar configuraciones propias. Para cargar esa configuración a nuestra aplicación se debe crear una clase de Configuración, en este caso se utilizará la clase DropwizardConfiguration.java :

/**
 *
 */
package com.raidentrance.config;

import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;

/**
 * @author raidentrance
 *
 */
public class DropwizardConfiguration extends Configuration {
	@NotEmpty
	private String appName;

	@JsonProperty
	public String getAppName() {
		return appName;
	}
	@JsonProperty
	public void setAppName(String appName) {
		this.appName = appName;
	}
}

Como se puede observar en la clase de configuración DropwizardConfiguration se define la nueva propiedad appName que contendrá el valor asignado en el archivo application.yml, las demás propiedades son mapeadas en campos de la clase padre Configuration.java.

Creando modelo de la aplicación

En este ejemplo se desarrollará un web service REST que devuelva una lista de usuarios, para conseguir esto se definirán los siguientes pojos:

package com.raidentrance.model;

/**
 * @author raidentrance
 *
 */
public class Role {
	private String name;
	private String description;

	public Role() {
	}

	public Role(String name, String description) {
		super();
		this.name = name;
		this.description = description;
	}
	.....
}
package com.raidentrance.model;

import com.fasterxml.jackson.annotation.JsonView;

/**
 * @author raidentrance
 *
 */
public class User {
	@JsonView(Views.Public.class)
	private String username;
	@JsonView(Views.Internal.class)
	private String password;
	@JsonView(Views.Public.class)
	private Role role;

	public User() {
	}

	public User(String username, String password, Role role) {
		super();
		this.username = username;
		this.password = password;
		this.role = role;
	}

	.....
}
/**
 *
 */
package com.raidentrance.model;

/**
 * @author raidentrance
 *
 */
public class Views {
	public static class Public {
	}
	public static class Internal extends Public {
	}
}

Las clases User y Role son Pojos que se utilizarán para serializar y des serializar objetos.

La clase Views será utilizada para devolver diferentes tipos de objetos JSON dependiendo del endpoint que se consulte, por ejemplo un endpoint para obtener los usuarios no debe devolver el password, pero un endpoint que recibe un usuario para crearlo si lo debe recibir para estos enpoints se utilizará una vista diferente.

Obteniendo información

Para obtener información de una fuente externa es posible utilizar JDBI o Hibernate, en este ejemplo no se utilizará ninguna de las dos debido a que ese ejemplo se explicará más adelante en un ejemplo separado. En este ejemplo se utilizará una clase auxiliar llamada SourceHelper.java que devolverá una lista pequeña con información.

/**
 *
 */
package com.raidentrance.data;

import java.util.ArrayList;
import java.util.List;
import com.raidentrance.model.Role;
import com.raidentrance.model.User;

/**
 * @author raidentrance
 *
 */
public class SourceHelper {
	public List<User> users = new ArrayList<>();

	public SourceHelper() {
		loadSampleData();
	}

	private void loadSampleData() {
		users.add(new User("raidentrance", "superSecret", new Role("ADMIN", "Administrator of the system")));
		users.add(new User("admin", "superSecret", new Role("USER", "Normal user in the system")));
	}

	public List<User> getUsers() {
		return users;
	}
}

Creando el servicio REST

A continuación se presenta el servicio REST utilizado, en este caso contendrá el endpoint GET “/users” el cual devolverá una lista de usuarios.

/**
 *
 */
package com.raidentrance.rest;

import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonView;
import com.raidentrance.data.SourceHelper;
import com.raidentrance.model.User;
import com.raidentrance.model.Views;

/**
 * @author raidentrance
 *
 */
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

	private SourceHelper helper = new SourceHelper();

	private static final Logger LOG = LoggerFactory.getLogger(UserResource.class);

	@GET
	@JsonView(Views.Public.class)
	public Response getUsers() {
		LOG.info("Getting all users");
		List<User> users = helper.getUsers();
		return Response.ok(users).build();
	}

}

A continuación se explican las anotaciones utilizadas:

  • @Path(“/users”)   : Endpoint base a utilizar
  • @Produces(MediaType.APPLICATION_JSON) : Significa que el endpoint devolverá objetos tipo JSON
  • @GET :El endpoint será accedido a través del método HTTP GET
  • @JsonView(Views.Public.class) : Significa que este endpoint solo devolverá los atributos anotados con la vista Public, en este ejemplo se puede observar en la clase User.java que el atributo password no será devuelto.

Creando la clase de aplicación

La clase de aplicación es la más importante en Dropwizard ya que será la responsable de iniciar la aplicación y configurar los web services utilizados.

/**
 *
 */
package com.raidentrance;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.raidentrance.config.DropwizardConfiguration;
import com.raidentrance.rest.UserResource;

import io.dropwizard.Application;
import io.dropwizard.setup.Environment;

/**
 * @author raidentrance
 *
 */
public class DropwizardApplication extends Application<DropwizardConfiguration> {

	private static final Logger LOG = LoggerFactory.getLogger(DropwizardApplication.class);

	@Override
	public void run(DropwizardConfiguration configuration, Environment environment) throws Exception {
		LOG.info("Starting app {}", configuration.getAppName());
		LOG.info("Registering resources");
		final UserResource resource = new UserResource();
		environment.jersey().register(resource);
		LOG.info("Resources registered");
	}

	public static void main(String[] args) throws Exception {
		new DropwizardApplication().run(args);
	}
}

Como se puede observar la clase aplicación recibe como genérico el nombre de la clase de configuración ya que con esta configurará la aplicación para poder iniciarla de forma correcta.

La línea LOG.info(“Starting app {}”, configuration.getAppName()); imprimirá el valor que definimos en el archivo yml en la propiedad appName, de este modo es posible definir configuraciones propias.

Otro punto importante a mencionar es que es en esta clase donde se registran los web services que va a utilizar la aplicación, para hacerlo se usan las siguientes líneas:

final UserResource resource = new UserResource(); environment.jersey().register(resource);

Ejecutando la aplicación

Para ejecutar la aplicación se debe compilar con maven a través del comando mvn clean install acceder al folder target y ejecutarlo como se muestra:

java -jar dropwizard-example-0.0.1-SNAPSHOT.jar server ../src/main/resources/application.yml 

Donde :

  • dropwizard-example-0.0.1-SNAPSHOT.jar : jar que contiene la aplicación y el server embebido.
  • server: Ejecuta la aplicación como un HTTP server.
  • ../src/main/resources/application.yml:Archivo yml de configuración, como se puede observar si en un futuro se quiere cambiar es posible arrancar la aplicación utilizando un yml diferente utilizando el la misma aplicación sin necesidad de recompilarla.

Una vez que se ejecuta la aplicación la salida será la siguiente:

INFO  [2016-09-27 18:59:04,533] org.eclipse.jetty.util.log: Logging initialized @1003ms
INFO  [2016-09-27 18:59:04,594] io.dropwizard.server.DefaultServerFactory: Registering jersey handler with root path prefix: /
INFO  [2016-09-27 18:59:04,594] io.dropwizard.server.DefaultServerFactory: Registering admin handler with root path prefix: /
INFO  [2016-09-27 18:59:04,594] com.raidentrance.DropwizardApplication: Starting app raidentrance
INFO  [2016-09-27 18:59:04,594] com.raidentrance.DropwizardApplication: Registering resources
INFO  [2016-09-27 18:59:04,597] com.raidentrance.DropwizardApplication: Resources registered
INFO  [2016-09-27 18:59:04,597] io.dropwizard.server.DefaultServerFactory: Registering jersey handler with root path prefix: /
INFO  [2016-09-27 18:59:04,597] io.dropwizard.server.DefaultServerFactory: Registering admin handler with root path prefix: /
INFO  [2016-09-27 18:59:04,598] io.dropwizard.server.ServerFactory: Starting DropwizardApplication
INFO  [2016-09-27 18:59:04,687] org.eclipse.jetty.setuid.SetUIDListener: Opened application@7555b920{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO  [2016-09-27 18:59:04,687] org.eclipse.jetty.setuid.SetUIDListener: Opened admin@4152d38d{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
INFO  [2016-09-27 18:59:04,689] org.eclipse.jetty.server.Server: jetty-9.3.9.v20160517
INFO  [2016-09-27 18:59:05,104] io.dropwizard.jersey.DropwizardResourceConfig: The following paths were found for the configured resources:

    GET     /users (com.raidentrance.rest.UserResource)

INFO  [2016-09-27 18:59:05,106] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@1cb7936c{/,null,AVAILABLE}
INFO  [2016-09-27 18:59:05,111] io.dropwizard.setup.AdminEnvironment: tasks = 

    POST    /tasks/log-level (io.dropwizard.servlets.tasks.LogConfigurationTask)
    POST    /tasks/gc (io.dropwizard.servlets.tasks.GarbageCollectionTask)

WARN  [2016-09-27 18:59:05,111] io.dropwizard.setup.AdminEnvironment:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!    THIS APPLICATION HAS NO HEALTHCHECKS. THIS MEANS YOU WILL NEVER KNOW      !
!     IF IT DIES IN PRODUCTION, WHICH MEANS YOU WILL NEVER KNOW IF YOU'RE      !
!    LETTING YOUR USERS DOWN. YOU SHOULD ADD A HEALTHCHECK FOR EACH OF YOUR    !
!         APPLICATION'S DEPENDENCIES WHICH FULLY (BUT LIGHTLY) TESTS IT.       !
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
INFO  [2016-09-27 18:59:05,116] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@19058533{/,null,AVAILABLE}
INFO  [2016-09-27 18:59:05,125] org.eclipse.jetty.server.AbstractConnector: Started application@7555b920{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO  [2016-09-27 18:59:05,126] org.eclipse.jetty.server.AbstractConnector: Started admin@4152d38d{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
INFO  [2016-09-27 18:59:05,126] org.eclipse.jetty.server.Server: Started @1597ms
INFO  [2016-09-27 18:59:13,735] com.raidentrance.rest.UserResource: Getting all users
0:0:0:0:0:0:0:1 - - [27/sep/2016:18:59:13 +0000] "GET /users HTTP/1.1" 200 183 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36" 70
INFO  [2016-09-27 18:59:46,577] com.raidentrance.rest.UserResource: Getting all users
0:0:0:0:0:0:0:1 - - [27/sep/2016:18:59:46 +0000] "GET /users HTTP/1.1" 200 183 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36" 2

Para ejecutar la petición se debe hacer uso de un cliente http a http://localhost:8080/users via GET y se obtendrá como resultado lo siguiente:

[
{
username: "raidentrance",
role: {
name: "ADMIN",
description: "Administrator of the system"
}
},
{
username: "admin",
role: {
name: "USER",
description: "Normal user in the system"
}
}
]

Puedes encontrar el código completo del ejemplo en el siguiente enlace:

https://github.com/raidentrance/dropwizard-example

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring Boot + REST Jersey (Agregando Spring Security 4) Parte 4


En el último post se explicó paso a paso la configuración de HATEOAS utilizando Spring boot Spring Boot + REST Jersey (Agregando Spring Hateoas y Dto) Parte 3, se tomará ese proyecto como base. Ahora se explicará de forma simple la configuración de Spring security, para este ejemplo se utilizará Basic authentication.

1. Configuración, Spring boot generó starter dependencies, estas dependencias contienen todos los recursos necesarios para utilizar el módulo de spring deseado de una forma simple y manejable de una forma más simple, las versiones de estas dependencias no son requeridas ya que se heredan del proyecto padre de spring boot. Para configurar Spring Security se requiere agregar la siguiente dependencia:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.Modificando nuestro repositorio, En el post Spring Boot + REST Jersey (Agregando Spring Hateoas y Dto) Parte 3 se crearon los repositorios UserRepository y RoleRepository. En este ejemplo se agregará un método a UserRepository que realizará una búsqueda de usuario por username.

/**
 *
 */
package com.raidentrance.repositories;

import org.springframework.data.repository.CrudRepository;
import com.raidentrance.entities.User;

/**
 * @author raidentrance
 *
 */
public interface UserRepository extends CrudRepository<User, Integer> {
	User findByUsername(String username);
}

No se debe realizar ninguna implementación de findByUsername, Spring data detectará en base a convención que se desea buscar un objeto Usuario a través del atributo username y realizará la implementación.

3. Agregando AuthenticatorService, AuthenticatorService será el responsable de realizar la autenticación en la aplicación, de tal modo que la lógica de esta clase será buscar al usuario en la base de datos así como su rol y devolverlo, la comparación entre el usuario y la contraseña enviada con lo que este autenticator devuelve será realizada por Spring.

/**
 *
 */
package com.raidentrance.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.raidentrance.entities.User;
import com.raidentrance.repositories.UserRepository;

/**
 * @author raidentrance
 *
 */
@Service
public class AuthenticatorService implements UserDetailsService {
	@Autowired
	private UserRepository userRepository;

	private static final Logger LOG = LoggerFactory.getLogger(AuthenticatorService.class);

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		LOG.info("Trying to authenticate to {}", username);
		User user = userRepository.findByUsername(username);
		if (user == null) {
			throw new UsernameNotFoundException("Username " + username + " not found");
		} else {
			Collection<? extends GrantedAuthority> authorities = getGrantedAuthorities(user);
			return new org.springframework.security.core.userdetails.User(username, user.getPassword(), authorities);
		}
	}

	private Collection<? extends GrantedAuthority> getGrantedAuthorities(User user) {
		List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
		list.add(new GrantedAuthority() {
			private static final long serialVersionUID = 2409931876244987359L;
			@Override
			public String getAuthority() {
				return user.getRole().getName();
			}
		});
		return list;
	}
}

4. Configurando Spring security,el siguiente paso es configurar los endpoints que serán protegidos así como el mecanismo de autenticación a utilizar.

/**
 *
 */
package com.raidentrance.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import com.raidentrance.service.AuthenticatorService;

/**
 * @author raidentrance
 *
 */
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
	@Autowired
	private AuthenticatorService authenticatorService;

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(authenticatorService);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.httpBasic().and().authorizeRequests().anyRequest().authenticated();
	}
}

El método configureGlobal es utilizado para definir quién va a realizar la búsqueda de los usuarios, para este ejemplo se utilizará  el AuthenticatorService creado previamente.

El método configure es utilizado para definir las url a las que se realizará la autenticación y que mecanismo se utilizará. En este caso al ser servicios web se realiza Basic authentication pero no es el único.

Enpoint:  http://localhost:8080/users

Header: Authorization Basic cmFpZGVudHJhbmNlOnN1cGVyU2VjcmV0

El valor header Authorization está compuesto del siguiente modo

Authorization Basic   : Nombre del header y tipo de autenticación.

cmFpZGVudHJhbmNlOnN1cGVyU2VjcmV0 : Valor, está compuesto del siguiente modo.

raidentrance:superSecret En base 64 de tal modo que :

raidentrance=username

superSecret=password

Probando utilizando CURL:

curl http://localhost:8080/users -XGET --user raidentrance:superSecret

Probando utilizando Postman

captura-de-pantalla-2016-09-08-a-las-10-14-35-a-m

captura-de-pantalla-2016-09-08-a-las-10-14-58-a-m

Puedes encontrar el código completo del ejemplo en el siguiente enlace:

https://github.com/raidentrance/spring-boot-example/tree/part4-adding-security

 

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com