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 (Manejo de errores)


Uno de los puntos más importantes al diseñar un api rest es el manejo de errores, en este post se explicará como implementar el manejo de errores utilizando Spring Boot + Jersey. Se tomará como base el ejemplo Spring Boot + REST Jersey Part 1.

Paso 1 : Crear clases para representar los errores

El primer paso será crear las clases necesarias para representar los errores, en este caso se crearán dos clases Error y 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;
	}

}

La clase error será utilizada para representar los errores en formato JSON, y contiene los siguientes atributos:

  • httpStatus: Contendrá el status http que devolverá la petición REST
  • message: Se utilizará para mostrar un pequeño mensaje de error que describa el problema
  • code : Representa algún código de error que pueda ser representativo para una aplicación

  • developerMessage: Se puede especificar la excepción generada para que un desarrollador pueda detectar de forma simple el problema

La anotación @JsonInclude(Include.NON_NULL) indica que si algún atributo es nulo no se mostrará en la respuesta.

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

}

La clase ServiceException es una excepción y será utilizada para arrojar los errores dentro de la aplicación, como se puede observar es posible crearla utilizando los parámetros necesarios para alimentar la clase ErrorMessage.

Paso 2 : Crear exception mappers

El siguiente paso es crear exception mappers, estos definirán el que se debe hacer en caso de algún tipo de excepción. Para este ejemplo se crearán 2 exception mappers uno en caso de que se produzca una excepción ServiceException y la otra en caso de que se genere una tipo Throwable en caso de cualquier otro tipo de 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&lt;ServiceException&gt; {

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

}

Para crear un exception mapper solo es necesario implementar la interfaz ExceptionMapper y especificar en el genérico el tipo de excepción que se manejará en este caso se utilizará 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&lt;Throwable&gt; {

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

}

Del mismo modo se creará otro exception mapper pero ahora para la clase Throwable, esto significa que cualquier otro tipo de error será atrapado por este exception handler y procesado, como se puede observar este exception mapper será utilizado para atrapar cualquier tipo de excepción que no sea manejada por la aplicación por esto devolveremos como status http un 500 que significa Internal Server Error.

Paso 3 : Registrando los Exception Mappers

Una vez que se crearon los Exception Mappers deben ser registrados en la aplicación. Para hacer esto se deben definir del mismo modo que los endpoints en la clase  JerseyConfig.
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);
	}
}

Paso 4 : Lanzar excepciones de prueba

Para probar que los exception mappers funcionan de forma correcta crearemos dos endpoints uno arrojará una ServiceException y el otro una NullPointerException:

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

Si ejecutamos el endpoint anterior, la respuesta será la siguiente con un status HTTP 404:

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

  • Endpoint que devuelve una NullPointerException:
@GET
@Path("/error/generic")
public Response sampleGenericError() {
	throw new NullPointerException();
}

Si ejecutamos el endpoint anterior, la respuesta será la siguiente con un status HTTP 500:

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

Si deseas aprender más sobre servicios web, recomendamos los siguientes libros:

Puedes encontrar el código completo en el siguiente enlace : https://github.com/raidentrance/spring-boot-example/tree/part5-adding-error-handling

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

5 Comandos, una aplicación web completa con JHipster en español


En el post anterior se explicó la configuración del entorno de trabajo con JHipster, para verlo ir a Crea una aplicación Web+REST (Spring boot + AngularJS) en minutos utilizando JHipster . En este post se explica paso a paso como crear una aplicación completa haciendo uso de comandos simples con JHipster.

Creación de entidades

En JHipster no es necesario escribir una sola línea de código para crear una aplicación web debido a que es una herramienta scaffolding lo cual permite crear la aplicación en base a comandos.

Definiendo aplicación a crear

La aplicación a crear será una aplicación para registrar departamentos, empleados y habilidades de una empresa, la idea de este post es crear la aplicación completa sin escribir una sola línea de código. Para conseguir esto se deben crear las siguientes entidades:

  • Departamento
  • Empleado
  • Habilidad

Creando entidad department

Lo primero será crear la entidad departamento utilizando el siguiente comando:

yo jhipster:entity department

Una vez que se ejecuta el comando, JHipster preguntará la siguiente información sobre la entidad que se desea construir:

  • Desea agregar un campo a la entidad?
    • Nombre del campo
    • Tipo del campo
    • Reglas de validación del campo

Para la entidad department se crearán el siguiente campo:

  • name
    • String

