/** * Copyright (C) 2012 Stephan Classen * Based on guice-perist (Copyright (C) 2010 Google, Inc.) * * 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 com.github.sclassen.guicejpa.testframework; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import com.github.sclassen.guicejpa.EntityManagerProvider; import com.github.sclassen.guicejpa.Transactional; import com.github.sclassen.guicejpa.UnitOfWork; import com.github.sclassen.guicejpa.testframework.exceptions.RuntimeTestException; import com.github.sclassen.guicejpa.testframework.exceptions.TestException; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import com.google.inject.Injector; /** * Worker for transactional tests. The worker instantiates the {@link TransactionalTask} and * executes them. * * @author Stephan Classen */ public class TransactionalWorker { private static final String DO_TRANSACTIONAL = "doTransactional"; private final TransactionalTasks tasks = new TransactionalTasks(); private final List<TransactionTestEntity> storedEntities = new ArrayList<TransactionTestEntity>(); @Inject private Injector injector; @Inject private UnitOfWork unitOfWork; @Inject private EntityManagerProvider emProvider; /** * Schedules a task for execution by this worker. * If more than one task are scheduled they will be called in the order they have been * scheduled. * * @param taskType the task to schedule for execution. */ public void scheduleTask(Class<? extends TransactionalTask> taskType) { checkTransactionalAnnotation(taskType); final TransactionalTask task = injector.getInstance(taskType); task.setWorker(this); tasks.add(task); } private void checkTransactionalAnnotation(Class<? extends TransactionalTask> taskType) { try { final Method method = taskType.getMethod(DO_TRANSACTIONAL); final Transactional annotation = method.getAnnotation(Transactional.class); checkNotNull(annotation, "@Transactional annotation missing on %s.%s", taskType.getSimpleName(), DO_TRANSACTIONAL); } catch (NoSuchMethodException e) { // should never occure. throw new RuntimeException(e); } } /** * Executes the previously specified tasks. All entities which were stored using * {@link TransactionalTask#storeEntity(TransactionTestEntity)} are collected by the worker.<p/> */ public void doTasks() { checkState(tasks.hasTasks(), "no tasks have been added to the worker."); checkState(tasks.hasNext(), "doTasks() has already been executed."); checkState(!unitOfWork.isActive(), "Active UnitOfWork found."); try { doNextTask(); } catch (TestException e) { // do nothing } catch (RuntimeTestException e) { // do nothing } checkState(!tasks.hasNext(), "One of the tasks forgot to call doOtherTasks()."); checkState(!unitOfWork.isActive(), "Active UnitOfWork after tasks found."); } /** * Check all stored entities if they actually have been persisted in the DB. */ @Transactional public void assertAllEntitesHaveBeenPersisted() { checkState(!storedEntities.isEmpty(), "no entities to check"); for (TransactionTestEntity storedEntity : storedEntities) { assertNotNull("At least one entity which should have been peristed was NOT found in the DB. " + tasks, emProvider.get().find(TransactionTestEntity.class, storedEntity.getId())); } } /** * Check all stored entities if they actually have NOT been persisted in the DB. */ @Transactional public void assertNoEntityHasBeenPersisted() { checkState(!storedEntities.isEmpty(), "no entities to check"); for (TransactionTestEntity storedEntity : storedEntities) { assertNull("At least one entity which should NOT have been peristed was found in the DB. " + tasks, emProvider.get().find(TransactionTestEntity.class, storedEntity.getId())); } } @VisibleForTesting void doNextTask() throws TestException { if (tasks.hasNext()) { final TransactionalTask task = tasks.next(); try { task.doTransactional(); } finally { storedEntities.addAll(task.getPersistedEntities()); } } } /** * Class holding the tasks of a worker. * * @author Stephan Classen */ private static class TransactionalTasks { private final List<TransactionalTask> tasks = new ArrayList<TransactionalTask>(); private int pos = 0; /** * @return {@code true} if there have already been tasks added. */ public boolean hasTasks() { return !tasks.isEmpty(); } /** * Add a task. * * @param task the task to add. * @throws IllegalStateException if {@link #next()} has already been called on this instance. */ public void add(TransactionalTask task) { checkState(pos == 0); tasks.add(task); } /** * @return {@code true} if there are more tasks. */ public boolean hasNext() { return pos < tasks.size(); } /** * @return the next task. * @throws IndexOutOfBoundsException if there are no more tasks. */ public TransactionalTask next() { final TransactionalTask result = tasks.get(pos); pos++; return result; } @Override public String toString() { final StringBuilder sb = new StringBuilder("Tasks["); String separator = ""; for (TransactionalTask t : tasks) { sb.append(separator); final String taskType = t.getClass().getSimpleName(); sb.append(taskType.replaceAll("\\$\\$EnhancerByGuice\\$\\$.*", "")); separator = ", "; } sb.append("]"); return sb.toString(); } } }