Consuming RESTful web services in Java with Jersey and FailSafe


One of the most common tasks is to consume RESTful web services in Java, in this post I will explain a design pattern using Jersey client.

Step 1 : Configuration

The project will include the dependencies described in the following pom.xml, as you can see there we have the following support:

  • Jackson : For serialization and deserialization
  • slf4j : For logging
  • failsafe: Api for retry logic

And at the end we can see we have the maven-compiler-plugin to define the Java version we are using.

Step 2 : Creating an abstract client

The next step will be to create an AbstractClient, you can use it in any rest client you want, lets see and analyze the code:


import java.io.IOException;

import java.util.Optional;

import java.util.concurrent.TimeUnit;

import java.util.logging.Logger;

import javax.ws.rs.client.Client;

import javax.ws.rs.client.ClientBuilder;

import javax.ws.rs.client.Entity;

import javax.ws.rs.client.WebTarget;

import javax.ws.rs.core.Response;

import javax.ws.rs.core.Response.Status;

import com.fasterxml.jackson.core.JsonParseException;

import com.fasterxml.jackson.core.type.TypeReference;

import com.fasterxml.jackson.databind.JsonMappingException;

import net.jodah.failsafe.Failsafe;

import net.jodah.failsafe.RetryPolicy;

/**

 * @author raidentrance

 *

 */

public abstract class AbstractClient {

	private String url;

	private String contextPath;

	private RetryPolicy defaultRetryPolicy;

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

	public AbstractClient(String url, String contextPath) {

		this.url = url;

		this.contextPath = contextPath;

	}

	public AbstractClient(String url, String contextPath, int maxRetries, int delay, TimeUnit unit) {

		this.url = url;

		this.contextPath = contextPath;

		defaultRetryPolicy = new RetryPolicy().retryIf((Response resp) -> {

			if (resp != null) {

				return resp.getStatusInfo().getFamily() == Status.Family.SERVER_ERROR;

			} else {

				return false;

			}

		}).withDelay(delay, unit).withMaxRetries(maxRetries);

	}

	protected WebTarget createClient(String path) {

		String assembledPath = assembleEndpoint(path);

		Client client = ClientBuilder.newClient();

		return client.target(assembledPath);

	}

	private String assembleEndpoint(String path) {

		String endpoint = url.concat(contextPath).concat(path);

		log.info(String.format("Calling endpoint %s", endpoint));

		return endpoint;

	}

	/**

	 * Execute a GET http request over the endpoint received with the content

	 * type specified

	 *

	 * @param endpoint

	 *            Defines the endpoint that will be executed

	 * @param type

	 *            Defines the content type it can be JSON,XML, etc

	 * @return A response object with the entity response and the Http status

	 */

	protected Response get(String endpoint, String type) {

		WebTarget client = createClient(endpoint);

		Optional result = getDefaultRetryPolicy();

		if (result.isPresent()) {

			return Failsafe.with(result.get()).get((response) -> client.request(type).get());

		} else {

			return client.request(type).get();

		}

	}

	/**

	 * Execute a PUT request over the endpoint received with the content type

	 * specified and sending the object received in the body

	 *

	 * @param endpoint

	 *            Endpoint will be executed

	 * @param type

	 *            Defines the content type, it can be JSON, XML, etc

	 * @param entity

	 *            Object will be sent in the body of the request

	 * @return A response object with the entity response and the Http status

	 */

	protected Response put(String endpoint, String type, Object entity) {

		WebTarget client = createClient(endpoint);

		Optional result = getDefaultRetryPolicy();

		if (result.isPresent()) {

			return Failsafe.with(result.get()).get((response) -> client.request(type).put(Entity.entity(entity, type)));

		} else {

			return client.request(type).put(Entity.entity(entity, type));

		}

	}

	/**

	 * Execute a POST request over the endpoint received with the content type

	 * specified and sending the object received in the body

	 *

	 * @param endpoint

	 *            Endpoint will be executed

	 * @param type

	 *            Defines the content type, it can be JSON, XML, etc

	 * @param entity

	 *            Object will be sent in the body of the request

	 * @return A response object with the entity response and the Http status

	 */

	protected Response post(String endpoint, String type, Object entity) {

		WebTarget client = createClient(endpoint);

		Optional result = getDefaultRetryPolicy();

		if (result.isPresent()) {

			return Failsafe.with(result.get())

					.get((response) -> client.request(type).post(Entity.entity(entity, type)));

		} else {

			return client.request(type).put(Entity.entity(entity, type));

		}

	}

	/**

	 * If there is a default retry policy it will be returned

	 *

	 * @return

	 */

	public Optional getDefaultRetryPolicy() {

		return (defaultRetryPolicy != null) ? Optional.of(defaultRetryPolicy) : Optional.empty();

	}

	/**

	 * Modify the current default retry policy

	 *

	 * @param maxRetries

	 *            Number of times that it will retry until a successful request

	 * @param delay

	 *            The time that will wait until the next attempt

	 * @param unit

	 *            Unit of the time of the delay, it can be in seconds, minutes,

	 *            etc.

	 */

	public void setDefaultRetryPolicy(int maxRetries, int delay, TimeUnit unit) {

		defaultRetryPolicy = new RetryPolicy()

				.retryIf((Response resp) -> resp.getStatusInfo().getFamily() == Status.Family.SERVER_ERROR)

				.withDelay(delay, unit).withMaxRetries(maxRetries);

	}

	/**

	 * Get a Response and Parse to type T. if the response is not 200 throw

	 * Exception

	 *

	 * @param response

	 *            the HTTP response

	 * @param entityType

	 *            is a generic type that the function will return.

	 * @param

	 *            The Generic Type that the method is returing

	 * @return T

	 * @throws Exception

	 *             if a problem occurs

	 * @throws IOException

	 * @throws JsonMappingException

	 * @throws JsonParseException

	 */

	protected abstract  T parseResponse(Response response,TypeReference entityType) throws Exception;

}

Now lets analyze the class:

  • Constructors: There are two ways to construct our object
    • Only with the url and the context path
    • Including also the maxRetries, delay and TimeUnit
  • createClient(String path) : Will create a Jersey client used to consume the api’s.

  • assembleEndpoint(String path) : Based on the Endpoint will build the url we are going to consume.

  • Response get(String endpoint, String type) : Generic get method that receives two parameters an endpoint and a type.

  • Response put(String endpoint, String type, Object entity): Generic put method that receives two parameters an endpoint and a type.

  • Response post(String endpoint, String type, Object entity): Generic post method that receives two parameters an endpoint and a type.

  • Optional getDefaultRetryPolicy() : if the parameters maxRetries, delay and TimeUnit were assigned it will return the retry policy to use.

  • setDefaultRetryPolicy(int maxRetries, int delay, TimeUnit unit): You can use it if you want to change the default policy .

  • abstract T parseResponse(Response response,TypeReference entityType) throws Exception: Your implementation has to override the parseResponse method to define how it will translate the response.

Step 3 : Define a class to set all the endpoints

Once we have our abstract class, we have to define an endpoints class, in our case it will be ApplicationEndpoints, it will centralize all the endpoints used in the client.


/**

 * @author raidentrance

 *

 */

public class ApplicationEndpoints {

	private static final String TICKER = "/ticker";

	private ApplicationEndpoints() {

	}

	public static String getTickers() {

		return TICKER;

	}

	public static String getTickerByBook(String book) {

		return TICKER.concat("?book=").concat(book);

	}

}

In this case we are using the api of bitso to get prices of crypto currencies, you can see the documentation here and query the api in the url https://api.bitso.com/v3/ticker/

Step 4 : Defining the error model

Now we have to handle errors, to do it we will analyze the api we are querying, lets see the error model:


{

    "success": false,

    "error": {

        "code": "0301",

        "message": "Unknown OrderBook xrp_mxnq"

    }

}

Now we have to translate it to java classes.

ErrorCode.java


import java.io.Serializable;

/**

 * @author raidentrance

 *

 */

public class ErrorCode implements Serializable {

	private String code;

	private String message;

	private static final long serialVersionUID = 1735206115257033120L;

	public ErrorCode() {

	}

	public ErrorCode(String code, String message) {

		super();

		this.code = code;

		this.message = message;

	}

	public String getCode() {

		return code;

	}

	public void setCode(String code) {

		this.code = code;

	}

	public String getMessage() {

		return message;

	}

	public void setMessage(String message) {

		this.message = message;

	}

}

ErrorMessage.java


import java.io.Serializable;

/**

 * @author raidentrance

 *

 */

public class ErrorMessage implements Serializable {

	private boolean success;

	private ErrorCode error;

	private static final long serialVersionUID = -8921696489057035324L;

	public ErrorMessage() {

	}

	public ErrorMessage(boolean success, ErrorCode error) {

		this.success = success;

		this.error = error;

	}

	public boolean isSuccess() {

		return success;

	}

	public void setSuccess(boolean success) {

		this.success = success;

	}

	public ErrorCode getError() {

		return error;

	}

	public void setError(ErrorCode error) {

		this.error = error;

	}

}

Once we have both classes we are able to deserialize the errors to java objects, now we just need to create an exception to propagate the errors.


import com.raidentrance.rest.error.model.ErrorMessage;

/**

 * @author raidentrance

 *

 */

public class ServiceException extends Exception {

	private ErrorMessage errorMessage;

	private static final long serialVersionUID = -7898115956660992515L;

	public ServiceException(ErrorMessage errorMessage) {

		this.errorMessage = errorMessage;

	}

	public ErrorMessage getErrorMessage() {

		return errorMessage;

	}

}

The ServiceException will be thrown when an error happens and it will contain the error message we receive from the api.

Step 5 : Creating the model

We defined the model for the errors, but now we have to define the model for our api’s, in this case we will be reading tickers with the following structure:

Payload.java


import com.fasterxml.jackson.annotation.JsonProperty;

/**

 * @author raidentrance

 *

 */

public class Payload {

	@JsonProperty("high")

	private String high;

	@JsonProperty("last")

	private String last;

	@JsonProperty("created_at")

	private String createdAt;

	@JsonProperty("book")

	private String book;

	@JsonProperty("volume")

	private String volume;

	@JsonProperty("vwap")

	private String vwap;

	@JsonProperty("low")

	private String low;

	@JsonProperty("ask")

	private String ask;

	@JsonProperty("bid")

