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):
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.
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?
Tentar interagir com o CDI/Weld de alguma forma para redefinir o comportamento dos interceptadores localmente?
Criar um patch do weld 1.1.23 e atualizar o JBoss?
Atualizar para JEE7, JBoss AS 8, que usa Weld 2 + CDI 1.1? ...Muitas melhorias interessantes
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):
Considere a classe ServidorCrudFacade extends CrudFacadeBase , que redefine o método da seguinte forma (apenas para demonstração):
Finalmente, considere a classe ServidorAction, que faz uso da fachada e define os métodos:
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:
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:
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?