2

I am currently working on an enterprise solution which is deployed as an EAR module in weblogic12c. I need to write a business logic where there will be entity manager methods which perform some updates on database using EntityManager interface with EclipseLink and I need to have an Interceptor around these methods which will intercept the method calls and push some data to JMS queue if the target method does not fail.

Since interceptors live inside same transaction and security context as the intercepted method, so if one fails, it causes the other to fail, i.e. if done by throwing an EJBException. My problem is regarding those transaction rollback exception which you cannot catch before the transaction is actually committed. So even if the intercepted method gets a RollbackException, the interceptor is not immediately terminated since it's running in the same transaction model, so it cannot catch those rollback exceptions. Eventually it is rolled back too, but in the meantime, data gets pushed to JMS queue which is not really desirable.

I also cannot take the JMS queue part out of the interceptor, because, as a software requirement, if the module fails to push data to jms, the transaction should fail and rollback too. So what I am trying to achieve here is, if the intercepted method fails, the transaction will fail too and there should not be any execution in the interceptor beyond context.proceed() method call.

Now, let's say I have a business method:

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
@Interceptors(TestInterceptor.class)
@Override
public TestBean update(TestBean testBean) {
    try {
        TestEntity testEntity = em.createNamedQuery("TestEntity.findById", TestEntity.class)
                .setParameter("id", testBean.getId())
                .getSingleResult();
        // only some bean conversions before merging
        testEntity.setName(testBean.getName());
        testEntity.setPhone(testBean.getPhone());
        testEntity.setImage(testBean.getImage());
        em.merge(testEntity);
        em.flush();
        testBean.setVersion(testEntity.getVersion());
        logger.debug("Update successful");
        return testBean;
    } catch (Exception ex) {
        logger.debug("update::exception: ", ex);
        throw ex;
    }
}

And a web service method:

@MethodPrivilege(MethodRole.GENERIC)
@WebMethod
@Override
public String testDataCRUD(@WebParam(name = "auth") AuthRequest authRequest,
                           @WebParam(name = "id") Long id,
                           @WebParam(name = "name") String name,
                           @WebParam(name = "phone") String phone,
                           @WebParam(name = "image") byte[] image,
                           @WebParam(name = "action") Action action) {
    ...
    // build testBean from received parameters
    ...
    try {
        testManager.update(testBean);
        return "SUCCESS";
    } catch(Exception ex) {
        return "FAILED -- UPDATE Error: " + ex.getMessage();
    }
}

And finally here is the interceptor method:

@AroundInvoke
private Object around(InvocationContext ctx) throws Exception {
    Object testParam = null;
    TestBean testBean = null;

    logger.debug("Test intercepted");

    try {
        testParam = ctx.proceed();
    } catch(Exception ex) {
        logger.debug("Interceptor exception ", ex);
        throw new EJBException("Target method raised an exception");
    }
    // do some bean conversion here
    ...
    ...
    // construct the message object here
    boolean success = jmsNotifier.send(message, false);
    if(!success) {
        throw new Exception("JMS failed.");
    }

    logger.debug("Successfully sent to jms");

    return testParam;
}

Now if the business method gets a RollbackException, the service layer can catch it because the business method initiated a new transaction. But so far I am unable to catch it in the try catch block around ctx.proceed(); as a result, the code goes on and pushes data to jms queue and then gets the exception upon completion of the transaction. How can I resolve this issue? Note: sorry for my bad English, please let me know if I need to clarify some part of it any farther.

Update: (After Display Name is missing's comment)

To describe why I am doing this - say for example I am adding an additional jar package to an existing ejb project, the service layer and the business / entity manager layer is from an existing application. Now, the additional ejb jar I am adding performs some additional stuffs around the target business methods and there can be many methods like this. So any of my colleagues can grab the jar, add it to their projects and I need a way so that it will require them a minimal amount of change in their business or entity methods. So far what I have tried is, all they need to do is to add an Interceptor class which then can use the functionality of the my plugin library. That way only change needed in the business layer is to add an extra line stating the @Interceptor annotation and existing code base not modified by a great extent.

Also tried adding em.flush() but it does not help in the described circumstances. Any help is much appreciated.

Community
  • 1
  • 1
Zobayer Hasan
  • 2,187
  • 6
  • 22
  • 38
  • I believe this is the same as this question, which also talks about difficulty wrapping `ctx.proceed()`: http://stackoverflow.com/questions/11774722/how-to-catch-and-wrap-exceptions-thrown-by-jta-when-a-container-managed-tx-ejb-c – Display Name is missing Jan 07 '15 at 16:59
  • The suggested answer in the post you are referring to requires heavy modification on business layer code. My situation is a little bit different. I am writing a pluggable module which is added through an Interceptor annotation. The business logic comes from existing application code and I cannot (or more precisely, do not want to) modify by a great extent. – Zobayer Hasan Jan 07 '15 at 22:17

1 Answers1

-1

Two things -

1) RollbackException is a runtime exceptions, so catch(Exception e) won't work. You would need something like catch(Throwable t) to intercept everything.

2) You could put your JMS logic inside the try block, that way if ctx.proceed() throws out, you'll never hit the JMS code.

Hope this helps.

Shawn Eion Smith
  • 417
  • 4
  • 18
  • It has been a long time, so thanks for answering. I haven't tried catching throwable object. Infact I didn't even know it can catch a rollback exception. Are you certain about it? About #2, I was throwing the exception, so I dont think jms code would be executed. – Zobayer Hasan Aug 31 '15 at 22:09
  • You should be able to unless the interceptor stack is eating it before it gets to you, but since you're seeing it in your logs I think it's pretty unlikely. – Shawn Eion Smith Sep 01 '15 at 15:05
  • Sorry for such late reply, but my attempts failed. The rollback exceptions are generated only after jpa tries to commit. Also it seems this is very hard to catch specific jpa exceptions.... – Zobayer Hasan Oct 01 '15 at 05:49