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

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:

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:

SmartGwt especifica 2 tipos de errores: de validación e irrecuperables.

Los errores de validación se representan en JSON de la siguiente manera

    {    "response":
           {   "status": -4,
           "errors":
               {   "field1": [
                       {"errorMessage": "First error on field1"},
                       {"errorMessage": "Second error on field1"}
                   ]
               }
           }
    }

de acuerdo a la documentación un error irrecuperable se representa de la siguiente forma

    {"response": {"status": -1, "data": "Error message..."}}

Pero cuando utilicé un RestDataSource en un SelectItem y ocurría un error en el servidor, la petición se reintentaba de forma cíclica. La solución fue agregar la propiedad totalRows a la respuesta.

    {"response": {"status": -1, "data": "Error message...", "totalRows": 0 }}

Fuentes:
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/docs/ErrorHandling.html
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/data/RestDataSource.html
http://forums.smartclient.com/showthread.php?t=22975

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 – Contexts

noviembre 8, 2012

Apache CXF – Contexts

Si necesitas información del contexto en un servicio web basado en Apache CXF, lo puedes hacer mediante la anotación @Resource y las clases WebServiceContext (JAX-WS) y MessageContext (JAX-RS).

@Path("/customers")
@WebService
public class CustomerService {

   @Resource WebServiceContext jaxwsContext;
   @Resource MessageContext jaxrsContext;

   @WebMethod
   @POST
   public void doIt(String b) {
       isUserInRole();
   };

   private void isUserInRole() throws WebApplicationException {
       if (jaxwsContext.getSecurityContext() != null) {
           // soap invocation
           jaxwsContext.getSecurityContext().isUserInRole(theRole);
       } else {
           // http-only jaxrs one
           jaxrsContext.getSecurityContext().isUserInRole(theRole);
       }  
   }
}

via Apache CXF — JAX-RS and JAX-WS.

Hace poco escribí sobre como buscar/filtrar recursos en el servidor, para ello utilizamos un objeto Criteria -del mismo tipo que el recurso- para crear una consulta JDOQL que nos permitiera obtener los resultados.

En este artículo mostraré una forma de utilizar la clase AdvancedCriteria de SmartGwt para realizar una consulta más compleja. Al igual que la vez anterior, vamos a utilizar el framework Apache CXF para nuestros servicios REST, y Jackson para la serialización y deserialización de los objetos en formato JSON. También utilizamos Datanucleus para la persistencia de datos.

Me base en este artículo que explica cómo integrar SmartGwt con ASP.Net.

Lo primero que vamos a necesitar es una clase AdvancedCriteria del lado del servidor


public class AdvancedCriteria {

static ObjectMapper om;

{

om=new ObjectMapper();

om.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);

}

public static class JDOQLData{

public String filter="";

public Map<String,Object> params=new HashMap<String, Object>();

public Class<?> type;

&nbsp;

public JDOQLData(Class<?> type) {

this.type=type;

}

}

&nbsp;

String _constructor;

Integer _startRow;

Integer _endRow;

String fieldName;

String operator;

Object value;

AdvancedCriteria[] criteria;

&nbsp;

public AdvancedCriteria() {

}

&nbsp;

