[RESOLVIDO] Erro de conversão no XStream usando vRaptor e RESTFulie

Olá pessoal,

Estou tendo problemas para fazer um post tendo a seguinte estrutura de objetos:

  • LoteDispensacaoDto

[code]@XStreamAlias( "loteDispensacao" )
public class LoteDispensacaoDto {

@XStreamImplicit( itemFieldName = "loteDispensacaoSolicitacao" )
private List< LoteDispensacaoSolicitacaoDto > loteDispensacaoSolicitacao;

// gets and sets

}[/code]

  • LoteDispensacaoSolicitacaoDto

[code]@XStreamAlias( "loteDispensacaoSolicitacao" )
public class LoteDispensacaoSolicitacaoDto {

@XStreamAsAttribute
private Long numSolicitacao;

@XStreamAsAttribute
private Long idInternamento;

@XStreamAsAttribute
private Long idLeito;

@XStreamAsAttribute
private Long idPlanoConvenio;

@XStreamImplicit( itemFieldName = "itemAprazamentoHorario" )
private ArrayList< ItemAprazamentoHorarioDto > itemAprazamentoHorario;

// gets and sets

}[/code]

  • ItemAprazamentoHorarioDto

[code]@XStreamAlias( "itemAprazamentoHorario" )
public class ItemAprazamentoHorarioDto implements Comparable< ItemAprazamentoHorarioDto > {

@XStreamAsAttribute
private Date dtcHorAprazamento;

@XStreamImplicit( itemFieldName = &quot;itemAprazamentoDispensacao&quot; )
private ArrayList&lt; ItemAprazamentoDispensacaoDto &gt; itemAprazamentoDispensacao;

// gets and sets

}[/code]

  • ItemAprazamentoDispensacaoDto

[code]@XStreamAlias( "itemAprazamentoDispensacao" )
public class ItemAprazamentoDispensacaoDto {

@XStreamAsAttribute
private Long idMaterial;

@XStreamAsAttribute
private Double qtdMaterial;

// gets and sets

}[/code]

Vale ressaltar que a mesma estrutura de classes que tenho no meu cliente eu tenho no meu servidor; e busquei utilizar as anotações [color=orange]@XStreamAsAttribute[/color] e [color=orange]@XStreamImplicit[/color] para tentar resolver o meu problema, mas vi que não consegui.

A chamada para o meu serviço é:

Na minha classe do serviço ( anotado com [color=orange]@Resource[/color] ) eu tenho o método:

	...

	@Post
	@Consumes
	@Path( &quot;/baixarLote&quot; )
	public void create( LoteDispensacaoDto loteDispensacao ) {
		...
	}

	...

Esse objeto passado como parâmetro no método post possui toda a estrutura de objetos dentro dele preenchida. Fiz uma inspeção no XML que está sendo passado na requisição e vai o exemplo:

&lt;?xml version=&quot;1.0&quot; ?&gt; &lt;loteDispensacao&gt; &lt;loteDispensacaoSolicitacao numSolicitacao="129" idInternamento="2780202" idLeito="10224" idPlanoConvenio="91"&gt; &lt;itemAprazamentoHorario dtcHorAprazamento="2011-02-02 22:00:00.0 GMT-03:00"&gt; &lt;itemAprazamentoDispensacao idMaterial="1129" qtdMaterial="1.0"&gt;&lt;/itemAprazamentoDispensacao&gt; &lt;/itemAprazamentoHorario&gt; &lt;/loteDispensacaoSolicitacao&gt; &lt;/loteDispensacao&gt;

Então, aparentemente, está fazendo o processo corretamente, só que com um detalhe, não está considerando as listas dentro dos objetos. Ou seja, o objeto principal ( LoteDispensacaoDto ) possui uma lista do tipo LoteDispensacaoSolicitacaoDto, que também tem uma lista do tipo ItemAprazamentoHorarioDto, que, por sinal, também tem uma lista do tipo ItemAprazamentoDispensacaoDto.

Após a chamada do meu cliente, já no servidor, está sempre acontecendo este erro:

