org.apache.tapestry5.internal.jpa.PersistenceContextSpecificEntityTransactionManager Maven / Gradle / Ivy
The newest version!
/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tapestry5.internal.jpa;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import org.apache.tapestry5.ioc.Invokable;
import org.apache.tapestry5.jpa.EntityTransactionManager.VoidInvokable;
import org.slf4j.Logger;
public class PersistenceContextSpecificEntityTransactionManager
{
private final Logger logger;
private final EntityManager entityManager;
private boolean transactionBeingCommitted;
private Deque> invokableUnitsForSequentialTransactions = new ArrayDeque>();
private Deque> invokableUnits = new ArrayDeque>();
private List> beforeCommitInvokables = new ArrayList>();
private List> afterCommitInvokables = new ArrayList>();
public PersistenceContextSpecificEntityTransactionManager(Logger logger,
EntityManager entityManager)
{
this.logger = logger;
this.entityManager = entityManager;
}
private EntityTransaction getTransaction()
{
EntityTransaction transaction = entityManager.getTransaction();
if (!transaction.isActive())
transaction.begin();
return transaction;
}
public void addBeforeCommitInvokable(Invokable invokable)
{
beforeCommitInvokables.add(invokable);
}
public void addAfterCommitInvokable(Invokable invokable)
{
afterCommitInvokables.add(invokable);
}
public T invokeInTransaction(Invokable invokable)
{
if (transactionBeingCommitted)
{
// happens for example if you try to run a transaction in @PostCommit hook. We can only
// allow VoidInvokables
// to be executed later
if (invokable instanceof VoidInvokable)
{
invokableUnitsForSequentialTransactions.push(invokable);
return null;
}
else
{
rollbackTransaction(getTransaction());
throw new RuntimeException(
"Current transaction is already being committed. Transactions started @PostCommit are not allowed to return a value");
}
}
final boolean topLevel = invokableUnits.isEmpty();
invokableUnits.push(invokable);
if (!topLevel)
{
if (logger.isWarnEnabled())
{
logger.warn("Nested transaction detected, current depth = " + invokableUnits.size());
}
}
final EntityTransaction transaction = getTransaction();
try
{
T result = invokable.invoke();
if (topLevel && invokableUnits.peek().equals(invokable))
{
// Success or checked exception:
if (transaction.isActive())
{
invokeBeforeCommit(transaction);
}
// FIXME check if we are still on top
if (transaction.isActive())
{
transactionBeingCommitted = true;
transaction.commit();
transactionBeingCommitted = false;
invokableUnits.clear();
invokeAfterCommit();
if (invokableUnitsForSequentialTransactions.size() > 0)
invokeInTransaction(invokableUnitsForSequentialTransactions.pop());
}
}
return result;
}
catch (final RuntimeException e)
{
if (transaction != null && transaction.isActive())
{
rollbackTransaction(transaction);
}
throw e;
}
finally
{
invokableUnits.remove(invokable);
}
}
private void invokeBeforeCommit(final EntityTransaction transaction)
{
for (Iterator> i = beforeCommitInvokables.iterator(); i.hasNext();)
{
Invokable invokable = i.next();
i.remove();
Boolean beforeCommitSucceeded = tryInvoke(transaction, invokable);
// Success or checked exception:
if (beforeCommitSucceeded != null && !beforeCommitSucceeded.booleanValue())
{
rollbackTransaction(transaction);
// Don't invoke further callbacks
break;
}
}
}
private void invokeAfterCommit()
{
for (Iterator> i = afterCommitInvokables.iterator(); i.hasNext();)
{
Invokable invokable = i.next();
i.remove();
Boolean afterCommitSucceeded = invokable.invoke();
// Success or checked exception:
if (afterCommitSucceeded != null && !afterCommitSucceeded.booleanValue())
{
if (invokableUnitsForSequentialTransactions.size() > 0) { throw new RuntimeException(
"After commit hook returned false but there are still uncommitted Invokables scheduled for the next transaction"); }
return;
}
}
}
private static T tryInvoke(final EntityTransaction transaction, Invokable invokable)
throws RuntimeException
{
T result;
try
{
result = invokable.invoke();
}
catch (final RuntimeException e)
{
if (transaction != null && transaction.isActive())
{
rollbackTransaction(transaction);
}
throw e;
}
return result;
}
private static void rollbackTransaction(EntityTransaction transaction)
{
try
{
transaction.rollback();
}
catch (Exception e)
{ // Ignore
}
}
}