All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.edugility.liquiunit.DataSourceDatabaseTesterRule Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
 *
 * Copyright (c) 2013-2014 Edugility LLC.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * The original copy of this license is available at
 * http://www.opensource.org/license/mit-license.html.
 */
package com.edugility.liquiunit;

import java.io.Closeable;
import java.io.InputStream;
import java.io.IOException;

import java.net.URL;

import java.sql.SQLException;

import javax.sql.DataSource;

import org.dbunit.AbstractDatabaseTester;
import org.dbunit.DataSourceDatabaseTester;
import org.dbunit.DefaultOperationListener;
import org.dbunit.IDatabaseTester;

import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.IDatabaseConnection;

import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.DefaultDataSet;
import org.dbunit.dataset.IDataSet;

import org.dbunit.dataset.datatype.IDataTypeFactory;

import org.dbunit.dataset.xml.XmlDataSet;

import org.junit.rules.ExternalResource;

import org.junit.runner.Description;

import org.junit.runners.model.Statement;

import org.xml.sax.InputSource;

/**
 * An {@link ExternalResource} that wraps JUnit tests with a {@link
 * DataSourceDatabaseTester} to ensure that the underlying database is
 * populated appropriately with test data.
 *
 * @author Laird Nelson
 *
 * @see dbUnit
 *
 * @see DataSourceDatabaseTester
 */
public class DataSourceDatabaseTesterRule extends ExternalResource {
  

  /*
   * Instance fields.
   */