	private String bid;

	public String getHigh() {

		return high;

	}

	public void setHigh(String high) {

		this.high = high;

	}

	public String getLast() {

		return last;

	}

	public void setLast(String 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 String getVolume() {

		return volume;

	}

	public void setVolume(String volume) {

		this.volume = volume;

	}

	public String getVwap() {

		return vwap;

	}

	public void setVwap(String vwap) {

		this.vwap = vwap;

	}

	public String getLow() {

		return low;

	}

	public void setLow(String low) {

		this.low = low;

	}

	public String getAsk() {

		return ask;

	}

	public void setAsk(String ask) {

		this.ask = ask;

	}

	public String getBid() {

		return bid;

	}

	public void setBid(String 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 + "]";

	}

}

Ticker.java


import com.fasterxml.jackson.annotation.JsonProperty;

/**

 * @author raidentrance

 *

 */

public class Ticker {

	@JsonProperty("success")

	private boolean success;

	@JsonProperty("payload")

	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 "Ticker [success=" + success + ", payload=" + payload + "]";

	}

}

TickerList.java


import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;

/**

 * @author maagapi

 *

 */

public class TickerList {

	@JsonProperty("success")

	private boolean success;

	@JsonProperty("payload")

	private List payload;

	public boolean isSuccess() {

		return success;

	}

	public void setSuccess(boolean success) {

		this.success = success;

	}

	public List getPayload() {

		return payload;

	}

	public void setPayload(List payload) {

		this.payload = payload;

	}

	@Override

	public String toString() {

		return "TickerList [success=" + success + ", payload=" + payload + "]";

	}

}

Once we defined the model, we can call the api’s.

Step 6 : Creating the RestClient

The last step will be create the RestClient this class will be the responsible to join all the pieces:


import java.io.IOException;

import java.io.StringReader;

import java.util.concurrent.TimeUnit;

import javax.ws.rs.core.MediaType;

import javax.ws.rs.core.Response;

import javax.ws.rs.core.Response.Status;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.type.TypeReference;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.raidentrance.rest.commons.AbstractClient;

import com.raidentrance.rest.endpoints.ApplicationEndpoints;

import com.raidentrance.rest.error.exception.ServiceException;

import com.raidentrance.rest.error.model.ErrorCode;

import com.raidentrance.rest.error.model.ErrorMessage;

import com.raidentrance.rest.model.Ticker;

import com.raidentrance.rest.model.TickerList;

/**

 * @author raidentrance

 *

 */

public class RestClient extends AbstractClient {

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

	public RestClient(String url, String contextPath) {

		super(url, contextPath);

	}

	public RestClient(String url, String contextPath, int maxRetries, int delay, TimeUnit unit) {

		super(url, contextPath, maxRetries, delay, unit);

	}

	public TickerList getTickers() throws Exception {

		return parseResponse(get(ApplicationEndpoints.getTickers(), MediaType.APPLICATION_JSON),

				new TypeReference() {

				});

	}

	public Ticker getTickerByBook(String book) throws Exception {

		return parseResponse(get(ApplicationEndpoints.getTickerByBook(book), MediaType.APPLICATION_JSON),

				new TypeReference() {

				});

	}

	@Override

	protected  T parseResponse(Response response, TypeReference entityType) throws ServiceException {

		int status = response.getStatus();

		log.info("Status {}", status);

		if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) {

			try {

				return new ObjectMapper().readValue(new StringReader(response.readEntity(String.class)), entityType);

			} catch (IOException e) {

				throw new ServiceException(

						new ErrorMessage(false, new ErrorCode("1000", "Request couldn't be processed")));

			}

		} else {

			throw new ServiceException(response.readEntity(ErrorMessage.class));

		}

	}

}

As you can see now just with one line of code we make an http request, handle the retries and parse the response.

Step 7 : Test it

In other posts we will show how to write unit tests for this kind of components, now lets just create a main method to test it:


public static void main(String[] args) throws Exception {

		RestClient client = new RestClient("https://api.bitso.com", "/v3", 3, 3, TimeUnit.SECONDS);

		TickerList tickers = client.getTickers();

		log.info("Getting tickers ");

		for (Payload payload : tickers.getPayload()) {

			log.info(payload.toString());

		}

		log.info("Getting ripple ticker");

		Ticker ripple = client.getTickerByBook("xrp_mxn");

		log.info(ripple.toString());

		log.info("Not existing ticker");

		Ticker alex = client.getTickerByBook("alex");

		log.info(alex.toString());

	}

If we execute the code we will see the following output (It can be different according with the ripple price :P):


mar 12, 2018 5:09:02 PM com.raidentrance.rest.commons.AbstractClient assembleEndpoint

INFORMACIÓN: Calling endpoint https://api.bitso.com/v3/ticker

[main] INFO com.raidentrance.rest.RestClient - Status 200

[main] INFO com.raidentrance.rest.RestClient - Getting tickers

[main] INFO com.raidentrance.rest.RestClient - Payload [high=187000.00, last=174233.33, createdAt=2018-03-12T23:09:02+00:00, book=btc_mxn, volume=244.45021807, vwap=175921.68443739, low=167000.00, ask=174216.10, bid=173560.15]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=13957.02, last=13388.86, createdAt=2018-03-12T23:09:02+00:00, book=eth_mxn, volume=340.00979171, vwap=13198.66244190, low=13000.00, ask=13388.86, bid=13052.63]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=0.00008704, last=0.00008600, createdAt=2018-03-12T23:09:02+00:00, book=xrp_btc, volume=44561.33425456, vwap=0.00008500, low=0.00008312, ask=0.00008678, bid=0.00008601]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=15.75, last=14.92, createdAt=2018-03-12T23:09:02+00:00, book=xrp_mxn, volume=688462.85426546, vwap=15.12242330, low=14.75, ask=15.08, bid=14.92]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=0.07688000, last=0.07550000, createdAt=2018-03-12T23:09:02+00:00, book=eth_btc, volume=56.85901332, vwap=0.07569318, low=0.07430000, ask=0.07641999, bid=0.07551000]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=0.11879867, last=0.11421000, createdAt=2018-03-12T23:09:02+00:00, book=bch_btc, volume=34.25655963, vwap=0.11580909, low=0.11336268, ask=0.11589999, bid=0.11430000]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=0.02011990, last=0.01965100, createdAt=2018-03-12T23:09:02+00:00, book=ltc_btc, volume=151.10013187, vwap=0.01966700, low=0.01942300, ask=0.01990000, bid=0.01965010]

[main] INFO com.raidentrance.rest.RestClient - Payload [high=3700.00, last=3404.61, createdAt=2018-03-12T23:09:02+00:00, book=ltc_mxn, volume=2501.73845947, vwap=3466.53149080, low=3392.80, ask=3465.97, bid=3404.71]

[main] INFO com.raidentrance.rest.RestClient - Getting ripple ticker

mar 12, 2018 5:09:03 PM com.raidentrance.rest.commons.AbstractClient assembleEndpoint

INFORMACIÓN: Calling endpoint https://api.bitso.com/v3/ticker?book=xrp_mxn

[main] INFO com.raidentrance.rest.RestClient - Status 200

[main] INFO com.raidentrance.rest.RestClient - Ticker [success=true, payload=Payload [high=15.75, last=14.92, createdAt=2018-03-12T23:09:03+00:00, book=xrp_mxn, volume=688462.85426546, vwap=15.12242330, low=14.75, ask=15.08, bid=14.92]]

[main] INFO com.raidentrance.rest.RestClient - Not existing ticker

mar 12, 2018 5:09:03 PM com.raidentrance.rest.commons.AbstractClient assembleEndpoint

INFORMACIÓN: Calling endpoint https://api.bitso.com/v3/ticker?book=alex

[main] INFO com.raidentrance.rest.RestClient - Status 400

Exception in thread "main" com.raidentrance.rest.error.exception.ServiceException

	at com.raidentrance.rest.RestClient.parseResponse(RestClient.java:68)

	at com.raidentrance.rest.RestClient.getTickerByBook(RestClient.java:51)

	at com.raidentrance.rest.RestClient.main(RestClient.java:83)

As you can see we are testing the following cases:

  • Get the price of all the crypto currencies
  • Get the prices of one crypto currency
  • Ask for a price that doesn’t exist and throw an exception with the message.

You can find all the code of this post in the following repository https://github.com/raidentrance/rest-jersey-client.

If you get an SunCertPathBuilderException: unable to find valid certification path to requested target remember that you have to install the certificate to do https requests you can see a guide about how to do it here.

If you like our posts follow us in our social networks https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contact: raidentrance@gmail.com

Crea web services REST utilizando Vert.x


Si escuchaste en algún lugar que Vert.x es un gran framework y quieres empezar a utilizarlo para tus aplicaciones, este post es la guía perfecta para eso.

Paso 1 Configuración

Veamos el archivo pom.xml.

La configuración anterior agrega la dependencia de Vertx al proyecto y define la versión 1.8 de java como la versión a utilizar en el proyecto(Vertx solo funciona con Java 1.8 o superior).

Paso 2 Creando el modelo de la aplicación

En este ejemplo mostraremos como buscar uno, buscar todos, crear y borrar un objeto User de una lista, el primer paso será crear la clase User.


import java.io.Serializable;

/**
 * @author raidentrance
 *
 */
public class User implements Serializable {
	private String username;
	private String password;

	private static final long serialVersionUID = -8672858398542565036L;

	public User() {
	}

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

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

La clase User contendrá los atributos username y password.

Paso 3 Creando un UserService

Una vez que se creó el modelo el siguiente paso será crear una clase llamada UserService, la cuál será la responsable de ejecutar la lógica de negocio de nuestros endpoints, en este ejemplo no utilizaremos una base de datos, solo tendremos algunos usuarios en memoria, con los siguientes métodos:

  • List findAll(): Devuelve todos los usuarios en la lista
  • Optional findByUsername(String username): Devuelve a un objeto usuario basado en el username

  • void create(User user): Agrega un usuario nuevo a la lista

UserService.java


import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import com.raidentrance.model.User;

/**
 * @author raidentrance
 *
 */
public class UserService {
	private List users = new ArrayList();

	public UserService() {
		users.add(new User("raidentrance", "superSecret"));
		users.add(new User("root", "superExtraSecret"));
		users.add(new User("dummy", "notSecret"));
	}

