[VRaptor] Interceptando chamada de Métodos

Pessoal tudo bem !?

Gostaria de saber se é possível interceptar a invocação dos mutators no VRaptor sem apelar para AOP. Eu poderia fazer isso utilizando CGlib mas para isso eu preciso alterar o funcionamento do VRaptor para que ele utilize um construtor específico dos objetos do Modelo.

Abaixo o código que com AspectJ que faz o que eu quero (mas infelizmente depende do aspectj :smiley: )

@Aspect
public class DirtyWatcherAspect {

	public static class BaseObjectImpl implements InternalMonjoObject {
		private Set<String> dirtFields = new HashSet<String>();

		public Set<String> getDirtFields() {
			return dirtFields;
		}

		public void addDirtField(String fieldName) {
			dirtFields.add(fieldName);
		}
	}

	@DeclareMixin("(@org.monjo.core.annotations.Entity *)")
	public static InternalMonjoObject createMoodyImplementation() {
		return new BaseObjectImpl();
	}

	@Before("call(void (@org.monjo.core.annotations.Entity *).set*(*))")
	public void listOperation(JoinPoint joinPoint) {
		InternalMonjoObject entity = (InternalMonjoObject) joinPoint.getTarget();
		entity.addDirtField(joinPoint.getSignature().getName());
	}
}

[]'s
Rodrigo

isso é feito nas entidades que vem do banco de dados?

você usa hibernate?

Oi Lucas (herói do VRaptor :smiley: )

Deixa eu esclarecer um pouco…

