/* * #%L * Nazgul Project: nazgul-core-persistence-test * %% * 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.test.persistence; import org.dbunit.dataset.IDataSet; import org.dbunit.operation.DatabaseOperation; import se.jguru.nazgul.core.algorithms.api.Validate; import se.jguru.nazgul.test.persistence.jpa.PersistenceProviderType; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; /** * Concrete implementation of the AbstractDbUnitAndJpaTest specification, providing * default behaviour and some utility methods to simplify automated (integration-)test * creation using an in-memory HSQL database. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ public abstract class StandardPersistenceTest extends AbstractDbUnitAndJpaTest { /** * The name of the standard persistence unit. */ public static final String DEFAULT_PERSISTENCE_UNIT = "InMemoryTestPU"; /** * The system property where the JPA Provider Class is assumed to be bound. */ public static final String JPA_PROVIDER_CLASS_SYSPROPKEY = "jpa_provider_class"; /** * Fallback/default PersistenceProviderType, unless defined by the system property * {@code JPA_PROVIDER_CLASS_SYSPROPKEY}. * * @see #JPA_PROVIDER_CLASS_SYSPROPKEY */ public static final PersistenceProviderType DEFAULT_PERSISTENCE_PROVIDER = PersistenceProviderType.ECLIPSELINK_2; /** * The name of the standard/default (in-memory) database. */ public static final String DEFAULT_DB_NAME = "inMemoryTestDatabase"; /** * The default userId within the in-memory unit test database. */ public static final String DEFAULT_DB_UID = "sa"; /** * The default password within the in-memory unit test database. */ public static final String DEFAULT_DB_PASSWORD = ""; /** * {@inheritDoc} */ @Override public final void setUp() throws Exception { // Perform standard setup super.setUp(); // Delegate to custom setup doCustomSetup(); } /** * Override this method to perform any custom setup. */ protected void doCustomSetup() { // Do nothing here; override to include custom behavior. } /** * Retrieves the DatabaseType used by this AbstractJpaPersistenceDbUnitTest. * * @return The DatabaseType used by this AbstractJpaPersistenceDbUnitTest. * StandardPersistenceTest uses an in-memory HSQL database. Override * in specific implementations/test cases to change the DatabaseType. * @see DatabaseType#HSQL */ @Override protected DatabaseType getDatabaseType() { return DatabaseType.HSQL; } /** * Defines the value {@code "testdata/" + getClass().getSimpleName() + "/persistence.xml"}, * implying that each concrete test class should define its own persistence.xml file for * test-scope usage. This defines a standard behaviour for JPA- and dbUnit-based tests. * Override in concrete test classes to use another persistence.xml file. * * @return the value {@code "testdata/" + getClass().getSimpleName() + "/persistence.xml"}. */ @Override protected String getPersistenceXmlFile() { return "testdata/" + getClass().getSimpleName() + "/persistence.xml"; } /** * Retrieves the standard PersistenceUnit name, {@code DEFAULT_PERSISTENCE_UNIT}. * Override in concrete subclasses to use another persistenceUnit. * * @return the standard PersistenceUnit name, {@code DEFAULT_PERSISTENCE_UNIT}. */ @Override protected String getPersistenceUnitName() { return DEFAULT_PERSISTENCE_UNIT; } /** * 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) */ @Override protected SortedMap<String, String> getEntityManagerFactoryProperties() { // Get standard properties final String jdbcDriverClass = getDatabaseType().getJdbcDriverClass(); final String jdbcURL = getDatabaseType().getUnitTestJdbcURL(DEFAULT_DB_NAME, getTargetDirectory()); final String persistenceProviderClass = System.getProperty(JPA_PROVIDER_CLASS_SYSPROPKEY, DEFAULT_PERSISTENCE_PROVIDER.getPersistenceProviderClass()); final SortedMap<String, String> toReturn = new TreeMap<String, String>(); final String[][] persistenceProviderProps = new String[][]{ // Generic properties {"javax.persistence.provider", persistenceProviderClass}, {"javax.persistence.jdbc.driver", jdbcDriverClass}, {"javax.persistence.jdbc.url", jdbcURL}, {"javax.persistence.jdbc.user", DEFAULT_DB_UID}, {"javax.persistence.jdbc.password", DEFAULT_DB_PASSWORD}, // OpenJPA properties {"openjpa.jdbc.DBDictionary", getDatabaseType().getDatabaseDialectClass()}, // 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"}, {"openjpa.Log", "DefaultLevel=WARN, Tool=INFO, RUNTIME=WARN, SQL=WARN"}, {"openjpa.jdbc.SchemaFactory", "native(ForeignKeys=true)"}, {"openjpa.jdbc.TransactionIsolation", "serializable"}, {"openjpa.RuntimeUnenhancedClasses", "supported"}, // Eclipselink properties {"eclipselink.deploy-on-startup", "true"}, {"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()} // Hibernate properties }; // 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; } /** * Invoked during setup to prepare the schema used for test. * * @param shutdownDatabase if {@code true}, the database should be shutdown after cleaning the schema. */ @Override protected void cleanupTestSchema(final boolean shutdownDatabase) { dropAllDbObjectsInPublicSchema(shutdownDatabase); } /** * Retrieves the name of the database used for this AbstractDbUnitAndJpaTest. * * @return the name of the database used for this AbstractDbUnitAndJpaTest. */ @Override protected String getDatabaseName() { return DEFAULT_DB_NAME; } /** * Standardized version of the setupDatabaseState method, using {@code DatabaseOperation#CLEAN_INSERT} * for operation and synthesizing the setupDataSetLocation from the testMethodName given. * * @param testMethodName The name of the active test method, such as {@code validateFoo}. * @see #setupDatabaseState(boolean, String) */ protected final void setupDatabaseState(@NotNull @Size(min = 1) final String testMethodName) { // Check sanity Validate.notEmpty(testMethodName, "testMethodName"); // Delegate setupDatabaseState(true, "testdata/" + getClass().getSimpleName() + "/setup_" + testMethodName + ".xml"); } /** * Sets up state within the database using the data provided. * * @param cleanBeforeInsert if {@code true}, performs a {@code DatabaseOperation#CLEAN_INSERT} and otherwise * performs a {@code DatabaseOperation#INSERT}. * @param setupDataSetLocation The resource path to the FlatXMLDataSet used to setup database data. */ protected final void setupDatabaseState(final boolean cleanBeforeInsert, @NotNull @Size(min = 1) final String setupDataSetLocation) { // Check sanity Validate.notEmpty(setupDataSetLocation, "setupDataSetLocation"); // Get the appropriate DatabaseOperation from dbUnit. final DatabaseOperation dbOp = cleanBeforeInsert ? DatabaseOperation.CLEAN_INSERT : DatabaseOperation.INSERT; IDataSet setupDataSet = null; try { // Fire the DatabaseOperation setupDataSet = getDataSet(setupDataSetLocation); dbOp.execute(iDatabaseConnection, setupDataSet); } catch (Exception e) { String dataSetContent = "<no content>"; if (setupDataSet != null) { dataSetContent = extractFlatXmlDataSet(setupDataSet); } throw new IllegalStateException("Could not setup database state. SetupDataSet: " + dataSetContent, e); } } /** * Retrieves an IDataSet holding expected Database state for the supplied testMethodName. * * @param testMethodName The name of the active test method, such as {@code validateFoo}. * @return An IDataSet holding the DataSet with expected data. */ protected final IDataSet getExpectedDatabaseState(@NotNull @Size(min = 1) final String testMethodName) { // Check sanity Validate.notEmpty(testMethodName, "testMethodName"); final String resourcePath = "testdata/" + getClass().getSimpleName() + "/expected_" + testMethodName + ".xml"; try { return getDataSet(resourcePath); } catch (Exception e) { throw new IllegalArgumentException("Could not acquire IDataSet from resourcePath [" + resourcePath + "]", e); } } /** * Convenience method performing standardized setup of the in-memory database and retrieving the * IDataSet pointing to the expected state following the test. Also begins the transaction, by * calling the {@code transaction.begin()} method. * * @param testMethodName The name of the testMethod, such as {@code validateCreatePersonEntity}. * @return the IDataSet pointing to the expected state following the test. */ protected final IDataSet performStandardTestDbSetup(@NotNull @Size(min = 1) final String testMethodName) { // Check sanity Validate.notEmpty(testMethodName, "testMethodName"); // Perform standard setup setupDatabaseState(testMethodName); final IDataSet toReturn = getExpectedDatabaseState(testMethodName); // Start a transaction, unless already started. if (!transaction.isActive()) { transaction.begin(); } // All done. return toReturn; } /** * Convenience method performing standardized setup of the in-memory database and retrieving the * IDataSet pointing to the expected state following the test. Also begins the transaction, by * calling the {@code transaction.begin()} method. Uses the {@code getTestMethodName() } method to * retrieve the name of the currently active test method. * * @return the IDataSet pointing to the expected state following the test. */ protected final IDataSet performStandardTestDbSetup() { return performStandardTestDbSetup(activeTestName.getMethodName()); } }