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

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 + Spring JDBC (English version)


In this post we will explain how to access to a database by using Spring boot + Spring JDBC step by step. For this we will use the project Spring Boot + REST Jersey Part 1 as base.

Step 1 : Configure the required dependencies

The first step will be add the required dependencies to the project, in this case we will use 2 spring-boot-starter-jdbc and mysql-connector-java.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
  • spring-boot-starter-jdbc : Contains all the necessary classes that we will use to enable spring jdbc.
  • mysql-connector-java: Contains the mysql driver to connect via JDBC.

Step 2: Create the tables we will use in the database

For this example we will use MySQL as database engine and we will create a database named jdbc_example with the following table:

CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);

And with the following data:

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

Step 3: Create the class to represent a User

Now we will create a POJO to represent the information stored in the User table.

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

/**
 * @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;
    }

}

Step 4: Add the database configuration to the project

The next step is to include the database configuration to the project, in order to do it we will edit the file application.properties with the following information:

spring.datasource.url=jdbc:mysql://localhost:3306/jdbc_example
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Step 5 : Create a DAO (Data access object)

Once Spring has the information to connect to the database the next step is create a data access object, it will be used to execute operations over the User table in the database:

/**
 *
 */
package com.raidentrance.dao;

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

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import com.raidentrance.model.ServiceException;
import com.raidentrance.model.User;

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

    @Autowired
    private JdbcTemplate jdbcTemplate;

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

    public User findByUsername(String username) throws ServiceException {
        User user = jdbcTemplate.query(new PreparedStatementCreator() {

            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                PreparedStatement ps = con.prepareStatement("select * from user where username=?");
                ps.setString(1, username);
                return ps;
            }
        }, new ResultSetExtractor<User>() {
            @Override
            public User extractData(ResultSet rs) throws SQLException, DataAccessException {
                if (rs.next()) {
                    User user = new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
                    return user;
                } else {
                    return null;
                }
            }
        });
        if (user != null) {
            return user;
        } else {
            throw new ServiceException(Status.NOT_FOUND.getStatusCode(), "User not found ", 4004);
        }
    }
}

In the previous code we can see the following points :

  • the @Componen annotation: It means that the object will be living in the spring context and we can access to the instance by using the @Autowired annotation.
  • @Autowired JdbcTemplate jdbcTemplate : The JdbcTemplate will use the configuration that we establish in the application.properties file to connect to our database. We use @Autowired to get a reference to the object that is living in the Spring context.
  • public List findAll() : This method will be used to get all the users in the table. As you can see this method receives the sql query that we want to execute and an object that implements the RowMapper interface, this object will be used to transform from a ResultSet to a Java list.
  • public User findByUsername(String username): The method findByUsername will be used to get a user by username. In this example we are using a PreparedStatement to prevent SQL Injection because this sql query receives a parameter. Other important difference is that this method is receiving a ResultSetExtractor instead a RowMapper and the reason to do it is because this method will return a single object in the response.
  • The last point is that we can see that in case that we cant find a user by its username we will throw a ServiceException with a message, code and http status.

Step 6: Using the DAO in our web service

Once we have a DAO created we have to use it in our endpoint, in future posts we will see that is a good practice to separate this logic in a separated service, but for now we will inject the DAO directly in the endpoint UserResource as follow:

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

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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.raidentrance.dao.UserDao;
import com.raidentrance.model.ServiceException;

/**
 * @author raidentrance
 *
 */

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

    @Autowired
    private UserDao userDao;

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

    @GET
    public Response getUsers() {
        log.info("Getting users");
        return Response.ok(userDao.findAll()).build();
    }

    @GET
    @Path("/user/{username}")
    public Response getUser(@PathParam("username")String username) throws ServiceException {
        log.info("Getting users");
        return Response.ok(userDao.findByUsername(username)).build();
    }

}

As you can see, to inject the DAO we just need to use the annotation @Autowired because the object lives in the spring context.

Step 7: Testing all together

To execute the application we have to execute the main class as in all the Spring boot applications and access to the following url http://localhost:8080/users, we can see the following output:

Captura de pantalla 2017-09-18 a las 2.20.01 p.m.

If we want to get a single user by using the username we will use the url http://localhost:8080/users/user/raidentrance and it will show the following output:

Captura de pantalla 2017-09-18 a las 2.21.40 p.m.

You can find the complete code in the url https://github.com/raidentrance/spring-boot-example/tree/part6-spring-jdbc .

And if you want to learn more about Spring boot and web services we recommend the following books:

Also you can find version in Spanish of this post here.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring boot + REST (Error Handling)


One of the most important points while we are developing a REST api is the error handling, in this post we will explain how to implement it by using Spring boot + Jersey. Te example that we are going to use as base is Spring Boot + REST Jersey Part 1.

Step 1:  Creating classes to represent the errors

The first step will be to create the necessary classes to represent the errors, in this case we will create two classes ErrorMessage and ServiceException.

ErrorMessage.java

package com.raidentrance.model;

import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

/**
 * @author raidentrance
 *
 */
@JsonInclude(Include.NON_NULL)
public class ErrorMessage implements Serializable {
    private Integer httpStatus;
    private String message;
    private Integer code;
    private String developerMessage;

    private static final long serialVersionUID = 5318063708359922770L;

    public ErrorMessage() {
    }

    public ErrorMessage(ServiceException ex) {
        this.httpStatus = ex.getHttpStatus();
        this.message = ex.getMessage();
        this.code = ex.getCode();
    }

    public ErrorMessage(Integer httpStatus, String message, Integer code) {
        super();
        this.httpStatus = httpStatus;
        this.message = message;
        this.code = code;
    }

    public Integer getHttpStatus() {
        return httpStatus;
    }

    public void setHttpStatus(Integer httpStatus) {
        this.httpStatus = httpStatus;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getDeveloperMessage() {
        return developerMessage;
    }

    public void setDeveloperMessage(String developerMessage) {
        this.developerMessage = developerMessage;
    }

}

The error class will be used to represent the errors in JSON format and it contains the following attributes:

  • httpStatus: Will contain the http status that will be returned
  • message: It is used to show a small error message that describes the problem
  • code: It is used to show a small error code that can be representative for an application
  • developerMessage: You can specify the generated exception in order to make easier than the developer find the problem

The annotation @JsonInclude(Include.NON_NULL) specify that if an attribute is null it won’t be in the response.

ServiceException.java

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

/**
 * @author raidentrance
 *
 */

public class ServiceException extends Exception{

    private Integer httpStatus;
    private String message;
    private Integer code;
    private String developerMessage;

    private static final long serialVersionUID = -528134378438377740L;

    public ServiceException(Integer httpStatus, String message, Integer code,String developerMessage) {
        this.httpStatus = httpStatus;
        this.message = message;
        this.code = code;
        this.developerMessage=developerMessage;
    }

    public ServiceException(Integer httpStatus, String message, Integer code) {
        this.httpStatus = httpStatus;
        this.message = message;
        this.code = code;
    }

    public ServiceException(ErrorMessage errorMessage){
        this.httpStatus = errorMessage.getHttpStatus();
        this.message = errorMessage.getMessage();
        this.code = errorMessage.getCode();
        this.developerMessage=errorMessage.getDeveloperMessage();
    }

    public String getDeveloperMessage() {
        return developerMessage;
    }

    public void setDeveloperMessage(String developerMessage) {
        this.developerMessage = developerMessage;
    }

    public Integer getHttpStatus() {
        return httpStatus;
    }

    public void setHttpStatus(Integer httpStatus) {
        this.httpStatus = httpStatus;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

}

The ServiceException class is an exception and will be used to throw the errors in the application, as you can see it receives the necessary parameters to populate the ErrorMessage class.

Step 2: Creating exception mappers

The next step is to create the exception mappers, it will define what to do if an exception happens. In this example we will generate 2 exception mappers one in case that a ServiceException happens and the other in case that a Throwable happens to handle any unconsidered error.

ServiceExceptionMapper.java

package com.raidentrance.error;

import java.io.PrintWriter;
import java.io.StringWriter;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import com.raidentrance.model.ErrorMessage;
import com.raidentrance.model.ServiceException;

/**
 * @author raidentrance
 *
 */

@Provider
public class ServiceExceptionMapper implements ExceptionMapper<ServiceException> {

    @Override
    public Response toResponse(ServiceException ex) {
        ErrorMessage errorMessage = new ErrorMessage();
        errorMessage.setCode(ex.getCode());
        errorMessage.setMessage(ex.getMessage());
        StringWriter errorStackTrace = new StringWriter();
        ex.printStackTrace(new PrintWriter(errorStackTrace));
        errorMessage.setDeveloperMessage(ex.toString());
        return Response.status(ex.getHttpStatus()).entity(errorMessage).type(MediaType.APPLICATION_JSON).build();
    }

}

To create an exception mapper we just need to implement the ExceptionMapper interface and specify in the generic part the type of the exception that we are going to handle, in this case we are specifying ServiceException.

GenericExceptionMapper.java

package com.raidentrance.error;

import java.io.PrintWriter;
import java.io.StringWriter;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import org.springframework.http.HttpStatus;

import com.raidentrance.model.ErrorMessage;

/**
 * @author raidentrance
 *
 */
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {

    public Response toResponse(Throwable ex) {
        ErrorMessage errorMessage = new ErrorMessage();
        errorMessage.setMessage(ex.getMessage());
        StringWriter errorStackTrace = new StringWriter();
        ex.printStackTrace(new PrintWriter(errorStackTrace));
        errorMessage.setDeveloperMessage(ex.toString());
        return Response.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).entity(errorMessage)
                .type(MediaType.APPLICATION_JSON).build();
    }

}

In the same way we are creating another exception mapper but now with the class Throwable, it means that if any other kind of error happen it will be cached by this exception mapper, it will be used for any exception that is not handled by the application, for this reason we will return an http status 500 that means internal server error.

Step 3: Registering the Exception Mappers

Once we created the exception mappers we need to register them in the application. We are going to do it in the same way that we define another endpoint in the application in the class JerseyConfig.

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

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

import com.raidentrance.error.GenericExceptionMapper;
import com.raidentrance.error.ServiceExceptionMapper;
import com.raidentrance.resource.UserResource;

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

Step 4: Throwing test exceptions

To test that the exception mappers are working correct we are going to create 2 endpoints one will throe a ServiceException and the other will throw a NullPointerException.

  • Endpoint that returns a ServiceException:
@GET
@Path("/error")
public Response sampleError() throws ServiceException {
    throw new ServiceException(HttpStatus.NOT_FOUND.value(), "Sample Error Message", 1);
}

If we execute the previous code, we will receive the following response with http status 404:

Captura de pantalla 2017-09-11 a las 9.26.56 a.m.

  • Endpoint that returns a NullPointerException:
@GET
@Path("/error/generic")
public Response sampleGenericError() {
    throw new NullPointerException();
}

If we execute the previous code, we will receive the following response with http status 500:

Captura de pantalla 2017-09-11 a las 9.29.47 a.m.

If you want to learn more about web services REST and Spring boot we recommend the following books:

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring Boot + REST Jersey (Adding Spring Security 4) Part 4


In the previous post we explained how to use HATEOAS step by step using Spring boot Spring Boot + REST Jersey (Adding Spring HATEOAS and MapStruct) Part 3, and we are going to use this project as base to implement Spring Security with basic authentication.

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-boot-starter-security dependency as follows:

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

Step 2: Modifying our Repository

In the post Spring Boot + REST Jersey (Adding Spring HATEOAS and MapStruct) Part 3 we created the repositories UserRepository and RoleRepository. In this example we are going to add a method to UserRepository that will find a user by 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);
}

We don’t need to implement the method findByUsername(String username) because Spring data will create the implementation based in a convention.

Step 3: Adding an AuthenticatorService

AuthenticatorService will be the responsible to execute the authentication in the application, the logic will be: Find a user and its role and return it, we don’t need to create a logic to compare users or passwords it will be done by 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;
	}
}

Step 4: Configuring Spring security

Once we created the AuthenticatorService we need to define which endpoints do we want to authenticate.

/**
 *
 */
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();
	}
}

The method configureGlobal is used to define who is going to search the user, in this example it will use AuthenticatorService previously created.

The method configure is used to define the protected url’s and the authentication mechanism. In this example we are going to use Basic authentication.

Enpoint:  http://localhost:8080/users

Header: Authorization Basic cmFpZGVudHJhbmNlOnN1cGVyU2VjcmV0