Eu utilizo um banco de dados não relacional chamado Mongo. Criei um framework para o mapeamento objeto - documentos chamado Monjo (https://github.com/rdllopes/monjo). Uma das features do Monjo é a habilidade dele fazer atualizações diferencias (leve em consideração que num banco não relacional um documento pode conter toda uma árvore de documentos). Isso é feito desprezando todas propriedades do objeto que não foram preenchidas. O problema é quando um usuário preenche uma propriedade com null (ou no caso do VRaptor, com uma String Vazia… ex: campo “Data de Despublicação” alterado de ‘01/07/2012’ para ‘’ e no Modelo a propriedade data de despublicação é do tipo Date…).

A solução mais simples (e porca) que vi seria adicionar uma propriedade com a lista de campos alterados em cada objeto e atualizar essa lista em cada um dos setters. Uma alternativa que achei razoável é utilizar AOP, mas isso forçaria todos usuários do Monjo a também utilizar aop.

Gostaria de saber se ao menos no VRaptor não seria possível resolver o problema de uma forma diferente.

[]'s
Rodrigo

o que vc quer usar é AOP por definição…

o que vc pode fazer é não usar o aspectJ (que seria a única dependência que vc adicionaria aos seus usuários, o que eu não vejo muito problema)

como vc receberia essas entidades? por parâmetros dos métodos dos controllers?

se sim, dá pra vc criar um interceptor que decora esses parâmetros com proxies. Algo assim:

@Intercepts(after=ParametersInstantiatorInterceptor)
public class MonjoInterceptor implements Interceptor {
     public MonjoInterceptor(Proxifier proxifier, MethodInfo info) {
           //guarda em fields
     }

     accepts => true?

     public void intercept(stack, method, instance) {
           Object[] parametros = this.info.getParameters();
           //pra todo parâmetro
               //se o parametro é uma entidade:
                  parametros[i] = this.proxifier.proxify(classeDoParametro, new MethodInvocation() {
                           //faça as mágicas aqui
                    }
     }
}

o único chato é que não dá pra vc fazer o mixin, então vc teria que usar alguma mágica para gerar os dirty fields e para buscá-los depois.

um jeito talvez feio mas efetivo é criar um componente que tem um Map de Entidade para dirty fields e usar esse mapa no Monjo…

outro jeito é usar cglib puro (o proxifier é do vraptor e wrappeia o cglib), daí dá pra fazer o mixin sem problemas

Pois é Lucas,

Depois de muita luta descobri que não seria assim tão fácil…

Pelo que eu entendi do código do VRaptor, o ParametersInstantiatorInterceptor faz duas coisas ( e por isso compromete uma solução mais simples).

  1. Ele instancia uma floresta de objetos que será usada como parâmetros do método alvo no controller, no meu caso através do Ognl
  2. Ele atribui às propriedade desses objetos os valores recebidos via parâmetros, no meu caso chamando os setters apropriado e fazendo as conversões necessárias

O problema é que a floresta de Objetos no passo 2 precisa ser uma floresta de proxies, porque depois que os setters já aconteceram o proxy já não funcionaria mais. A idéia do proxy é justamente interceptar essas chamadas de set*.

Talvez seja preciso configurar o Ognl para ele instanciar os proxies. Eu vi por cima a documentação do Ognl e parece que ele possui uma interface ClassResolver… o que me leva a um novo problema. Encontrar o código que recebe a class do ClassResolver, e que invoca o class.newInstace. Daí sim eu poderia alterar o código para usar um MonjoProxifier(class.newInstance) …

Algumas dúvidas…
É mais ou menos isso que eu falei mesmo, ou estou viajando? Você acha que essa solução seria viável? Se sim, como/onde eu poderia alterar/configurar o Ognl para ele utilizar o MonjoProxifier?

[]'s

olá rodrigousp,

a gente fez uma modificação no OgnlParametersProvider para ele usar o objeto instanciado como root antes de aplicar os setters…

se vc usar o último snapshot (que eu anexei aqui) é só criar um interceptor que roda antes do ParametersInstantiatorInterceptor
e setar um atributo de request com o mesmo nome do parâmetro, que o ognl vai usar como root. Assim vc pode colocar o proxy
lá.

seria mais ou menos assim:

@Intercepts(before=ParametersInstantiatorInterceptor.class)
public class MonjoInterceptor implements Interceptor {

      // receber talvez o ParameterNameProvider no construtor, pra descobrir o nome dele
      // receber o request (ou result) no construtor

      // accepts => true, ou só os métodos que recebem uma entidade como parâmetro

     public void intercept(....) {
           Entidade entidade = //cria o proxy
           request.setAttribute("nomeDaEntidadeNoController", entidade);
           stack.next(...);
     }
}

Que acha?

Bom valeu a ajuda Lucas mas acho que infelizmente essa solução não serve :frowning:

Ocorre o seguinte, a entidade que chega em tese é uma árvore de entidades com qualquer loucura de objetos (uma propriedade pode ser uma lista de entidades com lista de entidades). Então, eu não posso simplesmente criar só o proxy da entidade raíz. É preciso que todas entidades da árvore também sejam proxies. Verdade, eu poderia fazer isso na mão… Mas instanciar todos objetos de uma árvore na mão me parece muito esforço.

O ideal seria saber onde ocorre o class.newInstance e mudar esse ponto. Eu não sei se isso ocorre dentro do ognl, e se for não sei se é possível configurá-lo para que ele passe a usar meu proxifier.

[]'s
Rodrigo

é um cara chamado ReflectionBasedNullHandler.

vc precisa decorá-lo com o cara que cria os seus proxies.

@Component
public class MonjoParametersProvider extends OgnlParametersProvider {
     public MonjoParametersProvider(...) {
          super(...);
          //vc pode trocar o Object por alguma outra superclasse sua
          OgnlRuntime.setNullHandler(Object.class, new MongoNullHandler(new ReflectionBasedNullHandler()));
     }

}

isso só resolve o dos objetos internos, pra resolver o problema da raiz não tem uma extensão boa ainda, teria que ter o interceptor, ou criar um método protected no OgnlParametersProvider (daí eu faço aqui).

o que acha?

PS: essa é a parte mais zuada do VRaptor, ainda é um pouco difícil modificar as coisas =(

É lucas, isso tá dando trabalho.

Acabo de ler as classes…
Acho que não dá para simplesmente decorar os handlers. Na verdade, eu precisaria copiar o ReflectionBasedNullHandler e o GenericNullHandler (e.g.: MonjoReflectionBasedNullHandler e MonjoGenericNullHandler).

O ReflectionBasedNullHandler instancia o GenericNullHandler dentro do nullPropertyValue. Dentro do GenericNullHandler está a linha de código que preciso alterar…

Object instance = new Mirror().on(typeToInstantiate).invoke().constructor().withoutArgs();

No método nullPropertyValue ocorre a atribuição dessa instância recém criada.

  Method setter = findMethod(target.getClass(), "set" + propertyCapitalized, target.getClass(), getter.getReturnType());
  new Mirror().on(target).invoke().method(setter).withArgs(instance);

Num tem como decorar isso :frowning:
Alguma ideia!?

To mandando um outro snapshot agora…

sobrescreva o OgnlParametersProvider e sobrescreva o método nullHandler(), com o seu nullHandler decorado.
Vai funcionar tanto pra criar o root quanto pra setar as propriedades.

Ficou show a solução.

public class DirtWatcherOgnlParametersProvider extends OgnlParametersProvider{

	private EmptyElementsRemoval removal;

	public DirtWatcherOgnlParametersProvider(Converters converters, ParameterNameProvider provider, HttpServletRequest request, EmptyElementsRemoval removal, Container container) {
		super(converters, provider, request, removal, container);
		this.removal = removal;
	}
	
	@Override
	protected NullHandler nullHandler() {
		return new DirtWatcherNullHandler(new GenericNullHandler(removal));
	}
}

E o DirtWatcherNullHandler assim…

public class DirtWatcherNullHandler implements NullHandler{
	
	private NullHandler genericNullHandler;

	public DirtWatcherNullHandler(NullHandler nullHandler) {
		this.genericNullHandler = nullHandler;
	}

	@Override
	public <T> T instantiate(Class<T> class1) {
		T t = genericNullHandler.instantiate(class1);
		if (t instanceof IdentifiableDocument) {
			return DirtWatcherProxifier.proxify(t);
		}
		return t;
	}
}

Mas estou com algum problema com o guice devido a mudança na versão do VRaptor, eu acho.

SEVERE: Servlet.service() for servlet [default] in context with path [/plataforma-de-videos] threw exception [Filter execution threw an exception] with root cause
java.lang.ClassNotFoundException: br.com.caelum.vraptor.vraptor2.Info
	at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1643)
	at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1488)
	at br.com.caelum.vraptor.util.StringUtils.lowercaseFirst(StringUtils.java:35)
	at br.com.caelum.vraptor.interceptor.DefaultTypeNameExtractor.nameFor(DefaultTypeNameExtractor.java:61)
	at br.com.caelum.vraptor.interceptor.DefaultTypeNameExtractor.nameFor(DefaultTypeNameExtractor.java:51)
	at br.com.caelum.vraptor.ioc.guice.AbstractScope$ScopedProvider.getName(AbstractScope.java:66)
	at br.com.caelum.vraptor.ioc.guice.AbstractScope$ScopedProvider.get(AbstractScope.java:46)
	at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:40)
	at com.google.inject.internal.InjectorImpl$4$1.call(InjectorImpl.java:968)
	at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1014)
	at com.google.inject.internal.InjectorImpl$4.get(InjectorImpl.java:964)
	at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1003)
	at br.com.caelum.vraptor.ioc.guice.GuiceProvider$GuiceContainer.instanceFor(GuiceProvider.java:61)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:47)
	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.guice.GuiceProvider.provideForRequest(GuiceProvider.java:81)
	at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:89)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:244)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:550)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:380)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:243)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:188)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:166)
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:288)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
	at java.lang.Thread.run(Thread.java:636)

