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;
+ }
+
+ }
+}