denisfalqueto / trt6common

Framework comum para aplicações do TRT6, portada para jee6
2 stars 2 forks source link

CDI Interceptors não são executados em métodos sobrescritos que usam Generics #14

Open augustobreno opened 10 years ago

augustobreno commented 10 years ago

Nenhum interceptor (transações, tratamento de exceções) é executado quando uma subclasse sobrescreve um método da superclasse que possui parâmetros Genéricos, e a anotação do interceptador está na superclasse.

Cenário

Um exemplo real no trt6common:

Considere a classe base CrudFacadeBase e o método genérico abaixo (perceba que o interceptador de transações foi ativado):

@Transacional
public E saveAndFlush(E entity) {
    return getRepository().saveAndFlush((E) entity);
}

Considere a classe ServidorCrudFacade extends CrudFacadeBase, que redefine o método da seguinte forma (apenas para demonstração):

public Servidor saveAndFlush(Servidor entity) {
    Servidor servidor = em.merge(entity);
    em.flush();
    return servidor;
} 

Finalmente, considere a classe ServidorAction, que faz uso da fachada e define os métodos:

public void save(Servidor servidor) {
    getFacade().saveAndFlush(servidor);     
}

public **CrudFacadeBase** getFacade() {
    return this.facade;     
} 

Caso o método ServidorAction.save(...) seja invocado, o interceptador indicado por @Transactional (ou qualquer outro anotado) não será executado no método CrudFacadeBase.saveAndFlush(...). No entanto, se o método getFacade() acima tiver sua assinatura alterada para o tipo concreto, os interceptadores são acionados como esperado:

public **ServidorCrudFacade** getFacade() {
    return this.facade;     
}

O grande problema é que o método getFacade() não é definido naturalmente na classe ServidorAction, mas em uma superclasse genérica CrudActionBase, portando sempre baseado no superTipo ou em um tipo Genérico.

O que acontece?

O Weld se complicado no momento de decidir se um método deve ser interceptado ou não. Pelo que entendi do fonte do weld, ele tem um mapa que associa os métodos das classes a um conjunto de interceptadores, porém ele representa os métodos através da seguinte tupla: classe, nome do método, tipo dos parâmetros. Quando o generics + override entram na estrutura das classes, o Weld se perde para associar métodos a interceptadores.

Bug no Weld?

Aparentemente sim. Encontrei alguns links com casos semelhantes e uma issue no github do weld. Aparentemente o bug foi resolvido no Weld na versão 2.2.2.Final, bem distante da que o Jboss EAP 6.3 utiliza, weld 1.1.23.Final.

https://issues.jboss.org/browse/WELD-1672

http://stackoverflow.com/questions/13484258/cdi-interceptor-in-extended-class-doesnt-work-when-called-form-base-class-refer

http://stackoverflow.com/questions/5337879/ejb-interceptors-are-not-called-when-using-generic-interfaces

Workarround ?

A única forma que encontrei, por enquanto, é sobrescrevendo os métodos que usam a fachada na classe ServidorAction. Mas... não vale a pena, vai quebrar todo o trabalho de reuso que construímos. Sem falar que este problema deve surgir em outras situações.

Por exemplo, descobri uma consequência muito séria deste problema que estava "abafada". Os métodos da classe CrudFacadeBase deveriam ser todos transacionais, porém, visto que o interceptador não é acionado em alguns casos, não há transação disponível para o método. Se este método fizer uso do Repository duas vezes, como não há uma transação "pai" iniciada, o próprio Repository criará uma transação para si, uma para cada método, e a operação deixará de ser transacional. Veja exemplo hipotético:

@Transacional
public void saveAndAudit(E entity) {
    getRepository().save(entity);
    getRepository().audit(entity);
}

A operação acima será atômica quando o interceptador indicado por @Transacional for acionado. Porém, se o cenário descrito anteriormente ocorrer, o interceptador será ignorado e os métodos save() e audit() ocorrerão em transações diferentes... Isso pode ser desastroso!

Soluções?