Contribuindo para o vRaptor: @LoadWith

7 respostas
thiagobaptista

Salve, galera!

Não sei se aqui é o espaço ideal para discutir isso, mas a questão é que há um tempo venho querendo contribuir com o vRaptor, tanto por uma questão de aprendizado pessoal quanto pelo fato de eu o considerar um dos melhores frameworks MVC para Java.

Há um recurso novo no mais recente release dele que é a anotação @Load nos parâmetros dos métodos dos controladores, que faz com que uma entidade com um atributo "id" seja automagicamente carregada do banco de dados, dispensando a necessidade de criar mais código, como um DAO, para essa tarefa.

Por debaixo dos panos, o que o framework faz é recuperar o parâmetro "id" da entidade e, através de um EntityManager, mandar um "find" e recuperá-la.

Achei muito bacana, porém vejo que há situações em que seria necessário fazer mais que simplesmente chamar um "em.find()". Por exemplo, nas situações em que é necessário preencher alguma dependência dessa entidade, ou realizar algum processamento sobre ela.

Um bom exemplo disso é essa discussão no blog da Caelum, sobre o uso do padrão Repository, que exigiria injetar uma classe que implementasse esse padrão na entidade recuperada, mais ou menos assim:

public class XuxuRepository {
    
    @PersistenceContext
    private EntityManager em;

    public Xuxu buscar(Object id) {

        Xuxu xuxu = em.find(Xuxu.class, id);

        xuxu.setRepository(this);

        return xuxu;

    }

}

O que eu pensei em implementar seria uma outra anotação, que chamei de @LoadWith, que receberia como parâmetro um ".class" de uma classe-fábrica, talvez implementando uma interface específica e que, instanciando-a, delegaria a ela a carga dessa entidade. Ficaria mais ou menos assim:

// assumindo que cada classe dessa se encontra em um arquivo separado, com os devidos "imports" e demais firulas

public interface Loader<T> {

    T load(Object id);

}

public @interface LoadWith {

    Class<?> value();

}

public class XuxuLoader implements Loader<Xuxu> {

    private XuxuRepository repository; // como ele veio parar aqui  é uma outra história...

    Xuxu load(Object id) {
    
        return repository.buscar(id);

    }

}

E num método de um controlador qualquer:

@Resource
public class XuxuController {

    public void detalhar(@LoadWith(XuxuLoader.class) Xuxu xuxu);

}

A questão é que eu tenho dificuldade de imaginar como implementar isso... porque eu não tenho experiência ainda com contribuição para projetos de código aberto. :oops:

Eu sei que a classe responsável pera mágica é essa aqui. Mas estou com dificuldade em compreender exatamente como o método intercept() funciona, e como refatorá-lo para essa nova funcionalidade.

Como posso começar?

7 Respostas

Lucas_Cavalcanti

Olá Thiago!

muito obrigado pela contribuição e pela idéia!

na anotação vc pode usar:

Class<? extends Loader<?>> value();

para forçar a classe a ser um loader.

básicamente vc precisa alterar o método load() do interceptor… daí dado o nome e o tipo, ele procura o id na requisição e faz o load no finalzinho.

thiagobaptista

Oi Lucas! Que bom que você gostou! :D

Então, a questão é que eu estou com dificuldade para compreender como exatamente o método intercept() da classe ParameterLoaderInterceptor age.

As alterações parciais que eu fiz nesse método ficaram assim (pendendo refatorar):

for (int i = 0; i &lt; names.length; i++) {
	
	Iterable&lt;Load&gt; loads = Iterables.filter(asList(annotations[i]), Load.class);
	
	Iterable&lt;LoadWith&gt; loadsWithFactory = Iterables.filter(asList(annotations[i]), LoadWith.class);
	
	if (!isEmpty(loads)) {				
		Object loaded = load(names[i], types[i]);

        if (loaded == null) {                 	
        	result.notFound(); 
        	return;                 	
        }

        if (args != null) {                	
            args[i] = loaded;                    
        } else {                	
		    request.setAttribute(names[i], loaded);				    
        }
	}
	
	if (!isEmpty(loadsWithFactory)) {				
		Object loadedByFactory = loadWithFactory(names[i], types[i]);

        if (loadedByFactory == null) {                	
        	result.notFound();
        	return;                	
        }

        if (args != null) {                	
            args[i] = loadedByFactory;                    
        } else {                	
		    request.setAttribute(names[i], loadedByFactory);				    
        }
	}
	
}

Faltaria criar o tal método loadWithFactory(). Minha dúvida agora é entender o método como um todo e como obter a anotação para então dar um "antotacao.getValue()" e pegar o .class da classe-fábrica e instanciá-la usando reflection.

Lucas_Cavalcanti

o intercept é o método do Interceptor normal do vraptor :wink:

para pegar o valor da annotation, basta usar o iterable loadsWithFactory:

LoadWith anotacao = Iterables.getFirst(loadsWithFactory);
anotacao.getValue() ==> o que vc queria

talvez seja melhor criar outro interceptor ao invés de modificar o existente… assim a gente separa o @Load do @LoadWith.

wpivotto

Eu criei uma solução aqui https://gist.github.com/1710097

Ela mantem o padrão do Vraptor nos controllers (anotação @Load) e a seleção dos loaders é feita via anotação @LoadedBy nos models. Acho que assim fica mais limpo.
Caso o modelo não seja anotado ele delega para o loader default.

Algo mais ou menos assim:

package app.models;

import app.infra.LoadedBy;
import app.repositories.ProductLoader;

@javax.persistence.Entity
@LoadedBy(ProductLoader.class)
public class Product extends Entity {

	private String name;
	private Double price;

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setPrice(Double price) {
		this.price = price;
	}

	public Double getPrice() {
		return price;
	}
}

e o loader

package app.repositories;

import java.io.Serializable;

import org.hibernate.Session;

import app.infra.Loader;
import app.models.Product;
import br.com.caelum.vraptor.ioc.Component;

@Component
public class ProductLoader implements Loader<Product> {

	private final Session session;
	
	ProductLoader(Session session) {
		this.session = session;
	}

	public Product load(Class<?> type, Serializable id) {
		return (Product) session.get(type, id);
	}

}

e no controller:

@Resource
public class ProductController {

    @Delete("/products/{product.id}")
	public void destroy(@Load Product product) {
		repository.destroy(product);
		result.redirectTo(this).index();  
	}

}

testei aqui e funciona, da pra melhorar bastante, mas é um caminho…

thiagobaptista

Bacana sua solução, wpivotto! Realmente é melhor do que do jeito que eu imaginei, diminue bem o acoplamento entre as classes e mantém a necessidade de somente uma anotação no parâmetro.

Só sugiro renomear os pacotes, pra manter o padrão do vRaptor.

wpivotto

thiagobaptista:
Bacana sua solução, wpivotto! Realmente é melhor do que do jeito que eu imaginei, diminue bem o acoplamento entre as classes e mantém a necessidade de somente uma anotação no parâmetro.

Só sugiro renomear os pacotes, pra manter o padrão do vRaptor.

Concordo… foi somente de teste mesmo :smiley:

thiagobaptista

Então… fechou essa parada? Você irá implementar isso no vRaptor?

Criado 29 de janeiro de 2012
Ultima resposta 2 de fev. de 2012
Respostas 7
Participantes 3