/* * Hibernate Search, full-text search for your domain model * * 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.search.test; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.apache.lucene.analysis.core.StopAnalyzer; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.jdbc.Work; import org.hibernate.search.Search; import org.hibernate.search.SearchFactory; import org.hibernate.search.cfg.Environment; import org.hibernate.search.engine.integration.impl.ExtendedSearchIntegrator; import org.hibernate.search.hcore.util.impl.ContextHelper; import org.hibernate.search.test.util.MultitenancyTestHelper; import org.hibernate.search.test.util.TestConfiguration; import org.hibernate.search.testsupport.TestConstants; import org.hibernate.search.util.impl.FileHelper; import org.hibernate.search.util.logging.impl.Log; import org.hibernate.search.util.logging.impl.LoggerFactory; import org.hibernate.service.spi.ServiceRegistryImplementor; /** * Manages bootstrap and teardown of an Hibernate SessionFactory for purposes of * testing. * * It enforces some cleanup rules, and sets various configuration settings by default. * This class also takes care of schema creation for tests requiring multi-tenancy, * which is normally not supported by the HBM2DDL_AUTO setting. * * @author Sanne Grinovero * @since 5.4 */ public final class DefaultTestResourceManager implements TestResourceManager { private static final Log log = LoggerFactory.make(); private final TestConfiguration test; private final Path baseIndexDir; /* Each of the following fields needs to be cleaned up on close */ private SessionFactoryImplementor sessionFactory; private MultitenancyTestHelper multitenancy; private Session session; private SearchFactory searchFactory; private Map<String,Object> configurationSettings; public DefaultTestResourceManager(TestConfiguration test, Class<?> currentTestModuleClass) { this.test = test; this.baseIndexDir = createBaseIndexDir( currentTestModuleClass ); } @Override public void openSessionFactory() { if ( sessionFactory == null ) { sessionFactory = buildSessionFactory(); } else { throw new IllegalStateException( "there should be no SessionFactory initialized at this point" ); } } private SessionFactoryImplementor buildSessionFactory() { multitenancy = new MultitenancyTestHelper( test.multiTenantIds() ); Map<String, Object> settings = getConfigurationSettings(); multitenancy.forceConfigurationSettings( settings ); StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder() .applySettings( settings ); multitenancy.enableIfNeeded( registryBuilder ); ServiceRegistryImplementor serviceRegistry = (ServiceRegistryImplementor) registryBuilder.build(); MetadataSources ms = new MetadataSources( serviceRegistry ); Class<?>[] annotatedClasses = test.getAnnotatedClasses(); if ( annotatedClasses != null ) { for ( Class<?> entity : annotatedClasses ) { ms.addAnnotatedClass( entity ); } } Metadata metadata = ms.buildMetadata(); multitenancy.exportSchema( serviceRegistry, metadata, settings ); final SessionFactoryBuilder sfb = metadata.getSessionFactoryBuilder(); return (SessionFactoryImplementor) sfb.build(); } private Map<String,Object> getConfigurationSettings() { if ( configurationSettings == null ) { configurationSettings = new HashMap<>(); configurationSettings.put( "hibernate.search.lucene_version", TestConstants.getTargetLuceneVersion().toString() ); configurationSettings.put( "hibernate.search.default.directory_provider", "ram" ); configurationSettings.put( "hibernate.search.default.indexBase", getBaseIndexDir().toAbsolutePath().toString() ); configurationSettings.put( Environment.ANALYZER_CLASS, StopAnalyzer.class.getName() ); configurationSettings.put( "hibernate.search.default.indexwriter.merge_factor", "100" ); configurationSettings.put( "hibernate.search.default.indexwriter.max_buffered_docs", "1000" ); configurationSettings.put( org.hibernate.cfg.Environment.HBM2DDL_AUTO, "create-drop" ); test.configure( configurationSettings ); } return configurationSettings; } @Override public void closeSessionFactory() { if ( sessionFactory != null ) { sessionFactory.close(); sessionFactory = null; } if ( multitenancy != null ) { multitenancy.close(); multitenancy = null; } //Make sure we don't reuse the settings across SessionFactories configurationSettings = null; session = null; searchFactory = null; } @Override public Session openSession() { if ( session != null && session.isOpen() ) { throw new IllegalStateException( "Previously opened Session wasn't closed!" ); } session = getSessionFactory().openSession(); return session; } @Override public Session getSession() { return session; } @Override public SessionFactory getSessionFactory() { if ( sessionFactory == null ) { throw new IllegalStateException( "SessionFactory should be already defined at this point" ); } return sessionFactory; } @Override public void ensureIndexesAreEmpty() throws IOException { FileHelper.delete( getBaseIndexDir() ); } @Override public SearchFactory getSearchFactory() { if ( searchFactory == null ) { //Don't use this#openSession() as that would interfere with our sanity //verification for the tests to not open additional session instances. try ( Session session = getSessionFactory().openSession() ) { searchFactory = Search.getFullTextSession( session ).getSearchFactory(); } } return searchFactory; } @Override public ExtendedSearchIntegrator getExtendedSearchIntegrator() { return ContextHelper.getSearchIntegratorBySFI( sessionFactory ); } @Override public Path getBaseIndexDir() { return baseIndexDir; } public void defaultTearDown() throws Exception { handleUnclosedResources(); closeSessionFactory(); ensureIndexesAreEmpty(); } public void handleUnclosedResources() { if ( session != null && session.isOpen() ) { if ( session.isConnected() ) { session.doWork( new RollbackWork() ); } session.close(); session = null; log.debug( "Closing open session. Make sure to close sessions explicitly in your tests!" ); } else { session = null; } searchFactory = null; } private Path createBaseIndexDir(Class<?> currentTestModuleClass) { // Appending UUID to be extra-sure no directory is ever reused across the test suite as Windows might not be // able to delete the files after usage. See also // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4715154 return Paths.get( TestConstants.getIndexDirectory( TestConstants.getTempTestDataDir(), currentTestModuleClass ), UUID.randomUUID().toString().substring( 0, 8 ) ); } private static class RollbackWork implements Work { @Override public void execute(Connection connection) throws SQLException { connection.rollback(); } } }