[RESOLVIDO] VRaptor + ExtJS 4 (json mal formado)

Boa Noite a todos,

Estou a um tempo tentando integrar o ExtJS 4 com o VRaptor 3.4. Infelizmente a maioria dos exemplos de integração são antigos … datados de 2010 p/ trás quando o VRaptor precisava ainda de algumas "adaptações", mudanças estas que hoje já se encontram incorporadas na core do VRaptor como é o caso da ExtJSJson.class desenvolvida pelo nosso amigo Daniel Kist aqui do fórum.

Encontrei também exemplos de CRUD feitos pela Loiane, que participa aqui do fórum também e do fórum de ExtJS, porem feitos com SpringMVC.

De todo material que encontrei e venho estudado, infelizmente ainda não consegui fazer um misero "insert" de forma razoavel.

Selecionar as coisas do banco, montar as classes e serializa-las não tem misterio.

A tela de login, tbm não teve misterio pois eu tinha total controle sobre as fields … dizendo "olha… vc… tem que se chamar usuario.nome, p/ que o meu VRaptor carregue um objeto Usuario com o campo nome preenchido"

O problema todo comecou quando estou em uma View linkada com uma Controller, que trabalha com uma Store manipulando uma Model (tudo em JS) e todos eles trabalham com propriedades tipo … id, nome … e não meuModelo.id, meuModelo.nome.

Fazendo assim com que quando eu clico nos botao que disparam os inserts/updates … meu json vai todo errado p/ VRaptor, que por consequencia não consegue instanciar o objeto em questao.

Fiz uma baita gambiarra apenas para testar o insert, resgatei o parametro data do request e montei meu objeto na mao e devolvi p/ grid Ext … mas só p; testar.

@Post
    @Path("/ganho/tipoGanho/insert.json")
    @Transactional
    public void tipoGanhoInsert(TipoGanho tipoGanho) {
        
        tipoGanho = new TipoGanho();
        
        // Gambiarra p/ arrumar o json e carregar o objeto só p/ testar o insert

        // Formato que esta vindo o json -> {"id":"","nome":"Teste"}
        String json = request.getParameter("data").replace("{", "").replace("}", ""); 

        // Após o split: ["id":"", "nome":"Teste"]
        String[] keyValue = json.split(",");

        for(int i = 0; i<keyValue.length; i++) {
            String[] valueBruto = keyValue[i].split(":");
            if("nome".equals(valueBruto[0].replace("\"", ""))) {
                tipoGanho.setNome(valueBruto[1].replace("\"", "")); 
            }
        }

        // Inserindo um objeto montado na Gambiarra que tem a propriedade nome setado para Teste
        tipoGanhoDAO.salva(tipoGanho);
        
    	result.use(ExtJSJson.class).from(tipoGanho).success(tipoGanho != null).serialize();
    }

O Ext está postando o cabecalho desta forma:

data	{"id":"","nome":"Teste"}
dir	ASC
limit	limit
sort	nome
start	start
total	total

Quando na verdade deveria montar com aquele padrao de meuModelo.atributo … tipoGanho.id, tipoGanho.nome :roll:

Não sei como fazer isto… já pesquisei e estudei e testei de muitas formas… mas encalhei neste ponto.

Alguem saberia como eu posso mandar la do ExtJS 4 este json preparado p/ VRaptor receber no metodo da controller e já alimentar um objeto ?

Obrigado>

ele tá postando assim como form parameters ou como json?

Fala Lucas,

Anexei um print do Firebug com a aba Postar aberta do momento em que clico no “inserir”, no caso a postagem que vai para o metodo tipoGanhoInsert().

Abaixo se ajudar para algo tem o arquivo de Store da grid.

Ext.define('MyApp.store.TipoGanhos', {
    extend: 'MyApp.data.Store',
    model: 'MyApp.model.TipoGanho',
    autoLoad: true,
    remoteSort: false,
    pageSize: 15,
    autoLoad: {start: 0, limit: 15},
    proxy: {
        simpleSortMode: true,
        type: 'ajax',
        api: {
            read: '/ganho/tipoGanho/read.json',
            create: '/ganho/tipoGanho/insert.json',
            update: '/ganho/tipoGanho/update.json',
            destroy: '/ganho/tipoGanho/delete.json'
        },
        actionMethods: {
            read: 'POST',
            create: 'POST',
            update: 'POST', // Verificar depois o uso de outros verbos http (put)
            destroy: 'POST' // Verificar depois o uso de outros verbos http (delete)
        },  
        reader: {
            type: 'json',
            root: 'data',
            successProperty: 'success'
        },
        writer: {
            type: 'json',
            writeAllFields: true,
            encode: true,
            root: 'data'
        },
        extraParams: { 
            start : 'start',   
            limit : 'limit',  
            sort : 'nome',    
            dir : 'ASC', 
            total: 'total' 
        }, 
        listeners: {
            exception: function(proxy, response, operation){
                Ext.MessageBox.show({
                    title: 'REMOTE EXCEPTION',
                    msg: operation.getError(),
                    icon: Ext.MessageBox.ERROR,
                    buttons: Ext.Msg.OK
                });
            }
        }
    },
    listeners: {
        write: function(proxy, operation){
            var obj = Ext.decode(operation.response.responseText);
            if(obj.success){
                Ext.ux.Msg.flash({
                    msg: obj.message,
                    type: 'success'
                });
            }else{
                Ext.ux.Msg.flash({
                    msg: obj.message,
                    type: 'error'
                });
            }
        }
    }
});


então receba no controller:

public void metodo(int start, int limit, String sort, String dir, int total, String data) {
      MeuObjeto objeto = MeuObjeto.fromJSON(data);
}

ou vc recebe o MeuObjeto e cria um converter que transforma a string em json que veio

Valeu pelos toques Lucas.

Eu implementei das duas formas p/ testar e aprender… porem apesar de terem funcionado não sei qual é a melhor implementação (se é que estão corretas) :lol:

Dê uma olhada:

Utilizando como MeuObjeto.fromJSON(data)

public class TipoGanho {
.
.
    public static TipoGanho fromJSON(String data) {
		TipoGanho retorno = null;
		try {
			JSONObject obj = new JSONObject(data);
			retorno = new TipoGanho();
			retorno.setId(UtilObjeto.toLongNullSeBranco(obj.getString("id")));
			retorno.setNome(obj.getString("nome"));
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return retorno;
	}
}

Controller:

    @Post
    @Path("/ganho/tipoGanho/insert.json")
    @Transactional
       public void tipoGanhoInsert(String data) {
          TipoGanho tipoGanho = TipoGanho.fromJSON(data);
           tipoGanhoDAO.salva(tipoGanho);
    	   result.use(ExtJSJson.class).from(tipoGanho).success(tipoGanho!= null).serialize();
    }

Usando com converter:

@Convert(TipoGanho.class)
@ApplicationScoped
public class TipoGanhoConverter implements Converter<TipoGanho>{

	@Override
	public TipoGanho convert(String value, Class<? extends TipoGanho> type, ResourceBundle bundle) {
		if (value == null || value.equals("")) {
                    return null;
                }
		
		try {
			JSONObject obj = new JSONObject(value);
			TipoGanho retorno = new TipoGanho();
			retorno.setId(UtilObjeto.toLongNullSeBranco(obj.getString("id")));
			retorno.setNome(obj.getString("nome"));
                        return retorno;
            
                } catch (JSONException e) {
                        throw new ConversionError(MessageFormat.format(bundle.getString("converter.tipoGanho.erro"), value));
                }
         }
}

Controller:

@Post
    @Path("/ganho/tipoGanho/insert.json")
    @Transactional
	public void tipoGanhoInsert(TipoGanho data) {
        tipoGanhoDAO.salva(data);
    	result.use(ExtJSJson.class).from(data).success(data != null).serialize();
    }

Que tipo de implementação é mais elegante?
Eu poderia estar implementando de outra forma além de usar o JSONObject ?
Sabendo que se trata de uma aplicacao grande não terei como escapar de criar um Converter p/ cada Modelo?

Seriam essas minhas duvidas p/ encerrarmos este assunto…

Por enquanto Muitissimo obrigado.

acho o converter mais elegante, pq desacopla a conversão do controller.

vc pode usar o XStream pra fazer isso, ou outra biblioteca como o GSon.

Pra evitar fazer isso pra todo modelo, vc pode fazer um converter mais geral (pra Object ou pra uma interface que todo modelo que vai converter pra json implementa)

esboço aqui:

talvez tenha que incluir o type no xstream

qqer coisa me avisa pra eu atualizar o gist :wink:

Bom Dia Lucas,

Achei interessante a ideia dos meus modelos implementarem Jsonable e fiz como recomendado.

Só estou com um probleminha no meu converter agora…

Se eu utilizo esta implementacao (Igual a sugerida)

@Convert(Jsonable.class)
public class JsonableConverter implements Converter<Jsonable> {