	public List findAll() {
		return users;
	}

	public Optional findByUsername(String username) {
		for (User user : users) {
			if (user.getUsername().equals(username)) {
				return Optional.of(user);
			}
		}
		return Optional.empty();
	}

	public void create(User user) {
		users.add(user);
	}
}

Hasta este punto no hemos utilizado para nada Vertx, el siguiente paso será exponer la clase UserService como una api REST.

Paso 4 Exponiendo las apis vía REST

El siguiente paso será exponer los métodos mencionados anteriormente como api’s REST:

UserResource.java


import java.util.Optional;

import com.raidentrance.model.User;
import com.raidentrance.services.UserService;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.json.Json;
import io.vertx.ext.web.Router;

/**
 * @author raidentrance
 *
 */
public class UserResource extends AbstractVerticle {

	private UserService service = new UserService();

	@Override
	public void start(Future fut) {
		Router router = Router.router(vertx);
		router.get("/users")
				.handler(routingContext -> routingContext.response()
						.putHeader("content-type", "application/json; charset=utf-8")
						.end(Json.encodePrettily(service.findAll())));

		router.get("/users/:username").handler(routingContext -> {
			Optional result = service.findByUsername(routingContext.request().getParam("username"));
			if (result.isPresent()) {
				routingContext.response().setStatusCode(200)
						.putHeader("content-type", "application/json; charset=utf-8").end(Json.encode(result.get()));
			} else {
				routingContext.response().setStatusCode(404);
			}
		});
		router.post("/users").handler(routingContext -> {
			User user = Json.decodeValue(routingContext.getBodyAsString(), User.class);
			service.create(user);
			routingContext.response().setStatusCode(201).putHeader("content-type", "application/json; charset=utf-8")
					.end(Json.encodePrettily(user));
		});
		vertx.createHttpServer().requestHandler(router::accept).listen(config().getInteger("http.port", 8080),
				result -> {
					if (result.succeeded()) {
						fut.complete();
					} else {
						fut.fail(result.cause());
					}
				});
	}

}

Como se puede ver se creará un objeto router basado en el objeto vertx, en el cuál se agregarán las siguientes rutas:

  • router.get(/users) : Devuelve todos los objetos User en la lista

  • router.get(/users/:username): Devuelve al usuario cuyo username sea pasado como parámetro en caso de no existir alguno devuelve un status http 404.

  • router.post(/users):Add a new user to the list

Al final se creará un servidor http y se iniciará en el puerto definido.

Paso 5 Ejecutando la aplicación

El último paso será iniciar nuestra aplicación, para esto crearemos un objeto Vertx y desplegaremos el Verticle creado.


import com.raidentrance.resources.UserResource;

import io.vertx.core.Vertx;

/**
 * @author raidentrance
 *
 */
public class VertxApplication {
	private VertxApplication() {
	}

	public static void main(String[] args) {
		Vertx vertx = Vertx.vertx();
		vertx.deployVerticle(UserResource.class.getName());
	}
}

Una vez que se ejecuta la clase VertxApplication, el siguiente paso será validar las apis utilizando algún cliente HTTP accediendo a las siguientes url’s:

[
   {
      username:"raidentrance",
      password:"superSecret"
   },
   {
      username:"root",
      password:"superExtraSecret"
   },
   {
      username:"dummy",
      password:"notSecret"
   }
]
{
     username: "raidentrance",
     password: "superSecret"
}
{
     username: "raidentrance",
     password: "superSecret"
}

Puedes encontrar el código completo en el siguiente enlace https://github.com/raidentrance/vertx-example.

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

Primeros pasos con Spring boot 2


Como saben la versión de Spring boot 2 fue liberada hace poco y en Geeks México ya empezamos a hacer pruebas con esta nueva versión.

Paso 1 Configuración

El primer paso será configurar nuestro proyecto, para esto tendremos que configurar los siguientes puntos:

  • Proyecto padre: El primer paso será agregar el proyecto padre de Spring boot 2 el cual definirá todas las versiones a utilizar en las starter dependencies.
  • Agregar dependencias: Agregaremos la dependencia spring-boot-starter-web para crear un microservicio como ejemplo.
  • Configurar plugins: Se definirán 2 plugins, el primero para definir la versión de Java a utilizar (Recordemos que Spring boot 2 solo funciona con Java 8 o superior) y el segundo para generar un fat jar (Una aplicación que contiene un tomcat en si mismo).

pom.xml
Con la configuración anterior en el pom.xml los 3 puntos  mencionados quedarán configurados correctamente.

Paso 2 Creando un Controller

El siguiente paso será crear un controller, en este ejemplo se utilizará Spring mvc:


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author raidentrance
 *
 */
@RestController
public class SampleController {

	@RequestMapping("/")
	public String home() {
		return "Somos Geeks!";
	}
}

En el controller anterior devolverá la respuesta Somos Geeks! cuando se ejecute la url http://localhost:8080/.

Paso 3 Creando la clase aplicación

Como sabemos Spring boot necesita una clase que contenga un método main para iniciar la aplicación:


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

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

Al ejecutar la clase SpringBootSampleApplication se ejecutará un servidor tomcat y desplegará nuestra aplicación en el.

Paso 4 Agregar un banner a nuestra aplicación (Opcional)

En la versión anterior de Spring boot el banner era un archivo .txt que se mostraba cuando el servidor iniciaba, en la nueva versión de Spring boot 2 tenemos la posibilidad de poner un gif como banner, para hacerlo solo debemos poner un archivo con formato .gif, .txt, .jpg o .png con el nombre banner.

En nuestro ejemplo utilizaremos el siguiente gif:

banner

Paso 5 ejecutar nuestra aplicación

El siguiente paso será ejecutar nuestra aplicación, para hacerlo tenemos las siguientes opciones:

  • Ejecutar el comando mvn spring-boot:run
  • Ejecutar la clase que contiene el método main SpringBootSampleApplication
  • Ejecutar el jar generado utilizando java -jar nombre-de-la-aplicacion.jar

Al ejecutar cualquiera de las opciones anteriores veremos la siguiente salida:

                                      .#############:
                                   &##&ooooooooooo&######.
                                &##oooooooooooooooooooo####:
                              ##ooooooooooooooooooooooooooo###
                            :##oooooooooooooooooooooooooooooo8#
                           .#&ooooooooooooooooooooooooooooooooo#:
                           #&oooooooooooooooooooooooooooooooooo##:
                           #o&#.@oooooooooooooooooooooooo#@:oooo##.
                           #&& @#@ooooooooooooooooooooo@@# &#ooo###
                           #@ @###@oooooooooooooooooo&@###: @oooo##
                           #@:###:@ooooooooooooooooo&@@###@ @oooo##
                           #&###@ @ooooooooooooooooo# .###@ @oooo##
                           &.####@@oooooooooooooooo@  @###@.@oooo##
                           ##@@###@oooooooooooooooo@#####@.@ooooo#&
                          #ooo&@@#ooooooooooooooooo&#####@@:ooooo#*
                          :&#:oooooooooooooooooooooo@#@@oooooooo&#
                           *##o#oooooooooooooooooooooooooooooooo#&
                            .#o8oo#&ooooooooooooooooooo####oooo&#
                              ##o#&oo###:ooooo&&#####oo#ooooooo#
                                :#&oo#&oooooooooo8##ooooooooooooo##
                                   .##&o8#####&oooooo&##oooooooooo##
           :ooooo:  ####8ooooooooo:##oo#####&&8&#####:.#oooooooooo###
        ooooooo***:ooooooooooooooo#ooooooooooooooo#....ooooooooooo#&&#
     .**oooooo:***:o&########8&o##&ooooooooooooo&&.....ooooooooooo#oo&#
    ****oooo*****ooo           ##oooooooooooo#oo#......oooooooooo#&#oo#.
    ***oooo*****:oo            :ooooooooooo##&o&......#ooooooooo#:..#o##
     **:o***** 😮              .##oooo&###:oooo#.......&oooo##&......#&###
   *********..:                ###&&&ooooo##&oo#..........#&.........#o#:o#&
   ****** *                   &#oooooooooooo#8&:.....................:o#&oo#:
  ***:oo                      #&ooooooooooooo##:.....................:&#ooo8#
  *.                          #ooooooooooooooo#:.....................:#&oooo#&
                              #&oooooooooooooo&#.....................##ooooo##
                              ##oooooooooooooo&#....................##oooooo##
                              :#oooooooooooooo##....................##oooooo##
                                ##oooooooooooo#...................##ooooooo###
                                 ##oo:ooooo#8#..................&#:ooooooo###
                                 ##o#o###&#&##...............:##ooooo&&8###.
                                 ##&ooooooooo#  :##########&.  ooooooo#
                                  ##&&oooo&##                  #o### .#* &#
                                                               :#&  ####
                                                                 &###

2018-03-07 15:36:40.630  INFO 43046 --- [           main] c.r.SpringBootSampleApplication          : Starting SpringBootSampleApplication on m-C02RV1WXG8WP.lan with PID 43046 (/Users/maagapi/Documents/Github/spring-boot2/target/classes started by maagapi in /Users/maagapi/Documents/Github/spring-boot2)
2018-03-07 15:36:40.632  INFO 43046 --- [           main] c.r.SpringBootSampleApplication          : No active profile set, falling back to default profiles: default
2018-03-07 15:36:40.664  INFO 43046 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@4842439e: startup date [Wed Mar 07 15:36:40 CST 2018]; root of context hierarchy
2018-03-07 15:36:41.381  INFO 43046 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2018-03-07 15:36:41.403  INFO 43046 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-03-07 15:36:41.403  INFO 43046 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.28
2018-03-07 15:36:41.413  INFO 43046 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/maagapi/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]
2018-03-07 15:36:41.481  INFO 43046 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-03-07 15:36:41.481  INFO 43046 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 820 ms
2018-03-07 15:36:41.587  INFO 43046 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2018-03-07 15:36:41.590  INFO 43046 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-03-07 15:36:41.590  INFO 43046 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-03-07 15:36:41.591  INFO 43046 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-03-07 15:36:41.591  INFO 43046 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-03-07 15:36:41.833  INFO 43046 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@4842439e: startup date [Wed Mar 07 15:36:40 CST 2018]; root of context hierarchy
2018-03-07 15:36:41.892  INFO 43046 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String com.raidentrance.controller.SampleController.home()
2018-03-07 15:36:41.897  INFO 43046 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-03-07 15:36:41.898  INFO 43046 --- [           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.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-03-07 15:36:41.926  INFO 43046 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-07 15:36:41.926  INFO 43046 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-07 15:36:41.954  INFO 43046 --- [           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-07 15:36:42.055  INFO 43046 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-03-07 15:36:42.096  INFO 43046 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-03-07 15:36:42.100  INFO 43046 --- [           main] c.r.SpringBootSampleApplication          : Started SpringBootSampleApplication in 4.479 seconds (JVM running for 6.89)

Una vez que el servidor se inició correctamente podemos ejecutar nuestro endpoint accediendo a la url http://localhost:8080/ , mostrando la siguiente salida:

Somos Geeks!

Puedes encontrar el código completo en el siguiente enlace https://github.com/raidentrance/spring-boot2/tree/master/src/main.

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

Implementa búsquedas sobre Grafos utilizando BFS (Breadth First Search) con Java


BFS (Breadth first search) es un algoritmo para la búsqueda sobre grafos por amplitud, en este post se explicará como implementarlo con Java, se tomará como base el post Implementa un grafo de ciudades en Java y también te puede interesar el post Implementa búsquedas sobre Grafos utilizando DFS (Depth First Search) con Java.

Creando las clases base para la construcción del grafo

El grafo que se creará contendrá información de ciudades y la distancia entre ellas, para hacerlo necesitaremos 3 clases Node, Edge y Graph. A continuación se mostrará cada una de ellas con su explicación:

Node.java


import java.util.ArrayList;

import java.util.List;

/**

 * @author raidentrance

 *

 */

public class Node {

	private String city;

	private List adjacents = new ArrayList();

	public Node(String city) {

		this.city = city;

	}

	public void addEdge(Edge edge) {

		adjacents.add(edge);

	}

	public List getAdjacents() {

		return adjacents;

	}

	public String getCity() {

		return city;

	}

	@Override

	public String toString() {

		return "Node [city=" + city + ", adjacents=" + adjacents + "]";

	}

}

La clase Node representará una ciudad en nuestro grafo, como se puede ver la ciudad contiene una lista de Edges los cuáles representarán las uniones entre los nodos, más adelante se darán más detalles sobre la clase Edge.

Edge.java


/**

 * @author raidentrance

 *

 */

public class Edge {

	private Node origin;

	private Node destination;

	private double distance;

	public Edge(Node origin, Node destination, double distance) {

		this.origin = origin;

		this.destination = destination;

		this.distance = distance;

	}

	public Node getOrigin() {

		return origin;

	}

	public void setOrigin(Node origin) {

		this.origin = origin;

	}

	public Node getDestination() {

		return destination;

	}

	public void setDestination(Node destination) {

		this.destination = destination;

	}

	public double getDistance() {

		return distance;

	}

	public void setDistance(double distance) {

		this.distance = distance;

	}

	@Override

	public String toString() {

		return "Edge [origin=" + origin.getCity() + ", destination=" + destination.getCity() + ", distance=" + distance

				+ "]";

	}

}

La clase Edge representa la unión entre dos nodos, como se puede ver contiene un nodo origen, uno destino y la distancia entre ambos.

Graph.java


import java.util.ArrayList;

import java.util.List;

/**

 * @author raidentrance

 *

 */

public class Graph {

	private List nodes = new ArrayList();

	public void addNode(Node node) {

		nodes.add(node);

	}

	public List getNodes() {

		return nodes;

	}

	@Override

	public String toString() {

		return "Graph [nodes=" + nodes + "]";

	}

}

La clase Graph representará el conjunto de las ciudades junto con sus uniones y direcciones.

Creando el grafo

Una vez que contamos con las clases necesarias para crear nuestro grafo el siguiente paso será llenarlo con la información de las ciudades que utilizaremos, se utilizará el mismo grafo que en el ejemplo de DFS:

cities-graph

Analicemos el grafo:

  • Es posible ir del df a Toluca y a cuernavaca
  • Es posible ir de Toluca a Tlaxcala, Puebla, DF y Cuernavaca
  • Es posible ir de Cuernavaca al DF y a Puebla
  • Es posible ir de Puebla a Toluca y Tlaxcala
  • En este ejemplo solo podemos llegar a Tlaxcala pero no podemos ir a ningún otro lugar
  • En este ejemplo existe la ciudad de Cancún pero no hay una ruta hacia ella.

Veamos el código para construirlo:

MapBuilder.java


import com.raidentrance.graphs.Edge;

import com.raidentrance.graphs.Graph;

import com.raidentrance.graphs.Node;

/**

 * @author raidentrance

 *

 */

public class MapBuilder {

	private static final Graph instance = new Graph();

	private MapBuilder() {

	}

	public static Graph getGraph() {

		Node df = new Node("DF");

		Node toluca = new Node("Toluca");

		Node cuernavaca = new Node("Cuernavaca");

		Node puebla = new Node("Puebla");

		Node tlaxcala = new Node("Tlaxcala");

		Node cancun = new Node("Cancún");

		df.addEdge(new Edge(df, toluca, 100));

		df.addEdge(new Edge(df, cuernavaca, 90));

		toluca.addEdge(new Edge(toluca, puebla, 350));

		toluca.addEdge(new Edge(toluca, cuernavaca, 150));

		toluca.addEdge(new Edge(toluca, tlaxcala, 340));

		toluca.addEdge(new Edge(toluca, df, 100));

		cuernavaca.addEdge(new Edge(cuernavaca, df, 90));

		cuernavaca.addEdge(new Edge(cuernavaca, puebla, 100));

		puebla.addEdge(new Edge(puebla, tlaxcala, 20));

		puebla.addEdge(new Edge(puebla, toluca, 350));

		instance.addNode(df);

		instance.addNode(toluca);

		instance.addNode(cuernavaca);

		instance.addNode(puebla);

		instance.addNode(cancun);

		instance.addNode(tlaxcala);

		return instance;

	}

}

La clase MapBuilder tiene un método que devuelve un objeto de tipo Graph que representa el grafo definido en la imagen anterior.

Implementando BFS sobre el grafo

El siguiente paso será implementar el algoritmo BFS para búsqueda de ciudades en el grafo que definimos, para esto crearemos la clase GraphBfsSearch.

GraphBfsSearch.java


import java.util.HashSet;

import java.util.LinkedList;

import java.util.List;

import java.util.Optional;

import com.raidentrance.graphs.Edge;

import com.raidentrance.graphs.Graph;

import com.raidentrance.graphs.Node;

import com.raidentrance.map.MapBuilder;

/**

 * @author raidentrance

 *

 */

public class GraphBfsSearch {

	private Graph graph;

	public GraphBfsSearch() {

		graph = MapBuilder.getGraph();

	}

	private Optional<Node> getNode(String city) {

		List<Node> nodes = graph.getNodes();

		for (Node node : nodes) {

			if (node.getCity().equals(city)) {

				return Optional.of(node);

			}

		}

		return Optional.empty();

	}

	public boolean hasPathBfs(String source, String destination) {

		Optional<Node> start = getNode(source);

		Optional<Node> end = getNode(destination);

		if (start.isPresent() && end.isPresent()) {

			return hasPathBfs(start.get(), end.get());

		} else {

			return false;

		}

	}

	public boolean hasPathBfs(Node source, Node destination) {

		LinkedList<Node> nextToVisit = new LinkedList<>();

		HashSet<String> visited = new HashSet<>();

		nextToVisit.add(source);

		while (!nextToVisit.isEmpty()) {

			Node node = nextToVisit.remove();

			if (node.getCity().equals(destination.getCity())) {

				return true;

			}

			if (visited.contains(node.getCity())) {

				continue;

			}

			visited.add(node.getCity());

			for (Edge edge : node.getAdjacents()) {

				nextToVisit.add(edge.getDestination());

			}

		}

		return false;

	}

}

Analicemos cada uno de los componentes:

  • El atributo graph contiene el grafo a utilizar.
  • El constructor inicializa la variable graph con el grafo definido en la imágen anterior.
  • El método OptionalgetNode(String city): Recibe como parámetro una ciudad y devuelve el Nodo en el grafo en caso de existir que contenga el nombre de la ciudad.
  • El método boolean hasPathBfs(String source, String destination): Recibe dos ciudades, el origen y el destino, con esto ejecuta el método de búsqueda BFS para determinar si existe la ruta en el grafo.
  • El método booleanhasPathBfs(Node source, Node destination): Recibe un nodo origen y uno nodo destino siguiendo la siguiente lógica:
    • Se agrega el nodo origen a los nodos por visitar
    • Se ejecuta la siguiente lógica mientras existan ciudades por visitar
      • Remueve y devuelve el primer nodo por visitar
      • Si ese nodo es igual al destino devolvemos que si existe una ruta por visitar y termina el proceso
      • Valida si la ciudad que se está buscando ya fue visitada, en ese caso continua con la siguiente iteración
      • Agrega la ciudad que se validó a la lista de nodos visitados
      • itera todos los nodos adjacentes para agregarlos a las ciudades por visitar
    • En caso de no encontrar una ruta devuelve false.

Caso de ejemplo

Analicemos el método boolean hasPathBfs(Node source, Node destination) en el caso yendo de Puebla a Cuernavaca.

Inicio :Source =Puebla, Destination=Cuernavaca,

  • Se Agrega a nextToVisit la ciudad de puebla
  • la variable nextToVisit contiene a la ciudad de puebla, mientras no esté vacía se ejecutarán las siguientes iteraciones:

Iteración 1:  Visited={},NextToVisit={Puebla}

  • Se remueve de nextToVisit la ciudad de puebla y se asigna en la variable node
  • Se valida si la ciudad de la variable node (Puebla) es igual a la ciudad de destino (Cuernavaca), devuelve falso así que continua.
  • Se valida si la lista visited contiene la ciudad de Puebla, devuelve falso así que continua.
  • Agrega a visited la ciudad de Puebla
  • Obtiene todos los nodos adyacentes de puebla (Tlaxcala y Toluca) y los agrega a la lista nextToVisit

cities-graph

Iteración 1: Visited={Puebla},NextToVisit={Tlaxcala, Toluca}

  • Se remueve el primer elemento de nextToVisit (Tlaxcala) y se asigna en la variable node
  • Se valida si la ciudad de la variable node (Tlaxcala) es igual a la ciudad de destino (Cuernavaca), devuelve falso así que continua.
  • Se valida si la lista visited contiene la ciudad de Tlaxcala, devuelve falso así que continua.
  • Agrega a visited la ciudad de Tlaxcala
  • Agrega todos los nodos adyacentes de Tlaxcala a la lista nextToVisit, en este caso no hay ninguno así que continua.

graph - Page 1

Iteración 1: Visited={Tlaxcala,Puebla},NextToVisit={Toluca}

  • Se remueve el primer elemento de nextToVisit (Toluca) y se asigna en la variable node
  • Se valida si la ciudad de la variable node (Toluca) es igual a la ciudad de destino (Cuernavaca), devuelve falso así que continua.
  • Se valida si la lista visited contiene la ciudad de Toluca, devuelve falso así que continua.
  • Agrega a visited la ciudad de Toluca
  • Agrega todos los nodos adyacentes de Toluca (Puebla, Cuernavaca,Tlaxcala, DF) a la lista nextToVisit.

graph - Page 1

Iteración 1: Visited={Tlaxcala,Puebla,Toluca},NextToVisit={Puebla, Cuernavaca,Tlaxcala, DF}

  • Se remueve el primer elemento de nextToVisit (Puebla) y se asigna en la variable node
  • Se valida si la ciudad de la variable node (Puebla) es igual a la ciudad de destino (Cuernavaca), devuelve falso así que continua.
  • Se valida si la lista visited contiene la ciudad dePuebla, devuelve verdadero así que se salta las siguientes líneas del loop y se va a la siguiente iteración.

graph - Page 1

Iteración 1: Visited={Tlaxcala,Puebla,Toluca},NextToVisit={ Cuernavaca,Tlaxcala, DF}

  • Se remueve el primer elemento de nextToVisit (Cuernavaca) y se asigna en la variable node
  • Se valida si la ciudad de la variable node (Cuernavaca) es igual a la ciudad de destino (Cuernavaca), devuelve verdadero así que termina el programa indicando que si existe una ruta entre puebla y Cuernavaca.

graph - Page 1

Probando los distintos escenarios

Para terminar agregaremos un main a nuestra aplicación, para probar todos los posibles escenarios.


	public static void main(String[] args) {

		GraphBfsSearch graph = new GraphBfsSearch();

		System.out.println("\n\t Paths from DF \n");

		System.out.println(String.format("From DF to DF %s", graph.hasPathBfs("DF", "DF")));

		System.out.println(String.format("From DF to Toluca %s", graph.hasPathBfs("DF", "Toluca")));

		System.out.println(String.format("From DF to Cuernavaca %s", graph.hasPathBfs("DF", "Cuernavaca")));

		System.out.println(String.format("From DF to Puebla %s", graph.hasPathBfs("DF", "Puebla")));

		System.out.println(String.format("From DF to Tlaxcala %s", graph.hasPathBfs("DF", "Tlaxcala")));

		System.out.println(String.format("From DF to Cancún %s", graph.hasPathBfs("DF", "Cancún")));

		// Paths from Toluca

		System.out.println("\n\t Paths from Toluca \n");

		System.out.println(String.format("From Toluca to Toluca %s", graph.hasPathBfs("Toluca", "Toluca")));

		System.out.println(String.format("From Toluca to DF %s", graph.hasPathBfs("Toluca", "DF")));

		System.out.println(String.format("From Toluca to Cuernavaca %s", graph.hasPathBfs("Toluca", "Cuernavaca")));

		System.out.println(String.format("From Toluca to Puebla %s", graph.hasPathBfs("Toluca", "Puebla")));

		System.out.println(String.format("From Toluca to Tlaxcala %s", graph.hasPathBfs("Toluca", "Tlaxcala")));

		System.out.println(String.format("From Toluca to Cancún %s", graph.hasPathBfs("Toluca", "Cancún")));

		System.out.println("\n\t Paths from Cuernavaca \n");

		System.out.println(

				String.format("From Cuernavaca to Cuernavaca %s", graph.hasPathBfs("Cuernavaca", "Cuernavaca")));

		System.out.println(String.format("From Cuernavaca to DF %s", graph.hasPathBfs("Cuernavaca", "DF")));

		System.out.println(String.format("From Cuernavaca to Toluca %s", graph.hasPathBfs("Cuernavaca", "Toluca")));

		System.out.println(String.format("From Cuernavaca to Puebla %s", graph.hasPathBfs("Cuernavaca", "Puebla")));

		System.out.println(String.format("From Cuernavaca to Tlaxcala %s", graph.hasPathBfs("Cuernavaca", "Tlaxcala")));

		System.out.println(String.format("From Cuernavaca to Cancún %s", graph.hasPathBfs("Cuernavaca", "Cancún")));

		System.out.println("\n\t Paths from Puebla \n");

		System.out.println(String.format("From Puebla to Puebla %s", graph.hasPathBfs("Puebla", "Puebla")));

		System.out.println(String.format("From Puebla to Cuernavaca %s", graph.hasPathBfs("Puebla", "Cuernavaca")));

		System.out.println(String.format("From Puebla to DF %s", graph.hasPathBfs("Puebla", "DF")));

		System.out.println(String.format("From Puebla to Toluca %s", graph.hasPathBfs("Puebla", "Toluca")));

		System.out.println(String.format("From Puebla to Tlaxcala %s", graph.hasPathBfs("Puebla", "Tlaxcala")));

		System.out.println(String.format("From Puebla to Cancún %s", graph.hasPathBfs("Puebla", "Cancún")));

		System.out.println("\n\t Paths from Tlaxcala \n");

		System.out.println(String.format("From Tlaxcala to Tlaxcala %s", graph.hasPathBfs("Tlaxcala", "Tlaxcala")));

		System.out.println(String.format("From Tlaxcala to Puebla %s", graph.hasPathBfs("Tlaxcala", "Puebla")));

		System.out.println(String.format("From Tlaxcala to Cuernavaca %s", graph.hasPathBfs("Tlaxcala", "Cuernavaca")));

		System.out.println(String.format("From Tlaxcala to DF %s", graph.hasPathBfs("Tlaxcala", "DF")));

		System.out.println(String.format("From Tlaxcala to Toluca %s", graph.hasPathBfs("Tlaxcala", "Toluca")));

		System.out.println(String.format("From Tlaxcala to Cancún %s", graph.hasPathBfs("Tlaxcala", "Cancún")));

		System.out.println("\n\t Paths from Cancún \n");

		System.out.println(String.format("From Cancún to Cancún %s", graph.hasPathBfs("Cancún", "Cancún")));

		System.out.println(String.format("From Cancún to Tlaxcala %s", graph.hasPathBfs("Cancún", "Tlaxcala")));

		System.out.println(String.format("From Cancún to Puebla %s", graph.hasPathBfs("Cancún", "Puebla")));

		System.out.println(String.format("From Cancún to Cuernavaca %s", graph.hasPathBfs("Cancún", "Cuernavaca")));

		System.out.println(String.format("From Cancún to DF %s", graph.hasPathBfs("Cancún", "DF")));

		System.out.println(String.format("From Cancún to Toluca %s", graph.hasPathBfs("Cancún", "Toluca")));

	}

Al ejecutar el programa mostrará la siguiente salida:


	 Paths from DF 

From DF to DF true

From DF to Toluca true

From DF to Cuernavaca true

From DF to Puebla true

From DF to Tlaxcala true

From DF to Cancún false

	 Paths from Toluca 

From Toluca to Toluca true

From Toluca to DF true

From Toluca to Cuernavaca true

From Toluca to Puebla true

From Toluca to Tlaxcala true

From Toluca to Cancún false

	 Paths from Cuernavaca 

From Cuernavaca to Cuernavaca true

From Cuernavaca to DF true

From Cuernavaca to Toluca true

From Cuernavaca to Puebla true

From Cuernavaca to Tlaxcala true

From Cuernavaca to Cancún false

	 Paths from Puebla 

From Puebla to Puebla true

From Puebla to Cuernavaca true

From Puebla to DF true

From Puebla to Toluca true

From Puebla to Tlaxcala true

From Puebla to Cancún false

	 Paths from Tlaxcala 

From Tlaxcala to Tlaxcala true

From Tlaxcala to Puebla false

From Tlaxcala to Cuernavaca false

From Tlaxcala to DF false

From Tlaxcala to Toluca false

From Tlaxcala to Cancún false

	 Paths from Cancún 

From Cancún to Cancún true

From Cancún to Tlaxcala false

From Cancún to Puebla false

From Cancún to Cuernavaca false

From Cancún to DF false

From Cancún to Toluca false

De este modo se pueden realizar búsquedas sobre un grafo utilizando el algoritmo BFS.

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

Implementa búsquedas sobre Grafos utilizando DFS (Depth First Search) con Java


DFS (Depth First Search) es un algoritmo recursivo para la búsqueda sobre grafos, en este post se explicará como implementarlo con Java, se tomará como base el post Implementa un grafo de ciudades en Java.

Creando clases base para la construcción del grafo

El grafo que se creará contendrá información de ciudades y la distancia entre ellas, para hacerlo necesitaremos 3 clases Node, Edge y Graph. A continuación se mostrará cada una de ellas con su explicación:

Node.java


import java.util.ArrayList;
import java.util.List;

/**
 * @author raidentrance
 *
 */
public class Node {
	private String city;
	private List adjacents = new ArrayList();

	public Node(String city) {
		this.city = city;
	}

	public void addEdge(Edge edge) {
		adjacents.add(edge);
	}

	public List getAdjacents() {
		return adjacents;
	}

	public String getCity() {
		return city;
	}

	@Override
	public String toString() {
		return "Node [city=" + city + ", adjacents=" + adjacents + "]";
	}
}

La clase Node representará una ciudad en nuestro grafo, como se puede ver la ciudad contiene una lista de Edges los cuáles representarán las uniones entre los nodos, más adelante se darán más detalles sobre la clase Edge.
Edge.java


/**
 * @author raidentrance
 *
 */
public class Edge {
	private Node origin;
	private Node destination;
	private double distance;

	public Edge(Node origin, Node destination, double distance) {
		this.origin = origin;
		this.destination = destination;
		this.distance = distance;
	}

	public Node getOrigin() {
		return origin;
	}

	public void setOrigin(Node origin) {
		this.origin = origin;
	}

	public Node getDestination() {
		return destination;
	}

	public void setDestination(Node destination) {
		this.destination = destination;
	}

	public double getDistance() {
		return distance;
	}

	public void setDistance(double distance) {
		this.distance = distance;
	}

	@Override
	public String toString() {
		return "Edge [origin=" + origin.getCity() + ", destination=" + destination.getCity() + ", distance=" + distance
				+ "]";
	}
}

La clase Edge representa la unión entre dos nodos, como se puede ver contiene un nodo origen, uno destino y la distancia entre ambos.
Graph.java


import java.util.ArrayList;
import java.util.List;

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

	private List nodes = new ArrayList();

	public void addNode(Node node) {
		nodes.add(node);
	}

	public List getNodes() {
		return nodes;
	}

	@Override
	public String toString() {
		return "Graph [nodes=" + nodes + "]";
	}
}

La clase Graph representará el conjunto de las ciudades junto con sus uniones y direcciones.

Creando el grafo

Una vez que contamos con las clases necesarias para crear nuestro grafo el siguiente paso será llenarlo con la información de las ciudades que utilizaremos:

cities-graph

Analicemos el grafo:

  • Es posible ir del df a Toluca y a cuernavaca
  • Es posible ir de Toluca a Tlaxcala, Puebla, DF y Cuernavaca
  • Es posible ir de Cuernavaca al DF y a Puebla
  • Es posible ir de Puebla a Toluca y Tlaxcala
  • En este ejemplo solo podemos llegar a Tlaxcala pero no podemos ir a ningún otro lugar
  • En este ejemplo existe la ciudad de Cancún pero no hay una ruta hacia ella.

Veamos el código para construirlo:

MapBuilder.java


import com.raidentrance.graphs.Edge;
import com.raidentrance.graphs.Graph;
import com.raidentrance.graphs.Node;

/**
 * @author raidentrance
 *
 */
public class MapBuilder {
	private static final Graph instance = new Graph();

	private MapBuilder() {
	}

	public static Graph getGraph() {
		Node df = new Node("DF");
		Node toluca = new Node("Toluca");
		Node cuernavaca = new Node("Cuernavaca");
		Node puebla = new Node("Puebla");
		Node tlaxcala = new Node("Tlaxcala");
		Node cancun = new Node("Cancún");

		df.addEdge(new Edge(df, toluca, 100));
		df.addEdge(new Edge(df, cuernavaca, 90));

		toluca.addEdge(new Edge(toluca, puebla, 350));
		toluca.addEdge(new Edge(toluca, cuernavaca, 150));
		toluca.addEdge(new Edge(toluca, tlaxcala, 340));
		toluca.addEdge(new Edge(toluca, df, 100));

		cuernavaca.addEdge(new Edge(cuernavaca, df, 90));
		cuernavaca.addEdge(new Edge(cuernavaca, puebla, 100));

		puebla.addEdge(new Edge(puebla, tlaxcala, 20));
		puebla.addEdge(new Edge(puebla, toluca, 350));

		instance.addNode(df);
		instance.addNode(toluca);
		instance.addNode(cuernavaca);
		instance.addNode(puebla);
		instance.addNode(cancun);
		instance.addNode(tlaxcala);
		return instance;
	}
}

La clase MapBuilder tiene un método que devuelve un objeto de tipo Graph que representa el grafo definido en la imagen anterior.

Implementando DFS sobre el grafo

El siguiente paso será implementar el algoritmo DFS para búsqueda de ciudades en el grafo que definimos, para esto crearemos la clase GraphExampleApplication.


import java.util.HashSet;
import java.util.List;
import java.util.Optional;

import com.raidentrance.graphs.Edge;
import com.raidentrance.graphs.Graph;
import com.raidentrance.graphs.Node;
import com.raidentrance.map.MapBuilder;

/**
 * @author raidentrance
 *
 */
public class GraphExampleApplication {
	private Graph graph;

	public GraphExampleApplication() {
		graph = MapBuilder.getGraph();
	}

	private Optional getNode(String city) {
		List nodes = graph.getNodes();
		for (Node node : nodes) {
			if (node.getCity().equals(city)) {
				return Optional.of(node);
			}
		}
		return Optional.empty();
	}

	public boolean hasPathDfs(String source, String destination) {
		Optional start = getNode(source);
		Optional end = getNode(destination);
		if (start.isPresent() && end.isPresent()) {
			return hasPathDfs(start.get(), end.get(), new HashSet());
		} else {
			return false;
		}
	}

	private boolean hasPathDfs(Node source, Node destination, HashSet visited) {
		if (visited.contains(source.getCity())) {
			return false;
		}
		visited.add(source.getCity());
		if (source == destination) {
			return true;
		}
		for (Edge edge : source.getAdjacents()) {
			if (hasPathDfs(edge.getDestination(), destination, visited)) {
				return true;
			}
		}
		return false;
	}
}

Analicemos cada uno de los componentes:

  • El atributo graph contiene el grafo a utilizar.
  • El constructor inicializa la variable graph con el grafo definido en la imágen anterior.
  • El método OptionalgetNode(String city): Recibe como parámetro una ciudad y devuelve el Nodo en el grafo en caso de existir que contenga el nombre de la ciudad.
  • El método boolean hasPathDfs(String source, String destination): Recibe dos ciudades, el origen y el destino, con esto ejecuta el método de búsqueda DFS para determinar si existe la ruta en el grafo.
  • El método boolean hasPathDfs(Node source, Node destination, HashSet visited): Recibe un nodo origen, un nodo destino y un set con nodos visitados y sigue la siguiente lógica:
    • Si el nodo origen ya fue visitado devuelve false.
    • Agrega el nodo origen a los nodos visitados
    • Toma todos los nodos adyacentes del nodo origen y se ejecuta de forma recursiva.

Caso de ejemplo

Analicemos el método boolean hasPathDfs(Node source, Node destination, HashSet visited) en el caso yendo de Puebla a Cuernavaca.

Ejecución 1: Source =Puebla, Destination=Cuernavaca, Visited={}

  • No se ha visitado la ciudad origen, en este caso Puebla, así que continua.
  • Se agrega Puebla a la lista de ciudades visitadas
  • En este caso el origen y el destino son diferentes así que continua
  • Se iteran los nodos adyacentes para obtener el siguiente nodo origen, en este caso son (Tlaxcala y Toluca) y se ejecuta el método hasPathDfs de forma recursiva tomando el primer nodo como nuevo source veamos la siguiente ejecución.

cities-graph

Ejecución 2: Source =Tlaxcala, Destination=Cuernavaca, Visited={Puebla}

  • No se ha visitado la ciudad origen, en este caso Tlaxcala, así que continua.
  • Se agrega Tlaxcala a la lista de ciudades visitadas
  • En este caso el origen y el destino son diferentes así que continua
  • Se iteran los nodos adyacentes para obtener el siguiente nodo origen, en este caso Tlaxcala no tiene ninguno así que se devuelve false y se continua con la siguiente iteración.

cities-graph

Ejecución 3: Source =Toluca, Destination=Cuernavaca, Visited={Puebla,Tlaxcala}

  • No se ha visitado la ciudad origen, en este caso Toluca, así que continua.
  • Se agrega Toluca a la lista de ciudades visitadas
  • En este caso el origen y el destino son diferentes así que continua
  • Se iteran los nodos adyacentes para obtener el siguiente nodo origen, en este caso son (Puebla, Cuernavaca,Tlaxcala y DF) y se ejecuta el método hasPathDfs de forma recursiva tomando el primer nodo como nuevo source veamos la siguiente ejecución.

cities-graph

Ejecución 3: Source =Puebla, Destination=Cuernavaca, Visited={Puebla,Tlaxcala,Toluca}

  • La ciudad origen, en este caso Puebla, ya ha sido visitada, en este caso devuelve false y continua con la siguiente ciudad adyacente(Puebla, Cuernavaca,Tlaxcala y DF)

cities-graph

Ejecución 4: Source =Cuernavaca, Destination=Cuernavaca, Visited={Puebla,Tlaxcala,Toluca}

  • No se ha visitado la ciudad origen, en este caso Cuernavaca, así que continua.
  • Se agregaCuernavaca a la lista de ciudades visitadas
  • En este caso el origen y el destino son iguales así que devuelve verdadero, esto significa que si existe una ruta válida para ir desde Puebla hacia Toluca.

Probando los distintos escenarios

Para terminar agregaremos un main a nuestra aplicación, para probar todos los posibles escenarios.

	public static void main(String[] args) {

		System.out.println("\n\t Paths from DF \n");
		System.out.println(String.format("From DF to DF %s", new GraphExampleApplication().hasPathDfs("DF", "DF")));
		System.out.println(
				String.format("From DF to Toluca %s", new GraphExampleApplication().hasPathDfs("DF", "Toluca")));
		System.out.println(String.format("From DF to Cuernavaca %s",
				new GraphExampleApplication().hasPathDfs("DF", "Cuernavaca")));
		System.out.println(
				String.format("From DF to Puebla %s", new GraphExampleApplication().hasPathDfs("DF", "Puebla")));
		System.out.println(
				String.format("From DF to Tlaxcala %s", new GraphExampleApplication().hasPathDfs("DF", "Tlaxcala")));
		System.out.println(
				String.format("From DF to Cancún %s", new GraphExampleApplication().hasPathDfs("DF", "Cancún")));
		// Paths from Toluca

		System.out.println("\n\t Paths from Toluca \n");
		System.out.println(String.format("From Toluca to Toluca %s",
				new GraphExampleApplication().hasPathDfs("Toluca", "Toluca")));
		System.out.println(
				String.format("From Toluca to DF %s", new GraphExampleApplication().hasPathDfs("Toluca", "DF")));
		System.out.println(String.format("From Toluca to Cuernavaca %s",
				new GraphExampleApplication().hasPathDfs("Toluca", "Cuernavaca")));
		System.out.println(String.format("From Toluca to Puebla %s",
				new GraphExampleApplication().hasPathDfs("Toluca", "Puebla")));
		System.out.println(String.format("From Toluca to Tlaxcala %s",
				new GraphExampleApplication().hasPathDfs("Toluca", "Tlaxcala")));
		System.out.println(String.format("From Toluca to Cancún %s",
				new GraphExampleApplication().hasPathDfs("Toluca", "Cancún")));

		System.out.println("\n\t Paths from Cuernavaca \n");
		System.out.println(String.format("From Cuernavaca to Cuernavaca %s",
				new GraphExampleApplication().hasPathDfs("Cuernavaca", "Cuernavaca")));
		System.out.println(String.format("From Cuernavaca to DF %s",
				new GraphExampleApplication().hasPathDfs("Cuernavaca", "DF")));
		System.out.println(String.format("From Cuernavaca to Toluca %s",
				new GraphExampleApplication().hasPathDfs("Cuernavaca", "Toluca")));
		System.out.println(String.format("From Cuernavaca to Puebla %s",
				new GraphExampleApplication().hasPathDfs("Cuernavaca", "Puebla")));
		System.out.println(String.format("From Cuernavaca to Tlaxcala %s",
				new GraphExampleApplication().hasPathDfs("Cuernavaca", "Tlaxcala")));
		System.out.println(String.format("From Cuernavaca to Cancún %s",
				new GraphExampleApplication().hasPathDfs("Cuernavaca", "Cancún")));

		System.out.println("\n\t Paths from Puebla \n");
		System.out.println(String.format("From Puebla to Puebla %s",
				new GraphExampleApplication().hasPathDfs("Puebla", "Puebla")));
		System.out.println(String.format("From Puebla to Cuernavaca %s",
				new GraphExampleApplication().hasPathDfs("Puebla", "Cuernavaca")));
		System.out.println(
				String.format("From Puebla to DF %s", new GraphExampleApplication().hasPathDfs("Puebla", "DF")));
		System.out.println(String.format("From Puebla to Toluca %s",
				new GraphExampleApplication().hasPathDfs("Puebla", "Toluca")));
		System.out.println(String.format("From Puebla to Tlaxcala %s",
				new GraphExampleApplication().hasPathDfs("Puebla", "Tlaxcala")));
		System.out.println(String.format("From Puebla to Cancún %s",
				new GraphExampleApplication().hasPathDfs("Puebla", "Cancún")));

		System.out.println("\n\t Paths from Tlaxcala \n");
		System.out.println(String.format("From Tlaxcala to Tlaxcala %s",
				new GraphExampleApplication().hasPathDfs("Tlaxcala", "Tlaxcala")));
		System.out.println(String.format("From Tlaxcala to Puebla %s",
				new GraphExampleApplication().hasPathDfs("Tlaxcala", "Puebla")));
		System.out.println(String.format("From Tlaxcala to Cuernavaca %s",
				new GraphExampleApplication().hasPathDfs("Tlaxcala", "Cuernavaca")));
		System.out.println(
				String.format("From Tlaxcala to DF %s", new GraphExampleApplication().hasPathDfs("Tlaxcala", "DF")));
		System.out.println(String.format("From Tlaxcala to Toluca %s",
				new GraphExampleApplication().hasPathDfs("Tlaxcala", "Toluca")));
		System.out.println(String.format("From Tlaxcala to Cancún %s",
				new GraphExampleApplication().hasPathDfs("Tlaxcala", "Cancún")));

		System.out.println("\n\t Paths from Cancún \n");
		System.out.println(String.format("From Cancún to Cancún %s",
				new GraphExampleApplication().hasPathDfs("Cancún", "Cancún")));
		System.out.println(String.format("From Cancún to Tlaxcala %s",
				new GraphExampleApplication().hasPathDfs("Cancún", "Tlaxcala")));
		System.out.println(String.format("From Cancún to Puebla %s",
				new GraphExampleApplication().hasPathDfs("Cancún", "Puebla")));
		System.out.println(String.format("From Cancún to Cuernavaca %s",
				new GraphExampleApplication().hasPathDfs("Cancún", "Cuernavaca")));
		System.out.println(
				String.format("From Cancún to DF %s", new GraphExampleApplication().hasPathDfs("Cancún", "DF")));
		System.out.println(String.format("From Cancún to Toluca %s",
				new GraphExampleApplication().hasPathDfs("Cancún", "Toluca")));

	}

Salida:


	 Paths from DF 

From DF to DF true
From DF to Toluca true
From DF to Cuernavaca true
From DF to Puebla true
From DF to Tlaxcala true
From DF to Cancún false

	 Paths from Toluca 

From Toluca to Toluca true
From Toluca to DF true
From Toluca to Cuernavaca true
From Toluca to Puebla true
From Toluca to Tlaxcala true
From Toluca to Cancún false

	 Paths from Cuernavaca 

From Cuernavaca to Cuernavaca true
From Cuernavaca to DF true
From Cuernavaca to Toluca true
From Cuernavaca to Puebla true
From Cuernavaca to Tlaxcala true
From Cuernavaca to Cancún false

	 Paths from Puebla 

From Puebla to Puebla true
From Puebla to Cuernavaca true
From Puebla to DF true
From Puebla to Toluca true
From Puebla to Tlaxcala true
From Puebla to Cancún false

	 Paths from Tlaxcala 

From Tlaxcala to Tlaxcala true
From Tlaxcala to Puebla false
From Tlaxcala to Cuernavaca false
From Tlaxcala to DF false
From Tlaxcala to Toluca false
From Tlaxcala to Cancún false

	 Paths from Cancún 

From Cancún to Cancún true
From Cancún to Tlaxcala false
From Cancún to Puebla false
From Cancún to Cuernavaca false
From Cancún to DF false
From Cancún to Toluca false

De este modo se pueden realizar búsquedas sobre un grafo utilizando el algoritmo DFS.

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

Utiliza multiples bases de datos con Spring boot y Spring JDBC


Crear micro servicios que obtengan información de una base de datos es muy común, pero muchas veces la información que queremos obtener se encuentra en más de una base de datos, en este post explicaremos como escribir una aplicación REST spring boot que obtenga información de más de una base de datos.

Paso 1 Preparando las bases de datos

El primer paso será crear las dos bases de datos a las que conectaremos nuestra aplicación, en este ejemplo utilizaremos el motor de bases de datos MySQL, a continuación se presentan los scripts a utilizar para cada una:

  • Base de datos 1
create database database1;
use database1;
CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);
INSERT INTO USER (USERNAME,PASSWORD)VALUES('raidentrance','superSecret');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('john','smith');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('juan','hola123');
  • Base de datos 2