[quote]
09/02/2011 12:01:35 org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet default threw exception
com.thoughtworks.xstream.converters.ConversionException: itemAprazamentoHorario : itemAprazamentoHorario : itemAprazamentoHorario : itemAprazamentoHorario
---- Debugging information ----
message : itemAprazamentoHorario : itemAprazamentoHorario
cause-exception : com.thoughtworks.xstream.mapper.CannotResolveClassException
cause-message : itemAprazamentoHorario : itemAprazamentoHorario
class : br.com.scmba.ws.lancamento.item.conta.prontus.domain.entity.LoteDispensacaoDto
required-type : java.util.ArrayList
path : /loteDispensacao/loteDispensacaoSolicitacao/itemAprazamentoHorario
line number : 1

at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:89)
at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:63)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:76)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:246)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:218)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:162)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:82)
at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:63)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:76)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:60)
at com.thoughtworks.xstream.core.TreeUnmarshaller.start(TreeUnmarshaller.java:137)
at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.unmarshal(AbstractTreeMarshallingStrategy.java:33)
at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:923)
at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:909)
at com.thoughtworks.xstream.XStream.fromXML(XStream.java:861)
at br.com.caelum.vraptor.deserialization.XStreamXMLDeserializer.deserialize(XStreamXMLDeserializer.java:55)
at br.com.caelum.vraptor.interceptor.DeserializingInterceptor.intercept(DeserializingInterceptor.java:87)
at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:59)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.InstantiateInterceptor.intercept(InstantiateInterceptor.java:48)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:83)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.ExceptionHandlerInterceptor.intercept(ExceptionHandlerInterceptor.java:71)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.intercept(ParametersInstantiatorInterceptor.java:89)
at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:59)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:69)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.core.EnhancedRequestExecution.execute(EnhancedRequestExecution.java:23)
at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:92)
at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:58)
at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:89)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
at java.lang.Thread.run(Thread.java:619)[/quote]

A sensação que tenho é que ele não está conseguindo fazer a interpretação correta de uma lista dentro de outra lista, ou de qualquer lista dentro de um objeto passado por parâmetro.

Fazendo uma inspeção percebi que o XStream sempre cria um Map para fazer o de-para entre as classes anotadas com o tipo dela. E no meu exemplo ele esta associando, pelo que percebi, o tipo java.util.ArrayList a classe anotada LoteDispensacaoSolicitacaoDto ( por isso tentei utilizar a anotação @XStreamImplicit para sanar o problema ), pois essa classe está contida na classe LoteDispensacaoDto como um java.util.List.

Já busquei ver esse problema na internet mas não consegui encontrar a resolução do problema. Se puderem me ajudar…

Obrigado de antemão.

Abraço!

o problema é que o VRaptor não consegue ainda adivinhar quais classes vc vai usar do lado do servidor, pra poder
processar as annotations (vc precisa fazer isso explicitamente no XStream). Estamos vendo um jeito bom de fazer isso…

o jeito fácil de resolver é criando a classe:

@Component
public class CustomXStreamXMLDeserializer extends XStreamXMLDeserializer {
    //delegate constructor
    @Override
    protected XStream getXStream() {
         XStream stream = super.getXStream();
         stream.proccessAnnotations(new Class[] {
             LoteDispensacaoDto.class,
             LoteDispensacaoSolicitacaoDto.class,
             ...
         });
         return stream;
    }
}

Lucas,

Obrigado pela ajuda. Resolveu o meu problema. Tive que remover as anotações [color=orange]@XStreamAsAttribute[/color] e [color=orange]@XStreamImplicit[/color] das minhas classes que trafegam pelo serviço e adicionar esse CustomXStreamXMLDeserializer ao meu projeto.

Com essas mudanças eu consegui obter o objeto todo preenchido do lado do servidor, mas, como teste, eu criei no servidor um retorno como o abaixo:

E esse retorno está gerando no lado cliente o seguinte erro:

[quote]java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.RangeCheck(Unknown Source)
at java.util.ArrayList.get(Unknown Source)
at br.com.caelum.restfulie.feature.FollowRedirects.process(FollowRedirects.java:27)
at br.com.caelum.restfulie.request.ResponseChain.next(ResponseChain.java:21)
at br.com.caelum.restfulie.request.RequestStack.process(RequestStack.java:38)
at br.com.caelum.restfulie.request.RequestChain.next(RequestChain.java:20)
at br.com.caelum.restfulie.request.RequestStack.process(RequestStack.java:28)
at br.com.caelum.restfulie.http.DefaultHttpRequest.sendPayload(DefaultHttpRequest.java:38)
at br.com.caelum.restfulie.http.DefaultHttpRequest.post(DefaultHttpRequest.java:85)
at testes.Main.testWS(Main.java:153)
at testes.Main.main(Main.java:40)[/quote]

Pelo que andei pesquisando e depurando, o erro acontece porque ele espera um path como parâmetro no header para poder fazer o redirect para uma URI. Como não passei nenhuma URI, está dando erro no seguinte ponto da classe FollowRedirects ( linha 27 ):

Ou seja, não existe qualquer teste para saber se existe ou não o [color=blue]“Location”[/color] no header, o que estaria levando ao erro acima.

Muito obrigado pela ajuda!

Abraço!

[quote=bland]Lucas,

Obrigado pela ajuda. Resolveu o meu problema. Tive que remover as anotações [color=orange]@XStreamAsAttribute[/color] e [color=orange]@XStreamImplicit[/color] das minhas classes que trafegam pelo serviço e adicionar esse CustomXStreamXMLDeserializer ao meu projeto.

Com essas mudanças eu consegui obter o objeto todo preenchido do lado do servidor, mas, como teste, eu criei no servidor um retorno como o abaixo:

E esse retorno está gerando no lado cliente o seguinte erro:

[quote]java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.RangeCheck(Unknown Source)
at java.util.ArrayList.get(Unknown Source)
at br.com.caelum.restfulie.feature.FollowRedirects.process(FollowRedirects.java:27)
at br.com.caelum.restfulie.request.ResponseChain.next(ResponseChain.java:21)
at br.com.caelum.restfulie.request.RequestStack.process(RequestStack.java:38)
at br.com.caelum.restfulie.request.RequestChain.next(RequestChain.java:20)
at br.com.caelum.restfulie.request.RequestStack.process(RequestStack.java:28)
at br.com.caelum.restfulie.http.DefaultHttpRequest.sendPayload(DefaultHttpRequest.java:38)
at br.com.caelum.restfulie.http.DefaultHttpRequest.post(DefaultHttpRequest.java:85)
at testes.Main.testWS(Main.java:153)
at testes.Main.main(Main.java:40)[/quote]

Pelo que andei pesquisando e depurando, o erro acontece porque ele espera um path como parâmetro no header para poder fazer o redirect para uma URI. Como não passei nenhuma URI, está dando erro no seguinte ponto da classe FollowRedirects ( linha 27 ):

Ou seja, não existe qualquer teste para saber se existe ou não o [color=blue]“Location”[/color] no header, o que estaria levando ao erro acima.

Muito obrigado pela ajuda!

Abraço![/quote]

Inclui essa verificação ao pegar o Location. Enviei um pull request para o projeto.

de qualquer forma, bland, quando vc retorna o status created vc deveria passar a location.

no VRaptor:

result.use(status()).created("/item/23");

ou algo do tipo

Rafael, obrigado!

Lucas, porque deveria se existe um método chamado create que não tem a necessidade de passar parâmetros? Então esse método não deveria existir, é isso?

existe pq vc pode setar o header location na mão:

result.use(http()).addHeader("Location", "....");
result.use(status()).created();

principalmente pq vc pode adicionar várias locations.

a obrigatoriedade é por causa da especificação do 201 created no http

http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

Oi pessoal,

Na verdade a spec diz “should” o que não significa “must”. Se você não implementa o should não tem problema, você é parcialmente compliant. Se você implementa todos os should, você é compliant. Fica semi impossivel ser totalmente compliant sem colocar um proxy no meio para analisar todas as requisicoes (o proprio servidor pode ser parcially compliant).

Mas tenho sentido que vale a pena remover a opção do created sem Location sim. O problema é compatibilidade agora, remover tem que deixar claro o motivo.

Abraço!