How to create the header

The value of the header Authorization is created in the following way:

Authorization Basic   :  It is the name of the header and the authentication mechanism

cmFpZGVudHJhbmNlOnN1cGVyU2VjcmV0 : This is the value and it is the user:password in base64, in this way it is raidentrance:superSecret in base64.

Testing with CURL

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

Testing with 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

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

If you want to learn more about Spring and REST we recommend the following books:

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 (Adding Spring data) Part 2


In this post, I will explain in an easy way how to configure and use Spring data with Spring boot. In order to do it we will use a previous example Spring Boot + REST Jersey Part 1.

Step 1 (Including maven dependencies)

Spring boot generated starter dependencies, this dependencies contain all the necessary resources to use each of the spring modules in the easiest way. The versions of the necessary dependencies are not required because they come from the spring boot parent project. To configure Spring Data is necessary add the following dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

Step 2 (Configuring the datasource)

The next step will be create a file named application.properties, in this file we will define the information of the database connection, in this example we will show how to create a datasource for MySQL.

spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_users
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

In the file application.properties is possible to define multiples configurations for spring boot like database information, ports, context path, etc. This file can be a .properties or a .yml file.

Step 3 (Configuring repositories)

Spring Data uses repositories to access to the information, now we have to define the java package that will contain all the repositories, in this example will be com.raidentrance.repositories.

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

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @author raidentrance
 *
 */
@EnableTransactionManagement
@EnableJpaRepositories("com.raidentrance.repositories")
public class SpringDataConfig {

}

Step 4 (Creating the tables and data for the example)

The application will use a table named user with the following structure:

CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);

Now we have to insert some sample data:

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

Step 6 (Creating JPA entities)

Once we configured Spring Data, created the tables and populated them we have to create a JPA entity to represent the relational model in the domain model by creating the entity User.java.

/**
 *
 */
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.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;

    private static final long serialVersionUID = -5290198995172316155L;

    public Integer getIdUser() {
        return idUser;
    }

    public void setIdUser(Integer idUser) {
        this.idUser = idUser;
    }

    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;
    }

    @Override
    public String toString() {
        return "User [idUser=" + idUser + ", username=" + username + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((idUser == null) ? 0 : idUser.hashCode());
        result = prime * result + ((username == null) ? 0 : username.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (idUser == null) {
            if (other.idUser != null)
                return false;
        } else if (!idUser.equals(other.idUser))
            return false;
        if (username == null) {
            if (other.username != null)
                return false;
        } else if (!username.equals(other.username))
            return false;
        return true;
    }

}

Step 7 (Creating a Spring data repository)

Now we have to create a Spring data repository, this will be used to access to the database:

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

import org.springframework.data.repository.CrudRepository;

import com.raidentrance.entities.User;

/**
 * @author raidentrance
 *
 */
public interface UserRepository extends CrudRepository<User, Integer> {
}

UserRepository is an interface, the most common question is, do I have to create a class that implements the UserRepository interface ? the answer is NO, the only thing that we need to do is to define the interface and Spring Data will create the implementation for it.

Step 8 (Using the JPA Repository)

The last part is to use the repository in the application, in order to do it, we will integrate the component in the web service REST created in the part 1 of the tutorial Spring Boot + REST Jersey Part 1.

/**
 *
 */
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 {

    @Autowired
    private UserRepository userRepository;

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

}

In order to use the repository created we will use the annotation @Autowired, as you can see the method used to get all the users is findAll(), this method is not defined in the interface created, it is defined in the parent interface named CrudRepository.

Step 9 (Testing all together)

In order to test the project the only thing you need to do is execute the class SpringBootSampleApplication, when it finishes you have to execute the endpoint http://localhost:8080/users.

Output

[
{
idUser: 1,
username: "raidentrance",
password: "superSecret"
},
{
idUser: 2,
username: "john",
password: "smith"
},
{
idUser: 3,
username: "juan",
password: "hola123"
}
]

Next steps

In the following posts we will explain:

  • How to return a DTO instead of a list of entities in the web service REST
  • Add ExceptionMappers for error handling
  • Add support for HATEOAS
  • Add spring security
  • Add logging
  • Add different profiles for different environments

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

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

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com