create database database2;
use database2;
CREATE TABLE USER_LEGACY(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);
INSERT INTO USER_LEGACY (USERNAME,PASSWORD)VALUES('rocky','Adrianna');
INSERT INTO USER_LEGACY (USERNAME,PASSWORD)VALUES('ivanDrago','golpesFuertes');
INSERT INTO USER_LEGACY (USERNAME,PASSWORD)VALUES('apolloCreed','theBest');

Con esto tendremos dos bases de datos, las cuales contienen tablas diferentes y datos diferentes.

Paso 2 Crear aplicación Spring boot

En este ejemplo tomaremos como base el proyecto Spring Boot + REST Jersey Parte 1 que explica como configurar un proyecto básico de spring boot y le agregaremos las siguientes dependencias:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

Como se puede ver haremos la conexión utilizando Spring JDBC e incluiremos el driver de mysql para conectarnos a las bases de datos.

Paso 3 Crear clase para representar a los usuarios

Como se puede ver, aunque son dos bases de datos diferentes y tablas diferentes ambas comparten las mismas columnas y tipos de datos, por esto solo crearemos una clase User para representar la tabla USER que se encuentra en la base de datos database1 y la tabla USER_LEGACY que se encuentra en la base de datos database2.


/**
 *
 * @author raidentrance
 *
 */
public class User {
	private Integer id;
	private String user;
	private String password;

	public User() {
	}

	public User(Integer id, String user, String password) {
		super();
		this.id = id;
		this.user = user;
		this.password = password;
	}

	public Integer getId() {
		return id;
	}

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

	public String getUser() {
		return user;
	}

	public void setUser(String user) {
		this.user = user;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

Como se puede ver la clase cuenta con los atributos id, user y password, esto es suficiente para hacer el mapping en las dos tablas.

Paso 4 Agregar la configuración de las bases de datos

El siguiente paso será agregar al archivo application.properties los datos necesarios para conectarse a ambas bases de datos, para esto agregaremos las siguientes líneas:

#Settings for database conneection to database1
spring.datasource.url=jdbc:mysql://localhost:3306/database1
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#Settings for database conneection to database2
legacy.datasource.url=jdbc:mysql://localhost:3306/database2
legacy.datasource.username=root
legacy.datasource.password=root
legacy.datasource.driver-class-name=com.mysql.jdbc.Driver

Como se puede ver se incluye la información de ambos datasources.

Paso 5 Agregar configuración para ambos datasources

Agregar las líneas al archivo properties no es suficiente, ahora crearemos una clase de configuración que contendrá los dos datasources y jdbcTemplates a utilizar.


import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @author raidentrance
 *
 */
@Configuration
public class DatabaseConfig {
	@Bean(name = "dsSlave")
	@ConfigurationProperties(prefix = "legacy.datasource")
	public DataSource slaveDataSource() {
		return DataSourceBuilder.create().build();
	}

	@Bean(name = "dsMaster")
	@Primary
	@ConfigurationProperties(prefix = "spring.datasource")
	public DataSource masterDataSource() {
		return DataSourceBuilder.create().build();
	}

	@Bean(name = "jdbcSlave")
	@Autowired
	public JdbcTemplate slaveJdbcTemplate(@Qualifier("dsSlave") DataSource dsSlave) {
		return new JdbcTemplate(dsSlave);
	}

	@Bean(name = "jdbcMaster")
	@Autowired
	public JdbcTemplate masterJdbcTemplate(@Qualifier("dsMaster") DataSource dsMaster) {
		return new JdbcTemplate(dsMaster);
	}
}

En el código anterior se crean 4 objetos 2 de tipo DataSource y 2 de tipo JdbcTemplate :

  • DataSource
    • slaveDataSource : En la anotación @ConfigurationProperties(prefix = “legacy.datasource”) se define la configuración de la base de datos legacy, que en este caso es la database2, como se puede ver es posible asignarle un nombre al bean que se está generando, en este caso es dsSlave.
    • masterDataSource: Este método devolverá el datasource para la base de datos database1 con el nombre dsMaster.
    • slaveJdbcTemplate: Para utilizar Spring JDBC es necesario utilizar un objeto de este tipo, como se puede ver el método recibe un objeto de tipo datasource el cuál es inyectado gracias a la anotación @Autowired, en este caso existen 2 beans de este tipo, por esto es necesario incluir la anotación @Qualifier(“dsSlave”) para indicarle a Spring cuál de los dos datasources va a inyectar. Por último del mismo modo que en el anterior es posible definir un nombre al bean que se generará en este caso es jdbcSlave.
    • masterJdbcTemplate: Este método devolverá el objeto de tipo JdbcTemplate con referencia a el datasource dsMaster con el nombre de jdbcMaster.

Paso 6 Creación de los daos

Como tendremos dos tablas diferentes en dos bases de datos diferentes crearemos dos daos uno apuntando a la tabla user en la base de datos database1 y el otro apuntando a la tabla user_legacy en la base de datos database2.

UserDao.java


import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import com.raidentrance.model.api.User;

/**
 * @author raidentrance
 *
 */
@Component
public class UserDao {

	@Autowired
	@Qualifier("jdbcMaster")
	private JdbcTemplate jdbcTemplate;

	public List<User> findAll() {
		return jdbcTemplate.query("select * from user", new RowMapper<User>() {
			@Override
			public User mapRow(ResultSet rs, int arg1) throws SQLException {
				return new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
			}
		});
	}
}

En este DAO se inyecta la referencia al JDBC template utilizando @Autowired y un @Qualifier(“jdbcMaster”) esto es para determinar cuál de los dos jdbctemplates tiene que inyectar en esta referencia, en este caso es el que contiene el datasource que apunta a la base de datos database1.
UserLegacyDao.java

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import com.raidentrance.model.api.User;

/**
 * @author raidentrance
 *
 */
@Component
public class UserLegacyDao {
	@Autowired
	@Qualifier("jdbcSlave")
	private JdbcTemplate jdbcTemplate;

	public List<User> findAll() {
		return jdbcTemplate.query("select * from user_legacy", new RowMapper<User>() {
			@Override
			public User mapRow(ResultSet rs, int arg1) throws SQLException {
				return new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
			}
		});
	}
}

En este DAO se inyecta la referencia al JDBC template utilizando @Autowired y un @Qualifier(“jdbcSlave”) esto es para determinar cuál de los dos jdbctemplates tiene que inyectar en esta referencia, en este caso es el que contiene el datasource que apunta a la base de datos database2.

Paso 7 Creando un servicio común

Una vez que tenemos los dos DAOs en nuestra aplicación, el siguiente paso será crear un servicio común, en este se inyectarán ambos daos:


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.raidentrance.dao.UserDao;
import com.raidentrance.dao.UserLegacyDao;
import com.raidentrance.model.api.User;

/**
 * @author raidentrance
 *
 */
@Service
public class UserService {
	@Autowired
	private UserDao userDao;

	@Autowired
	private UserLegacyDao userLegacyDao;

	public List<User> getUsers() {
		return userDao.findAll();
	}

	public List<User> getLegacyUsers() {
		return userLegacyDao.findAll();
	}

}

Como se puede ver en esta clase se inyectan ambos daos, ya no es necesario utilizar algún qualifier porque son beans diferentes.

Paso 8 Exponiendo la información vía REST

El último paso será exponer esta información en servicios REST, para esto crearemos la siguiente clase:


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.Autowired;
import org.springframework.stereotype.Component;

import com.raidentrance.service.UserService;

/**
 * @author raidentrance
 *
 */

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

	@Autowired
	private UserService userService;

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

	@GET
	public Response getUsers() {
		log.info("Getting user");
		return Response.ok(userService.getUsers()).build();
	}

	@GET
	@Path("/legacy")
	public Response getLegacyUsers() {
		log.info("Getting user");
		return Response.ok(userService.getLegacyUsers()).build();
	}
}

En el código anterior se puede ver que se exponen 2 endpoints uno es /users que devolverá a todos los usuarios que se encuentran en la base de datos database1, y el otro es /users/legacy que devuelve a todos los usuarios que se encuentran en la base de datos database2.

Paso 9 Ejecutando los endpoints

Para ejecutar la aplicación solo ejecutaremos la clase SprinBootSampleApplication que es la que contiene el main de nuestra aplicación e invocaremos los siguientes endpoints para ver sus salidas:

  • GET /users

Salida:

[
   {
      "id": 1,
      "user": "raidentrance",
      "password": "superSecret"
   },
   {
      "id": 2,
      "user": "john",
      "password": "smith"
   },
   {
      "id": 3,
      "user": "juan",
      "password": "hola123"
   }
]
  • GET /users/legacy

Salida

[
   {
      "id": 1,
      "user": "rocky",
      "password": "Adrianna"
   },
   {
      "id": 2,
      "user": "ivanDrago",
      "password": "golpesFuertes"
   },
   {
      "id": 3,
      "user": "apolloCreed",
      "password": "theBest"
   }
]

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

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

Búsqueda binaria en Java paso a paso


El algoritmo de búsqueda binaria funciona sobre arreglos ordenados y es utilizado para buscar un elemento en los mismos.

Funcionamiento

El funcionamiento del algoritmo es simple y cuenta con las siguientes partes:

  • Datos de entrada :
    • Un arreglo ordenado
    • Un valor a buscar en el arreglo
  • Datos de salida:
    • La posición del elemento en el arreglo o -1 en caso de no encontrarlo

El algoritmo de búsqueda binaria sigue los siguientes pasos:

  • Verifica si el elemento a buscar es menor al máximo elemento en el arreglo y mayor al mínimo elemento del arreglo, en caso de no ser así  se devolverá -1 ya que sabemos que no se encuentra el elemento.
  • Obtiene el elemento que se encuentra en la mitad del arreglo y lo compara con el valor que se busca.
  • En caso de que el elemento sea mayor al valor que se busca se descartará la parte derecha y se volverá a ejecutar la misma validación pero solo sobre el lado izquierdo del arreglo.
  • El paso anterior se repetirá hasta encontrar el elemento
  • En caso de no encontrar el elemento se devolverá -1 para indicar que no se encontró.

Ejemplo práctico

A continuación presentaremos un ejemplo paso a paso de la búsqueda binaria, imaginemos el siguiente arreglo donde se desea buscar el número 400:

paso1

