Contribuindo para o vRaptor: @LoadWith

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:

[code]public class XuxuRepository {

@PersistenceContext
private EntityManager em;

public Xuxu buscar(Object id) {

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

    xuxu.setRepository(this);

    return xuxu;

}

}[/code]

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:

[code]// 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&lt;?&gt; value();

}

public class XuxuLoader implements Loader<Xuxu> {

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

Xuxu load(Object id) {

    return repository.buscar(id);

}

}
[/code]

E num método de um controlador qualquer:

[code]@Resource
public class XuxuController {

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

}[/code]

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?

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.

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

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):

[code]for (int i = 0; i < 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);				    
    }
}

}[/code]

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.

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.

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…

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.

[quote=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.[/quote]

Concordo… foi somente de teste mesmo :smiley:

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