/* * #%L * Nazgul Project: nazgul-core-persistence-api * %% * Copyright (C) 2010 - 2017 jGuru Europe AB * %% * Licensed under the jGuru Europe AB license (the "License"), based * on Apache License, Version 2.0; you may not use this file except * in compliance with the License. * * You may obtain a copy of the License at * * http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt * * 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. * #L% * */ package se.jguru.nazgul.core.persistence.api; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.rules.TestName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import javax.persistence.Query; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.SortedMap; import java.util.TreeMap; /** * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ public abstract class AbstractInMemoryJpaTest { // Our log private static final Logger log = LoggerFactory.getLogger(AbstractInMemoryJpaTest.class); /** * System property defining the JPA provider class name. */ public static final String JPA_SPECIFICATION_PROPERTY = "jpa_provider_class"; /** * The name of the PersistenceUnit, used in all unit tests derived from this AbstractInMemoryJpaTest. */ protected static final String UNITTEST_PERSISTENCE_UNIT = "InmemoryPU"; @Rule public TestName testName = new TestName(); // Internal state protected ClassLoader originalClassLoader; protected EntityManager unitTestEM; protected EntityTransaction trans; protected JpaPersistenceOperations unitUnderTest; protected String persistenceXmlFile; protected String jpaPersistenceProviderClass; @Before public void setupSharedState() throws Exception { log.info("Launching setup for [" + testName.getMethodName() + "]"); // Debug somewhat printSystemProperties(); // Find the Persistence XML file. persistenceXmlFile = "testdata/" + getPersistenceFileName() + ".xml"; log.debug("Using PersistenceXmlFile: " + persistenceXmlFile); // Stash the original ClassLoader; create the PersistenceRedirectionClassLoader originalClassLoader = getClass().getClassLoader(); final PersistenceRedirectionClassLoader redirectionClassLoader = new PersistenceRedirectionClassLoader(getClass().getClassLoader(), persistenceXmlFile); // Find the JPA persistence provider class. jpaPersistenceProviderClass = getJpaPersistenceProviderClass(originalClassLoader, redirectionClassLoader); // Assign the PersistenceRedirectionClassLoader as the Context ClassLoader. try { Thread.currentThread().setContextClassLoader(redirectionClassLoader); } catch (Throwable e) { throw new IllegalStateException("Could not assign the Thread Context ClassLoader", e); } // Create EntityManager and Transaction. unitTestEM = getEntityManager(UNITTEST_PERSISTENCE_UNIT); unitUnderTest = new JpaPersistenceOperations(unitTestEM); trans = unitTestEM.getTransaction(); trans.begin(); log.debug("EntityManager of type [" + unitTestEM.getClass().getCanonicalName() + "] created. Transaction is active: " + trans.isActive() + ". Delegating to custom setup."); // Delegate doCustomSetup(); } @After public final void teardownSharedState() throws Exception { // First, delegate doCustomTeardown(); // Restore the original ClassLoader try { Thread.currentThread().setContextClassLoader(originalClassLoader); } catch (Throwable thr) { System.err.println("Could not restore original ClassLoader [" + originalClassLoader + "]"); thr.printStackTrace(); } } protected abstract String getPersistenceFileName(); protected void doCustomTeardown() { // Do nothing. } protected void doCustomSetup() { // Do nothing. } protected final void dropDbTable(final String tableName) { // Check sanity if (!trans.isActive()) { trans.begin(); } try { final String tableVerificationSql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_NAME = '" + tableName + "'"; final String dropTableSql = "DROP TABLE IF EXISTS '" + tableName + "' CASCADE ;"; // // #1) Validate that the table actually exists. // final Query verificationQ = unitTestEM.createNativeQuery(tableVerificationSql); final List resultList = verificationQ.getResultList(); if (resultList.size() == 1) { final Query dropQ = unitTestEM.createNativeQuery(dropTableSql); int affectedRows = dropQ.executeUpdate(); log.info("Removed table. Rows affected [" + affectedRows + "]"); if (trans.getRollbackOnly()) { trans.rollback(); } else { trans.commit(); } } } catch (final Exception e) { log.info("Could not commit the DB Transaction.", e); } } /** * Commits the currently active EntityTransaction, and starts a new EntityTransaction. * * @see #commit(boolean) */ protected final void commitAndStartNewTransaction() { commit(true); } /** * Commits the currently active EntityTransaction, and starts a new EntityTransaction if so ordered. * * @param startNewTransaction if {@code true}, starts a new EntityTransaction following the commit of the * currently active one. */ protected final void commit(final boolean startNewTransaction) { try { trans.commit(); trans = unitTestEM.getTransaction(); } catch (Exception e) { throw new IllegalStateException("Could not create a new EntityTransacion", e); } if (startNewTransaction) { try { trans.begin(); } catch (Exception e) { throw new IllegalStateException("Could not begin() the newly created EntityTransaction.", e); } } } /** * Acquires the className of the JPA persistence provider. Normally retrieves it from the value of the System * property defined within the {@code JPA_SPECIFICATION_PROPERTY}. * * @param originalClassLoader The original ClassLoader used. * @param redirectionClassLoader The redirectionClassLoader not yet set when invoking this method. * @return A class name of the JPA Persistence Provider. * @see #JPA_SPECIFICATION_PROPERTY */ protected String getJpaPersistenceProviderClass(final ClassLoader originalClassLoader, final ClassLoader redirectionClassLoader) { // Find the JPA specification String candidate = System.getProperty(JPA_SPECIFICATION_PROPERTY); Assert.assertNotNull("The persistence provider System property [" + JPA_SPECIFICATION_PROPERTY + "] should " + "be set to contain the JPA persistence provider. This is normally done in a Maven profile.", candidate); // All done. return candidate; } // // Private helpers // private EntityManager getEntityManager(final String persistenceUnitName) { // Add final Map<String, String> extraProperties = getEntityManagerFactoryProperties(); extraProperties.put("eclipselink.persistencexml", persistenceXmlFile); final StringBuilder builder = new StringBuilder(" EntityManagerFactory properties\n"); for (Map.Entry<String, String> current : extraProperties.entrySet()) { builder.append(" [").append(current.getKey()).append("]: ").append(current.getValue()).append("\n"); } log.debug(builder.toString()); // Create an EntityManager factory. final EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName, extraProperties); return emf.createEntityManager(); } private void printSystemProperties() { // Debug somewhat final SortedMap<String, String> sysProps = new TreeMap<>(); final Properties props = System.getProperties(); for (String current : props.stringPropertyNames()) { sysProps.put(current, System.getProperty(current)); } try { final List<URL> cpList = Collections.list(Thread.currentThread().getContextClassLoader().getResources("")); for (int i = 0; i < cpList.size(); i++) { log.info("[" + i + "]: " + cpList.get(i).toString()); } } catch (Exception e) { log.error("Could not harvest classpath: " + e); } StringBuilder builder = new StringBuilder(" ===== [System Properties] ===== \n"); for (Map.Entry<String, String> current : sysProps.entrySet()) { String value = "[" + current.getKey() + "]: " + current.getValue(); builder.append(value).append("\n"); } builder.append(" ===== [End System Properties] ===== \n"); final String result = builder.toString(); log.info(result); } /** * Override to supply any additional EntityManagerFactory properties. * The properties are supplied as the latter argument to the * {@code Persistence.createEntityManagerFactory} method. * The properties supplied within this Map override property definitions * given in the persistence.xml file. * * @return Properties supplied to the EntityManagerFactory, implying they do not * need to be declared within the persistence.xml file. * @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map) */ protected Map<String, String> getEntityManagerFactoryProperties() { // Get standard properties // final String jdbcURL = getDatabaseType().getUnitTestJdbcURL(DEFAULT_DB_NAME, getTargetDirectory()); // final String persistenceProviderClass = getPersistenceProviderType().getPersistenceProviderClass(); final Map<String, String> toReturn = new TreeMap<String, String>(); final String[][] persistenceProviderProps = new String[][]{ // Generic properties {"javax.persistence.provider", jpaPersistenceProviderClass}, {"javax.persistence.jdbc.driver", "org.hsqldb.jdbcDriver"}, {"javax.persistence.jdbc.url", "jdbc:hsqldb:mem:unittestDatabaseID"}, {"javax.persistence.jdbc.user", "sa"}, {"javax.persistence.jdbc.password", ""}, // OpenJPA properties {"openjpa.jdbc.DBDictionary", "org.apache.openjpa.jdbc.sql.HSQLDictionary"}, // Note! // These OpenJPA provider properties are now replaced by the standardized // properties, "javax.persistence....". // It is now an exception to define both the standardized property and the // corresponding legacy openjpa property for the commented-out properties // below. These properties will remain commented-out to indicate which openjpa // properties are now replaced by javax.persistence properties. // // {"openjpa.ConnectionDriverName", jdbcDriverClass}, // {"openjpa.ConnectionURL", jdbcURL}, // {"openjpa.ConnectionUserName", DEFAULT_DB_UID}, // {"openjpa.ConnectionPassword", DEFAULT_DB_PASSWORD}, {"openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)"}, {"openjpa.InverseManager", "true"}, // // Valid values for log levels are: // TRACE, INFO, WARN, ERROR or FATAL. // // That is ... "DEBUG" is not among them. // // {"openjpa.Log", "DefaultLevel=TRACE, Tool=TRACE, RUNTIME=TRACE, SQL=INFO"}, {"openjpa.Log", "slf4j"}, {"openjpa.jdbc.SchemaFactory", "native(ForeignKeys=true)"}, {"openjpa.jdbc.TransactionIsolation", "serializable"}, {"openjpa.RuntimeUnenhancedClasses", "supported"}, {"openjpa.DynamicEnhancementAgent", "true"}, // Eclipselink properties // {"eclipselink.target-database", getDatabaseType().getHibernatePlatformClass()}, {"eclipselink.logging.level", "FINER"}, {"eclipselink.orm.throw.exceptions", "true"}, {"eclipselink.ddl-generation", "drop-and-create-tables"}, {"eclipselink.ddl-generation.output-mode", "database"}, // {"eclipselink.persistencexml", getPersistenceXmlFile()} }; // First, add the default values - and then overwrite them with // any given system properties. for (String[] current : persistenceProviderProps) { toReturn.put(current[0], current[1]); } // Now, overwrite with appropriate system properties final List<String> overridablePrefixes = Arrays.asList("javax.persistence.", "openjpa.", "eclipselink."); for (Map.Entry<Object, Object> current : System.getProperties().entrySet()) { final String currentPropertyName = "" + current.getKey(); for (String currentPrefix : overridablePrefixes) { if (currentPropertyName.trim().toLowerCase().startsWith(currentPrefix)) { toReturn.put(currentPropertyName, "" + current.getValue()); } } } // All done. return toReturn; } }