  /**
   * The underlying {@link DataSourceDatabaseTester} that forms the
   * basis of this {@link DataSourceDatabaseTesterRule}.
   *
   * 

This field may be {@code null}.

*/ protected final DataSourceDatabaseTester tester; /** * The {@link Description} describing the current JUnit test * underway. * *

This field may be {@code null}.

*/ private Description description; /** * The {@link IDataSet} that may have previously been installed in * the affiliated {@link #tester DataSourceDatabaseTester}. * *

This field may be {@code null}.

*/ private IDataSet oldDataSet; /** * The {@link InputStream} used to {@linkplain #createDataSet(URL) * logically open an IDataSet}, stored here as a {@link * Closeable}. * *

This field may be {@code null}.

* * @see #createDataSet(URL) * * @see #closeDataSet() */ private Closeable dataSetInputStream; /* * Constructors. */ /** * Creates a {@link DataSourceDatabaseTesterRule}. * * @param dataSource the {@link DataSource} that will back a new * {@link DataSourceDatabaseTester} {@linkplain * DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource) * created} by this constructor; must not be {@code null} * * @exception NullPointerException if {@code dataSource} is {@code * null} * * @see DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource) */ public DataSourceDatabaseTesterRule(final DataSource dataSource) { super(); this.tester = new DataSourceDatabaseTester(dataSource); } /** * Creates a {@link DataSourceDatabaseTesterRule}. * * @param dataSource the {@link DataSource} that will back a new * {@link DataSourceDatabaseTester} {@linkplain * DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource) * created} by this constructor; must not be {@code null} * * @param dataTypeFactory the {@link IDataTypeFactory} that * describes data types for the database to which the supplied * {@link DataSource} is notionally connected; may be {@code null} * * @exception NullPointerException if {@code dataSource} is {@code * null} * * @see DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource) */ public DataSourceDatabaseTesterRule(final DataSource dataSource, final IDataTypeFactory dataTypeFactory) { this(dataSource); this.installDataTypeFactory(dataTypeFactory); } /** * Creates a {@link DataSourceDatabaseTesterRule}. * * @param dataSource the {@link DataSource} that will back a new * {@link DataSourceDatabaseTester} {@linkplain * DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource, * String) created} by this constructor; must not be {@code null} * * @param schema the parameter value to supply to the {@link * DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource, * String)} constructor; may be {@code null} * * @see * DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource, * String) */ public DataSourceDatabaseTesterRule(final DataSource dataSource, final String schema) { super(); this.tester = new DataSourceDatabaseTester(dataSource, schema); } /** * Creates a {@link DataSourceDatabaseTesterRule}. * * @param dataSource the {@link DataSource} that will back a new * {@link DataSourceDatabaseTester} {@linkplain * DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource, * String) created} by this constructor; must not be {@code null} * * @param schema the parameter value to supply to the {@link * DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource, * String)} constructor; may be {@code null} * * @param dataTypeFactory the {@link IDataTypeFactory} that * describes data types for the database to which the supplied * {@link DataSource} is notionally connected; may be {@code null} * * @see * DataSourceDatabaseTester#DataSourceDatabaseTester(DataSource, * String) */ public DataSourceDatabaseTesterRule(final DataSource dataSource, final String schema, final IDataTypeFactory dataTypeFactory) { this(dataSource, schema); this.installDataTypeFactory(dataTypeFactory); } /** * Creates a {@link DataSourceDatabaseTesterRule}. * *

This constructor assumes that the supplied {@link * DataSourceDatabaseTester} is completely configured and this * {@link DataSourceDatabaseTesterRule} will therefore perform no * further configuration. Please see in particular the {@link * #getDataSet(Description)} method, which will consequently have no * effect.

* * @param tester the {@link DataSourceDatabaseTester} to which most * operations will delegate; must not be {@code null} * * @exception IllegalArgumentException if {@code tester} is {@code * null} */ public DataSourceDatabaseTesterRule(final DataSourceDatabaseTester tester) { super(); if (tester == null) { throw new IllegalArgumentException("tester", new NullPointerException("tester")); } this.tester = tester; } /* * Instance methods. */ /** * Ensures that every {@link IDatabaseConnection} produced during * the course of execution is configured to use the supplied {@link * IDataTypeFactory}. * * @param dataTypeFactory the {@link IDataTypeFactory} suitable for * the underlying database; may be {@code null} * * @see IOperationListener#connectionRetrieved(IDatabaseConnection) * * @see DatabaseConfig#PROPERTY_DATATYPE_FACTORY */ private final void installDataTypeFactory(final IDataTypeFactory dataTypeFactory) { if (dataTypeFactory != null && this.tester != null) { this.tester.setOperationListener(new DefaultOperationListener() { @Override public final void connectionRetrieved(final IDatabaseConnection connection) { super.connectionRetrieved(connection); if (connection != null) { final DatabaseConfig config = connection.getConfig(); if (config != null) { config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, dataTypeFactory); } } } }); } } /** * {@linkplain #getDataSet(Description) Finds an appropriate * IDataSet} for the {@linkplain #tester affiliated * DataSourceDatabaseTester} and {@linkplain * AbstractDatabaseTester#setDataSet(IDataSet) installs it} * immediately before invoking the {@link IDatabaseTester#onSetup()} * method. * * @exception Exception if an error occurs */ @Override public void before() throws Exception { if (this.tester != null) { final IDataSet oldDataSet = this.tester.getDataSet(); this.oldDataSet = oldDataSet; if (oldDataSet == null) { IDataSet newDataSet = this.getDataSet(this.description); if (newDataSet == null) { newDataSet = new DefaultDataSet(); } this.tester.setDataSet(newDataSet); } this.tester.onSetup(); } } /** * Invokes the {@link IDatabaseTester#onTearDown()} method. * * @see IDatabaseTester#onTearDown() */ @Override public void after() { if (this.tester != null) { try { this.tester.onTearDown(); } catch (final RuntimeException throwMe) { throw throwMe; } catch (final Exception everythingElse) { throw new RuntimeException(everythingElse); // TODO: ugly } finally { try { this.closeDataSet(); } catch (final IOException ignore) { } } this.tester.setDataSet(this.oldDataSet); } this.description = null; // XXX TODO INVESTIGATE: not sure this is proper } /** * Overrides the {@link ExternalResource#apply(Statement, * Description)} method to store the supplied {@link Description} * for usage by the {@link #before()} method internally and returns * the superclass' return value. * *

It must be assumed that this method may return {@code null} * since the superclass documentation does not mention whether the * return value must be non-{@code null}.

* * @param base the {@link Statement} to decorate; the superclass * documentation does not define what behavior will occur if this * parameter is {@code null} * * @param description the {@link Description} describing the test * underway; the superclass documentation does not define what * behavior will occur if this parameter is {@code null} * * @return a {@link Statement}; possibly {@code null} * * @see #before() * * @see ExternalResource#apply(Statement, Description) */ @Override public Statement apply(final Statement base, final Description description) { this.description = description; return super.apply(base, description); } /** * Follows various conventions, detailed below, in attempting to * locate and instantiate a new {@link IDataSet} instance * appropriate for the supplied {@link Description}. * *

This implementation first checks the {@link #tester * DataSourceDatabaseTester} indirectly or directly supplied to this * {@link DataSourceDatabaseTesterRule} at {@linkplain * #DataSourceDatabaseTesterRule(DataSource) construction time} to * see if {@linkplain DataSourceDatabaseTester#getDataSet() it * already has a IDataSet implementation installed}. * If so, then no further action is taken and that {@link IDataSet} * is returned. ({@code null} is returned if the supplied {@code * description} is {@code null}.)

* *

Otherwise, a {@linkplain ClassLoader#getResource(String) * classpath resource} named {@code * datasets/SIMPLE_TEST_CLASS_NAME/TEST_METHOD_NAME.xml} is sought * using the {@linkplain Thread#getContextClassLoader() context * classloader}, where {@code SIMPLE_TEST_CLASS_NAME} is the name of * the JUnit test {@link Class} currently running and {@code * TEST_METHOD_NAME} is the name of the JUnit test method currently * running.

* *

If that resource doesn't exist, then a {@linkplain * ClassLoader#getResource(String) classpath resource} named {@code * datasets/SIMPLE_TEST_CLASS_NAME.xml} is sought using the * {@linkplain Thread#getContextClassLoader() context classloader}, * where {@code SIMPLE_TEST_CLASS_NAME} is the name of the JUnit * test {@link Class} currently running.

* *

Once a resource is located in this manner, its {@link URL} is * passed to the {@link #createDataSet(URL)} method, and that * method's return value is returned.

* *

If no resource exists, then {@code null} is returned.

* * @param description a {@link Description} describing the JUnit * test being executed; may be {@code null} * * @return an {@link IDataSet} instance appropriate for the supplied * {@link Description}, or {@code null} * * @exception DataSetException if there was an error in constructing * the {@link IDataSet} * * @exception IOException if there was an input/output error * * @see #createDataSet(URL) */ protected IDataSet getDataSet(final Description description) throws DataSetException, IOException { final IDataSet returnValue; if (this.tester == null) { returnValue = null; } else { final IDataSet old = this.tester.getDataSet(); if (old == null && description != null) { final String simpleClassName; final Class testClass = description.getTestClass(); if (testClass == null) { simpleClassName = null; } else { simpleClassName = testClass.getSimpleName(); } final String methodName = description.getMethodName(); ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = ClassLoader.getSystemClassLoader(); if (cl == null) { cl = this.getClass().getClassLoader(); } } assert cl != null; URL url = cl.getResource(String.format("datasets/%s/%s.xml", simpleClassName, methodName)); if (url == null) { url = cl.getResource(String.format("datasets/%s.xml", simpleClassName)); } returnValue = this.createDataSet(url); } else { returnValue = old; } } return returnValue; } /** * Creates a new {@link IDataSet} implementation suitable for the * supplied {@link URL} and returns it. * *

This method may return {@code null}.

* *

Overrides of this method are permitted to return {@code * null}.

* *

This implementation returns {@code null} if a {@code null} * {@link URL} is supplied. Otherwise, a new {@link XmlDataSet} is * constructed with the {@linkplain URL#openStream() supplied * URL's affiliated InputStream} and * returned.

* * @param url the {@link URL} for which a new {@link IDataSet} * implementation should be returned; may be {@code null} * * @return a new {@link IDataSet} implementation, or {@code null} * * @exception DataSetException if an error occurs during {@link * IDataSet} construction * * @exception IOException if an error occurs during the processing * of the supplied {@link URL} * * @see #getDataSet(Description) */ protected IDataSet createDataSet(final URL url) throws DataSetException, IOException { final IDataSet returnValue; if (url == null) { returnValue = null; } else { final InputStream stream = url.openStream(); this.dataSetInputStream = stream; returnValue = new XmlDataSet(stream); } return returnValue; } /** * Notionally closes any resources held by the {@link IDataSet} * created by the {@link #createDataSet(URL)} method. * * @exception IOException if an error occurs during closing * * @see #createDataSet(URL) */ protected void closeDataSet() throws IOException { if (this.dataSetInputStream != null) { this.dataSetInputStream.close(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy