Para organizar el código del cliente utilizo Overlay Types.

En un método intenté regresar un valor de tipo Integer, lo cuál me arrojaba una excepción de tipo IllegalArgumentException.

    public class Sala extends JavaScriptObject{
        public native final Integer getId()/*-{
            return this.id;
        }-*/; 

        public final String getNombre(){
            return JSOHelper.getAttribute(this, "nombre");
        }
        protected Sala() {
        } 

    }

esto es debido a que, en Javascript, el atributo ‘this.id’ es de tipo number y JSNI no puede hacer la conversión automáticamente.
Debemos realizar la conversión de la siguiente manera

    public native final Integer getId()/*-{
        return this.id ? @java.lang.Integer::valueOf(I)(this.id) : null;
    }-*/;

Fuentes:
https://code.google.com/p/google-web-toolkit/issues/detail?id=2972#c6
https://developers.google.com/web-toolkit/doc/latest/DevGuideCodingBasicsJSNI#methods-fields
http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16432

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.

Para la implementación de un sistema de mensajes utilicé los llamados overlay types de GWT. Declaré una clase de la siguiente manera

    public class SystemMessage extends JavaScriptObject {</p><pre><code>    protected SystemMessage() {
    }

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

    public final native String getHtml()/*-{
        return this.html;
    }-*/;
}
</code></pre><p>

Agregué a esta clase un método para obtener los mensajes del sistema desde un servicio REST

    public static void getMessages(final AsyncCallback<SystemMessage[]> callback) {
        try {
            RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,
                    URLRESTSYSTEMMESSAGES);
            rb.sendRequest(null, new RequestCallback() {</p><pre><code>            @Override
            public void onResponseReceived(Request request,
                    Response response) {
                if (response.getStatusCode() == 200) {
                    JavaScriptObject jsResponse = JSON.decode(response
                            .getText());
                    DSResponse dsResponse = new DSResponse(JSOHelper
                            .getAttributeAsJavaScriptObject(jsResponse,
                                    "response"));
                    if (dsResponse.getStatus() == 0) {
                        JavaScriptObject[] array = JSOHelper
                            .getAttributeAsJavaScriptObjectArray(dsResponse.getJsObj(),
                                        "data");
                        SystemMessage[] messages = (SystemMessage[]) array;
                        callback.onSuccess(messages);
                    }
                } else {
                    callback.onFailure(new Exception(response.getText()));
                }
            }

            @Override
            public void onError(Request request, Throwable exception) {
                callback.onFailure(exception);
            }
        });
    } catch (Exception e) {
        callback.onFailure(e);
    }
}
</code></pre><p>

El servidor nos regresa una respuesta en JSON parecida a la siguiente

    {"response":{ "status": 0, "data": [{"id":"1", "html": "Mensaje 1 
"},{"id":"2", "html": "Mensaje 2 
"}]}}

El problema es que este código no marca errores, incluso funciona correctamente cuando lo ejecuto en DevMode. Pero cuando hice el deploy en un servidor JBoss e intenté ejecutarlo, simplemente no me mostraba los mensajes pero tampoco registraba algún error en la consola. Después de muchísimas líneas de log.info() llegué a la conclusión que el problema era el casting del array. Intenté hacer un ‘cast’ de un array de tipo JavascriptObject[] a uno de tipo SystemMessage[]

    SystemMessage[] messages = (SystemMessage[]) array;

Para hacer el casting de un array en Java tenemos que hacer el casting elemento por elemento.

    SystemMessage[] messages = new SystemMessage[array.length];
    for(int i=0; i<array.length; i++){
        messages[i] = (SystemMessage) array[i];
    }

Nota: intenté utilizar el método Arrays.copyOf() pero éste no ha sido implementado en GWT. Si intentas utilizarlo te arrojará una error parecido al siguiente

The method copyOf(JavaScriptObject[], int, Class) is undefined for the type Arrays

Cuando intentaba hacer un cálculo sencillo para colocar una ventana en determinada posición, me aparecía el siguiente error

com.google.gwt.dev.shell.HostedModeException: invoke arguments: JS value of type string, expected int

el problema era la forma en que establecí el tamaño de la ventana, utilicé el método setSize -que recibe como parámetros cadenas- e intenté invocar el método getWidth antes de haber pintado/mostrado el control en pantalla.

@Override
public void onModuleLoad() {
    Window win = new Window();
    win.setSize("320", "180");
    SC.say("Window width: "+win.getWidth()); // throws Exception
}

Después de mostrar el control si puedes utilizar el método getWidth()

@Override
public void onModuleLoad() {
    Window win = new Window();
    win.setSize("320", "180");
    win.show();
    SC.say("Window width: "+win.getWidth()); // "Window width: 320"
}

Si necesitas el valor del width antes de mostrar el control -como era mi caso- tienes que utilizar el método getWidthAsString() y convertir la cadena a Integer

@Override
public void onModuleLoad() {
    Window win = new Window();
    win.setSize("320", "180");
    SC.say("Window width: "+Integer.parseInt(win.getWidthAsString()));
}

Fuentes:
SmartClient Forums

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

Para facilitar el uso de servicios REST utilzamos JavascriptObject overlay types, los cuales nos permiten obtener un objeto a partir de una cadena JSON. Los overlay types sólo permiten ciertos tipos de datos como parámetros o valores de retorno. Entonces, para obtener un Map a partir de una cadena JSON como la siguiente, podemos utilizar la clase JSOHelper del framewrok SmartGwt:

{“id”:”menu1″,”opciones”:{“key1″:”value1″,”key2″:”value2″,”key3″:”value3”}}

tendríamos que hacerlo de la siguiente manera:

public class Menu extends JavaScriptObject{

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

public final native JavaScriptObject getNativeOpciones()/*-{return this.opciones}-*/;

public final LinkedHashMap<String,String> getOpciones(){

LinkedHashMap<String,String> linkedMap=new LinkedHashMap<String,String>();

Map map=JSOHelper.convertToMap(getNativeOpciones());

Iterator iterator=map.entrySet().iterator();

while(iterator.hasNext()){

Map.Entry item=(Map.Entry)iterator.next();

linkedMap.put((String)item.getKey(),(String)item.getValue());

}

return linkedMap;

}

}