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

Anuncios

1 comentario »

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s