public static AdvancedCriteria fromString(String value){

try {

return om.readValue(value, AdvancedCriteria.class);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

&nbsp;

// Getter's y Setter's ...

&nbsp;

public void toJDOQL(JDOQLData data){

if(this.criteria==null){

if(this.operator.equals(OperatorId.EQUALS.getValue())){

data.filter+=this.fieldName+" == ";

}else if(this.operator.equals(OperatorId.NOT_EQUAL.getValue())){

data.filter+=this.fieldName+" != ";

}else if(this.operator.equals(OperatorId.LESS_THAN.getValue())){

data.filter+=this.fieldName+" < ";

}else if(this.operator.equals(OperatorId.GREATER_THAN.getValue())){

data.filter+=this.fieldName+" > ";

}else if(this.operator.equals(OperatorId.LESS_OR_EQUAL.getValue())){

data.filter+=this.fieldName+" <= ";

}else if(this.operator.equals(OperatorId.GREATER_OR_EQUAL.getValue())){

data.filter+=this.fieldName+" >= ";

}

data.params.put(String.valueOf(data.params.size()+1), fixType(getPropertyType(data.type, this.fieldName), this.value) );

data.filter+=":"+data.params.size();

}else{

data.filter+="(";

for(int index=0;index<this.criteria.length;index++){

this.criteria[index].toJDOQL(data);

if(index+1<this.criteria.length){

if(this.operator.equals(OperatorId.AND.getValue())){

data.filter+=" && ";

}else if(this.operator.equals(OperatorId.OR.getValue())){

data.filter+=" || ";

}else if(this.operator.equals(OperatorId.NOT.getValue())){

data.filter+=" ! ";

}

}

}

data.filter+=")";

}

}

&nbsp;

private Object fixType(Class<?> type, Object value) {

return om.convertValue(value, type);

}

&nbsp;

private Class<?> getPropertyType(Class<?> clazz,String property){

try{

LinkedList<String> properties=new LinkedList<String>();

properties.addAll(Arrays.asList(property.split("\\.")));

Field field = null;

while(!properties.isEmpty()){

field = FieldUtils.getField(clazz,properties.removeFirst(),true);

clazz=field.getType();

}

return field.getType();

}catch(Exception e){

throw new RuntimeException(e);

}

}

}

Lo interesante de esta clase es que tiene un método estático fromString(String) que será utilizado por Apache CXF para crear un objeto de esta clase a partir de una cadena. También tiene un método recursivo toJDOQL(JDOQLData) que nos regresa un filtro y el listado de parámetros en un objeto JDOQLData para poder realizar la consulta en datanucleus. El método fixType y getPropertyType son para utilizar el tipo correcto -de acuerdo a la clase- en nuestro listado de parámetros.

Nuestro servicio REST quedaría de la siguiente forma


@GET

public String retrieve(@QueryParam("")Libro criteria,@QueryParam("")AdvancedCriteria advancedCriteria){

try {

List<Libro> libros = null;

if(advancedCriteria.get_constructor()!=null){

libros = Libro.getByAdvancedCriteria(advancedCriteria);

}else{

libros = Libro.getByCriteria(criteria);

}

Map<String, Object> response = new HashMap<String, Object>();

Map<String, Object> body = new HashMap<String, Object>();

body.put("status", 0);

body.put("data", libros);

response.put("response", body);

return json.writeValueAsString(response);

} catch (Exception e) {

log.error("", e);

}

return "{\"response\":{\"status\":-1, \"data\":\"Hubo un problema al buscar los Libros\"}}";

}

El framework Apache CXF se encarga de crear los objetos criteria(Libro) y advancedCriteria. Para saber cuál fue el que recibimos basta con verificar la propiedad _constructor, si es diferente de null sabemos que hemos recibido un AdvancedCriteria.

Finalmente realizamos la consulta en nuestra clase Libro


public static List<Libro> getByAdvancedCriteria(AdvancedCriteria criteria) {

List<Libro> libros;

PersistenceManager pm = PMF.get().getPersistenceManager();

Transaction tx = pm.currentTransaction();

try{

tx.begin();

JDOQLData data=new JDOQLData(Libro.class);

criteria.toJDOQL(data);

Query query = pm.newQuery(Libro.class,data.filter);

libros=(List<Libro>) query.executeWithMap(data.params);

tx.commit();

}finally{

if(tx.isActive()){

tx.rollback();

}

pm.close();

}

return libros;

}

En el cliente, utilizamos la clase AdvancedCriteria de SmartGwt para realizar la consulta


&nbsp;

AdvancedCriteria criteria=new AdvancedCriteria(OperatorId.OR,

new AdvancedCriteria[]{

new AdvancedCriteria("isbn",OperatorId.EQUALS, txtISBN.getValueAsString()),

new AdvancedCriteria(OperatorId.AND, new AdvancedCriteria[]{

new AdvancedCriteria("autor.nombre", OperatorId.EQUALS, "Gabriel García Márquez"),

new AdvancedCriteria("titulo", OperatorId.EQUALS, "Crónica de una muerte anunciada")

})

});

librosListGrid.fetchData(criteria);

&nbsp;

Finalmente al realizar el ‘fetch’ se realiza una petición a nuestro servicio con los siguientes párametros. Los cuales serán transformados a un objeto de nuestra clase AdvancedCriteria


&nbsp;

__gwt_ObjectId:793

operator:or

criteria:{"__gwt_ObjectId":788,"fieldName":"isbn","operator":"equals","value":"978-1400034956"}

criteria:{"__gwt_ObjectId":791,"operator":"or","criteria":[{"__gwt_ObjectId":789,"fieldName":"autor.nombre","operator":"equals","value":"Gabriel García Márquez"},{"__gwt_ObjectId":790,"fieldName":"titulo","operator":"equals","value":"Crónica de una muerte anunciada"}]}

_constructor:AdvancedCriteria

_operationType:fetch

_startRow:0

_endRow:75

_textMatchStyle:exact

_componentId:isc_ListGrid_0

_dataSource:isc_LibrosModule_3_0

isc_metaDataPrefix:_

isc_dataFormat:json

&nbsp;

Fuentes:
http://wiki.smartclient.com/display/Main/Integrating+with+ASP.Net+MVC
http://wiki.smartclient.com/display/Main/5.+Adding+support+for+AdvancedCriteria
http://www.objectdb.com/database/jdo/manual/chapter7#Query_Parameters
http://www.objectdb.com//database/jdo/manual/chapter7
http://stackoverflow.com/questions/935762/how-to-dynamically-build-jdo-queries-on-multiple-parameters