   private XStreamBuilder builder;

   public JsonableConverter(XStreamBuilder builder) {
       this.builder = builder;
   }
    
   public Jsonable convert(String value, Class<? extends Jsonable> type, ResourceBundle bundle) {
      if (Strings.isNullOrEmpty(value)) return null;

      XStream stream = builder.jsonInstance();

      String json = String.format("{\"%s\" : %s}", type.getName(), value);

      return (Jsonable) stream.fromXML(json);
   }

}

Acontece essa exception que eu nao soube resolver:

java.lang.UnsupportedOperationException: The JsonHierarchicalStreamDriver can only write JSON

Ai olhando no site do XStrem (http://xstream.codehaus.org/json-tutorial.html) tem uma parte "Read from JSON" que sugere a seguinte implementacao:

@Convert(Jsonable.class)
public class JsonableConverter implements Converter<Jsonable> {

   public Jsonable convert(String value, Class<? extends Jsonable> type, ResourceBundle bundle) {
      if (Strings.isNullOrEmpty(value)) {
          return null;
      }
      
      String json = "{\"data\":"+ value +"}";
      XStream xstream = new XStream(new JettisonMappedXmlDriver());
      xstream.alias("data", type);
      return (Jsonable) xstream.fromXML(json);
      
   }
   
}

Esta implementacao por sua vez conseguio chegar na parte onde tenta injetar as informações no meu Modelo Jsonable porem ele ta dando excecao de NumberFormat, pois trata-se de um insert e a propriedade id do objeto vem em branco e no modelo é Long.

//  data	{"id":"","nome":"Teste"}

com.thoughtworks.xstream.converters.ConversionException: For input string: "" : For input string: ""
---- Debugging information ----
message             : For input string: ""
cause-exception     : java.lang.NumberFormatException
cause-message       : For input string: ""
class               : br.com.teste.model.ganho.TipoGanho
required-type       : java.lang.Long
path                : /data/id
line number         : -1
-------------------------------

TipoGanho.java

@Entity
@Table(name="tipo_ganho")
public class TipoGanho implements Jsonable {
	
	@Id
	@GeneratedValue
	private Long id;
	
	private String nome;

        //getters e setters
}

Seria realmente só resolver esse probleminha de Conversão? ou o negocio é ajustar aquela primeira execption mencionada … a do "… The JsonHierarchicalStreamDriver can only write JSON".

E se for só arrumar o problema de conversão, como posso estar interceptando esse erro p/ setar um Nulo ali em vez de EmBranco ? já que se trata de um Insert e o banco é que deve setar aquele ID. ???

Desculpe a insistência é que gostaria muito de entender do inicio ao fim uma integracao do VRaptor com o ExtJS.

:wink:

o erro do driver acho que eu comi bola mesmo… tem que criar o xstream do jeito que vc fez mesmo…

o erro de conversão é isso mesmo… vc passou “” pra converter pra número e ele não consegue mesmo…

o que vc pode fazer é adicionar um converter pra Long (converter que estende de SingleValueConverter) que transforma “” pra null…
não sei dizer se já tem isso pronto no xstream.

Boa Noite Lucas,

Pesquisei sobre converters p/ XStream e não entendi muita coisa… ate tentei extender o LongConverter que já existe ou implementar o tal do SingleValueConverter mas não rolou.

Ai … VIVA O POG!!! :twisted: … meti um replace p/ tirar o atributo que vem em branco no caso do insert… que no meu projeto 100% das chaves se chama “id”

@Convert(Jsonable.class)
@ApplicationScoped
public class JsonableConverter implements Converter<Jsonable> {

   public Jsonable convert(String value, Class<? extends Jsonable> type, ResourceBundle bundle) {
      if (Strings.isNullOrEmpty(value)) {
          return null;
      }
      
      // POG - retirando o atributo ID caso seja insert (id:"")
      value = value.replace("\"id\":\"\",", "");
      
      String json = "{\"data\":"+ value +"}";
      XStream xstream = new XStream(new JettisonMappedXmlDriver());
      xstream.alias("data", type);
      return (Jsonable) xstream.fromXML(json);
   }
}

Bom, enfim … assim resolveu o problema… e me da tempo p/ mais tarde pesquisar o jeito certo de interceptar um converter do XStream… no caso ai … o LongConverter. :wink:

Obrigado pelas ajudas Lucas… depois que tudo estiver resolvido irei disponibilizar um crud com ExtJS 4 + VRaptor 3.4

:wink:

contribuições são sempre bem vindas =)

dependendo do que fizer, vc pode gerar um plugin e colocar no vraptor-contrib também =)
http://github.com/caelum/vraptor-contrib

[]'s

[quote=guivirtuoso]Boa Noite Lucas,

Pesquisei sobre converters p/ XStream e não entendi muita coisa… ate tentei extender o LongConverter que já existe ou implementar o tal do SingleValueConverter mas não rolou.

Ai … VIVA O POG!!! :twisted: … meti um replace p/ tirar o atributo que vem em branco no caso do insert… que no meu projeto 100% das chaves se chama “id”

@Convert(Jsonable.class)
@ApplicationScoped
public class JsonableConverter implements Converter<Jsonable> {

   public Jsonable convert(String value, Class<? extends Jsonable> type, ResourceBundle bundle) {
      if (Strings.isNullOrEmpty(value)) {
          return null;
      }
      
      // POG - retirando o atributo ID caso seja insert (id:"")
      value = value.replace("\"id\":\"\",", "");
      
      String json = "{\"data\":"+ value +"}";
      XStream xstream = new XStream(new JettisonMappedXmlDriver());
      xstream.alias("data", type);
      return (Jsonable) xstream.fromXML(json);
   }
}

Bom, enfim … assim resolveu o problema… e me da tempo p/ mais tarde pesquisar o jeito certo de interceptar um converter do XStream… no caso ai … o LongConverter. :wink:

Obrigado pelas ajudas Lucas… depois que tudo estiver resolvido irei disponibilizar um crud com ExtJS 4 + VRaptor 3.4

:wink: [/quote]

Boa tarde…

Estou tendo problema na hora de desearilizar um json, vindo do Extjs. O vraptor não esta conseguindo fazer isto.

Guivirtuoso, como fica o controller seu neste caso que você implementou?

Obrigado

como é o json vindo do extjs?

Rfuhr,

Oq fiz foi o seguinte:

Meus Modelos implementam uma interface chamada Jsonable, apenas p/ VRaptor saber qual Converter utilizar na hora da deserializacao.

Jsonable.java

public interface Jsonable {

}

Modelo.java

public class Modelo implements Jsonable {
	
	private Long id;
	private String nome;

        // getter e setters
}

No controller, recebo da seguinte forma:

public void metodo(Modelo data) {
   // No json que recebo tem a variavel data
}

Assim, o Vraptor tenta deserealizar o valor de data no Modelo, sendo que Modelo implementa Jsonable, ai como tenho um converter registrado p/ esse tipo de cara… o VRaptor utiliza ele.

JsonableConverter.java

@Convert(Jsonable.class)
@ApplicationScoped
public class JsonableConverter implements Converter<Jsonable>{

   public Jsonable convert(String value, Class<? extends Jsonable> type, ResourceBundle bundle) {
      if (Strings.isNullOrEmpty(value)) {
          return null;
      }
      
      // POG - retirando o atributo ID caso seja insert (id:"")
      value = value.replace("\"id\":\"\",", "");
      
      String json = "{\"data\":"+ value +"}";
      XStream xstream = new XStream(new JettisonMappedXmlDriver());
      xstream.alias("data", type);
      return (Jsonable) xstream.fromXML(json);
   }
}

Ai no caso está usando o XStream p/ ler o Json e transformar no Class type passado.

Dai nesse ponto ai até teve o POG que comentei quando é caso de insert… que o id vem em branco la do Ext… e como teria q ser long null p/ hibernate criar o ID, eu resolvi com essa coisa feia ai desse replace.

Com isso dai consegui converter numa boa o json no meu Modelo.

:wink: