/* * Hibernate OGM, Domain model persistence for NoSQL datastores * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.ogm.utils.jpa; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.persistence.EntityManagerFactory; import javax.persistence.SharedCacheMode; import javax.persistence.ValidationMode; import javax.persistence.spi.PersistenceUnitTransactionType; import javax.transaction.Status; import javax.transaction.TransactionManager; import org.hibernate.cfg.Configuration; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.jpa.HibernateEntityManagerFactory; import org.hibernate.ogm.exception.impl.Exceptions; import org.hibernate.ogm.jpa.HibernateOgmPersistence; import org.hibernate.ogm.utils.SkippableTestRunner; import org.hibernate.ogm.utils.TestEntities; import org.hibernate.ogm.utils.TestEntityManagerFactory; import org.hibernate.ogm.utils.TestEntityManagerFactoryConfiguration; import org.hibernate.ogm.utils.TestHelper; import org.hibernate.ogm.utils.TestEntityManagerFactory.Scope; import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.FrameworkField; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.TestClass; /** * A JUnit 4 runner for OGM tests using JPA. Based on a given set of entities, it manages a entity manager factory, which is used * throughout all test methods of the given test class. * <p> * The entities of the test are to be returned by a parameterless method annotated with {@link TestEntities} in form of * a {@code Class<?>[]}. * <p> * The used entity manager factory can be obtained by annotating a field of type {@link EntityManagerFactory} with the * {@link TestEntityManagerFactory} annotation. The runner will inject the factory in this field then. Depending on the * {@link TestEntityManagerFactory#scope() } setting, either the same entity manager factory instance will be used for all test * methods of a given test class or a new entity manager factory will be created and injected for each individual test method. * <p> * Finally the {@link Configuration} used for bootstrapping the factory can optionally be modified by annotating a * configuration method with the {@link TestEntityManagerFactoryConfiguration} a shown in the example below. * <p> * Usage example: * * <pre> * {@code * @RunWith(OgmJpaTestRunner.class) * public class AnimalFarmTest { * * @TestEntityManagerFactory * public EntityManagerFactory entityManagerFactory; * * @Test * public void shouldCountAnimals() throws Exception { * EntityManager em = entityManagerFactory.createEntityManager(); * ... * em.close(); * } * * @TestEntityManagerFactoryConfiguration * public static void configure(GetterPersistenceUnitInfo info) { * info.getProperties().setProperty( Environment.MONGODB_ASSOCIATIONS_STORE, AssociationStorage.COLLECTION.name() ); * } * * @TestEntities * public Class<?>[] getTestEntities() { * return new Class<?>[]{ PolarBear.class, Giraffe.class }; * } * } * } * </pre> * * @see OgmJpaTestCase Base class for tests which is configured with this runner for ease of use * @author Gunnar Morling * @author Guillaume Smet */ public class OgmJpaTestRunner extends SkippableTestRunner { private final Set<Field> testScopedFactoryFields; private final Set<Field> testMethodScopedFactoryFields; private EntityManagerFactory testScopedEntityManagerFactory; private EntityManagerFactory testMethodScopedEntityManagerFactory; public OgmJpaTestRunner(Class<?> klass) throws InitializationError { super( klass ); testScopedFactoryFields = getTestFactoryFields( getTestClass(), Scope.TEST_CLASS ); testMethodScopedFactoryFields = getTestFactoryFields( getTestClass(), Scope.TEST_METHOD ); } private static Set<Field> getTestFactoryFields(TestClass testClass, TestEntityManagerFactory.Scope scope) { Set<Field> testFactoryFields = new HashSet<Field>(); for ( FrameworkField frameworkField : testClass.getAnnotatedFields( TestEntityManagerFactory.class ) ) { Field field = frameworkField.getField(); if ( scope == field.getAnnotation( TestEntityManagerFactory.class ).scope() ) { field.setAccessible( true ); testFactoryFields.add( field ); } } return testFactoryFields; } @Override public void run(RunNotifier notifier) { if ( isTestScopedEntityManagerFactoryRequired() ) { testScopedEntityManagerFactory = buildEntityManagerFactory(); injectEntityManagerFactory( null, testScopedFactoryFields, testScopedEntityManagerFactory ); } try { super.run( notifier ); } finally { if ( testScopedEntityManagerFactory != null ) { cleanUpPendingTransactionIfRequired( testScopedEntityManagerFactory ); TestHelper.dropSchemaAndDatabase( testScopedEntityManagerFactory ); testScopedEntityManagerFactory.close(); } } } @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { // create test method scoped SF if required; it will be injected in createTest() if ( isTestMethodScopedEntityManagerFactoryRequired( method ) ) { testMethodScopedEntityManagerFactory = buildEntityManagerFactory(); } try { super.runChild( method, notifier ); } finally { if ( testMethodScopedEntityManagerFactory != null ) { cleanUpPendingTransactionIfRequired( testMethodScopedEntityManagerFactory ); testMethodScopedEntityManagerFactory.close(); } } } private boolean isTestScopedEntityManagerFactoryRequired() { return !isTestClassSkipped() && !areAllTestMethodsSkipped(); } private boolean isTestMethodScopedEntityManagerFactoryRequired(FrameworkMethod method) { return !testMethodScopedFactoryFields.isEmpty() && !super.isTestMethodSkipped( method ); } private void cleanUpPendingTransactionIfRequired(EntityManagerFactory entityManagerFactory) { SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) ( (HibernateEntityManagerFactory) entityManagerFactory ).getSessionFactory(); TransactionManager transactionManager = sessionFactory.getServiceRegistry().getService( JtaPlatform.class ).retrieveTransactionManager(); try { if ( transactionManager != null && transactionManager.getStatus() == Status.STATUS_ACTIVE ) { transactionManager.rollback(); } } catch (Exception e) { throw new IllegalStateException( "Error while cleaning up the pending transactions", e ); } } protected EntityManagerFactory buildEntityManagerFactory() { try { GetterPersistenceUnitInfo info = new GetterPersistenceUnitInfo(); info.setClassLoader( Thread.currentThread().getContextClassLoader() ); // we explicitly list them to avoid scanning info.setExcludeUnlistedClasses( true ); info.setJtaDataSource( new NoopDatasource() ); List<String> classNames = new ArrayList<String>(); for ( Class<?> clazz : getConfiguredEntityTypes() ) { classNames.add( clazz.getName() ); } info.setManagedClassNames( classNames ); info.setNonJtaDataSource( null ); info.setPersistenceProviderClassName( HibernateOgmPersistence.class.getName() ); info.setPersistenceUnitName( "default" ); final URL persistenceUnitRootUrl = new File( "" ).toURI().toURL(); info.setPersistenceUnitRootUrl( persistenceUnitRootUrl ); info.setPersistenceXMLSchemaVersion( "2.0" ); info.setProperties( new Properties() ); info.setSharedCacheMode( SharedCacheMode.ENABLE_SELECTIVE ); info.setTransactionType( PersistenceUnitTransactionType.RESOURCE_LOCAL ); info.setValidationMode( ValidationMode.AUTO ); for ( Map.Entry<String, String> entry : TestHelper.getDefaultTestSettings().entrySet() ) { info.getProperties().setProperty( entry.getKey(), entry.getValue() ); } applyTestSpecificSettings( info ); return new HibernateOgmPersistence().createContainerEntityManagerFactory( info, Collections.EMPTY_MAP ); } catch (Exception e) { throw new IllegalStateException( "Unable to build the entity manager factory", e ); } } private Class<?>[] getConfiguredEntityTypes() { for ( FrameworkMethod frameworkMethod : getTestClass().getAnnotatedMethods( TestEntities.class ) ) { Class<?>[] entityTypes = invokeTestEntitiesMethod( frameworkMethod ); if ( entityTypes == null || entityTypes.length == 0 ) { throw new IllegalArgumentException( "Define at least a single annotated entity" ); } return entityTypes; } throw new IllegalStateException( "The entities of the test must be retrievable via a parameterless method which is annotated with " + TestEntities.class.getSimpleName() + " and returns Class<?>[]." ); } private Class<?>[] invokeTestEntitiesMethod(FrameworkMethod frameworkMethod) { Method method = frameworkMethod.getMethod(); method.setAccessible( true ); if ( method.getReturnType() != Class[].class || method.getParameterTypes().length > 0 ) { throw new IllegalStateException( "Method annotated with " + TestEntities.class.getSimpleName() + " must have no parameters and must return Class<?>[]." ); } Class<?>[] entityTypes = null; try { entityTypes = (Class<?>[]) method.invoke( super.createTest() ); } catch (Exception e) { Exceptions.<RuntimeException>sneakyThrow( e ); } return entityTypes; } private void applyTestSpecificSettings(GetterPersistenceUnitInfo info) { try { for ( FrameworkMethod frameworkMethod : getTestClass().getAnnotatedMethods( TestEntityManagerFactoryConfiguration.class ) ) { Method method = frameworkMethod.getMethod(); method.setAccessible( true ); method.invoke( super.createTest(), info ); } } catch (Exception e) { Exceptions.<RuntimeException>sneakyThrow( e ); } } @Override protected Object createTest() throws Exception { Object test = super.createTest(); // inject SFs as per given scopes if ( !testScopedFactoryFields.isEmpty() ) { injectEntityManagerFactory( test, testScopedFactoryFields, testScopedEntityManagerFactory ); } if ( !testMethodScopedFactoryFields.isEmpty() ) { injectEntityManagerFactory( test, testMethodScopedFactoryFields, testMethodScopedEntityManagerFactory ); } return test; } private void injectEntityManagerFactory(Object test, Iterable<Field> fields, EntityManagerFactory sessionFactory) { for ( Field field : fields ) { try { if ( ( test == null && Modifier.isStatic( field.getModifiers() ) ) || ( test != null && !Modifier.isStatic( field.getModifiers() ) ) ) { field.set( test, sessionFactory ); } } catch (Exception e) { throw new RuntimeException( "Can't inject entity manager factory into field " + field ); } } } }