/* * This is eMonocot, a global online biodiversity information resource. * * Copyright © 2011–2015 The Board of Trustees of the Royal Botanic Gardens, Kew and The University of Oxford * * eMonocot is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * eMonocot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * The complete text of the GNU Affero General Public License is in the source repository as the file * ‘COPYING’. It is also available from <http://www.gnu.org/licenses/>. */ package org.emonocot.persistence.spatial; /** * This class is taken from the course Core Spring by SpringSource. The source * can be used legally, but credits go to the guys from Spring. * * This code was development at Geodan (http://www.geodan.com). * * @author janb (jan.boonen@geodan.nl) */ import geodb.GeoDB; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.List; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; import org.springframework.jdbc.datasource.DriverManagerDataSource; /** * A factory that creates a data source fit for use in a system test * environment. Creates a simple data source that connects to an in-memory * database pre-loaded with test data. * * This factory returns a fully-initialized DataSource implementation. When the * DataSource is returned, callers are guaranteed that the database schema and * test data will have been loaded by that time. * * Is a FactoryBean, for exposing the fully-initialized test DataSource as a * Spring bean. See {@link #getObject()}. */ public class GeoDBTestDataSourceFactory implements FactoryBean<Object>, InitializingBean { /** * */ private static Logger logger = LoggerFactory .getLogger(GeoDBTestDataSourceFactory.class); /** * */ private String testDatabaseName; /** * */ private Resource schemaLocation; /** * */ private List<Resource> schemaLocations; /** * */ private Resource testDataLocation; /** * The object created by this factory. */ private DataSource dataSource; /** * Creates a new TestDataSourceFactory for use in "bean" style. "Bean" style * means the default constructor is called and then properties are set to * configure this object. "Bean" style usage is nice when this object is * defined as a Spring bean, as setter-injection can be more descriptive * than constructor-injection from the point of view of a bean definition * author. * * @see {@link #setTestDatabaseName(String)} * @see {@link #setSchemaLocation(Resource)} * @see {@link #setTestDataLocation(Resource)} */ public GeoDBTestDataSourceFactory() { } /** * Creates a new TestDataSourceFactory fully-initialized with what it needs * to work. Fully-formed constructors are nice in a programmatic * environment, as they result in more concise code and allow for a class to * enforce its required properties. * * @param databaseName * the name of the test database to create * @param schemaLoc * the location of the file containing the schema DDL to export * to the database * @param dataLocation * the location of the file containing the test data to load into * the database */ public GeoDBTestDataSourceFactory(final String databaseName, final Resource schemaLoc, final Resource dataLocation) { setTestDatabaseName(databaseName); setSchemaLocation(schemaLoc); setTestDataLocation(dataLocation); } /** * Sets the name of the test database to create. * * @param databaseName * the name of the test database, i.e. "rewards" */ public final void setTestDatabaseName(final String databaseName) { this.testDatabaseName = databaseName; } /** * Sets the location of the file containing the schema DDL to export to the * test database. * * @param schemaLoc * the location of the database schema DDL */ public final void setSchemaLocation(final Resource schemaLoc) { this.schemaLocation = schemaLoc; } /** * Sets the location of the file containing the test data to load into the * database. * * @param dataLocation * the location of the test data file */ public final void setTestDataLocation(final Resource dataLocation) { this.testDataLocation = dataLocation; } /** * * @param schemaLoc set the locations of the schema */ public final void setSchemaLocations(final List<Resource> schemaLoc) { this.schemaLocations = schemaLoc; } /** * this method is automatically called by Spring after configuration to * perform a dependency check and init. */ public final void afterPropertiesSet() { if (testDatabaseName == null) { throw new IllegalArgumentException( "The name of the test database to create is required"); } if (schemaLocation == null && (schemaLocations == null || schemaLocations.isEmpty())) { throw new IllegalArgumentException( "The path to the database schema DDL is required"); } initDataSource(); } /** * implementing FactoryBean. * this method is automatically called by Spring to expose the DataSource as * a bean * @return the datasource * @throws Exception if there is a problem */ public final Object getObject() throws Exception { return getDataSource(); } /** * @return the class of object */ public final Class<?> getObjectType() { return DataSource.class; } /** * @return true if the object is a singleton */ public final boolean isSingleton() { return true; } /** * Factory method that returns the fully-initialized test data source. * Useful when this class is used programatically instead of deployed as a * Spring bean. * * @return the data source */ public final DataSource getDataSource() { if (dataSource == null) { initDataSource(); } return dataSource; } // static factory methods /** * Static factory method that creates a DataSource that connects to a test * database populated with test data. * * @param testDatabaseName * the name of the test database to create * @param schemaLocation * the database schema to export * @param testDataLocation * the database test data to load * @return the data source */ public static DataSource createDataSource(final String testDatabaseName, final Resource schemaLocation, final Resource testDataLocation) { return new GeoDBTestDataSourceFactory(testDatabaseName, schemaLocation, testDataLocation).getDataSource(); } /** * encapsulates the steps involved in initializing the data source: creating * it, and populating it. */ private void initDataSource() { // create the in-memory database source first this.dataSource = createDataSource(); if (logger.isDebugEnabled()) { logger.debug("Created in-memory test database '" + testDatabaseName + "'"); } // now populate the database by loading the schema and test data populateDataSource(); if (logger.isDebugEnabled()) { logger.debug("Exported schema in " + schemaLocation); } if (logger.isDebugEnabled()) { logger.debug("Loaded test data in " + testDataLocation); } } /** * * @return the datasource */ private DataSource createDataSource() { DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource(); // use the HsqlDB JDBC driver driverManagerDataSource.setDriverClassName("org.h2.Driver"); // have it create an in-memory database driverManagerDataSource.setUrl("jdbc:h2:mem:" + testDatabaseName + ";DB_CLOSE_DELAY=-1"); return driverManagerDataSource; } /** * */ private void populateDataSource() { TestDatabasePopulator populator = new TestDatabasePopulator(dataSource); populator.populate(); } /** * Populates a in memory data source with test data. */ private class TestDatabasePopulator { /** * */ private DataSource dataSource; /** * Creates a new test database populator. * * @param source * the test data source that will be populated. */ public TestDatabasePopulator(final DataSource source) { this.dataSource = source; } /** * Populate the test database by creating the database schema from * 'schema.sql' and inserting the test data in 'testdata.sql'. */ public void populate() { Connection connection = null; try { connection = dataSource.getConnection(); createDatabaseSchema(connection); if (testDataLocation != null) { insertTestData(connection); } } catch (SQLException e) { throw new RuntimeException( "SQL exception occurred acquiring connection", e); } finally { if (connection != null) { try { connection.close(); } catch (SQLException e) { // do nothing } } } } /** * create the application's database schema (tables, indexes, etc.). * @param connection Set the connection */ private void createDatabaseSchema(final Connection connection) { try { // add spatial capabilities to database: GeoDB.InitGeoDB(connection); if (schemaLocation != null) { String sql = parseSqlIn(schemaLocation); executeSql(sql, connection); } else { for (Resource location : schemaLocations) { String sql = parseSqlIn(location); executeSql(sql, connection); } } } catch (IOException e) { throw new RuntimeException( "I/O exception occurred accessing the database schema file", e); } catch (SQLException e) { throw new RuntimeException( "SQL exception occurred exporting database schema", e); } } /** * populate the tables with test data. * * @param connection Set the connection */ private void insertTestData(final Connection connection) { try { String sql = parseSqlIn(testDataLocation); executeSql(sql, connection); } catch (IOException e) { throw new RuntimeException( "I/O exception occurred accessing the test data file", e); } catch (SQLException e) { throw new RuntimeException( "SQL exception occurred loading test data", e); } } /** * utility method to read a .sql txt input stream. * * @param resource Set the resource * @return the parsed SQL * @throws IOException if there is a problem reading the resource file */ private String parseSqlIn(final Resource resource) throws IOException { InputStream is = null; try { is = resource.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(is)); StringWriter sw = new StringWriter(); BufferedWriter writer = new BufferedWriter(sw); for (int c = reader.read(); c != -1; c = reader.read()) { writer.write(c); } writer.flush(); return sw.toString(); } finally { if (is != null) { is.close(); } } } /** * utility method to run the parsed sql. * * @param sql Set the SQL * @param connection Set the connection * @throws SQLException if there is a problem executing the supplied SQL */ private void executeSql(final String sql, final Connection connection) throws SQLException { Statement statement = connection.createStatement(); statement.execute(sql); } } }