  • Evaluar que el número 400 es mayor al menor elemento del arreglo y menor al máximo elemento del arreglo, en este caso 10 es menor y 2222 es mayor a 400, entonces podemos pasar al siguiente paso.
  • Obtendremos el elemento de la mitad del arreglo utilizando la siguiente ecuación (límite inferior + límite superior)/2
  • En este caso la mitad del arreglo es 120, una vez hecho esto evaluaremos si 120 es mayor o menor al número que buscamos, que en este caso es 400, 400 es mayor a 120, esto significa que se encuentra del lado derecho del arreglo, así que el lado izquierdo será descartado como se muestra en la siguiente imagen:

paso2

  • Una vez hecho esto volveremos a dividir el arreglo, pero en este caso nuestro límite inferior no será 10 sino ahora será 200, y el nuevo valor de en medio será 500 como se muestra en la siguiente imagen:

paso3

  • Una vez más evaluaremos si el número 500 es mayor o menor al número que buscamos que es 400, en este caso 400 es menor a 500, entonces la parte de la derecha será descartada como se muestra en la siguiente imagen:

paso4

  • Con lo anterior el nuevo límite inferior es el 200 y el nuevo límite superior es 400 así que ejecutaremos nuestra ecuación para obtener el elemento de la mitad, en este caso es la posición 7 del arreglo que es 200 así que volveremos a reducir en uno el límite superior, con esto tanto el límite superior como el límite inferior se encuentran en la posición 8 como se muestra en la siguiente imagen:

paso 5

  • Listo ! encontramos el elemento que buscábamos en el arreglo, esto nos tomó 4 iteraciones en lugar de 9 si hubiéramos iterado el arreglo indice por indice, con esto se demuestra que es mucho más eficiente que iterar el arreglo posición por posición. La complejidad de una búsqueda lineal es de O(n) mientras que la complejidad de una búsqueda binaria es O(Log n) lo cuál indica que es mucho más eficiente, solo consideremos que para poder ejecutarla el arreglo debe estar ordenado.

Implementación de la búsqueda binaria en Java

A continuación se presenta el código para ejecutar la búsqueda binaria en Java:

public class ChallengeClass {
	public static int binarySearch(int[] array, int minLimit, int maxLimit, int value) {
		if (maxLimit >= 0 && array[minLimit] <= value && array[maxLimit] >= value) {
			int mid = getMidValue(minLimit, maxLimit);
			System.out.println(String.format("Límite inferior %d límite superior %d valor en el arreglo %d valor a buscar %d", minLimit,maxLimit,array[mid],value));
			if (array[mid] == value) {
				return mid;
			} else if (array[mid] <span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>< value) {
				return binarySearch(array, mid + 1, maxLimit, value);
			}
			return binarySearch(array, minLimit, mid - 1, value);
		}
		return -1;
	}

	public static int getMidValue(int minLimit, int maxLimit) {
		return (maxLimit + minLimit) / 2;
	}

	public static void main(String[] args) {
		int value = 400;
		int[] array = { 10, 15, 20, 40, 50, 100, 120, 200, 400, 500, 600, 800 ,2222};
		int result = binarySearch(array, 0, array.length - 1, value);
		System.out.println(String.format("Result %d", result));
	}
}

La implementación presentada fue hecha sin utilizar ningún api de Java para el manejo de búsquedas u ordenamientos.

Implementación de búsqueda binaria utilizando clases existentes en Java

Como sabemos Java provee de un framework para el manejo de colecciones y arreglos, a continuación se presenta el código para ejecutar la búsqueda binaria utilizando dichas clases:

import java.util.Arrays;

public class ChallengeClass {

	public static void main(String[] args) {
		int[] array = { 10, 15, 20, 40, 50, 100, 120, 200, 400, 500, 600, 800, 2222 };
		int result = Arrays.binarySearch(array, 400);
		System.out.println(String.format("Result %d", result));
	}
}

Es importante mencionar que el método binarySearch de la clase Arrays también requiere que el arreglo que se pase como parámetro se encuentre ordenado, en caso contrario los resultados pueden ser erróneos.

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