Documentar un servicio REST, con Swagger 2 (SpringFox)

Hace bastante tiempo publiqué un post en el que explicaba como documentar un servicio REST montado con Spring, con Swagger. Desde entonces, la librería usada para Spring ha cambiado bastante (incluso de nombre y de dueños…). En este post veremos como hacerlo con la nueva librería, SpringFox.

Concepto

Desde hace tiempo la antigua librería ‘swagger-springmvc‘, que servia para integrar swagger fácilmente en un proyecto Spring, ha pasado a llamarse SpringFox, y ha cambiado un poco. En sus últimas versiones soporta Swagger 2.0. Ya hablé un poco sobre Swagger en el post que comentaba antes, pero tenéis más info sobre swagger y sobre SpringFox en estos links:
http://swagger.io/
https://github.com/swagger-api
http://springfox.github.io/springfox/
https://github.com/springfox/springfox

En este post voy a documentar un servicio REST Spring con Swagger 2.0, usando SpringFox. Para ello usare como base un proyecto SpringBoot con un servicio REST ‘Infos’ que ya use en otros posts anteriores:
https://anotherdayanotherbug.wordpress.com/2015/03/16/tests-de-integracion-para-un-servicio-rest-con-spring/
https://anotherdayanotherbug.wordpress.com/2015/05/25/tests-de-integracion-usando-rest-assured/

Entorno usado:
Java JDK 1.8
Maven 3.3.9
Git 2.6.3
IDE Intellij 15.0.2 Ultimate version

Pasos

1. Creamos nuestro proyecto SpringBoot como siempre , con la versión 1.3. Lo principal que necesitamos es el pom.xml de Maven, la clase ‘main’ de SpringBoot … y listo.

2. Implementamos nuestro sencillo servicio REST. Un controller, que usa un Service, él cual usa un Repository (mockeado este último). Nuestra entity y el controller son estos:

public class Info {

    private Long id;
    private String infoText;
    private LocalDateTime creationDateTime;

    // getters and setters
}

@RestController
@RequestMapping("/api/infos")
public class InfoController {

    @Autowired
    private InfoService infoService;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Info> getAllInfos() {
        return infoService.findAll();
    }

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Info getInfo(@PathVariable Long id) {
        return infoService.findOne(id);
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Info> createInfo(@RequestBody Info info) {
        Info infoCreated = infoService.save(info);
        return new ResponseEntity<>(createHeadersWithLocation(infoCreated),
                HttpStatus.CREATED);
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.PUT, value = "{id}")
    public void updateInfo(@PathVariable Long id, @RequestBody Info info) {
        infoService.update(info.setId(id));
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.DELETE, value = "{id}")
    public void deleteInfo(@PathVariable Long id) {
        infoService.delete(id);
    }

    private HttpHeaders createHeadersWithLocation(Info info) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
                ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(info.getId())
                        .toUri());
        return httpHeaders;
    }
}

3. Vamos a empezar a integrarle Swagger 2 con SpringFox. Primero añadimos las librerías necesarias a nuestro pom.xml:

...
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.3.0</version>
        </dependency>
...

La principal que necesitamos es ‘springfox-swagger2‘.
La de ui es más bien un ‘webjar’ con la ui de swagger de siempre. Lo bueno es que esta todo en el jar, y no tenemos que bajarnos toda la ui en una subcarpeta del proyecto, como hice en el post anterior.

4. Creamos una clase de configuración de Spring, con la anotación @EnableSwagger2 y con el bean básico que necesita Swagger 2 (Docket):

package com.edwise.pocs.swagger2.config;

// imports...

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket newsApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("api-infos")
                .apiInfo(apiInfo())
                .directModelSubstitute(LocalDateTime.class, Date.class)
                .select()
                .paths(regex("/api.*"))
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Infos REST api")
                .description("PoC of a REST api, Infos")
                .termsOfServiceUrl("http://en.wikipedia.org/wiki/Terms_of_service")
                .contact("edwise.null@gmail.com")
                .license("Apache License Version 2.0")
                .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
                .version("2.0")
                .build();
    }
}

Esto es similar a la anterior configuración con la librería swagger-springmvc, pero aquí en lugar de un SwaggerSpringMvcPlugin, tenemos un bean llamado Docket, pero es similar. Tiene alguna mejora interesante. Por ejemplo, aquí estamos diciéndole que, a la hora de mostrar los tipos de dato LocalDateTime en la documentación de la API salgan como String (si no, te sale todos los objetos que contiene LocalDateTime, es un guarreo…)

5. Ya solo falta documentar nuestro api REST. Para eso, documentaremos primero la entity ‘Info’ así:

package com.edwise.pocs.swagger2.entity;

// imports...

@ApiModel(value = "Info entity", description = "Complete data of a entity Info")
public class Info {

    @ApiModelProperty(value = "The id of the info", required = false)
    private Long id;

    @ApiModelProperty(value = "The text of the info", required = true)
    private String infoText;

    @ApiModelProperty(value = "The date of the info", required = true)
    private LocalDateTime creationDateTime;

    // getters and settters
}

Apenas hay cambios respecto a la versión anterior. Usamos las anotaciones @ApiModel y @ApiModelProperty para documentar nuestro pojo.

6. Y ahora documentamos nuestro controller:

package com.edwise.pocs.swagger2.controller;

// imports...

@RestController
@RequestMapping("/api/infos")
@Api(value = "infos", description = "Infos API", produces = "application/json")
public class InfoController {

    @Autowired
    private InfoService infoService;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get Infos", notes = "Returns all infos")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Exits one info at least")
    })
    public List<Info> getAllInfos() {
        return infoService.findAll();
    }

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get one Info", notes = "Returns one info")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Exists this info")
    })
    public Info getInfo(@ApiParam(defaultValue = "1", value = "The id of the info to return")
                        @PathVariable Long id) {
        return infoService.findOne(id);
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Create Info", notes = "Create a info")
    @ApiResponses({
            @ApiResponse(code = 201, message = "Successful create of a info")
    })
    public ResponseEntity<Info> createInfo(@RequestBody Info info) {
        Info infoCreated = infoService.save(info);
        return new ResponseEntity<>(createHeadersWithLocation(infoCreated),
                HttpStatus.CREATED);
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.PUT, value = "{id}")
    @ApiOperation(value = "Update Info", notes = "Update a info")
    @ApiResponses({
            @ApiResponse(code = 204, message = "Successful update of a info")
    })
    public void updateInfo(@ApiParam(defaultValue = "1", value = "The id of the info to update")
                           @PathVariable Long id,
                           @RequestBody Info info) {
        infoService.update(info.setId(id));
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.DELETE, value = "{id}")
    @ApiOperation(value = "Delete Info", notes = "Delete a info")
    @ApiResponses({
            @ApiResponse(code = 204, message = "Successful delete of a info")
    })
    public void deleteInfo(@ApiParam(defaultValue = "1", value = "The id of the info to delete")
                           @PathVariable Long id) {
        infoService.delete(id);
    }

    private HttpHeaders createHeadersWithLocation(Info info) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
                ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(info.getId())
                        .toUri());
        return httpHeaders;
    }
}

Tampoco cambia nada, seguimos usando las mismas anotaciones para los controlers: @Api, @ApiOperation, @ApiResponses, @ApiParam
En el anterior post sobre Swagger las explico un poco por encima, aunque creo que viendo el código es bastante sencillo entender como funcionan.

7. Arrancamos nuestro proyecto SpringBoot (mvn spring-boot:run). Si accedemos a http://localhost:8080/swagger-ui.html podemos ver la interfaz de Swagger y juguetear con nuestro API.
ScreenSwagger2

Si queremos acceder directamente a la info (en formato json) de Swagger, accedemos a http://localhost:8080/v2/api-docs?group=api-infos. El formato ha cambiado bastante respecto a la versión anterior, echadle un vistazo.

El código de este post está en mi github, proyecto springfox-swagger2-example.

Expresiones regulares y ‘wildcards’ en servicios REST con JAX-RS

