package fr.openwide.core.jpa.more.util.transaction.service; import java.util.Collection; import java.util.Iterator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.Ordered; import org.springframework.stereotype.Service; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import fr.openwide.core.jpa.more.util.transaction.exception.TransactionSynchronizationException; import fr.openwide.core.jpa.more.util.transaction.model.ITransactionSynchronizationAfterCommitTask; import fr.openwide.core.jpa.more.util.transaction.model.ITransactionSynchronizationBeforeCommitTask; import fr.openwide.core.jpa.more.util.transaction.model.ITransactionSynchronizationTask; import fr.openwide.core.jpa.more.util.transaction.model.ITransactionSynchronizationTaskRollbackAware; import fr.openwide.core.jpa.more.util.transaction.model.TransactionSynchronizationTasks; import fr.openwide.core.jpa.more.util.transaction.util.ITransactionSynchronizationTaskMerger; import fr.openwide.core.jpa.util.EntityManagerUtils; @Service public class TransactionSynchronizationTaskManagerServiceImpl implements ITransactionSynchronizationTaskManagerService { private static final Class<?> TASKS_RESOURCE_KEY = TransactionSynchronizationTaskManagerServiceImpl.class; public static final String EXCEPTION_MESSAGE_NO_ACTUAL_TRANSACTION_ACTIVE = "No actual transaction active."; @Autowired(required = false) private Collection<ITransactionSynchronizationTaskMerger> transactionSynchronizationTaskMergers = ImmutableList.of(); @Autowired private ConfigurableApplicationContext configurableApplicationContext; @Autowired private EntityManagerUtils entityManagerUtils; @Override public void push(ITransactionSynchronizationBeforeCommitTask beforeCommitTask) { addSynchronizationIfNeeded(); autowireAndInitialize(beforeCommitTask); getTasksIfExist().getBeforeCommitTasks().add(beforeCommitTask); } @Override public void push(ITransactionSynchronizationAfterCommitTask afterCommitTask) { addSynchronizationIfNeeded(); autowireAndInitialize(afterCommitTask); getTasksIfExist().getAfterCommitTasks().add(afterCommitTask); } protected void autowireAndInitialize(ITransactionSynchronizationTask beforeCommitTask) { AutowireCapableBeanFactory autowireCapableBeanFactory = configurableApplicationContext.getAutowireCapableBeanFactory(); autowireCapableBeanFactory.autowireBean(beforeCommitTask); autowireCapableBeanFactory.initializeBean(beforeCommitTask, beforeCommitTask.getClass().getName()); } protected void addSynchronizationIfNeeded() { if (!TransactionSynchronizationManager.isActualTransactionActive()) { throw new IllegalStateException(EXCEPTION_MESSAGE_NO_ACTUAL_TRANSACTION_ACTIVE); } if (!isTaskSynchronizationActive()) { // Initialize the list of tasks that will be executed later bindContext(new TransactionSynchronizationTasks()); // Register the synchronization adapter (that will transmit events to this service) TransactionSynchronizationManager.registerSynchronization(new TaskTransactionSynchronizationAdapter()); } } protected boolean isTaskSynchronizationActive() { return TransactionSynchronizationManager.hasResource(TASKS_RESOURCE_KEY); } protected void bindContext(TransactionSynchronizationTasks tasks) { TransactionSynchronizationManager.bindResource(TASKS_RESOURCE_KEY, tasks); } protected TransactionSynchronizationTasks unbindContext() { return (TransactionSynchronizationTasks) TransactionSynchronizationManager.unbindResourceIfPossible(TASKS_RESOURCE_KEY); } private TransactionSynchronizationTasks getTasksIfExist() { return (TransactionSynchronizationTasks) TransactionSynchronizationManager.getResource(TASKS_RESOURCE_KEY); } protected TransactionSynchronizationTasks merge() { TransactionSynchronizationTasks tasks = getTasksIfExist(); if (tasks != null) { for (ITransactionSynchronizationTaskMerger merger : transactionSynchronizationTaskMergers) { merger.merge(tasks); } } return tasks; } @Override public void beforeClear() { TransactionSynchronizationTasks tasks = merge(); if (tasks == null) { return; } if (tasks.isFrozen()) { // If called after doBeforeCommit, there's nothing to do. return; } Iterator<ITransactionSynchronizationBeforeCommitTask> iterator = tasks.getBeforeCommitTasks().iterator(); while (iterator.hasNext()) { ITransactionSynchronizationBeforeCommitTask beforeCommitTask = iterator.next(); if (beforeCommitTask.shouldRunBeforeClear()) { try { beforeCommitTask.run(); entityManagerUtils.getCurrentEntityManager().flush(); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } // This exception MUST be thrown, as we want to rollback if anything goes wrong. // We better ignore other tasks, as they will have no effect on the current transaction. throw new TransactionSynchronizationException("Error while executing a 'before clear' task.", e); } // We mustn't execute the task again before commit iterator.remove(); // ... but we must execute afterRollback() if possible. if (beforeCommitTask instanceof ITransactionSynchronizationTaskRollbackAware) { ITransactionSynchronizationTaskRollbackAware rollbackAwareTask = (ITransactionSynchronizationTaskRollbackAware) beforeCommitTask; tasks.getAlreadyExecutedBeforeClearTasks().add(rollbackAwareTask); } } } } private void doBeforeCommit() { merge(); TransactionSynchronizationTasks tasks = merge(); if (tasks == null) { return; } tasks.freeze(); for (ITransactionSynchronizationBeforeCommitTask beforeCommitTask : tasks.getBeforeCommitTasks()) { try { beforeCommitTask.run(); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } // This exception MUST be thrown, as we want to rollback if anything goes wrong. // We better ignore other tasks, as they will have no effect on the current transaction. throw new TransactionSynchronizationException("Error while executing a 'before commit' task.", e); } } } private void doAfterCommit() { TransactionSynchronizationTasks tasks = getTasksIfExist(); if (tasks == null) { return; } Exception firstException = null; for (ITransactionSynchronizationAfterCommitTask afterCommitTask : tasks.getAfterCommitTasks()) { try { afterCommitTask.run(); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } if (firstException == null) { firstException = e; } else { firstException.addSuppressed(e); } } } if (firstException != null) { // We better throw an exception here, as we want the caller to know something went awry. throw new TransactionSynchronizationException("Error while executing an 'after commit' task.", firstException); } } private void doOnRollback() { Exception firstException = null; TransactionSynchronizationTasks tasks = getTasksIfExist(); if (tasks == null) { return; } for (ITransactionSynchronizationTaskRollbackAware afterCommitTask : Iterables.filter(Lists.reverse(tasks.getAfterCommitTasks()), ITransactionSynchronizationTaskRollbackAware.class)) { try { ((ITransactionSynchronizationTaskRollbackAware) afterCommitTask).afterRollback(); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } if (firstException == null) { firstException = e; } else { firstException.addSuppressed(e); } } } for (ITransactionSynchronizationTaskRollbackAware beforeCommitTask : Iterables.filter(Lists.reverse(tasks.getBeforeCommitTasks()), ITransactionSynchronizationTaskRollbackAware.class)) { try { ((ITransactionSynchronizationTaskRollbackAware) beforeCommitTask).afterRollback(); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } if (firstException == null) { firstException = e; } else { firstException.addSuppressed(e); } } } for (ITransactionSynchronizationTaskRollbackAware beforeClearTask : Lists.reverse(tasks.getAlreadyExecutedBeforeClearTasks())) { try { beforeClearTask.afterRollback(); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } if (firstException == null) { firstException = e; } else { firstException.addSuppressed(e); } } } if (firstException != null) { // We better throw an exception here, as we want the caller to know something went awry. throw new TransactionSynchronizationException( "Error while executing afterRollback() on a synchronization task.", firstException ); } } public class TaskTransactionSynchronizationAdapter extends TransactionSynchronizationAdapter { private TransactionSynchronizationTasks suspendedTasks = null; @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } @Override public void suspend() { suspendedTasks = unbindContext(); } @Override public void resume() { bindContext(suspendedTasks); } @Override public void beforeCommit(boolean readOnly) { doBeforeCommit(); } @Override public void afterCommit() { doAfterCommit(); } @Override public void afterCompletion(int status) { if (TransactionSynchronization.STATUS_ROLLED_BACK == status) { doOnRollback(); } unbindContext(); } } }