package fr.openwide.core.jpa.more.business.history.util; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import org.springframework.beans.factory.annotation.Autowired; import com.google.common.collect.Lists; import fr.openwide.core.jpa.business.generic.service.ITransactionScopeIndependantRunnerService; import fr.openwide.core.jpa.exception.SecurityServiceException; import fr.openwide.core.jpa.exception.ServiceException; import fr.openwide.core.jpa.more.util.transaction.model.ITransactionSynchronizationBeforeCommitTask; public class FactoredHistoryLogBeforeCommitWithDifferencesTask implements ITransactionSynchronizationBeforeCommitTask { @Autowired private ITransactionScopeIndependantRunnerService transactionScopeIndependantRunnerService; private final Set<HistoryLogBeforeCommitWithDifferencesTask<?, ?, ?, ?, ?>> tasks; public FactoredHistoryLogBeforeCommitWithDifferencesTask(Set<HistoryLogBeforeCommitWithDifferencesTask<?, ?, ?, ?, ?>> tasks) { super(); this.tasks = tasks; } /** * @return true, because this task requires its parameters to be still attached to the session when it executes. */ @Override public boolean shouldRunBeforeClear() { return true; } @Override public void run() throws Exception { final List<HistoryLogRunner<?>> runners = Lists.newArrayList(); for (HistoryLogBeforeCommitWithDifferencesTask<?, ?, ?, ?, ?> task : tasks) { runners.add(new HistoryLogRunner<>(task)); } for (HistoryLogRunner<?> runner : runners) { runner.prepareRetrieval(); } /* * Here lies the only interest of this class: executing all the objects fetches in one transaction, even if there * are hundreds. * It allows to take benefit from Hibernate's batch fetching, because: * <ul> * <li>first we fetch all the proxies * <li>and only then we initialize them, so Hibernate will "know" there are more objects of the same class * to be initialized, and will proceed with batch fetching. * </ul> */ transactionScopeIndependantRunnerService.run(true, new Callable<Void>() { @Override public Void call() throws Exception { for (HistoryLogRunner<?> runner : runners) { runner.retrieveReference(); } for (HistoryLogRunner<?> runner : runners) { runner.initializeReference(); } return null; } }); for (HistoryLogRunner<?> runner : runners) { runner.log(); } } private static class HistoryLogRunner<T> { private final HistoryLogBeforeCommitWithDifferencesTask<T, ?, ?, ?, ?> task; private Callable<T> referenceProvider; private T reference; public HistoryLogRunner(HistoryLogBeforeCommitWithDifferencesTask<T, ?, ?, ?, ?> task) { super(); this.task = task; } public void prepareRetrieval() { this.referenceProvider = task.getDifferenceGenerator().getReferenceProvider(task.getMainObject()); } public void retrieveReference() { try { this.reference = referenceProvider.call(); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } throw new IllegalStateException("Error retrieving a reference object for a diff", e); } } public void initializeReference() { try { task.getDifferenceGenerator().initializeReference(reference); } catch (RuntimeException e) { throw new IllegalStateException("Error initializing a reference object for a diff", e); } } public void log() throws ServiceException, SecurityServiceException { task.logNow(reference); } } }