package fr.openwide.core.test.jpa.more.business;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.Collection;
import org.apache.commons.lang3.mutable.MutableObject;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import fr.openwide.core.jpa.batch.executor.BatchExecutorCreator;
import fr.openwide.core.jpa.batch.runnable.ReadWriteBatchRunnable;
import fr.openwide.core.jpa.business.generic.model.GenericEntityReference;
import fr.openwide.core.jpa.exception.SecurityServiceException;
import fr.openwide.core.jpa.exception.ServiceException;
import fr.openwide.core.jpa.more.util.transaction.service.ITransactionSynchronizationTaskManagerService;
import fr.openwide.core.jpa.more.util.transaction.service.TransactionSynchronizationTaskManagerServiceImpl;
import fr.openwide.core.jpa.search.service.IHibernateSearchService;
import fr.openwide.core.test.jpa.more.business.entity.model.TestEntity;
import fr.openwide.core.test.jpa.more.business.entity.service.ITestEntityService;
import fr.openwide.core.test.jpa.more.business.util.transaction.model.TestCreateAfterCommitTask;
import fr.openwide.core.test.jpa.more.business.util.transaction.model.TestDeleteOnRollbackTask;
import fr.openwide.core.test.jpa.more.business.util.transaction.model.TestUseEntityBeforeCommitOrClearTask;
public class TestTransactionSynchronization extends AbstractJpaMoreTestCase {
@Rule
public ExpectedException exception = ExpectedException.none();
@Autowired
private ITransactionSynchronizationTaskManagerService transactionSynchronizationTaskManagerService;
@Autowired
private ITestEntityService testEntityService;
@Autowired
private IHibernateSearchService hibernateSearchService;
@Autowired
private BatchExecutorCreator batchExecutorCreator;
private TransactionTemplate writeTransactionTemplate;
private TransactionTemplate writeRequiresNewTransactionTemplate;
private TransactionTemplate readOnlyTransactionTemplate;
@Autowired
private void setTransactionTemplate(PlatformTransactionManager transactionManager) {
DefaultTransactionAttribute writeTransactionAttribute =
new DefaultTransactionAttribute(TransactionAttribute.PROPAGATION_REQUIRED);
writeTransactionAttribute.setReadOnly(false);
writeTransactionTemplate = new TransactionTemplate(transactionManager, writeTransactionAttribute);
DefaultTransactionAttribute writeRequiresNewTransactionAttribute =
new DefaultTransactionAttribute(TransactionAttribute.PROPAGATION_REQUIRES_NEW);
writeRequiresNewTransactionAttribute.setReadOnly(false);
writeRequiresNewTransactionTemplate = new TransactionTemplate(transactionManager, writeRequiresNewTransactionAttribute);
DefaultTransactionAttribute readOnlyTransactionAttribute =
new DefaultTransactionAttribute(TransactionAttribute.PROPAGATION_REQUIRED);
readOnlyTransactionAttribute.setReadOnly(true);
readOnlyTransactionTemplate = new TransactionTemplate(transactionManager, readOnlyTransactionAttribute);
}
@Test
public void testTaskNoTransaction() {
exception.expect(IllegalStateException.class);
exception.expectMessage(TransactionSynchronizationTaskManagerServiceImpl.EXCEPTION_MESSAGE_NO_ACTUAL_TRANSACTION_ACTIVE);
// Cannot push tasks if there's no transaction
transactionSynchronizationTaskManagerService.push(new TestCreateAfterCommitTask());
}
@Test
public void testAfterCommitTask() {
final TestCreateAfterCommitTask createAfterCommitTask = new TestCreateAfterCommitTask();
readOnlyTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
transactionSynchronizationTaskManagerService.push(createAfterCommitTask);
}
});
entityManagerClear();
Collection<GenericEntityReference<Long, TestEntity>> createdEntities = createAfterCommitTask.getCreatedEntities();
assertEquals(1, createdEntities.size());
TestEntity createdEntity = testEntityService.getById(createdEntities.iterator().next());
assertNotNull(createdEntity);
}
@Test
public void testRollbackTask() throws ServiceException, SecurityServiceException {
TestEntity entity = new TestEntity("entity");
testEntityService.create(entity);
final Long testEntityId = entity.getId();
writeTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
transactionSynchronizationTaskManagerService.push(new TestDeleteOnRollbackTask(testEntityId));
status.setRollbackOnly();
}
});
entityManagerClear();
assertNull(testEntityService.getById(testEntityId));
}
@Test
public void testNestedTransactions() throws ServiceException, SecurityServiceException {
TestEntity entityExpectedToBeDeleted = new TestEntity("entityExpectedToBeDeleted");
testEntityService.create(entityExpectedToBeDeleted);
final Long entityExpectedToBeDeletedId = entityExpectedToBeDeleted.getId();
TestEntity entityNOTExpectedToBeDeleted = new TestEntity("entityNOTExpectedToBeDeleted");
testEntityService.create(entityNOTExpectedToBeDeleted);
final Long entityNOTExpectedToBeDeletedId = entityNOTExpectedToBeDeleted.getId();
writeTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// Create a new transaction
writeRequiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// Should not be executed since this transaction will execute just fine
transactionSynchronizationTaskManagerService.push(new TestDeleteOnRollbackTask(entityNOTExpectedToBeDeletedId));
}
});
// Should not trigger the execution of the rollback task declared above, but only the one below
status.setRollbackOnly();
transactionSynchronizationTaskManagerService.push(new TestDeleteOnRollbackTask(entityExpectedToBeDeletedId));
}
});
entityManagerClear();
assertNull(testEntityService.getById(entityExpectedToBeDeletedId));
assertNotNull(testEntityService.getById(entityNOTExpectedToBeDeletedId));
}
@Test
public void testBeforeCommitOrClearTask() throws ServiceException, SecurityServiceException {
TestEntity entity = new TestEntity("entity");
testEntityService.create(entity);
final Long entityId = entity.getId();
final MutableObject<TestUseEntityBeforeCommitOrClearTask> taskReference = new MutableObject<>();
writeTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
TestEntity reloadedEntity = testEntityService.getById(entityId);
// This task will fail if executed when the entity is not in the session anymore
TestUseEntityBeforeCommitOrClearTask task = new TestUseEntityBeforeCommitOrClearTask(reloadedEntity);
taskReference.setValue(task);
transactionSynchronizationTaskManagerService.push(task);
// Should trigger the task's execution
transactionSynchronizationTaskManagerService.beforeClear();
entityManagerClear();
}
});
entityManagerClear();
assertEquals(1, taskReference.getValue().getExecutionCount());
}
@Test
public void testBeforeCommitOrClearTaskWithRollback() throws ServiceException, SecurityServiceException {
TestEntity entity = new TestEntity("entity");
testEntityService.create(entity);
final Long entityId = entity.getId();
final MutableObject<TestUseEntityBeforeCommitOrClearTask> taskReference = new MutableObject<>();
writeTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
TestEntity reloadedEntity = testEntityService.getById(entityId);
// This task will fail if executed when the entity is not in the session anymore
TestUseEntityBeforeCommitOrClearTask task = new TestUseEntityBeforeCommitOrClearTask(reloadedEntity);
taskReference.setValue(task);
transactionSynchronizationTaskManagerService.push(task);
// Should trigger the task's execution
transactionSynchronizationTaskManagerService.beforeClear();
entityManagerClear();
status.setRollbackOnly();
}
});
entityManagerClear();
assertEquals(1, taskReference.getValue().getExecutionCount());
assertEquals(1, taskReference.getValue().getRollbackCount());
}
@Test
public void testBeforeCommitOrClearTaskBatchExecutor() throws ServiceException, SecurityServiceException {
TestEntity entity1 = new TestEntity("entity1");
testEntityService.create(entity1);
final Long entityId1 = entity1.getId();
TestEntity entity2 = new TestEntity("entity2");
testEntityService.create(entity2);
final Long entityId2 = entity2.getId();
hibernateSearchService.flushToIndexes();
final Collection<TestUseEntityBeforeCommitOrClearTask> tasks = Lists.newArrayList();
batchExecutorCreator.newSimpleHibernateBatchExecutor().commitOnEnd().batchSize(1)
.run(TestEntity.class, ImmutableList.of(entityId1, entityId2), new ReadWriteBatchRunnable<TestEntity>() {
@Override
protected void executeUnit(TestEntity unit) {
// This task will fail if executed when the entity is not in the session anymore
TestUseEntityBeforeCommitOrClearTask task = new TestUseEntityBeforeCommitOrClearTask(unit);
tasks.add(task);
transactionSynchronizationTaskManagerService.push(task);
// The executor should trigger the task's execution just before each clear
}
});
entityManagerClear();
assertEquals(2, tasks.size());
for (TestUseEntityBeforeCommitOrClearTask task : tasks) {
assertEquals(1, task.getExecutionCount());
}
}
}