From b8b74db01ac92783788db03e9736def6f36c5b6b Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Tue, 21 Apr 2009 21:31:16 +0000 Subject: [PATCH] test datasource factory initial commit; needs review from Juergen on how to best make DataSource available as a Spring bean when desired --- org.springframework.test/ivy.xml | 2 +- .../jdbc/HsqlTestDataSourceConfigurer.java | 47 ++++++ .../test/jdbc/JdbcTestUtils.java | 2 +- .../jdbc/ResourceTestDatabasePopulator.java | 81 ++++++++++ .../test/jdbc/TestDataSourceConfigurer.java | 43 ++++++ .../jdbc/TestDataSourceConfigurerFactory.java | 32 ++++ .../test/jdbc/TestDataSourceFactory.java | 143 ++++++++++++++++++ .../test/jdbc/TestDatabasePopulator.java | 34 +++++ .../test/jdbc/TestDatabaseType.java | 24 +++ .../test/jdbc/TestDataSourceFactoryTests.java | 30 ++++ 10 files changed, 436 insertions(+), 2 deletions(-) create mode 100644 org.springframework.test/src/main/java/org/springframework/test/jdbc/HsqlTestDataSourceConfigurer.java create mode 100644 org.springframework.test/src/main/java/org/springframework/test/jdbc/ResourceTestDatabasePopulator.java create mode 100644 org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceConfigurer.java create mode 100644 org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceConfigurerFactory.java create mode 100644 org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceFactory.java create mode 100644 org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDatabasePopulator.java create mode 100644 org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDatabaseType.java create mode 100644 org.springframework.test/src/test/java/org/springframework/test/jdbc/TestDataSourceFactoryTests.java diff --git a/org.springframework.test/ivy.xml b/org.springframework.test/ivy.xml index 787db22f5df..63620b2bbf0 100644 --- a/org.springframework.test/ivy.xml +++ b/org.springframework.test/ivy.xml @@ -41,7 +41,7 @@ - + diff --git a/org.springframework.test/src/main/java/org/springframework/test/jdbc/HsqlTestDataSourceConfigurer.java b/org.springframework.test/src/main/java/org/springframework/test/jdbc/HsqlTestDataSourceConfigurer.java new file mode 100644 index 00000000000..d81bfc9aea3 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/jdbc/HsqlTestDataSourceConfigurer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.springframework.test.jdbc; + +import javax.sql.DataSource; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.util.ClassUtils; + +class HsqlTestDataSourceConfigurer extends TestDataSourceConfigurer { + + private static HsqlTestDataSourceConfigurer INSTANCE; + + public static synchronized HsqlTestDataSourceConfigurer getInstance() throws ClassNotFoundException { + if (INSTANCE == null) { + ClassUtils.forName("org.hsqldb.jdbcDriver", HsqlTestDataSourceConfigurer.class.getClassLoader()); + INSTANCE = new HsqlTestDataSourceConfigurer(); + } + return INSTANCE; + } + + public void configureConnectionProperties(SimpleDriverDataSource dataSource, String databaseName) { + dataSource.setDriverClass(org.hsqldb.jdbcDriver.class); + dataSource.setUrl("jdbc:hsqldb:mem:" + databaseName); + dataSource.setUsername("sa"); + dataSource.setPassword(""); + } + + public void shutdown(DataSource dataSource) { + new JdbcTemplate(dataSource).execute("SHUTDOWN"); + } + +} diff --git a/org.springframework.test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java b/org.springframework.test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java index a3b9784c231..0660a1e3edf 100644 --- a/org.springframework.test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java +++ b/org.springframework.test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java @@ -32,7 +32,7 @@ import java.io.IOException; public class JdbcTestUtils { /** - * Read a script from the LineNumberReaded and build a String containing the lines. + * Read a script from the LineNumberReader and build a String containing the lines. * @param lineNumberReader the LineNumberReader containing the script to be processed * @return String containing the script lines * @throws IOException diff --git a/org.springframework.test/src/main/java/org/springframework/test/jdbc/ResourceTestDatabasePopulator.java b/org.springframework.test/src/main/java/org/springframework/test/jdbc/ResourceTestDatabasePopulator.java new file mode 100644 index 00000000000..082c48c6561 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/jdbc/ResourceTestDatabasePopulator.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.springframework.test.jdbc; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; + +/** + * Populates a test DataSource from schema and test-data SQL defined in external resources. + * By default, looks for a schema.sql file and test-data.sql resource in the root of the classpath. + * + * May be configured. + * Call {@link #setSchemaLocation(Resource)} to configure the location of the database schema file. + * Call {@link #setTestDataLocation(Resource)} to configure the location of the test data file. + * Call {@link #setSqlScriptEncoding(String)} to set the encoding for the schema and test data SQL. + */ +public class ResourceTestDatabasePopulator implements TestDatabasePopulator { + + private Resource schemaLocation = new ClassPathResource("schema.sql"); + + private Resource testDataLocation = new ClassPathResource("test-data.sql"); + + private String sqlScriptEncoding; + + /** + * Sets the location of .sql file containing the database schema to create. + * @param schemaLocation the path to the db schema definition + */ + public void setSchemaLocation(Resource schemaLocation) { + this.schemaLocation = schemaLocation; + } + + /** + * Sets the location of the .sql file containing the test data to load. + * @param testDataLocation the path to the db test data file + */ + public void setTestDataLocation(Resource testDataLocation) { + this.testDataLocation = testDataLocation; + } + + /** + * Specify the encoding for SQL scripts, if different from the platform encoding. + */ + public void setSqlScriptEncoding(String sqlScriptEncoding) { + this.sqlScriptEncoding = sqlScriptEncoding; + } + + public void populate(JdbcTemplate template) { + createDatabaseSchema(template); + insertTestData(template); + } + + // create the application's database schema (tables, indexes, etc.) + private void createDatabaseSchema(JdbcTemplate template) { + // TODO SimpleJdbcTemplate is unnecessary now with Java5+ - make similar method available on JdbcTestUtils? + SimpleJdbcTestUtils.executeSqlScript(new SimpleJdbcTemplate(template), new EncodedResource(schemaLocation, sqlScriptEncoding), false); + } + + // populate the tables with test data + private void insertTestData(JdbcTemplate template) { + // TODO SimpleJdbcTemplate is unnecessary now with Java5+ - make similar method available on JdbcTestUtils? + SimpleJdbcTestUtils.executeSqlScript(new SimpleJdbcTemplate(template), new EncodedResource(testDataLocation, sqlScriptEncoding), false); + } + +} \ No newline at end of file diff --git a/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceConfigurer.java b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceConfigurer.java new file mode 100644 index 00000000000..bf65b9e0931 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceConfigurer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.springframework.test.jdbc; + +import javax.sql.DataSource; + +import org.springframework.jdbc.datasource.SimpleDriverDataSource; + +/** + * Encapsulates the configuration required to connect to a specific type of test database such as HSQLdb or H2. + * Create a subclass for each database type we wish to support. + * + * @see TestDataSourceConfigurerFactory + */ +abstract class TestDataSourceConfigurer { + + /** + * Configure the properties required to connect to databaseName. + * @param dataSource the data source to configure + * @param databaseName the name of the test database + */ + public abstract void configureConnectionProperties(SimpleDriverDataSource dataSource, String databaseName); + + /** + * Shutdown the test database backed by dataSource. + * @param dataSource the data source + */ + public abstract void shutdown(DataSource dataSource); + +} diff --git a/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceConfigurerFactory.java b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceConfigurerFactory.java new file mode 100644 index 00000000000..0d00c5f2175 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceConfigurerFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.springframework.test.jdbc; + +class TestDataSourceConfigurerFactory { + + public static TestDataSourceConfigurer getConfigurer(TestDatabaseType type) throws IllegalStateException { + try { + if (type == TestDatabaseType.HSQL) { + return HsqlTestDataSourceConfigurer.getInstance(); + } else { + throw new UnsupportedOperationException("Other types not yet supported"); + } + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Drivers for test database type [" + type + "] are not available in the classpath", e); + } + } + +} diff --git a/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceFactory.java b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceFactory.java new file mode 100644 index 00000000000..f51acd92915 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDataSourceFactory.java @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.springframework.test.jdbc; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.Assert; + +/** + * Returns a {@link DataSource} that connects to an in-memory test database pre-populated with test data. + * When the DataSource is returned, callers are guaranteed that the database schema and test data will have already been loaded. + * + * Can be configured. + * Call {@link #setDatabaseName(String)} to change the name of the test database. + * Call {@link #setDatabaseType(TestDatabaseType)} to set the test database type. + * Call {@link #setDatabasePopulator(TestDatabasePopulator)} to change the algorithm used to populate the test database. + */ +public class TestDataSourceFactory { + + private static Log logger = LogFactory.getLog(TestDataSourceFactory.class); + + private String databaseName; + + private TestDataSourceConfigurer dataSourceConfigurer; + + private TestDatabasePopulator databasePopulator; + + private DataSource dataSource; + + /** + * Creates a new TestDataSourceFactory that uses the default {@link ResourceTestDatabasePopulator}. + */ + public TestDataSourceFactory() { + setDatabaseName("testdb"); + setDatabaseType(TestDatabaseType.HSQL); + setDatabasePopulator(new ResourceTestDatabasePopulator()); + } + + /** + * Sets the name of the test database. + * Defaults to 'testdb'. + * @param name of the test database + */ + public void setDatabaseName(String name) { + Assert.notNull(name, "The testDatabaseName is required"); + databaseName = name; + } + + /** + * Sets the type of test database to use. + * Defaults to HSQL. + * @param type the test database type + */ + public void setDatabaseType(TestDatabaseType type) { + Assert.notNull(type, "The TestDatabaseType is required"); + dataSourceConfigurer = TestDataSourceConfigurerFactory.getConfigurer(type); + } + + /** + * Sets the helper that will be invoked to populate the test database with data. + * Defaults a {@link ResourceTestDatabasePopulator}. + * @param populator + */ + public void setDatabasePopulator(TestDatabasePopulator populator) { + Assert.notNull(populator, "The TestDatabasePopulator is required"); + databasePopulator = populator; + } + + // other public methods + + /** + * The factory method that returns the {@link DataSource} that connects to the test database. + */ + public DataSource getDataSource() { + if (dataSource == null) { + initDataSource(); + } + return dataSource; + } + + /** + * Destroy the test database. + * Does nothing if the database has not been initialized. + */ + public void destroyDataSource() { + if (dataSource != null) { + dataSourceConfigurer.shutdown(dataSource); + dataSource = null; + } + } + + // internal helper methods + + // encapsulates the steps involved in initializing the data source: creating it, and populating it + private void initDataSource() { + // create the in-memory database source first + dataSource = createDataSource(); + if (logger.isInfoEnabled()) { + logger.info("Created in-memory test database '" + databaseName + "'"); + } + if (databasePopulator != null) { + // now populate the database + populateDatabase(); + } + } + + protected DataSource createDataSource() { + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSourceConfigurer.configureConnectionProperties(dataSource, databaseName); + return dataSource; + } + + private void populateDatabase() { + TransactionTemplate template = new TransactionTemplate(new DataSourceTransactionManager(dataSource)); + template.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + databasePopulator.populate(new JdbcTemplate(dataSource)); + } + }); + } +} \ No newline at end of file diff --git a/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDatabasePopulator.java b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDatabasePopulator.java new file mode 100644 index 00000000000..8aa8e0e103f --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDatabasePopulator.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.springframework.test.jdbc; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * Strategy for populating a test database with test data. + * + * @see ResourceTestDatabasePopulator + */ +public interface TestDatabasePopulator { + + /** + * Populate the test database using the JDBC-based data access template provided. + * @param template the data access template to use to populate the db; already configured and ready to use + * @throws DataAccessException if an unrecoverable data access exception occurs during database population + */ + void populate(JdbcTemplate template); +} \ No newline at end of file diff --git a/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDatabaseType.java b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDatabaseType.java new file mode 100644 index 00000000000..6709af12189 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/jdbc/TestDatabaseType.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package org.springframework.test.jdbc; + +/** + * A supported test database type. + * @author Keith Donald + */ +public enum TestDatabaseType { + HSQL; +} diff --git a/org.springframework.test/src/test/java/org/springframework/test/jdbc/TestDataSourceFactoryTests.java b/org.springframework.test/src/test/java/org/springframework/test/jdbc/TestDataSourceFactoryTests.java new file mode 100644 index 00000000000..f57565a3cd1 --- /dev/null +++ b/org.springframework.test/src/test/java/org/springframework/test/jdbc/TestDataSourceFactoryTests.java @@ -0,0 +1,30 @@ +package org.springframework.test.jdbc; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; + +public class TestDataSourceFactoryTests { + TestDataSourceFactory factory = new TestDataSourceFactory(); + + @Test + public void testGetDataSource() { + StubTestDataSourcePopulator populator = new StubTestDataSourcePopulator(); + factory.setDatabasePopulator(populator); + factory.getDataSource(); + assertTrue(populator.populateCalled); + factory.destroyDataSource(); + } + + private static class StubTestDataSourcePopulator implements TestDatabasePopulator { + + private boolean populateCalled; + + public void populate(JdbcTemplate template) throws DataAccessException { + this.populateCalled = true; + } + + } +}