En el proyecto en el que estoy trabajando nos surgió hace poco la necesidad de que la ruta de un ‘endpoint’ de un servicio REST pudiera recibir, como variable en el path, una cadena que fuera una ruta de directorios, por ejemplo:

/micarpeta/otracarpeta/nombrefichero

Si eso lo intentamos hacer definiendo un ‘endpoint’ como este:

/api/files/{filepath}

… no funciona. La url completa sería /api/files/micarpeta/otracarpeta/nombrefichero, y no va a encontrarla, dado que no tenemos ningún endpoint definido así realmente.

Aunque inicialmente parece que no es posible conseguir un endpoint que se trague eso, si que se puede hacer :). Vamos a verlo.

A la hora de definir los ‘endpoints’ o urls de nuestros Resources (o Controllers en Spring), tenemos los básicos: endpoints fijos o los típicos con ‘path variable’:

Endpoint fijo:

/api/customers/orders

Con path variable:

/api/customers/{id}
/api/customers/{id}/orders/{idOrder}

Pero no son las únicas opciones, vamos a ver alguna más, En un Resource con url base ‘/api/foo’:

  • Podemos tener un ‘endpoint’ que tenga un parámetro como path variable que obligatoriamente sea un entero:
        
        @GET
        @Path("onlyinteger/{id : \\d+}")
        @Produces(MediaType.TEXT_PLAIN)
        public String getFooWithIntegerId(@PathParam("id") int id) {
            return Integer.toString(id);
        }
    

    De esta manera, si accedemos a la url ‘/api/foo/onlyinteger/ID_123’, no funcionará. Tiene que ser un número entero si o si: ‘/api/foo/onlyinteger/123’.

  • Podemos también concatenar varios parámetros, sin usar la barra ‘/’, por ejemplo, con un guión:
        @GET
        @Path("twoparams/{firstname}-{surname}")
        @Produces(MediaType.TEXT_PLAIN)
        public String getFooWithNameAndSurname(@PathParam("firstname") String firstname,
                                               @PathParam("surname") String surname) {
            return firstname + " " + surname;
        }
    

    Ejemplo de url: ‘/api/foo/twoparams/bilbo-baggins’.

  • Y si queremos que el parámetro sea, como lo que comentaba al comenzar el post, una cadena en la que pueda venir una ruta de ficheros:
        @GET
        @Path("wildcard/{subpath : .+}")
        @Produces(MediaType.TEXT_PLAIN)
        public String getFooWithWildcard(@PathParam("subpath") String subpath) {
            return subpath;
        }
    

    Si accediéramos, por ejemplo, a esta url: ‘/api/foo/wildcard/midir/anotherdir/ficName.txt’, funcionaria.

Si queréis juguetear con un ejemplo, en mi github he creado un proyecto (jaxrs-wildcards-example), arrancable con jetty desde maven (‘mvn jetty:run’), con el que podéis probar el código del post.

Implementar un cliente REST con JAX-RS (Jersey)

Hace tiempo escribí un post explicando como implementar un cliente REST con el cliente de Spring (RestTemplate), así que ahora haremos lo mismo pero con un cliente JAX-RS (Jersey).

Concepto:

La implementación Jersey de JAX-RS nos provee también de un cliente REST para poder acceder a servicios REST facilmente, de una manera muy similar a la del RestTemplate de Spring.

El cliente de Jersey es también configurable, y le podemos «registrar» módulos, por ejemplo para logging, para parsear los jsons (jackson), etc.

En el ejemplo, al igual que en el post de RestTemplate, veremos como llamar al servicio con los métodos básicos (GET, POST, PUT, DELETE), directamente desde una aplicación java desde su método ‘main’. Lo haremos, tanto atacando contra un servicio definido en apiary, como contra un servicio arrancado en local. Vamos a ello.

Entorno usado:
Java JDK 1.8
Maven 3.2.5
Git 2.5.1
IDE Intellij 14.1.4 Ultimate version

Ejemplo con apiary:

1. Usaré otra vez el servicio definido en mi cuenta de apiary, con un recurso ‘book’.
La url del servicio: http://docs.booksapi.apiary.io/

2. Creamos un proyecto maven básico, y añadimos las siguientes dependencias:

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>2.21</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.jaxrs</groupId>
            <artifactId>jackson-jaxrs-json-provider</artifactId>
            <version>2.6.1</version>
        </dependency>
    </dependencies>

La librería jersey-client es la única que necesitamos de Jersey. También necesitamos un ‘provider’ de Jackson, para el parseo de los jsons. Lo veremos al instanciar el cliente.

3. Creamos una típica clase con método main, con las rutas a nuestro servicio:

package com.edwise.pocs.jerseyrestclient;

public class AppBookRestApiary {

    private static final String URL_BASE_API =
            "http://private-114e-booksapi.apiary-mock.com/";
    private static final String BOOKS_RESOURCE = "books/";

    public static void main(String[] args) {
      
    }
}

4. Creamos una entity, exactamente igual que la usada en el post con RestTemplate:

package com.edwise.pocs.jerseyrestclient.model;

public class Book {
    private Long id;
    private String title;

    // getter and setters...

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

Entity muy simple, que cumple la ‘interfaz’ del servicio.

5. Instanciamos nuestro cliente con Jersey:

    public static void main(String[] args) {
        Client client = ClientBuilder.newClient(
                new ClientConfig().register(JacksonJsonProvider.class));
        WebTarget webTarget = client.target(URL_BASE_API).path(BOOKS_RESOURCE);
      
    }

Para instanciar un cliente, usamos un método estático de la clase ClientBuilder. Como parámetro hay que pasarle un ClientConfig, que en este caso le registramos el provider de Jackson, para el parseo de los jsons.
Por último, le pasamos la url base de nuestro servicio, así como el path concreto. Esto nos devuelve un objeto WebTarget que será el que usaremos para ‘hablar’ con el servicio.

6. Implementamos ahora la llamada a cada uno de los métodos que acepta el servicio:

– GET all: para obtener todos los ‘books’:

        Response response = webTarget
                .request(MediaType.APPLICATION_JSON_TYPE)
                .get();

        System.out.println();
        System.out.println("GET All StatusCode = " + response.getStatus());
        System.out.println("GET All Headers = " + response.getHeaders());
        System.out.println("GET Body (object list): ");
        Arrays.asList(response.readEntity(Book[].class))
                .forEach(book -> System.out.println("--> " + book));

Con el método request de la interfaz WebTarget haremos todas las peticiones. Le pasamos como parámetro el tipo a recibir, y llamamos al metodo get.
Esto nos devuelve un objeto Response en el que podemos acceder como siempre al código html devuelto, los ‘headers’, etc.
Para el parseo del json a objeto, usamos el método readEntity, pasandole el tipo como parámetro (un array en este caso).

– GET: para obtener un ‘book’ en concreto, por su id:

        Response response = webTarget.path("12")
                .request(MediaType.APPLICATION_JSON_TYPE)
                .get();

        System.out.println();
        System.out.println("GET StatusCode = " + response.getStatus());
        System.out.println("GET Headers = " + response.getHeaders());
        System.out.println("GET Body (object): "
                + response.readEntity(Book.class));

Es similar al anterior, pero le añadimos al path el id (quedaría como /books/12).
Esta vez en el readEntity el tipo a recibir es un único objeto, no un array.

– POST: para insertar un ‘book’ nuevo:

        Book bookToInsert = createBook(null, "New book title");
        Response response = webTarget
                .request()
                .post(Entity.entity(bookToInsert, MediaType.APPLICATION_JSON_TYPE));

        System.out.println();
        System.out.println("POST executed");
        System.out.println("POST StatusCode = " + response.getStatus());
        System.out.println("POST Header location = "
                + response.getHeaders().get("location"));

En este caso el método request no necesita tipo a recibir, dado que no devuelve ‘body’ el servicio para el POST. Con el método post le pasamos el objeto a insertar, gracias a la clase Entity y su método estático entity en el que además le pasamos el MediaType al que parsear el objeto.
Para obtener un header en concreto, usamos su método get, en este caso obtenemos el ‘location’.

– PUT: para actualizar un ‘book’ en concreto, por su id:

        Book bookToUpdate = createBook(123L, "Book title updated");
        Response response = webTarget.path("123")
                .request()
                .put(Entity.entity(bookToUpdate, MediaType.APPLICATION_JSON_TYPE));

        System.out.println();
        System.out.println("PUT StatusCode = " + response.getStatus());
        System.out.println("PUT Headers = " + response.getHeaders());

Muy similar al POST, excepto que le añadimos al ‘path’ el id del recurso a actualizar. Usamos el método put.

– DELETE: para borrar un ‘book’ en concreto, por su id:

        Response response = webTarget.path("12")
                .request()
                .delete();

        System.out.println();
        System.out.println("DELETE StatusCode = " + response.getStatus());
        System.out.println("DELETE Headers = " + response.getHeaders());

No necesita mucha explicación: añadimos al path el id del recurso a borrar, y llamamos al método delete.

Ejemplo contra un servicio en local:

1. Si no lo tenemos todavía, descargamos mi proyecto integrationtests-rest-example de mi github, y lo arrancamos con ‘mvn spring-boot:run’.

2. Creamos una clase para la entity que nos devuelve el servicio:

package com.edwise.pocs.jerseyrestclient.model;

import java.time.LocalDateTime;

public class Info {

    private Long id;
    private String infoText;
    private LocalDateTime creationDateTime;

    // getters and setters...

    @Override
    public String toString() {
        return "Info{" +
                "id=" + id +
                ", infoText='" + infoText + '\'' +
                ", creationDateTime=" + creationDateTime +
                '}';
    }
}

3. Como en la entity tenemos un campo de tipo LocalDateTime (de la nueva API Date de Java 8, especificación JSR310), necesitamos una de los submódulos de jackson para parseo. Añadimos la siguiente dependencia en nuestro pom.xml:

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.6.1</version>
        </dependency>

4. Creamos otra clase con método main en la que implementaremos las llamadas a los métodos del servicio (igual que antes). En el método, instanciamos el cliente REST de la siguiente manera:

package com.edwise.pocs.jerseyrestclient;

// imports...

public class AppInfoRestLocal {

    private static final String URL_BASE_API = "http://localhost:8080/api/";
    private static final String INFO_RESOURCE = "info/";

    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        Client client = ClientBuilder.newClient(
                new ClientConfig().register(new JacksonJsonProvider(mapper)));
        WebTarget webTarget = client.target(URL_BASE_API).path(INFO_RESOURCE);

    }

Para crear el cliente en este caso, necesitamos ‘personalizar’ el provider de Jackson para que parsee correctamente los campos de tipo ‘Java 8 API Date’. Para ello instanciamos un mapper de Jackson, le registramos el módulo de para la jsr310 (JavaTimeModule ahora, JSR310Module ha sido deprecado hace poco), y usamos ese mapper para crear nuestro provider de Jackson para jsons.

5. El resto es casi identico al caso anterior, podéis ver el código en mi github: AppInfoRestLocal

Todo el código de los dos ejemplos está subido en mi github, proyecto jersey-rest-client-example. Se puede ejecutar cada ejemplo ejecutando su clase ‘main’, la cual ejecuta todos los métodos.

Tests de integración usando REST-assured

Vamos a volver un poco más con el testing 🙂 En mi último post sobre este tema, tests de integración contra un servicio REST, con Spring, expliqué como implementar esos tests de integración con las herramientas que nos provee Spring (MockMvc principalmente). Como ya comenté en alguno de mis posts de testing, hay otras librerías para realizar este tipo de test, como es REST-Assured. Y es la que vamos a usar en este para implementar unos tests completos contra un servicio REST, de manera muy sencilla y legible.

Concepto:
En varios posts anteriores expliqué el concepto de tests de integración y configuramos tanto maven como Spring para poder implementarlos, y en otro implementé los tests usando MockMvc de Spring y la librería jsonpath.
En este caso, voy a implementar unos tests de integración contra un servicio REST (el mismo usado en los últimos posts, un REST para un recurso ‘Info’), pero esta vez lo haré usando la librería REST-Assured, en lugar del clásico MockMvc. Está librería nos ofrece una manera muy limpia y eficiente de realizar este tipo de tests, en «formato» BDD: Given – When – Then
Como base usaré el mismo proyecto que ya monté en esos posts, con toda la configuración de maven y Spring para tener tests de integración.

Entorno usado:
Java JDK 1.8
Maven 3.2.5
Git 1.9.5
IDE Intellij 14.1.3 Ultimate version

Pasos:

1. Recordamos como es el servicio REST a testear, este es el controller:

package com.edwise.pocs.itrestassured.controller;

// imports...

@RestController
@RequestMapping("/api/info/")
public class InfoController {

    @Autowired
    private InfoService infoService;

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Info> getAllInfos() {
        return infoService.findAll();
    }

    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, value = "{id}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Info getInfo(@PathVariable Long id) {
        return infoService.findOne(id);
    }

    @RequestMapping(method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Info> createInfo(@RequestBody Info info) {
        Info infoCreated = infoService.save(info);
        return new ResponseEntity<>(createHeadersWithLocation(infoCreated),
                HttpStatus.CREATED);
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.PUT, value = "{id}")
    public void updateInfo(@PathVariable Long id, @RequestBody Info info) {
        infoService.update(info.setId(id));
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequestMapping(method = RequestMethod.DELETE, value = "{id}")
    public void deleteInfo(@PathVariable Long id) {
        infoService.delete(id);
    }

    private HttpHeaders createHeadersWithLocation(Info info) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
                ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(info.getId())
                        .toUri());
        return httpHeaders;
    }
}

Es el mismo controller del post anterior de testing, excepto que he implementado el método del post como debe ser.

2. Añadimos la dependencia de la librería REST-assured en nuestro pom.xml:

        <dependency>
            <groupId>com.jayway.restassured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>2.4.1</version>
            <scope>test</scope>
        </dependency>

Con ‘scope’ test, por supuesto, solo lo necesitamos en nuestros tests.

3. Tendremos la misma clase de tests de integración que en el post anterior:

package com.edwise.pocs.itrestassured.controller;

// imports...

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest({"server.port=0"})
public class InfoControllerIT {
    
}

Con la misma configuración de Spring, ya explicada en su momento.

4. Primero necesitamos configurar un pequeño detalle en REST-assured: el puerto al que atacar. En nuestro test de integración, con la anotación ‘@WebIntegrationTest({«server.port=0»})’ lo que le decimos a Spring, entre otras cosas, es que arranque un servidor con todo el contexto necesario, en el puerto que le decimos ahí. Si el puerto es 0, usará un puerto aleatorio… así que haremos lo siguiente para configurarlo en REST-assured:

package com.edwise.pocs.itrestassured.controller;

// imports...

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest({"server.port=0"})
public class InfoControllerIT {

    @Value("${local.server.port}")
    private int serverPort;

    @Before
    public void setUp() {
        RestAssured.port = serverPort;
    }
   
}

Con la anotación de Spring @Value obtenemos el puerto sobre el que se ha arrancado, y en el método @Before de Junit se lo seteamos a Rest-Assured.

5. Implementamos nuestro primer test con REST-assured, el método GET /api/info/{id}, que obtiene un objeto Info por su id. Lo implementaremos así:

    @Test
    public void getInfo_InfoFound_ShouldReturnCorrectInfo() {
        given()
            .accept(ContentType.JSON)
            .pathParam("id", INFO_ID_1234)
        .when()
            .get("/api/info/{id}")
        .then()
            .statusCode(HttpStatus.SC_OK)
            .body(INFO_TEXT_FIELD, equalTo(INFO_TEXT_1234))
            .body(CREATION_DATE_TIME_FIELD, notNullValue());
    }

Todo el test es una linea de código (aunque lo hemos separado por partes, para ser más legible). En la parte de ‘given‘ prepararíamos ciertas cosas del test, en la de ‘when‘ realizamos el test, y en ‘then‘ hacemos las comprobaciones o ‘assertions’. El método ‘given’ lo importamos estaticamente.
En este caso, marcamos como que esperamos un json, seteamos en el parámetro id de la url un id, y con eso hacemos la petición GET. Después comprobamos el código http, y con el método ‘body‘ podemos hacer comprobaciones sobre los campos del json devuelto, en este caso con matchers de hamcrest (equalTo y notNullValue).

6. Implementamos ahora de la misma manera el GET /api/info/ que nos devolverá todos:

    @Test
    public void getAll_InfosFound_ShouldReturnFoundInfos() {
        given()
            .accept(ContentType.JSON)
        .when()
            .get("/api/info/")
        .then()
            .statusCode(HttpStatus.SC_OK)
            .body(INFO_TEXT_FIELD,
                    hasItems(INFO_TEXT_1234, INFO_TEXT_4567, INFO_TEXT_7892))
            .body(CREATION_DATE_TIME_FIELD, everyItem(notNullValue()));

    }

Muy similar al anterior, aquí en las comprobaciones con el método ‘body’ usamos otros matchers de hamcrest, en este caso que afectan a listas (hasItems y everyItems). Comprobando siempre cómodamente sobre un campo del json.

7. Continuamos con los POST y PUT, respectivamente:

    @Test
    public void postInfo_InfoCorrect_ShouldReturnCreatedStatusAndNoBody() {
        given()
            .body(createMockInfo(INFO_TEXT_1234_NEW, CREATION_DATE_TIME_NEW))
            .contentType(ContentType.JSON)
        .when()
            .post("/api/info/")
        .then()
            .statusCode(HttpStatus.SC_CREATED)
            .body(isEmptyString())
            .header(HEADER_LOCATION, containsString("/api/info/" + INFO_ID_1234));
    }

    @Test
    public void putInfo_InfoCorrect_ShouldReturnNoContentStatus() {
        given()
            .body(createMockInfo(INFO_TEXT_1234_UPDATED, CREATION_DATE_TIME_UPDATED))
            .contentType(ContentType.JSON)
            .pathParam("id", INFO_ID_1234)
        .when()
            .put("/api/info/{id}")
        .then()
            .statusCode(HttpStatus.SC_NO_CONTENT);
    }

    private Info createMockInfo(String text, LocalDateTime creationDateTime) {
        return new Info()
                .setInfoText(text)
                .setCreationDateTime(creationDateTime);
    }

En los dos casos, en el ‘given’, con ‘body’ lo que hacemos es pasarle el objeto a enviar. Podriamos pasarselo en formato json en un String, pero no es necesario, él se encarga de hacer el parseo. En el caso del POST, en las comprobaciones, revisamos que el ‘location’ de la respuesta corresponda con la url del nuevo recurso creado.

8. Y por último, el DELETE:

    @Test
    public void deleteInfo_ShouldReturnNoContentStatus() {
        given()
            .pathParam("id", INFO_ID_1234)
        .when()
            .delete("/api/info/{id}")
        .then()
            .statusCode(HttpStatus.SC_NO_CONTENT);
    }

Nada nuevo en este caso.

Con esto habriamos terminado nuestro test de integración. La clase completa la podeís ver aquí.
Como vemos, nos quedan unos tests de integración muy limpios, y siguiendo la «nomenclatura» típica de Given-When-Then, y en plan ‘fluent interface’. A mi personalmente me gustan bastante los tests usando MockMvc, pero hay que reconocer que REST-assured facilita mucho tanto la implementación como la legibilidad de nuestros tests.

Si queréis descargaros el proyecto completo, lo tenéis como siempre en mi github, proyecto integrationtests-restassured-example.

 

Implementar un cliente REST con Spring: RestTemplate

He hablado ya mucho de servicios REST y de como montarlos pero, y ¿si queremos acceder a un servicio REST desde nuestra aplicación? Hay varias maneras, desde hacerlo «a pelo» creando una conexión http, o usando clientes Rest. Para este post vamos a ver en concreto el cliente de Spring, RestTemplate.

Concepto:
Spring nos provee de un cliente Rest, RestTemplate, muy sencillo de usar. El se encarga de realizar la conexión http, lo único que hace falta es pasarle la url del servicio contra el que conectar. El mismo RestTemplate gestiona sus propios ‘messageConverters’, con los que parsear los datos enviados y recibidos de/a json, por ejemplo. Solo es necesario añadir en nuestro proyecto la dependencia necesaria (Jackson, en nuestro caso).

En el ejemplo veremos como llamar al servicio con los métodos básicos (GET, POST, PUT, DELETE). Para hacerlo más sencillo, lo haremos directamente desde un programa java desde su método ‘main’. Además, lo haremos atacando contra un servicio definido en apiary. También realizaremos otro ejemplo contra un servicio arrancado en local, en concreto el proyecto integrationtests-rest-example de mi github.

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.1.2 Ultimate version

Ejemplo con apiary:

1. Voy a usar como servicio uno definido en mi cuenta de apiary, con un recurso ‘book’ Para el que no conozca apiary, es una web que nos ofrece el poder definir ‘mocks’ de servicios REST, contra los que poder atacar. Es muy útil a la hora de desarrollar.
En concreto vamos a atacar contra este servicio: http://docs.booksapi.apiary.io/

2. Nos creamos un proyecto maven básico (por ejemplo, con el archetype ‘maven-archetype-quickstart’), y añadimos las siguientes dependencias:

   
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.5.3</version>
        </dependency>
        ...
    </dependencies>

La dependencia de ‘spring-web’ es precisamente para poder tener acceso a la clase RestTemplate. La ‘jackson-databind’ es necesaria para el parseo de datos a json.

3. Creamos una clase con método main, en la que implementaremos los ejemplos:

package com.edwise.pocs.springrestclient;

public class AppBookRestApiary {

    private static final String URL_API_BOOKS =
            "http://private-114e-booksapi.apiary-mock.com/books/";

    public static void main(String[] args) {

    }

}

