/* * 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; import static org.fest.assertions.Assertions.assertThat; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Environment; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.jpa.HibernateEntityManagerFactory; import org.hibernate.ogm.OgmSessionFactory; import org.hibernate.ogm.boot.OgmSessionFactoryBuilder; import org.hibernate.ogm.cfg.OgmProperties; import org.hibernate.ogm.cfg.impl.ConfigurableImpl; import org.hibernate.ogm.cfg.impl.InternalProperties; import org.hibernate.ogm.datastore.document.options.AssociationStorageType; import org.hibernate.ogm.datastore.impl.DatastoreProviderType; import org.hibernate.ogm.datastore.spi.DatastoreConfiguration; import org.hibernate.ogm.datastore.spi.DatastoreProvider; import org.hibernate.ogm.dialect.impl.GridDialects; import org.hibernate.ogm.dialect.spi.GridDialect; import org.hibernate.ogm.model.key.spi.EntityKey; import org.hibernate.ogm.options.navigation.GlobalContext; import org.hibernate.ogm.util.impl.Log; import org.hibernate.ogm.util.impl.LoggerFactory; import com.arjuna.ats.arjuna.coordinator.TxControl; import com.sun.tools.javac.util.ServiceLoader; /** * @author Emmanuel Bernard <emmanuel@hibernate.org> * @author Sanne Grinovero <sanne@hibernate.org> */ public class TestHelper { private static final Log log = LoggerFactory.make(); private static final String TX_CONTROL_CLASS_NAME = "com.arjuna.ats.arjuna.coordinator.TxControl"; private static final GridDialectTestHelper HELPER = determineGridDialectTestHelper(); private static final GridDialectType GRID_DIALECT_TYPE = determineGridDialectType(); static { Class<?> txControlClass = loadClass( TX_CONTROL_CLASS_NAME ); if ( txControlClass != null ) { // set 2 hours timeout on transactions: enough for debug, but not too high in case of CI problems. try { Method timeoutMethod = txControlClass.getMethod( "setDefaultTimeout", int.class ); timeoutMethod.invoke( null, 60 * 60 * 2 ); } catch ( NoSuchMethodException e ) { log.error( "Found TxControl class, but unable to set timeout" ); } catch ( IllegalAccessException e ) { log.error( "Found TxControl class, but unable to set timeout" ); } catch ( InvocationTargetException e ) { log.error( "Found TxControl class, but unable to set timeout" ); } TxControl.setDefaultTimeout( 60 * 60 * 2 ); } } private TestHelper() { } private static GridDialectTestHelper determineGridDialectTestHelper() { for ( GridDialectTestHelperType gridType : GridDialectTestHelperType.values() ) { Class<GridDialectTestHelper> testDialectClass = gridType.loadGridDialectTestHelperClass(); if ( testDialectClass != null ) { return instantiate( gridType.loadGridDialectTestHelperClass() ); } } ServiceLoader<GridDialectTestHelper> testHelper = ServiceLoader.load( GridDialectTestHelper.class ); if ( testHelper.iterator().hasNext() ) { return testHelper.iterator().next(); } return instantiate( GridDialectTestHelperType.HASHMAP.loadGridDialectTestHelperClass() ); } private static GridDialectType determineGridDialectType() { Class<? extends GridDialect> gridDialectClass = getCurrentGridDialectClass(); for ( GridDialectType gridDialectType : GridDialectType.values() ) { Class<? extends GridDialect> testDialectClass = gridDialectType.loadGridDialectClass(); if ( testDialectClass != null && testDialectClass.isAssignableFrom( gridDialectClass ) ) { return gridDialectType; } } return GridDialectType.HASHMAP; } private static <T extends GridDialectTestHelper> GridDialectTestHelper instantiate(Class<T> testableGridDialectClass) { if ( testableGridDialectClass == null ) { return new HashMapTestHelper(); } try { GridDialectTestHelper testableGridDialect = testableGridDialectClass.newInstance(); log.debugf( "Using TestGridDialect %s", testableGridDialectClass ); return testableGridDialect; } catch (Exception e) { throw new RuntimeException( e ); } } public static long getNumberOfEntities(EntityManager em) { return getNumberOfEntities( em.unwrap( Session.class ) ); } public static GridDialectType getCurrentDialectType() { return GRID_DIALECT_TYPE; } public static DatastoreProviderType getCurrentDatastoreProviderType() { return DatastoreProviderTypeHolder.INSTANCE; } public static Class<? extends GridDialect> getCurrentGridDialectClass() { return GridDialectClassHolder.INSTANCE; } public static GridDialect getCurrentGridDialect(DatastoreProvider datastoreProvider) { return HELPER.getGridDialect( datastoreProvider ); } public static <D extends DatastoreConfiguration<?>> Class<D> getCurrentDatastoreConfiguration() { @SuppressWarnings("unchecked") // relies on the fact that the caller assigns correctly; that's ok for this purpose Class<D> configurationType = (Class<D>) HELPER.getDatastoreConfigurationType(); return configurationType; } public static long getNumberOfEntities( Session session ) { return HELPER.getNumberOfEntities( session ); } public static long getNumberOfEntities(SessionFactory sessionFactory) { return HELPER.getNumberOfEntities( sessionFactory ); } public static Map<String, Object> extractEntityTuple(Session session, EntityKey key) { return HELPER.extractEntityTuple( session, key ); } public static long getNumberOfAssociations(Session session) { return HELPER.getNumberOfAssociations( session ); } public static long getNumberOfAssociations(SessionFactory sessionFactory) { return HELPER.getNumberOfAssociations( sessionFactory ); } /** * Returns the number of associations of the given type. * <p> * Optional operation which only is supported for document datastores. */ public static long getNumberOfAssociations(SessionFactory sessionFactory, AssociationStorageType type) { return HELPER.getNumberOfAssociations( sessionFactory, type ); } public static boolean backendSupportsTransactions() { return HELPER.backendSupportsTransactions(); } public static <T> T get(Session session, Class<T> clazz, Serializable id) { return session.get( clazz, id ); } public static void dropSchemaAndDatabase(Session session) { if ( session != null ) { dropSchemaAndDatabase( session.getSessionFactory() ); } } public static void dropSchemaAndDatabase(EntityManagerFactory emf) { if ( emf != null ) { dropSchemaAndDatabase( ( (HibernateEntityManagerFactory) emf ).getSessionFactory() ); } } public static void dropSchemaAndDatabase(SessionFactory sessionFactory) { // if the factory is closed, we don't have access to the service registry if ( sessionFactory != null && !sessionFactory.isClosed() ) { try { HELPER.dropSchemaAndDatabase( sessionFactory ); } catch ( Exception e ) { log.warn( "Exception while dropping schema and database in test", e ); } } } public static void checkCleanCache(SessionFactory sessionFactory) { assertThat( getNumberOfEntities( sessionFactory ) ).as( "Entity cache should be empty" ).isEqualTo( 0 ); assertThat( getNumberOfAssociations( sessionFactory ) ).as( "Association cache should be empty" ).isEqualTo( 0 ); } public static Map<String, String> getDefaultTestSettings() { Map<String, String> settings = new HashMap<>(); settings.put( OgmProperties.ENABLED, "true" ); settings.put( Environment.HBM2DDL_AUTO, "none" ); settings.put( "hibernate.search.default.directory_provider", "ram" ); settings.putAll( HELPER.getAdditionalConfigurationProperties() ); return settings; } public static StandardServiceRegistry getDefaultTestStandardServiceRegistry(Map<String, Object> settings) { StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder(); for ( Entry<String, String> setting : getDefaultTestSettings().entrySet() ) { registryBuilder.applySetting( setting.getKey(), setting.getValue() ); } for ( Entry<String, Object> setting : settings.entrySet() ) { registryBuilder.applySetting( setting.getKey(), setting.getValue() ); } return registryBuilder.build(); } private static MetadataSources getMetadataSources(Class<?>... entityTypes) { MetadataSources sources = new MetadataSources(); for ( Class<?> entityType : entityTypes ) { sources.addAnnotatedClass( entityType ); } return sources; } private static Metadata getDefaultTestMetadata(Map<String, Object> settings, Class<?>... entityTypes) { StandardServiceRegistry serviceRegistry = getDefaultTestStandardServiceRegistry( settings ); MetadataSources sources = getMetadataSources( entityTypes ); return sources.getMetadataBuilder( serviceRegistry ).build(); } public static OgmSessionFactory getDefaultTestSessionFactory(Class<?>... entityTypes) { return getDefaultTestSessionFactory( Collections.<String, Object>emptyMap(), entityTypes ); } public static OgmSessionFactory getDefaultTestSessionFactory(Map<String, Object> settings, Class<?>... entityTypes) { return getDefaultTestMetadata( settings, entityTypes ) .getSessionFactoryBuilder() .unwrap( OgmSessionFactoryBuilder.class ) .build(); } public static <D extends DatastoreConfiguration<G>, G extends GlobalContext<?, ?>> G configureOptionsFor(Map<String, Object> settings, Class<D> datastoreType) { ConfigurableImpl configurable = new ConfigurableImpl(); settings.put( InternalProperties.OGM_OPTION_CONTEXT, configurable.getContext() ); return configurable.configureOptionsFor( datastoreType ); } @SuppressWarnings("unchecked") static <T> Class<T> loadClass(String className) { try { return (Class<T>) Class.forName( className, true, TestHelper.class.getClassLoader() ); } catch ( ClassNotFoundException e ) { //ignore -- try using the class loader of context first } catch ( RuntimeException e ) { // ignore } try { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if ( contextClassLoader != null ) { return (Class<T>) Class.forName( className, false, contextClassLoader ); } else { return null; } } catch ( ClassNotFoundException e ) { return null; } } private static class GridDialectClassHolder { private static final Class<? extends GridDialect> INSTANCE = getGridDialectClass(); private static Class<? extends GridDialect> getGridDialectClass() { // We need to instantiate the GridDialect service as the GridDialect selection relies on having the // DatastoreProvider service initialized. // This dialect instance is initialized with the default configuration and is not aware of the additional settings // set in the test itself. This is not an issue per se as this dialect is not used for anything else. StandardServiceRegistry isolatedServiceRegistry = getDefaultTestStandardServiceRegistry( Collections.<String, Object>emptyMap() ); Object gridDialect = isolatedServiceRegistry.getService( GridDialect.class ); Class<? extends GridDialect> gridDialectClass = GridDialects.getWrappedDialect( (GridDialect) gridDialect ); StandardServiceRegistryBuilder.destroy( isolatedServiceRegistry ); return gridDialectClass; } } private static class DatastoreProviderTypeHolder { private static final DatastoreProviderType INSTANCE = getDatastoreProvider(); private static DatastoreProviderType getDatastoreProvider() { StandardServiceRegistry isolatedServiceRegistry = getDefaultTestStandardServiceRegistry( Collections.<String, Object>emptyMap() ); Object datastoreProviderProperty = isolatedServiceRegistry .getService( ConfigurationService.class ) .getSettings() .get( OgmProperties.DATASTORE_PROVIDER ); StandardServiceRegistryBuilder.destroy( isolatedServiceRegistry ); if ( datastoreProviderProperty == null ) { return null; } String value = datastoreProviderProperty.toString(); // try to resolve by short name DatastoreProviderType byShortName = findByShortName( value ); if ( byShortName != null ) { return byShortName; } // try to resolve by exact class name DatastoreProviderType byClassName = findByExactClassName( value ); if ( byClassName != null ) { return byClassName; } // try to resolve by assignable type (allows matching on subtypes of a data store provider) DatastoreProviderType byAssignableType = findByAssignableType( value ); if ( byAssignableType != null ) { return byAssignableType; } return null; } private static DatastoreProviderType findByShortName(String value) { if ( DatastoreProviderType.isShortName( value ) ) { return DatastoreProviderType.byShortName( value ); } return null; } private static DatastoreProviderType findByExactClassName(String value) { for ( DatastoreProviderType provider : DatastoreProviderType.values() ) { if ( provider.getDatastoreProviderClassName().equals( value ) ) { return provider; } } return null; } private static DatastoreProviderType findByAssignableType(String value) { Class<?> configuredProviderClass = loadClass( value ); if ( configuredProviderClass == null ) { return null; } for ( DatastoreProviderType provider : DatastoreProviderType.values() ) { Class<?> availableProviderClass = loadClass( provider.getDatastoreProviderClassName() ); if ( availableProviderClass != null && availableProviderClass.isAssignableFrom( configuredProviderClass ) ) { return provider; } } return null; } } }