Spring – Utilizar una expresión regular para especificar la ruta del controlador

Supongamos que tenemos un servicio web para obtener un listado de facturas de una fecha específica en la siguiente url

api.server.com/facturas/2014-11-24

nuestro controlador sería

    @RequestMapping(value = "/facturas/{fecha}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List<Factura> searchFacturas(@PathVariable Date fecha){
        List<Factura> facturas = service.searchFacturas(fecha);
        return facturas;
    }

Ya vimos como convertir parámetros de tipo fecha

Pero que pasa si necesitamos crear otro servicio para obtener las facturas de un cliente con la siguiente URL

api.server.com/facturas/CQRO01

si implementamos el servicio tal como el anterior tendríamos un conflicto con la URL ya que no le hemos indicado de alguna forma como distinguir entre un id de cliente y una fecha. Para distinguir podemos utilizar expresiones regulares

    @RequestMapping(value = "/facturas/{fecha:d{4}-d{2}-d{2}}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List<Factura> searchFacturas(@PathVariable Date fecha){
        List<Factura> facturas = service.searchFacturas(fecha);
        return facturas;
    }

    @RequestMapping(value = "/facturas/{cliente:w{6}}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List<Factura> searchFacturas(@PathVariable String cliente){
        List<Factura> facturas = service.searchFacturas(cliente);
        return facturas;
    }

Fuentes:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-uri-templates-regex
http://stackoverflow.com/questions/17889604/how-to-use-a-regex-path-mapper-variable-in-a-requestmapping
http://stackoverflow.com/questions/18422368/regex-in-spring-controller

Anuncios

AngularJS – Invocar un servicio REST y adaptar la respuesta a nuestro modelo

Desarrollé un servicio web que regresa una respuesta al estilo de SmartGWT

{
"response": {
"status": 0,
"startRow": 0,
"endRow": 76,
"totalRows": 546,
"data": [
{"field1": "value", "field2": "value"},
{"field1": "value", "field2": "value"},
… 76 total records …
]
}
}

Queremos utilizar estos datos en nuestra plantilla

distritos.html

...
<label class="item item-input item-select">
                <div class="input-label">
                    Distrito
                </div>
                <select>
                    <option ng-repeat="distrito in distritos">
                        {{ distrito.descripcion }}
                    </option>
                </select>
            </label>
...

Hay dos formas para poder consumir esta respuesta en nuestra plantilla -a través de un servicio de AngularJS-

Una es utilizar una función transformResponse en la configuración del servicio de la siguiente manera

services.js

'use strict'
angular.module('Equinox.services', ['ngResource'])

    .factory('Distritos', ['$resource', function ($resource) {
        return $resource('http://localhost:8080/equinox/catalogos/distritos', {}, {
            'query': {method: 'GET', isArray: true, transformResponse: function(data){
                return angular.fromJson(data).response.data
            }}
        })
    }])

controllers.js

angular.module('Equinox.controllers', [])

    .controller('DistritosCtrl', ['$scope', 'Distritos', function ($scope, Distritos) {
        $scope.distritos = Distritos.query()

    }])

y la segunda forma es utilizar la respuesta tal y como viene del servidor

services.js

'use strict'
angular.module('Equinox.services', ['ngResource'])

    .factory('Distritos', ['$resource', function ($resource) {
        return $resource('http://localhost:8080/equinox/catalogos/distritos', {}, {
            'query': {method: 'GET', isArray: false}
        })
    }])

controllers.js

angular.module('Equinox.controllers', [])

    .controller('DistritosCtrl', ['$scope', 'Distritos', function ($scope, Distritos) {
        Distritos.query().$promise.then(function(data){
            $scope.distritos = data.response.data
        })
    }])

Fuentes:

Spring – Permitir peticiones de otros dominios (CORS)

Al invocar un servicio web (REST) desde una aplicación móvil (ioniccordova/phonegap), me aparecía el siguiente error:

XMLHttpRequest cannot load http://localhost:8080/equinox/catalogos/distritos. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin http://localhost:8100 is therefore not allowed access.

El servidor se está ejecutando mediante mvn jetty:run (localhost:8080) mientras que el cliente está ejecutandose mediante grunt serve (localhost:8100). Siendo dominios diferentes, el navegador bloquea la invocación del servicio por cuestiones de seguridad.

La solución es permitir el acceso CORS. El servicio web esta hecho con Spring, así que para permitir esto necesitamos crear y configurar un filtro.

SimpleCORSFilter.java

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class SimpleCORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}

}

y para configurarlo

web.xml

...
<filter>
        <filter-name>SimpleCORSFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>simpleCorsFilter</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>SimpleCORSFilter</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>
...

applicationContext.xml

...
<bean class="mx.gob.tribunalqro.equinox.filters.SimpleCORSFilter" name="simpleCorsFilter"></bean>
...

Nota:
Esta es una implementación muy sencilla y que además agrega el encabezado a todas las peticiones, no sólo a los servicios web. Para ello, necesitamos colocar nuestros servicios en una url como api/v1/resource y mapear el filtro a esas url’s

Fuentes:

Java – Convertir una página específica de un TIFF multipágina a JPG

Para la digitalización de documentos utilizamos escáneres que producen archivos TIFF, estos permiten almacenar varias imágenes en el mismo archivo. Estas imágenes se guardan en una base de datos para su posterior consulta.

Para poder desplegar estas imágenes en una aplicación web, necesitamos convertirlas a un formato estándar para la web (JPG, PNG ó GIF). Esto se puede lograr utilizando la librería image magick. Para usarla en Java existen 2 opciones: JMagick e im4java. Decidí utilizar im4java debido a los riesgos de usar JNI en una aplicación web (http://im4java.sourceforge.net/docs/faq.html)

Lo primero es agregar la dependencia de im4java a nuestro proyecto, archivo pom.xml si usas maven o agregar el jar a tu build path.

   <dependency>
      <groupId>org.im4java</groupId>
      <artifactId>im4java</artifactId>
      <version>1.4.0</version>
   </dependency>

Utilizamos un arreglo de bytes para cargar la imágen desde la base de datos y lo pasamos como un stream a la librería im4java para seleccionar la página que deseamos convertir al formato jpg.

    public byte[] getPagina(Integer pagina) throws Exception{
        byte[] imgPagina = null;
        byte[] imagen = loadImageFromDatabase();
        try{
        IMOperation op = new IMOperation();
        op.addImage(String.format("-[%d]", pagina - 1)); // read from stdin and specify page number with 1-based index
        op.addImage("jpg:-");               // write to stdout in jpg-format
        ByteArrayInputStream fis = new ByteArrayInputStream(imagen);
        ByteArrayOutputStream fos = new ByteArrayOutputStream();
        // Pipe pipe = new Pipe(fis,fos);
        Pipe pipeIn  = new Pipe(fis,null);
        Pipe pipeOut = new Pipe(null,fos);
        // set up command
        ConvertCmd convert = new ConvertCmd();
        convert.setInputProvider(pipeIn);
        convert.setOutputConsumer(pipeOut);
        convert.run(op);
        imgPagina = fos.toByteArray();
        }catch(Exception e){
            throw new Exception(
                    "Error al obtener la imágen de la página " + pagina, e);
        }
        return imgPagina;
    }

este método lo podemos utilizar en un servicio web/servlet para servir la imágen a través de una url

    @GET
    @Path("documento/{id}/imagen/pagina/{numero}")
    @Produces("image/jpeg")
    public Response getPaginaPromocion(@PathParam("id")Integer id, @PathParam("numero")Integer numero){
        Response response = null;
        try{
            Documento doc = Documento.get(id);
            response = Response.ok(doc.getPagina(numero), "image/jpeg").build();
        }catch(Exception e){
            String error = String.format("Error al obtener la página %d del documento %s", numero, id);
            log.error(error, e);
            response = Response.serverError().entity(String.format("{\"response\": {\"status\": -1, \"data\": \"%s\"}}", error)).build();
        }
        return response;
    }

Fuentes:

http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=16615#p61020
http://stackoverflow.com/questions/4809314/imagemagick-is-converting-only-the-first-page-of-the-pdf
http://im4java.sourceforge.net/docs/dev-guide.html#piping
http://stackoverflow.com/questions/2091454/byte-to-inputstream-or-outputstream
http://im4java.sourceforge.net/api/

Jackson JSON – Deserializar un array en una petición REST

En un servicio REST (jax-rs) puedes enviar los parámetros de una petición de varias formas: en la URL (@QueryParam), en el cuerpo del mensaje con el encabezado “application/x-www-form-urlencoded” (@FormParam), como parte de la URL (@PathParam).

Pero que pasa si el cliente envía toda la información en el cuerpo del mensaje en formato JSON.
Por ejemplo:

    {
        "tipo":"URGENTE",
        "actores":[
        {
            "nombre":"Juan",
            "apellidos":"Peréz",
            "idEstado":null,
            "idMunicipio":null,
            "idColonia":null,
            "calle":"Calle 2",
            "numero":"155",
            "codigoPostal":"76800"
        }
        ],
        "demandados":[
        {
            "nombre":"María",
            "apellidos":"López",
            "idEstado":null,
            "idMunicipio":null,
            "idColonia":null,
            "calle":"Calle 4",
            "numero":"45",
            "codigoPostal":"76800"
        }
        ]
    }

Podríamos crear una clase que reflejará esta estructura, pero si solo nos interesa algún campo o la petición no tiene nada que ver con nuestro modelo, no parece la mejor opción.

En este caso, en nuestro modelo existiría una clase Actor y Demandado que comparten los mismos campos, por lo que podríamos usar la herencia.

    public class Parte {
        String id;
        String nombre;
        String apellidos;
        String idEstado;
        String idMunicipio;
        String idColonia;
        String calle;
        String numero;
        String codigoPostal;
        public Parte() {
        }
        //... getter's y setter's, métodos, etc.
    }
    public class Actor extends Parte{
        //... otras propiedades
        public Actor() {
        }
        //... otros métodos
    }
    public class Demandado extends Parte{
        //... otras propiedades
        public Actor() {
        }
        //... otros métodos
    }

en nuestro servicio REST (Apache CXF) podríamos deserializar el arreglo de Actores de la siguiente forma

    @Path("solicitar/")
    public String solicitar(String data){
        String response = null;
        try{
            ObjectMapper mapper=new ObjectMapper();
            mapper.configure(SerializationConfig.Feature.WRITE<em>DATES</em>AS<em>TIMESTAMPS, false);
            mapper.configure(Feature.FAIL</em>ON<em>UNKNOWN</em>PROPERTIES, false);
            Map<String, Object> map = mapper.readValue(data, new TypeReference<Map<String, Object>>(){});
            String tipo = (String) map.get("tipo");
            List
<Parte> actores = mapper.convertValue(map.get("actores"), new TypeReference
<List<Parte>>() {});
            //... registrar la solicitud
            response = mapper.writeValueAsString(actores);
        }catch(Exception e){
            String error = "Ocurrió un problema al hacer la solicitud";
            log.error(error, e);
            response = String.format("{\"response\":{\"status\": -1, \"data\": \"%s\"}}", error);
        }
        return response;
    }

Fuentes:
http://wiki.fasterxml.com/JacksonDataBinding
http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-DealingwithParameters

Apache CXF – Ejemplo de firma de un documento XML

Apache CXF – Ejemplo de firma de un documento XML

Los beneficios de firmar digitalmente un documento XML en nuestros servicios web son que nos provee autenticación, integridad de los datos y el no repudio de la fuente que invoca el servicio.

Para ver un ejemplo de cómo puedes configurar Apache CXF para que utilice y verifique las firmas en un servicio web, sigue el enlace.

via Adding X.509 security headers to Apache CXF SOAP calls | Glen Mazza’s Weblog.