3
votes

I need to audit invocations of ejb beans. Saying audit I mean write informations such as current logged user, method name, additional description to a database. I decided to do it by use of CDI decorator:

@Decorator
public class AccountServiceBeanDecorator implements AccountService {

  @Inject
  @Delegate
  @Any
  AccountService accountService;

  @EJB
  private AuditService auditService;

  @Override
  public Account createAccount(Account account) {
    auditService.saveAudit("Method: createAccount", currentUser, "Creating account by admin");
    return accountService.createAccount(account);
  }

}

and the decorated class:

@Stateless
public class AccountServiceBean implements AccountService {

   @Override
   public Account createAccount(Account account) {
     ... 
   }
}

Now if I call AccountService from another ejb stateless bean, what will happen with transaction?:

@Stateless
public ApplicationFacadeBean implements ApplicationFacade {

  @EJB
  private AccountService accountService;

  @Override
  public Account createAccount(Account account) {
    return accountService.createAccount(account);
  }

}

I wanted to log transaction status in decorator (AccountServiceBeanDecorator) and decorated class (AccountServiceBean), so I injected TransactionSynchronizationRegistry as a resource in both classes:

@Decorator
public class AccountServiceBeanDecorator implements AccountService {

  @Inject
  @Delegate
  @Any
  AccountService accountService;

  @EJB
  private AuditService auditService;

  @Resource
  private TransactionSynchronizationRegistry reg;

  @Override
  public Account createAccount(Account account) {
    log.info("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
    log.info("tx ({}): {}", new Object[] {reg.getTransactionStatus(), reg.getTransactionKey()});
    log.info("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
    auditService.saveAudit("Method: createAccount", currentUser, "Creating account by admin");
    return accountService.createAccount(account);
  }

}

and

@Stateless
public class AccountServiceBean implements AccountService {

   @Resource
   private TransactionSynchronizationRegistry reg;

   @Override
   public Account createAccount(Account account) {

    log.info("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
    log.info("tx ({}): {}", new Object[] {reg.getTransactionStatus(), reg.getTransactionKey()});
    log.info("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
     ... 
   }
}

I received strange behavior:

  • log from decorator

    tx (0): JavaEETransactionImpl: txId=6 nonXAResource=null jtsTx=null localTxStatus=0 syncs=[com.sun.ejb.containers.ContainerSynchronization@68fb15d0]]] 
    
  • NullPointerException on second log (reg is null).

Can anybody explain it to me? Wheter AccountServiceBean class is called within the same transaction as ApplicationFacade?

Thank you

2

2 Answers

1
votes

first: i would not mixing ejbs with cdi interceptors. ejbs has it on interceptor implementations.

second: interceptors are executed in the same transaction as the ejb where the interceptor is around.

possible solution:

  • create a correct ejb interceptor
  • put the interceptor around the method / class
  • create a second ejb (MyLoggerBean) with a method like this logToDatabase(String message) and annotate this method with @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  • inside the interceptor create a class member like this: @EJB private MyLoggerBean loggerBean
  • inside your @AroundInvoke annotated method you could call loggerBean. logToDatabase(...)

this would create a new transaction from inside the current transaction of the ejb where the interceptor is around

--> i know my english is not very good. but i hope that you understand what i think should work. if i have the time, i make e example on github...

0
votes

Hmm... what container are you using? Generally I wouldn't suspect a CDI decorator to work on an EJB... I can't think of anything in the JEE spec that I've encountered that would give evidence either way.

Faced with your problem though, I did this with an interceptor, not a decorator. These are supported by the EJB spec... Anyway, here's my code, you would need to grab the variables from the context in your case:

import java.lang.reflect.Method;

import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

public class InvocationCountInterceptor {
    @Inject
    private InvocationCounter counter;

    @AroundInvoke
    public Object intercept(InvocationContext ctx) throws Exception {
        Object returnValue = ctx.proceed();
        Class<? extends Object> className = ctx.getTarget().getClass();
        Method methodName = ctx.getMethod();
        counter.incrementCounter(className, methodName);
        return returnValue;
    }
}

Then whatever EJB or EJB Method you want to audit, I just added this: @Interceptors(InvocationCountInterceptor.class)