GeoBack. Un sencillo ejemplo. 3ª parte

En este tercer Post de pruebas de GeoBack, vamos a realizar pruebas a nuestra API REST con Postman. Es una herramienta muy útil, que nos permite realizar peticiones HTTP hacia direcciones URL donde tengamos desplegadas nuestras APIs .

Ejecutar con SpringBoot y Maven.

Vamos a compilar y ejecutar nuestra aplicación. Al estar desarrollado con Spring Boot, no necesitamos nada más que Maven para compilar, ya que disponemos de un Tomcat embebido en nuestro JAR. Para compilar nuestro módulo, nos ponemos en la carpeta del proyecto y simplemente ejecutamos este comando:

mvn package

Si queremos evitar que nos ejecute los test, lo haremos añadiendo esta opción:

mvn package  -Dmaven.test.skip=true

Ahora, simplemente tenemos que ejecutar nuestro JAR que está en la carpeta target:

java -jar geoback-0.0.1-SNAPSHOT.jar

Ya tenemos nuestra aplicación disponible en HTTP.

Pruebas con Postman.

Postman lo puedes descargar directamente de su web. Es una de las mejores herramientas para probar APIs. En su web tenéis documentación por si queréis profundizar en su uso. Tiene interesantes características, si os registráis, pero para las pruebas que vamos a hacer no lo necesitaremos.

Vamos a hacer las pruebas más básicas posibles, que no es más que probar cada uno de los métodos del Controller. Pero antes de nada, vamos a fijarnos bien en cómo tienen que estar construidos los request para acceder con nuestro token.

postman_1

Como véis en la imagen, hemos creado un Environment en el que incluir las propiedades comunes a todos los request. En el recuadro rojo está el menú para gestionar los Environments. Es totalmente opcional, pero recomendable, utilizarlos.

Nuestras propiedades van a ser las siguentes:

DEVHOST: localhost
DEVPORT: 9001
ENVIRONMENT: layer/geojson
JWT_AUTH: 
Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJIZWN0b3IiLCJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiQURNSU4ifQ.Rz1rY8WPsuOjUxs12qbn2QwspmCXRZgU_SLxXoc_Wv_lzjT-MZQV9lHz41ReucM4uB_ZEOyuZN_Ba8KibOmqIA
PROTOCOL: http

Lo más destacable es el parámetro JWT_AUTH. Aquí tenemos que indicar el Token con el que vamos a realizar los request.

tokenGen

Ya vimos en el Post anterior, cómo se generan los Token JWT de nuestro ejemplo. Ahora,  generaremos los request con Postman. Como hemos indicado anteriormente, haremos una prueba por cada método del CRUD. Cada request irá con un método diferente (POST, GET, PUT y DELETE) y la correspondiente URL.

postman_create_1

Hay que fijarse, principalmente, en dos pestañas. La primera es la pestaña Headers, en la cual tendremos que añadir nuestra autenticación, generada anteriormente, (no olvidar la palabra Bearer delante del Token) e indicar que estamos enviado los datos en formato JSON. Esto es común a todos los request que hagamos.

postman_create_2

La segunda pestaña importante es la pestaña Body. En este caso tendremos que indicar que estará en formato JSON y, seleccionando raw, escribiremos el cuerpo de nuestro request (que en este caso, tiene que ser una representación en JSON de un objeto de la clase Layer).

Vayamos con las pruebas.

Probando método 1: Create

Creamos un Layer con los siguientes datos:

Method: POST

URL: http://localhost:9001/layer/crud/

Body: {
       "token":"PRUEBAS_CRUD",
       "name":"createTest",
       "description":"Test de prueba del metodo create.",
       "series":"PRUEBAS",
       "geoData":{
                  "type": "FeatureCollection",
                  "features": [{
                                "type": "Feature",
                                "geometry": {"type":"Polygon","coordinates":[[[-9.034818,41.880571],[-8.984433,42.592775],[-9.392884,43.026625],[-7.97819,43.748338],[-6.754492,43.567909],[-5.411886,43.57424],[-4.347843,43.403449],[-3.517532,43.455901],[-1.901351,43.422802],[-1.502771,43.034014],[0.338047,42.579546],[0.701591,42.795734],[1.826793,42.343385],[2.985999,42.473015],[3.039484,41.89212],[2.091842,41.226089],[0.810525,41.014732],[0.721331,40.678318],[0.106692,40.123934],[-0.278711,39.309978],[0.111291,38.738514],[-0.467124,38.292366],[-0.683389,37.642354],[-1.438382,37.443064],[-2.146453,36.674144],[-3.415781,36.6589],[-4.368901,36.677839],[-4.995219,36.324708],[-5.37716,35.94685],[-5.866432,36.029817],[-6.236694,36.367677],[-6.520191,36.942913],[-7.453726,37.097788],[-7.537105,37.428904],[-7.166508,37.803894],[-7.029281,38.075764],[-7.374092,38.373059],[-7.098037,39.030073],[-7.498632,39.629571],[-7.066592,39.711892],[-7.026413,40.184524],[-6.86402,40.330872],[-6.851127,41.111083],[-6.389088,41.381815],[-6.668606,41.883387],[-7.251309,41.918346],[-7.422513,41.792075],[-8.013175,41.790886],[-8.263857,42.280469],[-8.671946,42.134689],[-9.034818,41.880571]]]},
                                "properties": {}
                              }]
                  }
      }

Comprobamos que la respuesta obtenida es un Layer, representado en JSON, como indicamos en el método del Controller, pero con el id (este será el id que utilizaremos en las siguientes pruebas).

{
    "id": "5aa6c86ee285aa1dc419ab80",
    "token": "PRUEBAS_CRUD",
    "name": "createTest",
    "series": "PRUEBAS",
    "description": "Test de prueba del metodo create.",
    "geoData": {
                "type": "FeatureCollection",
                "features": [{
                              "type": "Feature",
                              "geometry": {
                                           "type": "Polygon",
                                           "coordinates": [[[
                                                          -9.034818,
                                                          41.880573
                                                          ],
                                                          [
                                                          -8.984433,
                                                          42.592777
                                                          ],
                                                          ...

 

Probando método 2: Read

Leemos el Layer que hemos creado en la prueba anterior, indicando en la URL (método GET) el id por el que vamos a buscar.

Method: GET

URL: http://localhost:9001/layer/crud/5aa6c86ee285aa1dc419ab80

Body: {}

La respuesta será exactamente la misma que en el método Create.

Probando método 3: Update

Ahora, vamos a añadir una propiedad a nuestro Layer. Haremos unos cambios en la descripción, y lo actualizaremos.

Method: PUT

URL: http://localhost:9001/layer/crud/

Body: {
       "id":"5aa6c86ee285aa1dc419ab80",
       "token":"PRUEBAS_CRUD",
       "name":"updateTest",
       "description":"Test de prueba del metodo update.",
       "series":"PRUEBAS",
       "geoData":{
                  "type": "FeatureCollection",
                  "features": [{
                                "type": "Feature",
                                "geometry": {"type":"Polygon","coordinates":[[[-9.034818,41.880571],[-8.984433,42.592775],[-9.392884,43.026625],[-7.97819,43.748338],[-6.754492,43.567909],[-5.411886,43.57424],[-4.347843,43.403449],[-3.517532,43.455901],[-1.901351,43.422802],[-1.502771,43.034014],[0.338047,42.579546],[0.701591,42.795734],[1.826793,42.343385],[2.985999,42.473015],[3.039484,41.89212],[2.091842,41.226089],[0.810525,41.014732],[0.721331,40.678318],[0.106692,40.123934],[-0.278711,39.309978],[0.111291,38.738514],[-0.467124,38.292366],[-0.683389,37.642354],[-1.438382,37.443064],[-2.146453,36.674144],[-3.415781,36.6589],[-4.368901,36.677839],[-4.995219,36.324708],[-5.37716,35.94685],[-5.866432,36.029817],[-6.236694,36.367677],[-6.520191,36.942913],[-7.453726,37.097788],[-7.537105,37.428904],[-7.166508,37.803894],[-7.029281,38.075764],[-7.374092,38.373059],[-7.098037,39.030073],[-7.498632,39.629571],[-7.066592,39.711892],[-7.026413,40.184524],[-6.86402,40.330872],[-6.851127,41.111083],[-6.389088,41.381815],[-6.668606,41.883387],[-7.251309,41.918346],[-7.422513,41.792075],[-8.013175,41.790886],[-8.263857,42.280469],[-8.671946,42.134689],[-9.034818,41.880571]]]},
                                "properties": {"times":1}
                              }]
                 }
      }

La respuesta será nuestro Layer ya actualizado.

Probando método 4: Delete

Ya sólo queda eliminar el Layer de prueba. Para ello, únicamente llamamos al método delete, indicando en la URL el id de nuestro Layer (igual que en método Read)

Method: DELETE

URL: http://localhost:9001/layer/crud/5aa6c86ee285aa1dc419ab80

Nos devuelve un Status 200 OK. Podemos comprobar que se ha eliminado, entrando en nuestra base de datos.

Y así finalizamos este ejemplo, que esperamos os sea de utilidad y como siempre, os emplazamos a la zona de comentarios para que compartáis vuestras dudas o impresiones sobre este tema.

GeoBack. Ejemplo 1/3, ejemplo 2/3.

 

 

Anuncios

GeoBack. Un sencillo ejemplo. 2ª parte

En la primera parte de este post, vimos como hacer un sencillo Api para almacenar un Layer, el cual contiene datos georeferenciados en un GeoJSON, con GeoBack. En esta segunda parte de nuestro ejemplo , veremos como configurar la seguridad con Json Web Tokens.

Accesos seguros con JWT.

JWT es una sencilla y limpia manera de proteger los accesos a un Api. Se trata de un sistema compacto y portable, que auto-contiene toda la información necesaria. Para esta ocasión, hemos utilizado el ejemplo de este Pascal Alma (ejemplo de JWT), y en este Post tenéis muy bien explicado el concepto de Json Web Tokens, y cómo integrarlo con Spring Security (explicacion de JWT con Spring).

Lo más importante será ver cómo se crea el Token con el que vamos a autenticar nuestros requests. Gracias a este proceso de autenticación, se pueden restringir y contabilizar los accesos. Por poner un ejemplo, si estamos desarrollando una aplicación Android, tendremos que registrar el id del dispositivo (o una clave generada a partir del certificado con el que firmamos nuestra App, si esta está abierta al público) y de esta manera, cuando recibamos el request con el Token, podremos contabilizar el acceso desde ese dispositivo. Por supuesto, tendremos que integrar la gestión de estos accesos en nuestro sistema.

public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        // Do do anything specific here
        System.out.println(authentication.toString());
        
        // En este punto llamaríamos al servicio de conteo de accesos 
        // por token y registramos el acceso.

    }

}

Si nos fijamos en el código, en la clase JwtAuthenticationSuccessHandler.java, el método  onAuthenticationSucces se ejecuta en cada acceso a la Api, siempre y cuando el Token sea correcto. Es aquí donde tendríamos que hacer la gestión de accesos.

En la web de JWT se puede ver cómo se constituyen los Token, su estructura y cómo se generan. Como puede observarse en la sección de Libraries, existen implementaciones para los lenguajes de programación más importantes (por ejemplo, si tenemos una aplicación para iOS escrita en objective-c, añadimos al proyecto la librería correspondiente y generamos el Token con nuestra palabra secreta).

Generando el Token

En nuestro ejemplo, hemos utilizado la clase JwtTokenGenerator.java para generar un Token de ejemplo.

public static String generateToken(JwtUserDto u, String secret) {
    Claims claims = Jwts.claims().setSubject(u.getUsername());
    claims.put("userId", u.getId() + "");
    claims.put("role", u.getRole());

    return Jwts.builder()
            .setClaims(claims)
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();
}

El método generateToken genera el Token a partir de la clave secreta y los datos que queramos añadir para identificarlo, que quedarán el Payload del Token (ver documentacion de JWT).

Este Token que genera en un String, será lo que vamos a utilizar para autorizar nuestros request al hacer llamadas a nuestra Api.

tokenGen

Ejecutamos el método main de la clase de ejemplo. Esto nos da una salida por pantalla como en la imagen anterior, con el Token que utilizaremos.

Cuando hagamos las pruebas en Postman, veremos cómo incluir un parámetro en el Header de los Requests con esta información. En este parámetro indicaremos el Token con el que vamos a realizar las llamadas securizadas.

En la siguiente entrada de esta serie de posts, veremos cómo hacer pruebas de nuestra Api con la herramienta Postman.

 

GeoBack. Un sencillo ejemplo. 1ª Parte

En este post vamos a mostraros un ejemplo de uso de GeoBack. Simplemente, haremos un CRUD Controller con Spring Boot, el cual nos permitirá manipular capas como objetos de tipo Layer que contienen un documento GeoJson y una serie de metadatos.

Esta será nuestra clase Layer, que es un sencillo POJO (Plain Old Java Object) con alguna peculiaridad:

@Document(collection = "layers")
public class Layer extends AbstractDomainObject{

    private String token;
    private String name;
    private String series;
    private String description;
    private FeatureCollection geoData;

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSeries() {
        return series;
    }

    public void setSeries(String series) {
        this.series = series;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public FeatureCollection getGeoData() {
        return geoData;
    }

    public void setGeoData(FeatureCollection geoData) {
        this.geoData = geoData;
    }
}

Como vemos, al añadir la etiqueta @Document, diremos a Spring que se trata de una clase que representa el tipo de documento que almacenaremos en la base de datos. Cabe destacar, que en el atributo geoData se almacenan el GeoJson con los datos georeferenciados en un FeatureCollection.

Como queremos hacer las cosas un poco ordenadas,  usamos un patrón Service Layer. Con Spring Data necesitamos tres capas diferenciadas en el código:

  • La capa de modelo, que en este caso se completaría con un Spring Data Repository.
  • La capa de servicio, que utilizamos para dotar de mayor abstracción al sistema.
  • La capa de controller, que es donde aparece REST y nos dará la puerta de acceso de nuestra Api.

 

Capa de modelo.

Sólo necesitamos definir un interface que herede de MongoRepository.

public interface LayerCRUDRepository extends MongoRepository<Layer,String> 
{
// Aqui definiriamos los demas metodos.
}

Tenemos que indicar a los genéricos de MongoRepository, que nuestro documento a almacenar es de tipo Layer y que su Id es de tipo String. No hay que añadir ningún método a la clase, ya que estamos desarrollando un CRUD y nos sobra con los métodos definidos en la clase padre (MongoRepository).

Capa Servicio.

En el servicio definimos qué es lo que podemos hacer con nuestros Layer. De momento, sólo tenemos los 4 métodos del CRUD (crear, leer, actualizar y eliminar un Layer), pero podemos añadir otras, como por ejemplo buscar todos los Layer con un token determinado, o un listado de Layer de una serie determinada.

Consta de un interface:

public interface LayerCRUDService {

    // C
    Layer create(Layer layer);
    // R
    Layer read(String id);
    // U
    Layer update(Layer layer);
    // D
    void delete(String id);
}

y su implementación:

@Service
public class LayerCRUDServiceImpl implements LayerCRUDService{

    private LayerCRUDRepository layerCRUDRepository;

    @Autowired
    public void setLayerCRUDRepository(LayerCRUDRepository layerCRUDRepository) {
        this.layerCRUDRepository = layerCRUDRepository;
    }

    @Override
    public Layer create(Layer layer) {
        return layerCRUDRepository.save(layer);
    }

    @Override
    public Layer read(String id) {
        return layerCRUDRepository.findOne(id);
    }

    @Override
    public Layer update(Layer layer) {
        return layerCRUDRepository.save(layer);
    }

    @Override
    public void delete(String id) {
        layerCRUDRepository.delete(id);
    }
}

En la implementación, únicamente tenemos que definir el repository que vamos a utilizar, y luego emplear los métodos que hereda de MongoRepository.

Capa Rest controller.

A continuación, se muestra el Controller con los cuatro métodos de acceso al Api con sus respectivos protocolos:

@RestController
@RequestMapping("/layer/crud")
public class LayerCRUDController {

    private LayerCRUDService layerCRUDService;

    @Autowired
    public void setLayerCRUDService(LayerCRUDService layerCRUDService) {
        this.layerCRUDService = layerCRUDService;
    }

    // C
    @RequestMapping(value = "/",method = RequestMethod.POST)
    public Layer create(@RequestBody Layer layer){
        return layerCRUDService.create(layer);
    }

    // R
    @RequestMapping(value = "/{id}",method = RequestMethod.GET)
    public Layer read(@PathVariable("id") String id){
        return layerCRUDService.read(id);
    }

    // U
    @RequestMapping(value = "/",method = RequestMethod.PUT)
    public Layer update(@RequestBody Layer layer){
        return layerCRUDService.update(layer);
    }

    // D
    @RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
    public void delete(@PathVariable("id") String id){
        layerCRUDService.delete(id);
    }
}

Tenemos que inyectar nuestro LayerCRUDService para poder utilizar sus métodos. La etiqueta @RestController nos indica que los métodos de esta clase serán accesibles a través de un servicio Rest, en la ruta definida en @RequestMapping.

También vemos que cada método tiene definidos una ruta propia (que se añade a la anterior), y un RequestMethod que tendremos que utilizar en nuestras llamadas al Api.

En la segunda parte del post, os enseñaremos cómo utilizamos JWT para dotar de seguridad al Api, configurando una autorización en los request. También haremos pruebas con la herramienta Postman.

Todo el código esta en esta rama de Github.

Administrando datos georeferenciados con GeoBack

GeoBack es una herramienta de código abierto para almacenar datos georeferenciados conforme al estándar GeoJSON. Es un proyecto de código abierto y es totalmente libre de uso.

Está implementado en lenguaje Java sobre Spring Boot, con Spring Data y MongoDB. Los accesos al Api están securizados con JWT. Incluye un Spring Converter  y un módulo Jackson para usar los objetos de dominio propios de Spring Data Mongo.

GeoBack

A lo largo de este post, vamos a entrar un poco en detalle expliando cómo está desarrollado.

Utiliza estándar GeoJSON para guardar los datos

GeoJSON es un formato estándar abierto, diseñado para representar elementos geográficos sencillos junto con sus atributos no espaciales, basado en JSON. El formato es ampliamente utilizado en aplicaciones de cartografía en entornos web, al permitir el intercambio de datos de manera rápida, ligera y sencilla.

Gracias a ser un formato estándar, muchas de las tecnologías Web relacionadas con mapas lo utilizan (Google Maps Api, Leaflet y Openlayers, así como CartoDB o Mapbox).

Spring Boot

El proyecto está implementado con Spring Boot, lo que nos permite aprovechar todas sus ventajas, que son:

  • Es ideal para una arquitectura de micro-servicios ya que permite una gran abstracción.
  • Fácil de escalar. Permite ser integrado con Docker u otros sistemas de contenedores.
  • Ecosistema Spring.
  • No necesitamos ningún servidor de aplicaciones en nuestra máquina, ya que viene embebido y pre-configurado.
  • Automatización de la configuración, haciéndola muy sencilla.

Spring Data y MongoDB

Las bases de datos no relacionales se antojan ideales a la hora de almacenar datos georeferenciados. Por ello, GeoBack está preparado para funcionar sobre MongoDB.

Existen otras bases de datos, como MySQLPostgreSQL, con características especiales para trabajar con datos georeferenciados. Podríamos hacer unos sencillos cambios en el código para poder utilizar cualquiera de estas bases de datos, gracias a la potencia de Spring Data y a que utilizamos el estándar GeoJSON.

Desde hace un tiempo, MongoDB incluye los índices 2dsphere. Estos índices permiten hacer consultas que calculan geometrías en una esfera similar a la de la Tierra. El índice 2dsphere admite todas las consultas geoespaciales de MongoDB (inclusión, intersección y proximidad), lo que nos permite realzar complicados cálculos de forma sencilla y a gran velocidad.

Api REST y Seguridad

Al incluir las capacidades Web de Srping Boot, el proyecto viene preparado para funcionar como una Api REST, para ello debemos implementar los controladores que serán nuestro enlace con la base de datos. Las principales ventajas de utilizar este sistema son su gran versatilidad a la hora de encajar en cualquier sistema (no dependemos de ningún lenguaje o plataforma específica), su fiabilidad y su fácil escalabilidad. Por poner un ejemplo, podemos tener varios geovisores con fines distintos que trabajen con la misma base de datos.

Pero estos sistemas tan “abiertos” podrían ser accesibles por cualquiera, es por ello que es necesario protegerlos. El ecosistema Spring tiene a Spring Security, el cual nos evita implementar la mayoría de mecanismos para poder proteger estos sistemas. 

En este caso, hemos añadido Json Web Tokens (JWT)  implementada sobre Spring Security. Esto nos permite securizar todos los accesos a la Api mediante autorización. De esta forma, podemos identificar y contabilizar los accesos a nuestro Api mediante el uso de tokens pre-generados.

 Qué es eso de Spring Converter y Jackson

GeoBack tiene la peculiaridad de utilizar los objetos de dominio propios de Spring Data para poder integrarlo de forma sencilla. Para poder utilizar estos objetos necesitamos que nuestra Api sea capaz de aceptar un documento en formato JSON y serializarlo en un objeto java (en este caso de una clase que implemente el interface GeoJson). Para este fin, se ha implementado un módulo Jackson con un serializer y un deserializer como se puede ver en este código.

Con este módulo Jackson podemos codificar y descodificar correctamente entre formato GeoJson y POJOs independientemente de su complejidad.

Un Spring Converter se encarga sencillamente de “transferir” los atributos de un objeto de una clase java a otra. Esto nos permitirá crear un Controller que acepte cualquier tipo de geometría, ya que el propio backend será el encargado de decodificarla y almacenarla correctamente por muy compleja que sea. Ademas lo hará de manera estándar, para que pueda ser entendido por un sistema completamente independiente de GeoBack.

Aquí podéis ver en detalle el código fuente de este converter.

Open source

Este proyecto está totalmente libre de uso. Lo podéis modificar y utilizarlo a vuestro antojo. Pronto publicaremos En este enlace tenéis un ejemplo de uso implementando un controlador, con su correspondiente servicio, que nos permita administrar layers en una base de datos.

– En este enlace se encuentra el repositorio GitHub.