blob: 43405f2660ce92e58a867b169b747fb20ed8ef22 [file] [log] [blame]
/**
* 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<Invokable<?>> invokableUnitsForSequentialTransactions = new ArrayDeque<Invokable<?>>();
private Deque<Invokable<?>> invokableUnits = new ArrayDeque<Invokable<?>>();
private List<Invokable<Boolean>> beforeCommitInvokables = new ArrayList<Invokable<Boolean>>();
private List<Invokable<Boolean>> afterCommitInvokables = new ArrayList<Invokable<Boolean>>();
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<Boolean> invokable)
{
beforeCommitInvokables.add(invokable);
}
public void addAfterCommitInvokable(Invokable<Boolean> invokable)
{
afterCommitInvokables.add(invokable);
}
public <T> T invokeInTransaction(Invokable<T> invokable)
{
final EntityTransaction transaction = getTransaction();
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(transaction);
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());
}
}
try
{
T result = invokable.invoke();
if (topLevel && invokableUnits.peek().equals(invokable))
{
// Success or checked exception:
boolean isActive = transaction.isActive();
if (isActive)
{
invokeBeforeCommit(transaction);
// FIXME check if we are still on top
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<Invokable<Boolean>> i = beforeCommitInvokables.iterator(); i.hasNext();)
{
Invokable<Boolean> 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<Invokable<Boolean>> i = afterCommitInvokables.iterator(); i.hasNext();)
{
Invokable<Boolean> 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> T tryInvoke(final EntityTransaction transaction, Invokable<T> 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
}
}
}