Olá, tenho uma dúvida avançada sobre o VRaptor. Não sei ao certo se estamos esbarrando em uma limitação dele ou se apenas o estamos usando de forma errada.
Aqui vai a controller inicial:[code]package br.com.teste;
// Vários imports
@Resource
@RequestScoped
public class UsuarioController {
private final Result result;
private final UsuarioDao dao;
public UsuarioController(Result result, UsuarioDao dao) {
this.result = result;
this.dao = dao;
}
@Get
@Path("/meuprojeto/{login}/perfil")
public void mostrarPerfil(String login) {
Usuario u = dao.procurarPorLogin(login);
// Várias coisas que faço com o usuário e/ou com o VRaptor.
}
@Post
@Path("/meuprojeto/urlqualquer/{login}")
public void outroMetodo(String login) {
Usuario u = dao.procurarPorLogin(login);
// Várias coisas que faço com o usuário e/ou com o VRaptor.
result.use(Results.logic()).redirectTo(UsuarioController.class).list(login);
}
// Outros métodos
}[/code]Isso funciona perfeitamente. No entanto não estamos satisfeitos e gostaríamos que os parâmetros fossem do tipo Usuario. Lá vai a primeira tentativa:[code]
@Get
@Path("/meuprojeto/{usuario.login}/perfil")
public void mostrarPerfil(Usuario usuario) {
// Várias coisas que faço com o usuário e/ou com o VRaptor.
}
@Post
@Path("/meuprojeto/urlqualquer/{usuario.login}")
public void outroMetodo(Usuario usuario) {
// Várias coisas que faço com o usuário e/ou com o VRaptor.
result.use(Results.logic()).redirectTo(UsuarioController.class).list(usuario);
}[/code]Isto falha miseravelmente. O VRaptor não pesquisa a entidade no banco de dados, o que ele faz é instanciar o Usuario e chamar os setters.
Esta outra tentativa é mais feliz:
[code]
@Get
@Path("/meuprojeto/{usuario}/perfil")
public void mostrarPerfil(Usuario usuario) {
// Várias coisas que faço com o usuário e/ou com o VRaptor.
}
@Post
@Path("/meuprojeto/urlqualquer/{usuario}")
public void outroMetodo(Usuario usuario) {
// Várias coisas que faço com o usuário e/ou com o VRaptor.
result.use(Results.logic()).redirectTo(UsuarioController.class).list(usuario);
}[/code]E para que o VRaptor saiba como instanciar o usuário, usamos isso:[code]package br.com.teste;
// Vários imports
@ApplicationScoped
@Convert(Usuario.class)
public class UsuarioConverter implements Converter<Usuario> {
private final UsuarioDao dao;
public UsuarioConverter(UsuarioDao dao) {
this.dao = dao;
}
public Usuario convert(String value, Class<? extends Usuario> type, ResourceBundle bundle) {
return dao.procurarPorLogin(value);
}
}[/code]E então, até aqui, a controller funciona como esperado.
No entanto, quando eu uso o metodo outroMetodo, que tem um redirectTo, o VRaptor gera umas URLs assim:
/meuprojeto/br.com.teste.Usuario@45f7e2/perfil
Apesar de a conversão de ida String->Usuario ter usado o Converter, a conversão de volta Usuario->String sempre usa o método toString().
Utilizar o método toString() sempre é muito restritivo, pois ele já é utilizado para outras finalidades.
Para contornar este problema, existem algumas soluções possíveis. Mas todas elas são meio gambiarrosas.
A primeira delas é criar um Router herdando de DefaultRouter e sobreescrever o método urlFor por um quase idêntico, mas com uma diferença:return matches.next().urlFor(type, method, creator.instanceWithParameters(resourceMethod, params));
É substituído por:String toReturn = matches.next().urlFor(type, method, creator.instanceWithParameters(resourceMethod, params));
for (Object param : params) {
if (param instanceof Usuario) toReturn = toReturn.replace(param.toString(), ((Usuario) param).getLogin());
}
return toReturn;
Este código é uma gambiarra grotesca, que consiste em consertar a URL errada que foi gerada.
Para tentar fazer um código menos gambiarroso, analisamos que a variável matches é do tipo Iterator<Route> e a implementação concreta de Route é FixedMethodStrategy.
O Iterator<Route> é criado pela classe PathAnnotationRoutesParser, que por sua vez utiliza a RouteBuilder. RouteBuilder instancia a FixedMethodStrategy, injetando nela uma instância de DefaultParametersControl.
O método fillUri(Object) de DefaultParametersControl utiliza o método toString() e é aí que é a origem do problema.
A classe PathAnnotationRoutesParser está fortemente acoplada a RouteBuilder, que por sua vez está fortemente acoplada a FixedMethodStrategy e a DefaultParametersControl, o que significa que substituir a implementação de alguma destas classes por outra coisa é algo difícil.
Uma outra possibilidade mais atraente para resolver isso sem precisar desta gambiarra seria criar uma espécie de decorator de Route e fazer o matcher conter instâncias apenas do decorator. Mas isso continua sendo algo trabalhoso.
Então vem a minha pergunta: Estamos usando o VRaptor de uma forma inadequada ou não prevista? Caso seja inadequada, eu devo me contentar em receber apenas Strings e números como parâmetros extraídos da URL e devo deixar coisas mais complexas apenas para dados de formulários? Caso contrário, gostaria de sugerir uma nova interface para um “Converter reverso”, e então o meu converter ficaria assim:[code]package br.com.teste;
// Vários imports
@ApplicationScoped
@Convert(Usuario.class)
public class UsuarioConverter implements Converter<Usuario>, ReverseConverter<Usuario> {
private final UsuarioDao dao;
public UsuarioConverter(UsuarioDao dao) {
this.dao = dao;
}
public Usuario convert(String value, Class<? extends Usuario> type, ResourceBundle bundle) {
return dao.procurarPorLogin(value);
}
public String reverseConvert(Usuario usuario, ResourceBundle bundle) {
return usuario.getLogin();
}
}[/code]Essa interface hipotética ReverseConverter resolveria este tipo de problema.