package fr.openwide.core.test.jpa.batch;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.querydsl.jpa.impl.JPAQuery;
import fr.openwide.core.commons.util.functional.Joiners;
import fr.openwide.core.jpa.batch.executor.SimpleHibernateBatchExecutor;
import fr.openwide.core.jpa.batch.runnable.ReadOnlyBatchRunnable;
import fr.openwide.core.jpa.batch.runnable.ReadWriteBatchRunnable;
import fr.openwide.core.jpa.exception.SecurityServiceException;
import fr.openwide.core.jpa.exception.ServiceException;
import fr.openwide.core.jpa.query.IQuery;
import fr.openwide.core.jpa.query.Queries;
import fr.openwide.core.test.business.person.model.Person;
import fr.openwide.core.test.business.person.model.QPerson;
public abstract class AbstractTestSimpleHibernateBatchExecutor extends AbstractTestHibernateBatchExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTestSimpleHibernateBatchExecutor.class);
protected abstract SimpleHibernateBatchExecutor newSimpleHibernateBatchExecutor();
@Test
public void readWrite() {
List<Long> toExecute = Lists.newArrayList(personIds);
final List<Long> executed = Lists.newArrayList();
SimpleHibernateBatchExecutor executor = newSimpleHibernateBatchExecutor();
executor.batchSize(10);
executor.run(Person.class, toExecute, new ReadWriteBatchRunnable<Person>() {
@Override
public void executeUnit(Person unit) {
LOGGER.warn("Executing: " + unit.getDisplayName());
unit.setLastName(NEW_LASTNAME_VALUE);
try {
personService.update(unit);
executed.add(unit.getId());
} catch (ServiceException | SecurityServiceException e) {
throw new IllegalStateException(e);
}
}
});
assertEquals(toExecute, executed);
assertAllPersonsNamed(NEW_LASTNAME_VALUE);
}
@Test
public void readWriteInsideTransaction() {
writeRequiredTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
readWrite();
}
});
assertAllPersonsNamed(NEW_LASTNAME_VALUE);
}
@Test
public void readOnly() {
List<Long> toExecute = Lists.newArrayList(personIds);
SimpleHibernateBatchExecutor executor = newSimpleHibernateBatchExecutor();
executor.batchSize(10);
executor.run(Person.class, toExecute, new ReadOnlyBatchRunnable<Person>() {
@Override
public void executeUnit(Person unit) {
LOGGER.warn("Executing: " + unit.getDisplayName());
unit.setLastName(NEW_LASTNAME_VALUE);
try {
personService.update(unit);
} catch (ServiceException | SecurityServiceException e) {
throw new IllegalStateException(e);
}
}
});
assertNoPersonNamed(NEW_LASTNAME_VALUE);
}
@Test
public void readOnlyInsideTransaction() {
writeRequiredTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
readOnly();
}
});
assertNoPersonNamed(NEW_LASTNAME_VALUE);
}
@Test
public void customNonConsumingQuery() {
List<Long> toExecute = Lists.newArrayList(Iterables.skip(personIds, 50));
Collections.sort(toExecute);
IQuery<Person> query = Queries.fromQueryDsl(
new JPAQuery<Person>(getEntityManager())
.from(QPerson.person)
.where(QPerson.person.id.in(toExecute))
.orderBy(QPerson.person.id.desc())
);
List<Long> expectedExecuted = Lists.newArrayList(toExecute);
Collections.sort(expectedExecuted, Ordering.natural().reverse());
final List<Long> executed = Lists.newArrayList();
SimpleHibernateBatchExecutor executor = newSimpleHibernateBatchExecutor();
executor.batchSize(10);
executor.runNonConsuming("Person query", query, new ReadOnlyBatchRunnable<Person>() {
@Override
public void executeUnit(Person unit) {
LOGGER.warn("Executing: " + unit.getDisplayName());
executed.add(unit.getId());
}
});
assertEquals(expectedExecuted, executed);
}
@Test
public void customConsumingQuery() {
List<Long> toExecute = Lists.newArrayList(personIds);
Collections.sort(toExecute);
IQuery<Person> query = Queries.fromQueryDsl(
new JPAQuery<Person>(getEntityManager())
.from(QPerson.person)
.where(QPerson.person.lastName.like("Lastname%"))
.orderBy(QPerson.person.id.desc())
);
List<Long> expectedExecuted = Lists.newArrayList(toExecute);
Collections.sort(expectedExecuted, Ordering.natural().reverse());
final List<Long> executed = Lists.newArrayList();
SimpleHibernateBatchExecutor executor = newSimpleHibernateBatchExecutor();
executor.batchSize(10);
executor.runConsuming("Person query", query, new ReadWriteBatchRunnable<Person>() {
@Override
public void executeUnit(Person unit) {
LOGGER.warn("Executing: " + unit.getDisplayName());
/* Remove the "Lastname" prefix, which "consumes" this element
* (e.g. it removes this element from the query's results)
*/
unit.setLastName(NEW_LASTNAME_VALUE);
try {
personService.update(unit);
executed.add(unit.getId());
} catch (ServiceException | SecurityServiceException e) {
throw new IllegalStateException(e);
}
}
});
assertEquals(expectedExecuted, executed);
}
private static class SequentialFailingRunnable<T> extends PartitionCountingRunnable<T> {
@Override
public void preExecutePartition(List<T> partition) {
LOGGER.warn("Executing partition: " + Joiners.onComma().join(partition));
}
@Override
public void executePartition(List<T> partition, int partitionIndex) {
super.executePartition(partition, partitionIndex);
switch (partitionIndex) {
case 0: // First executePartition: succeed
LOGGER.warn("executePartition#{}: Succeeding", partitionIndex);
break;
case 1: // Second executePartition: fail
LOGGER.warn("executePartition#{}: Failing", partitionIndex);
throw new TestBatchException1();
default: // Should not happen
Assert.fail();
}
}
}
@Test
public void preExecuteErrorDefaultBehavior() {
SimpleHibernateBatchExecutor executor = newSimpleHibernateBatchExecutor();
executor.batchSize(10);
Exception runException = null;
PreExecuteFailingRunnable<Person> runnable = new PreExecuteFailingRunnable<>();
try {
executor.run(Person.class, personIds, runnable);
} catch (Exception e) {
runException = e;
}
assertThat(runException, instanceOf(IllegalStateException.class));
assertThat(runException.getCause(), instanceOf(ExecutionException.class));
assertThat(runException.getCause().getCause(), instanceOf(TestBatchException1.class));
assertEquals(0, runnable.getExecutedPartitionCount());
}
@Test
public void preExecuteErrorCustomBehavior() {
SimpleHibernateBatchExecutor executor = newSimpleHibernateBatchExecutor();
executor.batchSize(10);
Exception runException = null;
PreExecuteFailingRunnable<Person> runnable = new PreExecuteFailingRunnable<Person>() {
@Override
public void onError(ExecutionException exception) {
throw new TestBatchException2(exception);
}
};
try {
executor.run(Person.class, personIds, runnable);
} catch (Exception e) {
runException = e;
}
assertThat(runException, instanceOf(TestBatchException2.class));
assertThat(runException.getCause(), instanceOf(ExecutionException.class));
assertThat(runException.getCause().getCause(), instanceOf(TestBatchException1.class));
assertEquals(0, runnable.getExecutedPartitionCount());
}
@Test
public void executePartitionErrorDefaultBehavior() {
SimpleHibernateBatchExecutor executor = newSimpleHibernateBatchExecutor();
executor.batchSize(10);
Exception runException = null;
SequentialFailingRunnable<Person> runnable = new SequentialFailingRunnable<>();
try {
executor.run(Person.class, personIds, runnable);
} catch (Exception e) {
runException = e;
}
assertThat(runException, instanceOf(IllegalStateException.class));
assertThat(runException.getCause(), instanceOf(ExecutionException.class));
assertThat(runException.getCause().getCause(), instanceOf(TestBatchException1.class));
assertEquals(2, runnable.getExecutedPartitionCount());
}
@Test
public void executePartitionErrorCustomBehavior() {
SimpleHibernateBatchExecutor executor = newSimpleHibernateBatchExecutor();
executor.batchSize(10);
Exception runException = null;
SequentialFailingRunnable<Person> runnable = new SequentialFailingRunnable<Person>() {
@Override
public void onError(ExecutionException exception) {
throw new TestBatchException2(exception);
}
};
try {
executor.run(Person.class, personIds, runnable);
} catch (Exception e) {
runException = e;
}
assertThat(runException, instanceOf(TestBatchException2.class));
assertThat(runException.getCause(), instanceOf(ExecutionException.class));
assertThat(runException.getCause().getCause(), instanceOf(TestBatchException1.class));
assertEquals(2, runnable.getExecutedPartitionCount());
}
}