Una vez hecho esto JHipster preguntará si se desea agregar una relación a la entidad que se generó, en este caso se seleccionará que si se desea y se proporcionará la siguiente información:

  • Nombre de la otra entidad : Employee
  • Nombre de la relación  :employee
  • Tipo de relación : one-to-many
  • Nombre de la relación en la otra entidad: department

Cuando se termine JHipster preguntará lo siguiente:

  • Si desea crear otra relación: Seleccionaremos en este ejemplo que no.
  • Si se desea utilizar DTO para los endpoints que se generen. es importante mencionar que dichos DTO’s son creados utilizando MapStruct, para más información sobre esto ver el post Mapeo de Beans con MapStruct : Seleccionaremos en este ejemplo que no.
  • Si se desea que el servicio REST acceda directamente al repositorio de Spring data o si se desea que se genere un servicio separado para esto: Seleccionaremos en este ejemplo Yes, generate a separate service class.
  • Si se desea agregar paginación a la entidad consultada:Seleccionaremos en este ejemplo no.

Una vez hecho esto JHipster notificará los archivos a sobre escribir, seleccionaremos que si deseamos que lo haga y la entidad se generará de forma completa.

Creando entidad employee

En la definición de la entidad department se definió que existirá una relación entre ella y employee, pero falta definir la entidad employee con el fin de definir los campos que contendrá y las reglas de cada uno. Para esto ejecutaremos el siguiente comando:

yo jhipster:entity employee

Una vez que se ejecuta el comando, agregaremos los siguientes campos a la entidad:

  • name
    • String
  • age
    • Integer
  • salary
    • Double

El siguiente paso es definir las relaciones que tendrá la entidad employee, para este caso serán 2 la que se registrarán una con la entidad ya creada department y una con la entidad Skills que contendrá las habilidades del empleado, para hacerlo se responderá lo siguiente:

  • Desea agregar una relación a otra entidad: Si
    • Nombre de la otra entidad: Department
    • Nombre de la relación: department
    • Tipo de relación: many-to-one
    • Cuando se muestre esta relación en la aplicación, que campo de Department desea utilizar : id
    • Desea agregar validaciones a la relación: No
  • Desea agregar una relación a otra entidad : Si
    • Nombre de la otra entidad : Skill
    • Nombre de la relación  :skill
    • Tipo de relación : one-to-many
    • Nombre de la relación en la otra entidad: employee

Creando entidad skill

La última entidad será skill la cuál contendrá información sobre las habilidades del empleado. Para generarla ejecutaremos lo siguiente:

yo jhipster:entity skill

Una vez que se ejecuta el comando, agregaremos los siguientes campos a la entidad:

  • name
    • String
  • yearsExperience
    • Integer

Una vez definidos los campos de la entidad skill se definirá la relación que tiene con la entidad empleado:

  • Desea agregar una relación a otra entidad: Si
    • Nombre de la otra entidad: Employee
    • Nombre de la relación: employee
    • Tipo de relación: many-to-one
    • Cuando se muestre esta relación en la aplicación, que campo de Employee desea utilizar : id
    • Desea agregar validaciones a la relación: No

Probando todo junto

Con estos comandos ya se tiene lista la aplicación completa de JHipster lo único que resta es ejecutar los siguientes dos comandos:

mvn clean install

Construirá la aplicación de nuevo con los nuevos cambios generados con Spring boot.

mvn spring-boot:run

Ejecutará la aplicación y la pondrá disponible en la siguiente URL http://localhost:8080/.

A continuación se muestra como se ve la aplicación:

Captura de pantalla 2016-10-04 a las 9.00.18 a.m..png

Como se puede observar el menu Entities ahora contiene las 3 entidades que se generaron:

  • Department
  • Employee
  • Skill

Department

La primera pantalla de entidad que se mostrará será department ya que no depende de ninguna otra:

Captura de pantalla 2016-10-04 a las 9.09.08 a.m..png

Como se puede observar la entidad Department tiene la siguiente funcionalidad:

  • Crear nuevos departamentos
  • Ver departamentos existentes
  • Editar departamentos existentes
  • Borrar departamentos

Employee

La pantalla employee se ve como se muestra a continuación

Captura de pantalla 2016-10-04 a las 9.12.18 a.m..png

Como se puede observar la entidad Employee tine la siguiente funcionalidad:

  • Crear nuevos empleados
  • Ver empleados existentes
  • Editar departamentos existentes
  • Borrar departamentos existentes

Nota : Como se puede observar en la columna Department aparece un número el cual es el id del departamento con un link a la pantalla del departamento, la razón por la que aparece el id es porque se seleccionó “Cuando se muestre esta relación en la aplicación, que campo de Department desea utilizar”

Skill

La pantalla employee se ve como se muestra a continuación

captura-de-pantalla-2016-10-04-a-las-9-20-44-a-m

Otros recursos creados

Además de las pantallas creadas JHipster crea lo siguiente de forma automática:

Web services REST y documentación de los mismos:

Captura de pantalla 2016-10-04 a las 9.22.01 a.m..png

Estadísticas de uso de los servicios web:

Captura de pantalla 2016-10-04 a las 9.24.43 a.m..png

Código siguiendo buenas prácticas

Es importante saber que si se desea modificar la aplicación para cambiar la apariencia o mostrar otro tipo de información es posible hacerlo modificando el código, ya que el código generado por JHipster sigue buenas prácticas de desarrollo lo cual lo hace muy fácil de modificar.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

 

Spring Boot + REST Jersey (Agregando Spring Hateoas y Dto) Parte 3


En los ejemplos anteriores se explicó la configuración básica de Jersey utilizando Spring boot y la configuración de Spring data en el mismo proyecto. En este tutorial se explicará de forma simple tomando los dos proyectos anteriores como base el uso y funcionamiento de HATEOAS en REST utilizando Spring boot + Jersey.

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

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

2. Agregando una entidad extra, en el ejemplo anterior se explicó de forma simple como utilizar Spring data para obtener información y Spring Jersey para exponerla en web services REST. En dicho ejemplo se utilizó una entidad llamada User para realizar las pruebas, en este ejemplo se modificará la entidad User y se agregará una nueva llamada 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;

	........
}

Ahora se modifica la entidad usuario.

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

3. Actualización a scripts sql, la entidad usuario ha sido modificada y se ha agregado una entidad nueva llamada role, para soportar esto se deben realizar las siguientes modificaciones al schema de la la base de datos, del mismo modo que en el ejemplo anterior la definición del schema se realizará en el archivo schema.sql y la inserción de los datos de ejemplo en el archivo init.sql.

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 es la abreviatura de Hypermedia as the Engine of Application state y permitirá navegar a través de los recursos REST, de tal modo que cada uno de los recursos devueltos debe contener un link al recurso completo. Con esto es posible acceder a los recursos de forma completa sin necesidad de documentación.

4. Creando Dtos con soporte para HATEOAS, lo primero que se debe hacer para agregar soporte para HATEOS es crear Dto’s con soporte a los links para realizar la navegación.

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

..........
}

Se puede observar que los Dto’s creados heredan de la clase ResourceSupport la cuál brindará soporte para agregar links a los recursos que serán devueltos por los servicios REST.

5. Creando un AbstractAssembler, a continuación se muestra un assembler abstracto, el cuál servirá de base para generar los links para los recursos que serán devueltos por los servicios rest.

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

6. Creando mapper, el siguiente paso es crear un mapper para transformar de Entidades a Dtos de forma simple, para hacer esto se hará uso de MapStruct, para más información sobre la configuración de MapStruct Mapeo de Beans con 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);
}

7. Agregando Endpoints, El primer paso es asegurar que se cuenta con los endpoints tanto para “users” como para “roles”. Más adelante se detallarán los endpoints que contiene cada uno de ellos.

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 {
......
}

8. Registrando los servicios, Cómo se explicó en posts anteriores es necesario registrar cada servicio en Jersey para que funcione de forma correcta.

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

9. Creando los assemblers , el siguiente paso es crear los assemblers, estos serán los responsables de 2 puntos transformar de entities a Dto’s y agregar los links de hateoas.

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

10. Agregando Repositorio para la entidad Role

/**
 *
 */
package com.raidentrance.repositories;
 
import org.springframework.data.repository.CrudRepository;
 
import com.raidentrance.entities.Role;
 
/**
 * @author raidentrance
 *
 */
public interface RoleRepository extends CrudRepository<Role, Integer>{
 
}

11. Detallando los endpoints necesarios , A continuación se muestran los endpoints que se utilizarán para exponer los recursos y para generar la navegación entre los servicios.

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

12. Probando todo junto ,para ejecutar la aplicación el primer punto es generar el mapper de MapStruct utilizando el goal mvn generate-sources una vez generados los mappers es necesario compilar el proyecto y el proyecto queda listo para probarlo. Para esto se ejecutará la clase principal de la aplicación SprinBootSampleApplication, con esto se iniciará un contenedor.

Probando los recursos:

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"
}
]
}

 

Puedes encontrar el código completo del ejemplo en el siguiente enlace:
https://github.com/raidentrance/spring-boot-example/tree/part3-adding-hateoas .

 

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com