// all the configured qualifier keys
Set<Class<? extends Annotation>> emQualifiers = emHolder.isSet() ?
new HashSet<Class<? extends Annotation>>(Arrays.asList(Default.class)) :
transactionHelper.resolveEntityManagerQualifiers(transactionalAnnotation, targetClass);
TransactionBeanStorage transactionBeanStorage = TransactionBeanStorage.getInstance();
boolean isOutermostInterceptor = transactionBeanStorage.isEmpty();
boolean outermostTransactionAlreadyExisted = false;
if (isOutermostInterceptor)
{
// a new Context needs to get started
transactionBeanStorage.startTransactionScope();
}
// the 'layer' of the transactional invocation, aka the refCounter
@SuppressWarnings("UnusedDeclaration")
int transactionLayer = transactionBeanStorage.incrementRefCounter();
Exception firstException = null;
try
{
for (Class<? extends Annotation> emQualifier : emQualifiers)
{
EntityManager entityManager = resolveEntityManagerForQualifier(emQualifier);
EntityManagerEntry entityManagerEntry = createEntityManagerEntry(entityManager, emQualifier);
transactionBeanStorage.storeUsedEntityManager(entityManagerEntry);
EntityTransaction transaction = getTransaction(entityManagerEntry);
if (!transaction.isActive())
{
transaction.begin();
}
else if (isOutermostInterceptor)
{
outermostTransactionAlreadyExisted = true;
}
//don't move it before EntityTransaction#begin() and invoke it in any case
beforeProceed(entityManagerEntry);
}
return invocationContext.proceed();
}
catch (Exception e)
{
firstException = e;
// we only cleanup and rollback all open transactions in the outermost interceptor!
// this way, we allow inner functions to catch and handle exceptions properly.
if (isOutermostInterceptor)
{
Set<EntityManagerEntry> entityManagerEntryList =
transactionBeanStorage.getUsedEntityManagerEntries();
if (!outermostTransactionAlreadyExisted)
{
// We only commit transactions we opened ourselfs.
// If the transaction got opened outside of our interceptor chain
// we must not handle it.
// This e.g. happens if a Stateless EJB invokes a Transactional CDI bean
// which uses the BeanManagedUserTransactionStrategy.
rollbackAllTransactions(entityManagerEntryList);
}
// drop all EntityManagers from the request-context cache
transactionBeanStorage.cleanUsedEntityManagers();
}
// give any extensions a chance to supply a better error message
e = prepareException(e);
// rethrow the exception
throw e;
}
finally
{
// will get set if we got an Exception while committing
// in this case, we rollback all later transactions too.
boolean commitFailed = false;
// commit all open transactions in the outermost interceptor!
// For Resource-local this is a 'JTA for poor men' only, and will not guaranty
// commit stability over various databases!
// In case of JTA we will just commit the UserTransaction.
if (isOutermostInterceptor)
{
if (!outermostTransactionAlreadyExisted)
{
// We only commit transactions we opened ourselfs.
// If the transaction got opened outside of our interceptor chain
// we must not handle it.
// This e.g. happens if a Stateless EJB invokes a Transactional CDI bean
// which uses the BeanManagedUserTransactionStrategy.
if (firstException == null)
{
// only commit all transactions if we didn't rollback
// them already
Set<EntityManagerEntry> entityManagerEntryList =
transactionBeanStorage.getUsedEntityManagerEntries();
boolean rollbackOnly = isRollbackOnly(transactionalAnnotation);
if (!rollbackOnly)
{
// but first try to flush all the transactions and write the updates to the database
for (EntityManagerEntry currentEntityManagerEntry : entityManagerEntryList)
{
EntityTransaction transaction = getTransaction(currentEntityManagerEntry);
if (transaction != null && transaction.isActive())
{
try
{
if (!commitFailed)
{
currentEntityManagerEntry.getEntityManager().flush();
if (!rollbackOnly && transaction.getRollbackOnly())
{
// don't set commitFailed to true directly
// (the order of the entity-managers isn't deterministic
// -> tests would break)
rollbackOnly = true;
}
}
}
catch (Exception e)
{
firstException = e;
commitFailed = true;
break;
}
}
}
}
if (rollbackOnly)
{
commitFailed = true;
}
// and now either commit or rollback all transactions
for (EntityManagerEntry currentEntityManagerEntry : entityManagerEntryList)
{
EntityTransaction transaction = getTransaction(currentEntityManagerEntry);
if (transaction != null && transaction.isActive())
{
try
{
// last chance to check it (again)
if (commitFailed || transaction.getRollbackOnly())
{
transaction.rollback();
}
else
{
transaction.commit();
}
}
catch (Exception e)
{
firstException = e;
commitFailed = true;
}
}
}
}
}
// and now we close the open transaction scope
transactionBeanStorage.endTransactionScope();
onCloseTransactionScope();
}
transactionBeanStorage.decrementRefCounter();
if (commitFailed && firstException != null /*null if just #getRollbackOnly is true*/)
{
//noinspection ThrowFromFinallyBlock
throw firstException;