Friday, July 16, 2010

@ApplicationException is evil... sort of

Historically EJB has frowned on RuntimeExceptions. Throwing them results in your transaction getting rolled back and your bean instance being immediately destroyed. You're welcome to try your transaction again ... just as long as you weren't keeping your data in your @Stateful session bean, cause, you know, the container just destroyed that... hope you didn't need it to retry your commit.

In EJB 3.0 the @ApplicationException type was added so that beans could throw RuntimeExceptions and not have their beans destroyed and have the choice to rollback any transaction in progress. Great! Only... how do you really use this for built-in exception types? XML you say? Yuk!

<ejb-jar>
    <assembly-descriptor>
        <application-exception>java.lang.RuntimeExceptions</application-exception>
    </assembly-descriptor>
</ejb-jar>

And is the above even a good idea? Definitely not! With something like that you're just asking for trouble. The bad part is that it effectively shuts off transaction exception handling for all beans in the entire application. Currently, though, this is your only option.

The problem is it is hard to use this annotation responsibly. It's often too bold and too difficult to take the hard line that a specific exception type is always fine to throw. It is completely lacking in pragmatism. There is no ability for developers to make a more refined choice.

Bottom line, it should be possible to specify how you would like a RuntimeException handled for a specific bean or method. Imagine @ApplicationException where modified like so:

@java.lang.annotation.Target({TYPE, METHOD})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public static @interface ApplicationException {
    Class value();

    boolean rollback() default false;
}
Now, we can do things a little more refined:
@LocalBean
public static class OrangeBean {

    @ApplicationException(RuntimeException.class)
    public void doSomething() {

    }

    public void doSomethingElse() {
        
    }
}

In the above, the OrangeBean doSomething method would be allowed to throw a RuntimeException without a transaction rollback or the bean instance being destroyed.

As well, the annotation could be placed on the class level. Say we have an @Stateful bean that wraps an EXTENDED PersistenceContext so that business logic could more easily be done elsewhere, perhaps by an @Stateless bean that uses Bean-Managed Transactions and wishes to batch process several transactions against the EXTENDED persistence context in a loop:

@Stateful
@ApplicationException(javax.persistence.PersistenceException.class)
@TransactionAttribute(MANDATORY)
public static class EntityManagerWrapper implements EntityManager {

    @PersistenceContext(type = EXTENDED)
    private EntityManager delegate;

    @Override
    public void persist(Object o) {
        delegate.persist(o);
    }

    @Override
    public  T merge(T t) {
        return delegate.merge(t);
    }

    @Override
    public void remove(Object o) {
        delegate.remove(o);
    }

    @Override
    public  T find(Class tClass, Object o) {
        return delegate.find(tClass, o);
    }

    //... and so on
}

The EntityManager API does not throw checked exceptions, only derivatives of javax.persistence.PersistenceException which is itself a RuntimeException. To wrap an EntityManager the bean also need the ability to throw javax.persistence.PersistenceException without causing its destruction and transaction rollback. In that vein, the bean is marked @TransactionAttribute(MANDATORY) so it isn't possible to use the bean without already having a transaction in progress, making it clear that transaction management is not it's responsibility.

In another example, say we would like to create a bean that wishes not be destroyed when a RuntimeException is thrown, but it would still like the transactions it starts (via @TransactionAttribute(REQUIRES_NEW)) to be rolled back should a RuntimeException occur.

@Stateful
@TransactionAttribute(REQUIRES_NEW)
@ApplicationException(value = RuntimeException.class, rollback = true)
public static class YellowBean {

    public void doSomething() {

    }

    public void doSomethingElse() {

    }
}

Naturally, to make this API work with more than one exception type per method or bean, we would of course need a new @ApplicationExceptions (note the plural) to group several @ApplicationException annotations -- for those that are not annotation-aware, you cannot use the same annotation twice on a type, method, field or any member.

@java.lang.annotation.Target({TYPE, METHOD})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public static @interface ApplicationExceptions {
    ApplicationException[] value() default {};
}
With that it would be possible to do all of the above for a few different exception types:
@Stateful
public static class RedBean {

    @ApplicationExceptions({
        @ApplicationException(NumberFormatException.class),
        @ApplicationException(ArrayIndexOutOfBoundsException.class),
        @ApplicationException(value = RuntimeException.class, rollback = true)
    })
    public void doSomething() {

    }

    public void doSomethingElse() {

    }
}

In the above the NumberFormatException and ArrayIndexOutOfBoundsException are considered OK and will not cause instance destruction or transaction rollback, however the '@ApplicationException(value = RuntimeException.class, rollback = true)' acts as a default clause of sorts and says all other RuntimeExceptions do not cause instance destruction, but do cause transaction rollback.

Conclusion: Being able to be more specific with @ApplicationException would be a great improvement. I definitely plan to propose it in EJB.next. If you like the idea, please leave a comment as numbers do greatly increase the likelihood of it being added. Other ideas on how to achieve a similar result more then welcome!