Tendremos la url a nuestro servicio (http://private-114e-booksapi.apiary-mock.com/books/) directamente en una constante.

4. Creamos una clase para la entity que nos devuelve el servicio:

package com.edwise.pocs.springrestclient.model;

public class Book {
    private Long id;
    private String title;

    public Long getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

Necesitamos este bean o entity para recuperar los datos, etc. Es tan sencillo como revisar el servicio, ver sus campos e implementar una clase java con esos campos. Jackson se encargará del parseo a/desde json.

5. Creamos nuestro objeto RestTemplate, con el que accederemos al servicio. Al crearse, por defecto ya tiene los converters necesarios para el parseo a/desde json, al tener como dependencia la librería Jackson.

    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();

    }

Es asi de simple. Tiene también disponible algún otro constructor, con parámetros, como los converters, etc. Pero con el constructor sin parámetros nos sobra.

6. Ahora probaremos cada uno de los métodos que acepta el servicio:

– GET all: para obtener todos los ‘books’:

    ResponseEntity<Book[]> response =
                restTemplate.getForEntity(URL_API_BOOKS, Book[].class);

    System.out.println();
    System.out.println("GET All StatusCode = " + response.getStatusCode());
    System.out.println("GET All Headers = " + response.getHeaders());
    System.out.println("GET Body (object list): ");
    Arrays.asList(response.getBody())
                .forEach(book -> System.out.println("--> " + book));

Tanto para get como para post, tenemos disponible dos tipos de métodos: getForObject/postForObject y getForEntity/postForEntity. Los segundos te devuelven una respuesta completa (es el que uso en el ejemplo), en la que poder obtener tanto headers, como código http devuelto, etc. Los ‘xxxForObject’ devuelven directamente el objeto (el body de la respuesta).
En este caso, le tenemos que pasar la url y el tipo esperado para el body (array de clase ‘Book’ en nuestro caso). Mostramos luego el código http, los headers y, en bucle, todos los ‘books’ devueltos. El método ‘getBody’ nos devolverá un ‘Book[]’, al haberlo parametrizado así en la llamada al get.

– GET: para obtener un ‘book’ en concreto, por su id:

    ResponseEntity<Book> response =
                restTemplate.getForEntity(URL_API_BOOKS + "{id}", Book.class, 12L);

    System.out.println();
    System.out.println("GET StatusCode = " + response.getStatusCode());
    System.out.println("GET Headers = " + response.getHeaders());
    System.out.println("GET Body (object): " + response.getBody());

Similar al caso anterior. En este caso, le pasamos Book.class como tipo esperado.
Por otro lado, el tercer parámetro serían las ‘pathvariables’ del endpoint. En este caso el id.

– POST: para insertar un ‘book’ nuevo:

    Book bookToInsert = createBook(null, "New book title");
    ResponseEntity<Book> response =
                restTemplate.postForEntity(URL_API_BOOKS, bookToInsert, Book.class);

    System.out.println();
    System.out.println("POST executed");
    System.out.println("POST StatusCode = " + response.getStatusCode());
    System.out.println("POST Header location = " + response.getHeaders().getLocation());

Creamos un nuevo ‘Book’, sin id, y lo enviamos por post, con un ‘postForEntity’. En el segundo parámetro le pasamos lo que seria el ‘body’ de la request (nuestro objeto) y en el tercer parámetro el tipo.
En este caso no mostramos el body de la respuesta, dado que lo único que nos devuelve es el ‘location’ (más info en mi anterior post)

– PUT: para actualizar un ‘book’ en concreto, por su id:

    Book bookToUpdate = createBook(123L, "Book title updated");
    restTemplate.put(URL_API_BOOKS + "{id}", bookToUpdate, 123L);

    System.out.println();
    System.out.println("PUT executed");

Aquí cambia un poco la cosa. Excepto para get y post, para el resto de métodos http, RestTemplate nos ofrece unos métodos más simples, demasiado en mi opinión. Como vemos en este caso, el método ‘put’ no devuelve nada. Si necesitáis poder comprobar el código http, los headers o algún otro dato de la respuesta, echadle un vistazo al método ‘exchange’. Es un método con el que podemos realizar cualquier tipo de llamada (GET, POST, etc).

– DELETE: para borrar un ‘book’ en concreto, por su id:

    restTemplate.delete(URL_API_BOOKS + "{id}", 12L);

    System.out.println();
    System.out.println("DELETE executed");

Igual que el caso de put 😦

Ejemplo contra un servicio en local:

1. Si no lo tenemos ya, nos descargamos mi proyecto integrationtests-rest-example de mi github, y lo arrancamos con ‘mvn spring-boot:run’.

2. Como antes, creamos una clase con método main, en la que implementaremos los ejemplos (igual que antes) y también creamos una clase para la entity que nos devuelve el servicio:

package com.edwise.pocs.springrestclient.model;

import java.time.LocalDateTime;

public class Info {

    private Long id;
    private String infoText;
    private LocalDateTime creationDateTime;

    public Long getId() {
        return id;
    }

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

    public String getInfoText() {
        return infoText;
    }

    public void setInfoText(String infoText) {
        this.infoText = infoText;
    }

    public LocalDateTime getCreationDateTime() {
        return creationDateTime;
    }

    public void setCreationDateTime(LocalDateTime creationDateTime) {
        this.creationDateTime = creationDateTime;
    }

    @Override
    public String toString() {
        return "Info{" +
                "id=" + id +
                ", infoText='" + infoText + '\'' +
                ", creationDateTime=" + creationDateTime +
                '}';
    }
}

En este caso usamos la nueva API Date de Java 8. Para que Jackson la parsee correctamente, tenemos que añadir una dependencia más:

    <dependencies>
        ...
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.5.3</version>
        </dependency>
    </dependencies>

4. El resto es prácticamente igual al caso anterior, no voy a poner el código, pero está disponible en mi github: AppInfoRestLocal

Tenéis todo el código de los dos ejemplos preparado para probarlo en mi github, proyecto spring-rest-client-example. Se puede ejecutar cada ejemplo ejecutando su clase ‘main’, la cual ejecuta todos los casos.

Cómo devolver el resultado de un POST en un servicio REST

En varios posts he implementado, con un framework u otro, un servicio REST normalmente muy sencillo. Al ser tan sencillos normalmente no cumplo al 100% las «especificaciones» o buenas prácticas que se recomiendan para considerar un servicio como REST. En este post voy a explicar como se debe implementar un POST, el resultado que devolvemos en él y en concreto el modo de hacerlo en Java.

Concepto:

En la mayoría de ejemplos de servicios REST que he implementado en este blog, para no complicar el código, lo que hago es simplemente devolver un 201 (CREATED). Incluso en algunos casos devuelvo en el body el objeto completo recién creado. Pero lo más correcto es no devolver nada en el ‘body’, y devolver en el ‘location’ de los ‘headers’ la url con la que acceder a ese recurso recién creado. La respuesta debería ser algo así:

HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:8080/api/info/12345
Content-Length: 0

Vamos a ver como devolver esta respuesta tanto en un proyecto con Spring MVC como en un proyecto con JAX-RS. Para ello modificaré los REST que hice para los posts de implementar un servicio Rest con Spring e implementar un servicio Rest con JAX-RS (Jersey).

Por último, comentaremos brevemente como sería si usáramos Spring HATEOAS.

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.5
IDE Intellij 14.1.1 Ultimate version

Con Spring MVC:

1. Usaremos el proyecto spring-rest-example el cual implementé en mi post Implementar un servicio Rest con Spring. Si abrimos nuestro controller, veremos que en el caso del POST no devolvemos nada:

    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public void insertUser(@RequestBody User user) {
        userService.save(user);
    }

2. Lo que necesitamos es devolver en los headers el location relleno con la url del objeto creado, sería algo así:

    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<User> insertUser(@RequestBody User user) {
        User userSaved = userService.save(user);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(...);

        return new ResponseEntity<>(httpHeaders, HttpStatus.CREATED);
    }

Creamos un objeto HttpHeaders, y necesitamos setearle el location (de tipo URI). Y en el return del método devolvemos un ResponseEntity con esos headers y el código HTTP de respuesta que corresponde a un POST (201 – Created).

La primera idea que se nos puede venir a la cabeza es meterle la url directamente, con un String o quizá obteniendo el dominio de alguna variable o propiedad, que tengamos en una constante. Con esto funcionaria, pero… vamos a hacerlo un poco mejor 🙂

3. Usaremos un builder que nos provee Spring para crear URIs: ServletUriComponentsBuilder, de la siguiente manera:

    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<User> insertUser(@RequestBody User user) {
        User userSaved = userService.save(user);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
                ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(userSaved.getId())
                        .toUri());

        return new ResponseEntity<>(httpHeaders, HttpStatus.CREATED);
    }

Con esto creamos nuestra URI, los métodos del builder son bastante autoexplicativos: con la request actual, le añadimos un path variable ‘/{id}’, y le metemos el id en esa variable.
(El código en mi github está algo refactorizado, pero es lo mismo 😛 )

4. Arrancando con el plugin maven de Spring Boot, como siempre, podemos probar nuestro POST. La respuesta del POST /api/users será algo así:

HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:8080/api/users/45332
Content-Length: 0
Date: Mon, 06 Apr 2015 18:23:50 GMT

5. (Bonus) Testing: Si tenemos test unitario de nuestro controller, se complica un poco. Tenemos que, o bien mockear los métodos del builder (es estático, necesitaríamos PowerMock o similar), o simplemente añadimos una MockHttpServletRequest al contexto, cosa que es lo que he hecho en el método @Before, para no complicarme mucho. El test completo, aquí
Si lo que tenemos son tests de integración, no habría que cambiar nada si el test está bien configurado.

Con JAX-RS (Jersey):

1. Usaremos el proyecto jersey-rest-example el cual implementé en mi post Implementar un servicio REST con JAX-RS (Jersey). Si abrimos nuestro controller, ahora mismo tenemos lo siguiente:

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response insertUser(User user) {
        userService.save(user);
        return Response.status(Response.Status.CREATED).build();
    }

