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

GWT – JSON dates

marzo 11, 2013

JSON no tiene un tipo de dato Date, así que cuando transfieres fechas mediante JSON debes convenir un formato de fecha -por ejemplo, yyyy-MM-dd’T’hh:mm:ss.SZ- Un ejemplo de fecha en este formato sería “2013-03-08T16:23:35.000+0000”.

Para facilitar un poco las cosas, decidí utilizar un Overlay Type para la representación de mi objeto y acceder a sus propiedades mediante getter’s y setter’s. Estos métodos realizan la conversión de cadena a fecha, y viceversa, con la ayuda de la clase DateTimeFormat.

    public class Audiencia extends JavaScriptObject {</p><pre><code>    private final static DateTimeFormat dateTimeFormat = DateTimeFormat.getFormat("yyyy-MM-dd'T'hh:mm:ss.SZ");

    public final native int getId()/*-{
        return this.id;
    }-*/;

    public final Date getInicio(){
        return dateTimeFormat.parseStrict(getInicioNative());
    };

    private final native String getInicioNative()/*-{
        return this.inicio;
    }-*/;

    public final Date getFin(){
        return dateTimeFormat.parseStrict(getFinNative());
    };

    private final native String getFinNative()/*-{
        return this.fin;
    }-*/;

    protected Audiencia() {
    }

    public static void getAudiencias(String idSala,
            final AsyncCallback&lt;Audiencia[]&gt; callback) {
        try {
            String queryParams = "?sala="+URL.encodeQueryString(idSala);
            RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,
                    Consts.REST_AUDIENCIAS + queryParams);
            rb.sendRequest(null, new RequestCallback() {

                @Override
                public void onResponseReceived(Request request,
                        Response response) {
                    if(response.getStatusCode() == 200){
                        JavaScriptObject jso = JSON.decode(response.getText());
                        DSResponse ds = new DSResponse(JSOHelper.getAttributeAsJavaScriptObject(jso, "response"));
                        if(ds.getStatus() == 0){
                            Record[] records = ds.getData();
                            Audiencia[] audiencias = new Audiencia[records.length];
                            for(int i=0;i&lt;records.length;i++){
                                audiencias[i] = (Audiencia) records[i].getJsObj();
                            }
                            callback.onSuccess(audiencias);
                        }else{
                            callback.onFailure(new Exception(ds.getAttributeAsString("data")));
                        }
                    }else{
                        callback.onFailure(new Exception(response.getStatusText()));
                    }
                }

                @Override
                public void onError(Request request, Throwable exception) {
                    callback.onFailure(exception);
                }
            });
        } catch (Exception e) {
            callback.onFailure(e);
        }
    }

}
</code></pre><p>

Esto es necesario porque el método JSON.decode) crea el objeto Javascript con propiedades (inicio y fin) de tipo String.

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

Para obtener el tipo de dato de las propiedades de un objeto podemos utilizar la reflexión.

Por ejemplo si tenemos las siguientes clases

public class Libro{
String isbn;
String titulo;
Autor autor;
}
public class Autor{
String id;
String nombre;
Date fechaNacimiento;
}

Un método para obtener el tipo de dato de una propiedad, incluso propiedades anidadas, sería el siguiente

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 invocaríamos de la siguiente manera

Class<?> type=getPropertyType(Libro.class,"autor.fechaNacimiento");
System.out.println(type.toString()); //imprime java.util.Date

Notesé el uso de la clase FieldUtils de la librería Apache Commons Lang, cuyo método getField busca el campo recursivamente en la jerarquía de las clase y sus interfaces. Para agregar la librería a nuestro proyecto hay que agregar la siguiente dependencia


<dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-lang3</artifactId>
 <version>3.1</version>
 </dependency>

Para listar todas las propiedades de un objeto recursivamente podemos utilizar como ejemplo el código de la clase FieldUtils.

Con la librería Jackson podemos obtener un esquema JSON de cualquier objeto. El detalle es que los tipos de datos que obtendríamos serían sólo los permitidos en JSON (string, number, boolean, object, array, null).

ObjectMapper mapper=new ObjectMapper();
System.out.println(mapper.generateJsonSchema(Libro.class));

Fuentes:
http://stackoverflow.com/questions/11125409/find-the-field-type-using-jackson-json
http://stackoverflow.com/questions/3567372/access-to-private-inherited-fields-via-reflection-in-java
http://wiki.fasterxml.com/JacksonJavaDocs
http://jackson.codehaus.org/1.8.8/javadoc/org/codehaus/jackson/map/ObjectMapper.html#generateJsonSchema(java.lang.Class)
http://wiki.fasterxml.com/JacksonTreeModel

Cuando diseñas la URL de un recurso, no queda muy claro cómo vas a realizar una búsqueda o aplicar un filtro. Por ejemplo, si tuvieramos el recurso “Productos” tendríamos el siguiente esquema para las URL’s

GET   http://localhost:8080/rest/productos   Regresa un listado de productos en formato JSON

Pero resultaría muy inefeiciente cargar TODO el listado de productos en una página. Resulta más conveniente aplicar un filtro al listado y regresar solamente un subconjunto de productos al cliente, por ejemplo, de acuerdo al tipo de producto. Para ello la forma más recomendable sería pasando un listado de parámetros en la URL de la siguiente forma

http://localhost:8080/rest/productos?tipo=OFICINA&marca=SCRIBE

Si solamente quieres realizar la paginación del listado de productos, puedes agregar párametros como startRow=0 y endRow=75 ó start=0 y limit=75 o utilizar un encabezado personalizado cómo “X-Range: 0-75”

A continuación mostrare la implementación de una búsqueda simple en un servicio REST. Para ello utilicé SmartGwt para la interfaz de usuario, Apache CXF para los servicios REST, Jackson para la serialización y Datanucleus (JDO) para la persistencia de los datos.

Primero creamos la interfaz que tendrá como fuente de datos un servicio REST

ListGrid grdProductos=new ListGrid();
grdProductos.setDataSource(new RestDataSource(){
{
setDataURL("/rest/productos");
setDataFormat(DSDataFormat.JSON);

DataSourceField fldId=new DataSourceField("id", FieldType.TEXT);
fldId.setPrimaryKey(true);
fldId.setHidden(true);
DataSourceField fldNombre=new DataSourceField("nombre", FieldType.TEXT, "Producto");
DataSourceField fldTipo=new DataSourceField("tipo", FieldType.TEXT, "Tipo");
DataSourceField fldMarca=new DataSourceField("marca", FieldType.TEXT, "Marca");
setFields(fldId,fldNombre,fldTipo,fldMarca);
}
});
Criteria criteria=new Criteria();
criteria.addCriteria("tipo","OFICINA");
criteria.addCriteria("marca", "SCRIBE");
grdProductos.setInitialCriteria(criteria);
grdProductos.setAutoFetchData(true);

Simplemente creamos un Grid para desplegar los productos y establecemos un filtro inicial (para mantener breve el ejemplo, en realidad, estos parámetros los seleccionaría el usuario).

Cuando se cargué el Grid automáticamente (autoFecthData=true) se hara un Request a la URL de nuestro servicio REST parecida a la siguiente

http://localhost:8080/rest/productos?__gwt_ObjectId=170&amp;tipo=OFICINA&marca=SCRIBE&_operationType=fetch&_startRow=0&_endRow=75&_textMatchStyle=exact&_componentId=isc_ListGrid_0&_dataSource=isc_ProductosModule_2_0&isc_metaDataPrefix=_&isc_dataFormat=json

Cómo podemos ver el Grid automáticamente envía los parámetros del filtro en la URL -incluídos los de paginación-.

Ahora vamos a ver como implementamos el servicio REST con la ayuda de Apache CXF y Jackson

@Path("productos")
public class ProductosResource {
{
json=new ObjectMapper();
log=LoggerFactory.getLogger(ProductosResource.class);
}

ObjectMapper json;
Logger log;

@GET
public String retrieve(@QueryParam("")Producto criteria){
try {
List<Producto> Productos = Producto.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", Productos);
response.put("response", body);
return json.writeValueAsString(response);
} catch (Exception e) {
log.error("", e);
}
return "{\"response\":{\"status\":-1, \"data\":\"Hubo un problema al obtener los Productos\"}}";
}
}

CXF construye automáticamente una instancia de un Producto en base a los parámetros, esta instancia la utilizaremos para filtrar el listado al comparar sus propiedades con los registros en la base de datos.

Finalmente tenemos que implementar nuestra clase Producto de la siguiente manera

@PersistenceCapable(detachable="true")
public class Producto {

@PrimaryKey
@Persistent(valueStrategy=IdGeneratorStrategy.UUIDHEX)
String id;
String nombre;
String tipo;
String marca;

public Producto() {
}

public String getId() {
return id;
}

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

public String getNombre() {
return nombre;
}

public void setNombre(String nombre) {
this.nombre = nombre;
}

public String getTipo() {
return tipo;
}

public void setTipo(String tipo) {
this.tipo = tipo;
}

public String getMarca() {
return marca;
}

public void setMarca(String marca) {
this.marca = marca;
}

public static List<Producto> getByCriteria(Producto criteria) {
List<Producto> Productos;
PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction tx = pm.currentTransaction();
try{
tx.begin();
StringBuffer filter=new StringBuffer();
List<Object> parameters=new ArrayList<Object>();
if(criteria.getId()!=null){
filter.append("id == :id");
parameters.add(criteria.getId());
}
if(criteria.getNombre()!=null){
if(filter.length()>0)filter.append(" && ");
filter.append("nombre == :nombre");
parameters.add(criteria.getNombre());
}
if(criteria.getMarca()!=null){
if(filter.length()>0)filter.append(" && ");
filter.append("marca == :marca");
parameters.add(criteria.getMarca());
}
Query query = pm.newQuery(Producto.class,filter.toString());
Productos=(List<Producto>) query.executeWithArray(parameters.toArray());
tx.commit();
}finally{
if(tx.isActive()){
tx.rollback();
}
pm.close();
}
return Productos;
}

}

Lo interesante es que creamos un filtro dinámico de acuerdo a los valores de los campos en el objeto Criteria.

Conclusión:

Aquí mostré como podemos implementar una búsqueda o filtro básico en nuestros servicios REST. Rara vez vamos a requerir solamente esto, usualmente se presentan casos en los que el filtro o la búsqueda es mucho más complejo. Si utilizas SmartGWT para el código del cliente, existe la clase AdvancedCriteria, que nos permite realizar filtros complejos. El detalle es mapear esta clase a una que podamos utilizar en el lado del servidor y hacer la búsqueda correspondiente mediante JDOQL. Pero esa….. esa es otra historia……

Fuentes:
http://stackoverflow.com/questions/5020704/how-to-design-restful-search-filtering
http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Parameterbeans
https://developers.google.com/appengine/docs/java/datastore/jdo/relationships?hl=es
http://stackoverflow.com/questions/935762/how-to-dynamically-build-jdo-queries-on-multiple-parameters
http://db.apache.org/jdo/jdoql.html
http://www.elasticsearch.org/guide/reference/query-dsl/
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/data/AdvancedCriteria.html
http://cxf.apache.org/docs/jax-rs-advanced-features.html#JAX-RSAdvancedFeatures-FIQLsearchqueries

Si desarrollas aplicaciones web seguramente utilizas Firebug para analizar los envíos y respuestas del servidor. Pero hay ocasiones que necesitas hacer una prueba de un servicio REST al que sólo es necesario enviar un bloque de información, podría ser un fragmento JSON o XML. Pues a la fecha no es posible hacer esto con firebug, así que necesitamos otra herramienta que nos permita hacer esto. En Linux podemos hacerlo fácilmente mediante el comando curl, pero es más fácil hacerlo desde una interfaz gráfica como la extensión para Firefox RESTClient.