dica: pra vc não precisar guardar o removal, faça o seguinte:

 @Override  
    protected NullHandler nullHandler() {  
        return new DirtWatcherNullHandler(super.nullHandler());  
    }  

o erro do VRaptor2 é pq eu apaguei esse pacote pra poder caber aqui no guj =S

use esse jar:
https://oss.sonatype.org/content/repositories/snapshots/br/com/caelum/vraptor/3.3.3-SNAPSHOT/vraptor-3.3.3-20110329.205406-1.jar

na verdade use esse:
https://oss.sonatype.org/content/repositories/snapshots/br/com/caelum/vraptor/3.3.2-SNAPSHOT/vraptor-3.3.2-20110329.205836-1.jar

(problemas de versão)

Acho que estou fazendo alguma coisa de errado.

Coloquei o tomcat em debug mode mas o meu DirtWatcherOgnlParametersProvider não é chamado.

Como eu registro o DirtWatcherOgnlParametersProvider? Precisa colocar alguma notação?

Vlw.
[]'s
Rodrigo

precisa anotar com @Component

iiiiiiiiiiiiééééééééééééésssssssssss!!!

Carai… Funcionou! Não acredito! Os cara aqui estão pulando de alegria!

:smiley:

Vlw Lucas.

Né possível… :frowning:

Lucas, parece que tem algum bug ainda…
Poderia dar uma olhada !?

29/03/2011 20:27:09 org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [default] in context with path [/plataforma-de-videos] threw exception
br.com.caelum.vraptor.VRaptorException: Unable to find converter for java.util.List
at br.com.caelum.vraptor.core.DefaultConverters.to(DefaultConverters.java:59)
at br.com.caelum.vraptor.http.ognl.ListAccessor.setProperty(ListAccessor.java:86)
at ognl.OgnlRuntime.setProperty(OgnlRuntime.java:2225)
at ognl.ASTProperty.setValueBody(ASTProperty.java:127)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
at ognl.SimpleNode.setValue(SimpleNode.java:279)
at ognl.ASTChain.setValueBody(ASTChain.java:227)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
at ognl.SimpleNode.setValue(SimpleNode.java:279)
at ognl.Ognl.setValue(Ognl.java:737)
at ognl.Ognl.setValue(Ognl.java:783)
at br.com.caelum.vraptor.http.ognl.OgnlParametersProvider.setProperty(OgnlParametersProvider.java:181)
at br.com.caelum.vraptor.http.ognl.OgnlParametersProvider.createParameter(OgnlParametersProvider.java:149)
at br.com.caelum.vraptor.http.ognl.OgnlParametersProvider.getParametersFor(OgnlParametersProvider.java:100)
at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.getParametersFor(ParametersInstantiatorInterceptor.java:107)
at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.intercept(ParametersInstantiatorInterceptor.java:79)
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.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.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 com.r7.videos.security.interceptor.SecurityInterceptor.intercept(SecurityInterceptor.java:72)
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.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:56)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:56)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:56)
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.guice.GuiceProvider.provideForRequest(GuiceProvider.java:81)
at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:89)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:244)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:550)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:380)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:243)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:188)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:288)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:636)

tem idéia de qual é o objeto envolvido na request que dá esse erro? e se possível os parâmetros passados

não vai funcionar se o seu modelo tiver uma List raw, sem generics

Sorry, esqueci de colocar o corpo do post…

Aqui:

video.categoriesId[0]	125
video.description	Desculpa de Bebado
video.geoblock	false
video.geoblockedByCategor...	false
video.headline	Entretenimento
video.httpPath	4d91d092a1dcf6cc6e3ad309.mp4
video.id	4d91d092a1dcf6cc6e3ad308
video.publicationEnd	
video.publicationStart	1301593200000
video.subtitle	
video.tags[0]	bebado
video.tags[1]	calma ai
video.tags[2]	acidente
video.thumbnail	4d91d092a1dcf6cc6e3ad309.jpg
video.thumbnailHost	http://10.110.0.161/akamai/thumb/2011/03/
video.title	Calma aiii
video.updateDate	1301440418372

A entidade é a que segue:

public class Video implements IdentifiableDocument<ObjectId> {
	private Boolean authblock;
	private List<Category> categories;
	private List<Integer> categoriesId;
	private List<String> tags;
	private String description;
	private Boolean geoblock;
	private Boolean geoblockedByCategory;
	private String group;
	private String headline;
	private Boolean highlighted;
	private ObjectId id;
	private Date initialDate;
	private Date lastPublication;
	private String pathHttp;
	private Date publicationEnd;
	private Date publicationStart;
	private Status status;
	private String subtitle;
	private String thumbnail;
	private Integer thumbnailVersion;
	private String title;
	private Date updateDate;
	private Long views;
        // um monte de getters e setters

Ou seja, o erro não ocorre apenas com rawtypes. O fork que você está trabalhando está no github?