2. Necesitamos, igual que antes devolver en los headers el location relleno con la url de nuestro recurso, en este caso, de esta manera:

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response insertUser(User user) {
        User userSaved = userService.save(user);
        URI uri = ...;
        return Response.created(uri).build();
    }

Aquí devolvemos un Response, que creamos con su método ‘created’ (esto le asigna ya como http status el 201 – Created) y le pasamos la uri que se asignará en el location.

3. Y al igual que antes, vamos a hacerlo con un builder que nos de la url, en este caso usaremos uno de la especificación JAX-RS: UriBuilder. Para ello, eso sí, necesitamos inyectar en nuestro controller un UriInfo, que se obtiene del contexto de la aplicación:

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response insertUser(User user) {
        User userSaved = userService.save(user);
        UriBuilder uriBuilder = uriInfo.getRequestUriBuilder();
        URI uri = uriBuilder.path(userSaved.getId().toString()).build();
        return Response.created(uri).build();
    }

Necesitamos un bean de contexto, UriInfo, con el que podremos obtener el builder concreto para nuestra URI. una vez con el builder, con su método ‘path’ le añadimos el id de nuestro recurso, y listo.
(El código en mi github está algo refactorizado, pero es lo mismo 😛 )

4. Arrancamos la aplicación (ya sea con un tomcat o con un glassfish, como vimos en el post de JAX-RS), y el servicio nos devolverá lo siguiente al hacer el POST:

HTTP/1.1 201 Created
Server: GlassFish Server Open Source Edition  4.1
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition  4.1  Java/Oracle Corporation/1.8)
Location: http://localhost:8080/api/users/45332
Date: Mon, 06 Apr 2015 18:50:13 GMT
Content-Length: 0

5. (Bonus) Testing: aquí, si también tenemos test unitario, igualmente se complica un poco. En este caso si he mockeado todo el tema de la UriInfo. El tests completo, aquí.

Con Spring HATEOAS:

No quiero entrar mucho en detalle, porque creo que esto da para otro post aparte que escribiré pronto, pero no quería terminar este post sin nombrar el concepto HATEOAS, así como el modulo de Spring que lo implementa. Si usamos Spring HATEOAS en nuestro proyecto, no necesitamos obtener el link a través de ningún builder, ya que nuestro propio recurso tendrá su link «self». Más información sobre HATEOAS aquí, así como sobre el proyecto Spring. Lo veremos en otro post.

Nada más. Recordad que todo el código de este post está subido en los proyectos spring-rest-example y jersey-rest-example de mi github.

Implementar un servicio Rest con JAX-RS (Jersey)

En breve seguiré con la serie de post de Spring Boot, pero antes quería escribir uno sobre como montar un servicio REST, pero sin nada de Spring, usando JAX-RS. La idea sobre todo es por, más adelante, intentar montar Swagger encima de un proyecto ‘no Spring’. Esto último lo dejaremos para otro post, por supuesto. Ahora vamos a montar un servicio REST básico usando Jersey (implementación de JAX-RS). El servicio será prácticamente idéntico al que montamos en el post Implementar un servicio Rest con Spring

Concepto:
Ya hablé un poco sobre el concepto REST en el post de Rest con Spring.
Respecto a JAX-RS, es la especificación oficial de servicios Rest que ofrecen los chicos de Java, y Jersey es su implementación más ‘standard’ (o ‘reference implementation’).

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.4
IDE Intellij 14 Ultimate version
Server Tomcat 8.0.14
Server GlassFish 4.1 (opcional)

Pasos:

1. Creamos un nuevo proyecto maven en Intellij con el archetype ‘quickstart’ (los pasos a seguir para la creación del proyecto en el IDE son los mismos de mi post POC con Spring Boot, los pasos 1 al 4).

2. Añadimos las dependencias maven de jersey que necesitamos, en nuestro pom:

...
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>2.13</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
        </dependency>

        ...
    </dependencies>
...

La primera, en dependencyManagement, es la dependencia principal para montar un proyecto con jersey.
El artefacto jersey-container-servlet es el básico si queremos tener soporte servidor.
Las otras dos son dependencias jackson necesarias para el parseo de jsons.

2. Vamos a montar una aplicación web sin web.xml. Para ello crearemos la clase que hace las veces de ese web.xml, heredando de ResourceConfig:

package com.edwise.pocs.jerseyrest;

import org.glassfish.jersey.server.ResourceConfig;
import javax.ws.rs.ApplicationPath;

@ApplicationPath("api")
public class RestApplication extends ResourceConfig {

}

Por ahora es suficiente así. Con esto configuramos una aplicación web, en la ruta ‘/api’.

3. Para que maven no se queje a la hora de compilar nuestro proyecto, diciendo que no hay ‘web.xml’, necesitamos añadir la propiedad ‘failOnMissingWebXml’ a la configuración del plugin war de maven (lo cual nos obliga a añadir toda la configuración de ese plugin en nuestro pom.xml):

...
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
                <version>2.1.1</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
...

4. Comenzamos a implementar nuestro servicio en si. Para ello usamos la misma entidad User que ya usamos en el post de Rest con Spring. Podéis ver el código aquí.
Como tenemos un campo fecha en nuestro User que usa la librería joda-time, añadimos también al pom.xml las dependencias necesarias (joda-time y jackson-datatype-joda).
Podéis ver esto más en detalle en el paso 2 de aquel post.

5. Implementamos el servicio que usara nuestro controller (en jax-rs se les suele llamar ‘resource’ a los controllers, pero yo lo llamaré controller). El servicio es idéntico al usado en el post de Rest con Spring. Podéis ver el código en github, de la interfaz y del implementado (es un mock).

6. Vamos ahora con la implementación del controller:

package com.edwise.pocs.jerseyrest.resource;

import com.edwise.pocs.jerseyrest.entity.User;
import com.edwise.pocs.jerseyrest.service.UserService;

import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;

@Path("users")
public class UserController {

    @Inject
    private UserService userService;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List getAllUsers() {
        return userService.findAll();
    }

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getUserById(@PathParam("id") long userId) {
        Response response;
        User user = userService.findById(userId);
        if (user == null) {
            response = Response.status(Response.Status.NOT_FOUND).build();
        } else {
            response = Response.ok(user).build();
        }
        return response;
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response insertUser(User user) {
        userService.save(user);

        return Response.status(Response.Status.CREATED).build();
    }

    @PUT
    @Path("/{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response updateUser(@PathParam("id") long userId, User user) {
        Response response;
        User userOld = userService.findById(userId);
        if (userOld != null) {
            userService.update(userOld.copyFrom(user));
            response = Response.status(Response.Status.NO_CONTENT).build();
        } else {
            response = Response.status(Response.Status.NOT_FOUND).build();
        }

        return response;
    }

    @DELETE
    @Path("/{id}")
    public Response deleteUser(@PathParam("id") long userId) {
        userService.delete(userId);
        return Response.status(Response.Status.NO_CONTENT).build();
    }
}

Lo explicamos un poco, aunque es bastante sencillito:
@Inject: como el @Autowired de Spring. Para ello, el servicio tiene que estar registrado en el contexto. Lo vemos más adelante…
@Path: similar al @RequestMapping de Spring. Con el tendremos en ‘/users’ nuestro servicio REST (en ‘/api/users’, por lo que pusimos en nuestra clase RestApplication)
@GET, @POST, @PUT, @DELETE: totalmente autoexplicativas: marcan cada método de la clase con el método HTTP usado para acceder a ellos (en Spring esto lo hacemos también en el @RequestMapping)
@Produces y @Consumes: El tipo de dato que devuelve o necesita como entrada. También lo teniamos en el @RequestMapping en Spring)
Clase Response: para devolver la información y el httpStatus, en plan builder con fluent interface.

7. Con esto ya tendríamos todo montado, pero faltan un par de temas importantes. El primero es la inyección de dependencias (DI) en JEE. No tenemos Spring, con lo que hay que configurar la CDI de JEE. Inicialmente es suficiente con poner un fichero beans.xml vacío bajo un directorio WEB-INF o META-INF en nuestra aplicación. Creamos entonces, dentro del directorio ‘main’, la siguiente estructura: ‘webapp/WEB-INF’ y dentro creamos nuestro beans.xml.

8. Ya podríamos arrancar nuestro servidor… pero no Tomcat. Si arrancamos esto con tomcat, nos va a dar un error en el @Inject del controller donde injectamos el servicio. Esto es por que Tomcat es solo un contenedor de servlets. Para tener CDI automática necesitamos un contenedor JEE, por ejemplo, GlassFish. Con GlassFish, ya tendríamos solucionado ese primer punto. Pero vamos a intentar resolverlo para Tomcat.

9. Para Tomcat tenemos que registrar los beans que vamos a usar en nuestra clase RestApplication. Quedaría así:

package com.edwise.pocs.jerseyrest;

import com.edwise.pocs.jerseyrest.service.UserService;
import com.edwise.pocs.jerseyrest.service.impl.UserServiceMock;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;

import javax.ws.rs.ApplicationPath;

@ApplicationPath("api")
public class RestApplication extends ResourceConfig {

    public RestApplication() {
        packages("com.edwise.pocs.jerseyrest");

        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(new UserServiceMock()).to(UserService.class);
            }
        });

    }
}

Para que el controller se registre basta la primera linea, donde le decimos en que paquetes buscar. Para el servicio, es necesario registrarlo usando un AbstractBinder, en el que ‘bindeamos’ la interfaz con la implementación que queremos asociarle.
Y con esto, ahora si, podríamos desplegarlo en un tomcat. Pero aún queda un pequeño detalle.

10. Al igual que nos paso en el post del servicio con Spring, en este tenemos también un campo fecha de tipo LocalDate, de la librería joda-time. Para poder realizar correctamente el parseo de json, necesitamos configurar correctamente el mapper que se encarga de ello. Al igual que con Spring, necesitamos registrar el JodaModule. Para ello necesitamos, por un lado, implementar un provider de json:

package com.edwise.pocs.jerseyrest.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JsonMapperProvider implements ContextResolver {

    final ObjectMapper objectMapper;

    public JsonMapperProvider() {
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JodaModule());
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> arg0) {
        return objectMapper;
    }

}

En él, registramos el modulo JodaModule, aparte de configurar un poco como queremos el formato de las fechas.

11. Para que registre correctamente los provider de Jackson, es necesario modificar otra vez nuestra RestApplication, que quedaría ya finalmente así:

package com.edwise.pocs.jerseyrest;

import com.edwise.pocs.jerseyrest.service.UserService;
import com.edwise.pocs.jerseyrest.service.impl.UserServiceMock;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;

import javax.ws.rs.ApplicationPath;

@ApplicationPath("api")
public class RestApplication extends ResourceConfig {

    public RestApplication() {
        packages("com.edwise.pocs.jerseyrest");

        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(new UserServiceMock()).to(UserService.class);
            }
        });

        register(JacksonFeature.class);
    }
}

En la linea resaltada, lo que hacemos es registrar una feature de Jackson. De esta manera ya si que pillará el provider, y tendremos un parseo correcto de las fechas en el servicio.

12. Creamos el war ejecutando ‘mvn clean package’ o desde la tool window de maven en Intellij. Y lo desplegamos en un tomcat (ya sea en Intellij, si tenemos la versión Ultimate, o a mano, en un tomcat aparte). Si accedemos desde un navegador o un cliente Rest a http://localhost:8080/api/users/ (o http://localhost:8080/jersey-rest-example-1.1/api/users/ si no lo tenemos desplegado en el raíz), nos devolverá algo como esto:

    [
       {
           "id": 12,
           "name": "Gandalf",
           "type": 1,
           "phone": "666554433",
           "birthDate": "1911-01-02"
       },
       {
           "id": 140,
           "name": "Aragorn",
           "type": 1,
           "phone": "661534411",
           "birthDate": "1923-07-16"
       },
       {
           "id": 45332,
           "name": "Frodo",
           "type": 2,
           "phone": "666222211",
           "birthDate": "1951-11-24"
       }
    ]

Si queréis descargarlos el proyecto completo, lo tenéis aquí. Hasta la próxima! 🙂

ACTUALIZADO: He actualizado el proyecto en github con los cambios que he hecho para el post Cómo devolver el resultado de un POST en un servicio REST.

Documentar un servicio REST, con Swagger

ACTUALIZACIÓN 21-12-2015: La librería ‘swagger-springmvc’ ha sido deprecada, ahora ha pasado a llamarse SpringFox. He escrito un nuevo post sobre como documentar un servicio REST con Swagger usando esa nueva librería SpringFox.

 

¡Ahora sí! Esta vez si que vamos a usar Swagger, para documentar un servicio REST implementado con Spring, y sobre Spring Boot. Para ello usaremos el servicio que ha preparamos en el post anterior.

Concepto:
Dado el auge de los servicios REST, se ha convertido en casi una obligación tener una buena documentación de esos mismos servicios, para que el cliente pueda fácilmente usarlo. Entre otras, Swagger parece que está destacando bastante últimamente, como librería para documentar fácilmente servicios REST en java.
Para más información de Swagger, aquí unos links interesantes:
http://swagger.io/
https://github.com/wordnik/swagger-spec
https://github.com/wordnik/swagger-ui
https://github.com/martypitt/swagger-springmvc

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.4
IDE Intellij 13.1.4 Community version

Pasos:

1. Como comenté antes, vamos a usar como base el proyecto que hicimos en el último post, en el que teníamos ya implementado un sencillo servicio REST en Spring, sobre un proyecto Spring Boot. Podéis bajaros el proyecto directamente de github.

2. Añadimos las dependencias maven necesarias a nuestro pom.xml, para poder usar swagger, en concreto swagger-springmvc, que es el que necesitamos para montarlo sobre Spring Boot.

    ...
       <dependency>
           <groupId>com.mangofactory</groupId>
           <artifactId>swagger-springmvc</artifactId>
           <version>0.8.8</version>
       </dependency>
    ...

Con esta dependencia es suficiente para toda la parte ‘back’ de swagger.

3. Para que Swagger funcione, necesitamos definir en nuestro contexto de Spring un bean ‘SwaggerSpringMvcPlugin‘. Para ello, podemos definirlo directamente, en nuestra clase ‘Application.java’ (que la tenemos marcada como @Configuration) o crear una nueva clase de configuración. Haremos esto último, para que todo este un poco más organizado:

package com.edwise.pocs.swagger.config;

import com.mangofactory.swagger.configuration.SpringSwaggerConfig;
import com.mangofactory.swagger.plugin.SwaggerSpringMvcPlugin;
import com.wordnik.swagger.model.ApiInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerSpringMvcConfig {

    private SpringSwaggerConfig springSwaggerConfig;

    @Autowired
    public void setSpringSwaggerConfig(SpringSwaggerConfig springSwaggerConfig) {
        this.springSwaggerConfig = springSwaggerConfig;
    }

    @Bean
    public SwaggerSpringMvcPlugin customImplementation() {
        return new SwaggerSpringMvcPlugin(this.springSwaggerConfig)
                .apiInfo(apiInfo())
                .includePatterns(".*/*.*");
    }

    private ApiInfo apiInfo() {
        return new ApiInfo(
                "Users API",
                "Your user database!",
                "http://userweb.userapi.com/Terms_of_service",
                "userapi.manager@gmail.com",
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0.html"
        );
    }
}

Principalmente lo que hacemos en esta clase de configuración es cargar el bean SwaggerSpringMvcPlugin, en el método ‘customImplementation’, añadiéndole cierta información básica de configuración:
– Con la clase ‘ApiInfo‘ le metemos varios textos generales para nuestra api.
– con el método ‘includePatterns‘ filtramos los servicios que queremos que muestre nuestra documentación Swagger (en este caso mostramos todos).

4. Añadimos a nuestra clase base para Spring Boot ‘Application.java’ la anotación @EnableSwagger. Tambien podriamos añadirla en la clase creada antes. El único requisito es que la clase sea de configuración (@Configuration).

5. Arrancamos nuestra aplicación Spring Boot como siempre (mvn spring-boot:run o desde la tool window de maven en Intellij). Si accedemos a la url http://localhost:8080/api-docs nos devolverá un json con cierta información de nuestros servicios REST (entre otras cosas los datos que le pasamos en ApiInfo en nuestra clase de configuración). Ya tenemos swagger funcionando realmente, pero ahora faltaría documentar un poco nuestro REST, y poder verlo en alguna web un poco en condiciones…

6. Vamos a empezar por bajarnos la interfaz web para swagger. Para ello podemos, o añadir la dependencia maven, o bajarnos toda la interfaz y copiarla en nuestro proyecto. Dado que necesitamos modificarle alguna cosa (ahora lo veremos), lo mejor es lo segundo. Para ello, nos descargamos el repositorio git de aquí y copiamos todo el contenido de la carpeta ‘dist’ a una subcarpeta en nuestro proyecto, dentro de ‘src/main/webapp’ llamada ‘swagger’. Nuestra estructura de carpetas quedaría tal que así:

Estructura carpetas

Estructura de carpetas

7. Vamos a ‘costumizar’ un poco la interfaz, ya que por defecto apunta al swagger de ejemplo que tienen en la web (la petstore esa…). Abrimos el archivo index.html que tenemos directamente en nuestra subcarpeta ‘swagger’ y buscamos la siguiente linea en código javascript:

        ...
	url = "http://petstore.swagger.wordnik.com/v2/swagger.json";
        ...

Y sustituimos esa url por la nuestra, que será así: http://localhost:8080/api-docs . O podemos también no hardcodear la url de vuestro server, y dejarlo un poco más bonito sustituyéndolo por algo así:

        ...
	url = window.location.protocol + "//" + window.location.host + "/api-docs";
        ...

Con esto ya podemos arrancar nuestro server y accediendo a http://localhost:8080/swagger/index.html, ver la interfaz web de swagger funcionando contra nuestro servicio. Veremos algo como esto:

Swagger UI filtrada

Swagger por defecto

Entre otras cosas veremos que a nuestro servicio lo ha llamado ‘user-controller’ y que hay otro servicio más, basic-error-controller. Este último es parte de Spring, no le hagamos mucho caso.
Nos sigue faltando documentar en condiciones nuestro servicio REST (por ahora swagger nos muestra todo con textos por defecto), y ya de paso filtrar los servicios para que muestre solo el nuestro. ¡Vamos con ello!

7. Vamos a filtrar los servicios que mostramos. Podríamos filtrar por el nombre de nuestro servicio, pero para poder añadir otros, vamos a filtrar por la ruta del servicio, que sea ‘/api/loquesea’. Nuestro servicio actual es directamente /user, así que lo primero que vamos a hacer es modificar su url. En nuestra clase UserController modificamos el RequestMapping para dejarlo así:

...
@RestController
@RequestMapping("/api/user")
public class UserController {
...

Y ahora modificamos nuestra clase de configuración de Swagger, SwaggerSpringMvcConfig, para que filtre los servicios por un patrón:

    ...
    @Bean
    public SwaggerSpringMvcPlugin customImplementation() {
        return new SwaggerSpringMvcPlugin(this.springSwaggerConfig)
                .apiInfo(apiInfo())
                .includePatterns(".*api/*.*");
    }
    ...

Si arrancamos el servidor y accedemos otra vez a http://localhost:8080/swagger/index.html veremos que ahora ya no nos muestra nada más que nuestro servicio. (Ojo, la url para acceder al servicio REST ahora es: http://localhost:8080/api/user)

8. Ahora vamos a documentar nuestro servicio en condiciones, directamente en nuestras clases java (nada de xmls, ni historias de esas). Para ello swagger proporciona distintas anotaciones java. Vamos a ver y añadir algunas a nuestro servicio:
Primero documentaremos nuestro modelo, la clase User, quedaría así:

...
import com.wordnik.swagger.annotations.ApiModel;
import com.wordnik.swagger.annotations.ApiModelProperty;
import org.joda.time.LocalDate;

@ApiModel(value = "User entity", description = "Complete info of a entity user")
public class User {

    @ApiModelProperty(value = "The id of the user")
    private Long id;

    @ApiModelProperty(value = "The name of the user", required = true)
    private String name;

    @ApiModelProperty(value = "The type of the user", required = true)
    private Integer type;

    @ApiModelProperty(value = "The phone of the user", required = false)
    private String phone;

    @ApiModelProperty(value = "The birthDay of the user", required = false)
    private LocalDate birthDate;

    // getters, setters, etc
}

Las anotaciones son bastante ‘autoexplicativas’, pero por aclarar:
@ApiModel documenta la entity, con una descripción corta (value) y una descripción más larga (description).
@ApiModelProperty documenta las propiedades, con una descripción (value) y si es obligatoria (required). También contiene otros parámetros de posibles valores (allowableValues), ocultarlo a swagger (hidden) y otros.

Ahora documentamos nuestro controller y cada uno de sus métodos, quedando así:

...
// imports...

@RestController
@RequestMapping("/api/user")
@Api(value = "users", description = "Users API")
public class UserController {

    private static final int RESPONSE_CODE_OK = 200;
    private static final int RESPONSE_CODE_CREATED = 201;
    private static final int RESPONSE_CODE_NOCONTENT = 204;
    private static final int RESPONSE_CODE_NOTFOUND = 404;

    @Autowired
    private UserService userService;

    @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get Users", response = List.class, notes = "Returns all users")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_OK, message = "All users in db")
    })
    public List getAllUsers() {
        return userService.findAll();
    }

    @RequestMapping(value = "/{userId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "Get one User", response = ResponseEntity.class, notes = "Returns one user")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_OK, message = "Exists this user"),
            @ApiResponse(code = RESPONSE_CODE_NOTFOUND, message = "No exists this user")
    })
    public ResponseEntity getUser(@PathVariable long userId) {
        User user = userService.findById(userId);
        ResponseEntity response;
        if (user == null) {
            response = new ResponseEntity<>(HttpStatus.NOT_FOUND);
        } else {
            response = new ResponseEntity<>(user, HttpStatus.OK);
        }
        return response;
    }

    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    @ApiOperation(value = "Insert User", notes = "Insert a complete new User")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_CREATED, message = "User inserted")
    })
    public void insertUser(@RequestBody User user) {
        userService.save(user);
    }

    @RequestMapping(value = "/{userId}", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.CREATED)
    @ApiOperation(value = "Update User", notes = "Updates a user")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_CREATED, message = "User updated")
    })
    public void updateUser(@PathVariable long userId, @RequestBody User user) {
        User userOld = userService.findById(userId);
        if (userOld != null) {
            userService.update(userOld.copyFrom(user));
        }
    }

    @RequestMapping(value = "/{userId}", method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @ApiOperation(value = "Delete User", notes = "Deletes a user")
    @ApiResponses({
            @ApiResponse(code = RESPONSE_CODE_NOCONTENT, message = "Deleted user")
    })
    public void deleteUser(@PathVariable long userId) {
        userService.delete(userId);
    }
}

Aunque aquí también son bastante legibles las anotaciones, explico un poco:
@Api documenta el servicio REST en si. Va a ser la descripción que salga en el listado, entre otras cosas.
@ApiOperation documenta cada método del servicio.
@ApiResponses documenta las posibles respuestas del método, con mensaje explicativo.
(Hay alguna anotación más, para los parámetros, etc, pero estas son las que me parecen más interesantes)

9. Arrancamos el servidor, con mvn spring-boot:run o desde la tool window de maven en Intellij. Accedemos a http://localhost:8080/swagger/index.html y veremos algo como antes, pero mucho más detallado y documentado 🙂

Swagger UI por defecto

Swagger UI filtrada y documentada

Swagger permite probar el servicio desde su propia interfaz, consultar el modelo a enviar o recibido, etc. Jugad un poco con él 🙂

Si quieres bajarte el proyecto completo, lo he subido a mi github 🙂
Cualquier duda o sugerencia, podéis dejarme un comentario.