diff --git a/org.springframework.jdbc/.classpath b/org.springframework.jdbc/.classpath
index 8f39dc5929..0336a77928 100644
--- a/org.springframework.jdbc/.classpath
+++ b/org.springframework.jdbc/.classpath
@@ -15,5 +15,6 @@
+
diff --git a/org.springframework.jdbc/ivy.xml b/org.springframework.jdbc/ivy.xml
index a433afa453..16facba880 100644
--- a/org.springframework.jdbc/ivy.xml
+++ b/org.springframework.jdbc/ivy.xml
@@ -30,6 +30,7 @@
+
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/DatabasePopulator.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/DatabasePopulator.java
new file mode 100644
index 0000000000..1b2c6bfbc9
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/DatabasePopulator.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.jdbc.datasource.embedded;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * Strategy for populating a database with data.
+ *
+ * @see ResourceDatabasePopulator
+ */
+public interface DatabasePopulator {
+
+ /**
+ * Populate the 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.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabase.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabase.java
new file mode 100644
index 0000000000..057ef92ec7
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabase.java
@@ -0,0 +1,31 @@
+/*
+ * 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.jdbc.datasource.embedded;
+
+import javax.sql.DataSource;
+
+/**
+ * A handle to an EmbeddedDatabase instance.
+ * Is a {@link DataSource}.
+ * Adds a shutdown operation so the embedded database instance can be shutdown.
+ */
+public interface EmbeddedDatabase extends DataSource {
+
+ /**
+ * Shutdown this embedded database.
+ */
+ public void shutdown();
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.java
new file mode 100644
index 0000000000..ce77dd67f9
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.java
@@ -0,0 +1,139 @@
+/*
+ * 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.jdbc.datasource.embedded;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+
+/**
+ * A builder that provides a fluent API for constructing an embedded database.
+ * Usage example:
+ *
+ * EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
+ * EmbeddedDatabase db = builder.schema("schema.sql").testData("test-data.sql").build();
+ * db.shutdown();
+ *
+ *
+ * @author Keith Donald
+ */
+public class EmbeddedDatabaseBuilder {
+
+ private EmbeddedDatabaseFactory databaseFactory;
+
+ private ResourceDatabasePopulator databasePopulator;
+
+ private ResourceLoader resourceLoader;
+
+ /**
+ * Creates a new embedded database builder.
+ */
+ public EmbeddedDatabaseBuilder() {
+ init(new DefaultResourceLoader());
+ }
+
+ /**
+ * Sets the name of the embedded database
+ * Defaults to 'testdb' if not called.
+ * @param databaseType the embedded database type
+ * @return this, for fluent call chaining
+ */
+ public EmbeddedDatabaseBuilder name(String databaseName) {
+ databaseFactory.setDatabaseName(databaseName);
+ return this;
+ }
+
+ /**
+ * Sets the type of embedded database.
+ * Defaults to HSQL if not called.
+ * @param databaseType the embedded database type
+ * @return this, for fluent call chaining
+ */
+ public EmbeddedDatabaseBuilder type(EmbeddedDatabaseType databaseType) {
+ databaseFactory.setDatabaseType(databaseType);
+ return this;
+ }
+
+ /**
+ * Sets the location of the schema SQL to run to create the database structure.
+ * Defaults to classpath:schema.sql if not called.
+ * @param sqlResource the sql resource location
+ * @return this, for fluent call chaining
+ */
+ public EmbeddedDatabaseBuilder schema(String sqlResource) {
+ databasePopulator.setSchemaLocation(resourceLoader.getResource(sqlResource));
+ return this;
+ }
+
+ /**
+ * Sets the location of the schema SQL to run to create the database structure.
+ * Defaults to classpath:test-data.sql if not called
+ * @param sqlResource the sql resource location
+ * @return this, for fluent call chaining
+ */
+ public EmbeddedDatabaseBuilder testData(String sqlResource) {
+ databasePopulator.setTestDataLocation(resourceLoader.getResource(sqlResource));
+ return this;
+ }
+
+ /**
+ * Build the embedded database.
+ * @return the embedded database
+ */
+ public EmbeddedDatabase build() {
+ return databaseFactory.getDatabase();
+ }
+
+ /**
+ * Factory method that creates a embedded database builder that loads resources relative to the provided class.
+ * @param clazz the class to load relative to
+ * @return the embedded database builder
+ */
+ public static EmbeddedDatabaseBuilder relativeTo(final Class> clazz) {
+ ResourceLoader loader = new ResourceLoader() {
+ public ClassLoader getClassLoader() {
+ return getClass().getClassLoader();
+ }
+
+ public Resource getResource(String location) {
+ return new ClassPathResource(location, clazz);
+ }
+ };
+ return new EmbeddedDatabaseBuilder(loader);
+ }
+
+ /**
+ * Factory method that builds a default EmbeddedDatabase instance.
+ * The default is HSQL with a schema created from classpath:schema.sql and test-data loaded from classpatH:test-data.sql.
+ * @return an embedded database
+ */
+ public static EmbeddedDatabase buildDefault() {
+ return new EmbeddedDatabaseBuilder().build();
+ }
+
+ private EmbeddedDatabaseBuilder(ResourceLoader loader) {
+ init(loader);
+ }
+
+ private void init(ResourceLoader loader) {
+ databaseFactory = new EmbeddedDatabaseFactory();
+ databasePopulator = new ResourceDatabasePopulator();
+ databaseFactory.setDatabasePopulator(databasePopulator);
+ resourceLoader = loader;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurer.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurer.java
new file mode 100644
index 0000000000..534277a864
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurer.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.jdbc.datasource.embedded;
+
+import javax.sql.DataSource;
+
+import org.springframework.jdbc.datasource.SimpleDriverDataSource;
+
+/**
+ * Encapsulates the configuration required to create, connect to, and shutdown a specific type of embedded database such as HSQLdb or H2.
+ * Create a subclass for each database type we wish to support.
+ *
+ * @see EmbeddedDatabaseConfigurerFactory
+ */
+abstract class EmbeddedDatabaseConfigurer {
+
+ /**
+ * Configure the properties required to create and connect to the embedded database instance.
+ * @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 embedded database instance that backs dataSource.
+ * @param dataSource the data source
+ */
+ public abstract void shutdown(DataSource dataSource);
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerFactory.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerFactory.java
new file mode 100644
index 0000000000..57d51a94d4
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerFactory.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.jdbc.datasource.embedded;
+
+class EmbeddedDatabaseConfigurerFactory {
+
+ public static EmbeddedDatabaseConfigurer getConfigurer(EmbeddedDatabaseType type) throws IllegalStateException {
+ try {
+ if (type == EmbeddedDatabaseType.HSQL) {
+ return HsqlEmbeddedDatabaseConfigurer.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.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactory.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactory.java
new file mode 100644
index 0000000000..b52700178b
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactory.java
@@ -0,0 +1,191 @@
+/*
+ * 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.jdbc.datasource.embedded;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+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 EmbeddedDatabase} instance pre-populated with test data.
+ * When the database 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(EmbeddedDatabaseType)} to set the test database type.
+ * Call {@link #setDatabasePopulator(DatabasePopulator)} to change the algorithm used to populate the database.
+ * Call {@link #getDatabase()} to the {@link EmbeddedDatabase} instance.
+ */
+public class EmbeddedDatabaseFactory {
+
+ private static Log logger = LogFactory.getLog(EmbeddedDatabaseFactory.class);
+
+ private String databaseName;
+
+ private EmbeddedDatabaseConfigurer dataSourceConfigurer;
+
+ private DatabasePopulator databasePopulator;
+
+ private EmbeddedDatabase database;
+
+ /**
+ * Creates a new EmbeddedDatabaseFactory that uses the default {@link ResourceDatabasePopulator}.
+ */
+ public EmbeddedDatabaseFactory() {
+ setDatabaseName("testdb");
+ setDatabaseType(EmbeddedDatabaseType.HSQL);
+ setDatabasePopulator(new ResourceDatabasePopulator());
+ }
+
+ /**
+ * 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(EmbeddedDatabaseType type) {
+ Assert.notNull(type, "The TestDatabaseType is required");
+ dataSourceConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(type);
+ }
+
+ /**
+ * Sets the helper that will be invoked to populate the test database with data.
+ * Defaults a {@link ResourceDatabasePopulator}.
+ * @param populator
+ */
+ public void setDatabasePopulator(DatabasePopulator populator) {
+ Assert.notNull(populator, "The TestDatabasePopulator is required");
+ databasePopulator = populator;
+ }
+
+ // other public methods
+
+ /**
+ * Factory method that returns the embedded database instance.
+ */
+ public EmbeddedDatabase getDatabase() {
+ if (database == null) {
+ initDatabase();
+ }
+ return database;
+ }
+
+ // internal helper methods
+
+ // encapsulates the steps involved in initializing the data source: creating it, and populating it
+ private void initDatabase() {
+ // create the in-memory database source first
+ database = new EmbeddedDataSourceProxy(createDataSource());
+ if (logger.isInfoEnabled()) {
+ logger.info("Created embedded database '" + databaseName + "'");
+ }
+ if (databasePopulator != null) {
+ // now populate the database
+ populateDatabase();
+ }
+ }
+
+ private DataSource createDataSource() {
+ SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
+ dataSourceConfigurer.configureConnectionProperties(dataSource, databaseName);
+ return dataSource;
+ }
+
+ private void populateDatabase() {
+ TransactionTemplate template = new TransactionTemplate(new DataSourceTransactionManager(database));
+ template.execute(new TransactionCallbackWithoutResult() {
+ @Override
+ protected void doInTransactionWithoutResult(TransactionStatus status) {
+ databasePopulator.populate(new JdbcTemplate(database));
+ }
+ });
+ }
+
+ class EmbeddedDataSourceProxy implements EmbeddedDatabase {
+ private DataSource dataSource;
+
+ public EmbeddedDataSourceProxy(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ public Connection getConnection() throws SQLException {
+ return dataSource.getConnection();
+ }
+
+ public Connection getConnection(String username, String password)
+ throws SQLException {
+ return dataSource.getConnection(username, password);
+ }
+
+ public int getLoginTimeout() throws SQLException {
+ return dataSource.getLoginTimeout();
+ }
+
+ public PrintWriter getLogWriter() throws SQLException {
+ return dataSource.getLogWriter();
+ }
+
+ public void setLoginTimeout(int seconds) throws SQLException {
+ dataSource.setLoginTimeout(seconds);
+ }
+
+ public void setLogWriter(PrintWriter out) throws SQLException {
+ dataSource.setLogWriter(out);
+ }
+
+ public boolean isWrapperFor(Class> iface) throws SQLException {
+ return dataSource.isWrapperFor(iface);
+ }
+
+ public T unwrap(Class iface) throws SQLException {
+ return dataSource.unwrap(iface);
+ }
+
+ public void shutdown() {
+ shutdownDatabase();
+ }
+
+ }
+
+ private void shutdownDatabase() {
+ if (database != null) {
+ dataSourceConfigurer.shutdown(database);
+ database = null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java
new file mode 100644
index 0000000000..30085eb3eb
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.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.jdbc.datasource.embedded;
+
+/**
+ * A supported embedded database type.
+ * @author Keith Donald
+ */
+public enum EmbeddedDatabaseType {
+ HSQL;
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/HsqlEmbeddedDatabaseConfigurer.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/HsqlEmbeddedDatabaseConfigurer.java
new file mode 100644
index 0000000000..b5ee37ef39
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/HsqlEmbeddedDatabaseConfigurer.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.jdbc.datasource.embedded;
+
+import javax.sql.DataSource;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.SimpleDriverDataSource;
+import org.springframework.util.ClassUtils;
+
+class HsqlEmbeddedDatabaseConfigurer extends EmbeddedDatabaseConfigurer {
+
+ private static HsqlEmbeddedDatabaseConfigurer INSTANCE;
+
+ public static synchronized HsqlEmbeddedDatabaseConfigurer getInstance() throws ClassNotFoundException {
+ if (INSTANCE == null) {
+ ClassUtils.forName("org.hsqldb.jdbcDriver", HsqlEmbeddedDatabaseConfigurer.class.getClassLoader());
+ INSTANCE = new HsqlEmbeddedDatabaseConfigurer();
+ }
+ 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.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/ResourceDatabasePopulator.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/ResourceDatabasePopulator.java
new file mode 100644
index 0000000000..aee842d818
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/ResourceDatabasePopulator.java
@@ -0,0 +1,220 @@
+/*
+ * 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.jdbc.datasource.embedded;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.util.StringUtils;
+
+/**
+ * Populates a database 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 ResourceDatabasePopulator implements DatabasePopulator {
+
+ private static final Log logger = LogFactory.getLog(ResourceDatabasePopulator.class);
+
+ 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 populate.
+ * @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) {
+ executeSqlScript(template, new EncodedResource(schemaLocation, sqlScriptEncoding), false);
+ }
+
+ // populate the tables with test data
+ private void insertTestData(JdbcTemplate template) {
+ executeSqlScript(template, new EncodedResource(testDataLocation, sqlScriptEncoding), false);
+ }
+
+ // From SimpleJdbcTestUtils - TODO address duplication
+
+ /**
+ * Execute the given SQL script.
+ * The script will normally be loaded by classpath. There should be one statement
+ * per line. Any semicolons will be removed. Do not use this method to execute
+ * DDL if you expect rollback.
+ * @param simpleJdbcTemplate the SimpleJdbcTemplate with which to perform JDBC operations
+ * @param resource the resource (potentially associated with a specific encoding)
+ * to load the SQL script from.
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error.
+ * @throws DataAccessException if there is an error executing a statement
+ * and continueOnError was false
+ */
+ static void executeSqlScript(JdbcTemplate jdbcTemplate,
+ EncodedResource resource, boolean continueOnError) throws DataAccessException {
+ if (logger.isInfoEnabled()) {
+ logger.info("Executing SQL script from " + resource);
+ }
+ long startTime = System.currentTimeMillis();
+ List statements = new LinkedList();
+ try {
+ LineNumberReader lnr = new LineNumberReader(resource.getReader());
+ String script = readScript(lnr);
+ char delimiter = ';';
+ if (!containsSqlScriptDelimiters(script, delimiter)) {
+ delimiter = '\n';
+ }
+ splitSqlScript(script, delimiter, statements);
+ for (String statement : statements) {
+ try {
+ int rowsAffected = jdbcTemplate.update(statement);
+ if (logger.isDebugEnabled()) {
+ logger.debug(rowsAffected + " rows affected by SQL: " + statement);
+ }
+ }
+ catch (DataAccessException ex) {
+ if (continueOnError) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("SQL: " + statement + " failed", ex);
+ }
+ }
+ else {
+ throw ex;
+ }
+ }
+ }
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ if (logger.isInfoEnabled()) {
+ logger.info("Done executing SQL script from " + resource + " in " + elapsedTime + " ms.");
+ }
+ }
+ catch (IOException ex) {
+ throw new DataAccessResourceFailureException("Failed to open SQL script from " + resource, ex);
+ }
+ }
+
+ // From JdbcTestUtils - TODO address duplication
+
+ /**
+ * 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
+ */
+ static String readScript(LineNumberReader lineNumberReader) throws IOException {
+ String currentStatement = lineNumberReader.readLine();
+ StringBuilder scriptBuilder = new StringBuilder();
+ while (currentStatement != null) {
+ if (StringUtils.hasText(currentStatement)) {
+ if (scriptBuilder.length() > 0) {
+ scriptBuilder.append('\n');
+ }
+ scriptBuilder.append(currentStatement);
+ }
+ currentStatement = lineNumberReader.readLine();
+ }
+ return scriptBuilder.toString();
+ }
+
+ /**
+ * Does the provided SQL script contain the specified delimiter?
+ * @param script the SQL script
+ * @param delim charecter delimiting each statement - typically a ';' character
+ */
+ static boolean containsSqlScriptDelimiters(String script, char delim) {
+ boolean inLiteral = false;
+ char[] content = script.toCharArray();
+ for (int i = 0; i < script.length(); i++) {
+ if (content[i] == '\'') {
+ inLiteral = !inLiteral;
+ }
+ if (content[i] == delim && !inLiteral) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Split an SQL script into separate statements delimited with the provided delimiter character. Each
+ * individual statement will be added to the provided List
.
+ * @param script the SQL script
+ * @param delim charecter delimiting each statement - typically a ';' character
+ * @param statements the List that will contain the individual statements
+ */
+ static void splitSqlScript(String script, char delim, List statements) {
+ StringBuilder sb = new StringBuilder();
+ boolean inLiteral = false;
+ char[] content = script.toCharArray();
+ for (int i = 0; i < script.length(); i++) {
+ if (content[i] == '\'') {
+ inLiteral = !inLiteral;
+ }
+ if (content[i] == delim && !inLiteral) {
+ if (sb.length() > 0) {
+ statements.add(sb.toString());
+ sb = new StringBuilder();
+ }
+ }
+ else {
+ sb.append(content[i]);
+ }
+ }
+ if (sb.length() > 0) {
+ statements.add(sb.toString());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilderTests.java b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilderTests.java
new file mode 100644
index 0000000000..e292e46d3c
--- /dev/null
+++ b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilderTests.java
@@ -0,0 +1,52 @@
+package org.springframework.jdbc.datasource.embedded;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+
+public class EmbeddedDatabaseBuilderTests {
+
+ @Test
+ public void testBuildDefaults() {
+ EmbeddedDatabase db = EmbeddedDatabaseBuilder.buildDefault();
+ JdbcTemplate template = new JdbcTemplate(db);
+ assertEquals("Keith", template.queryForObject("select NAME from T_TEST", String.class));
+ db.shutdown();
+ }
+
+ @Test
+ public void testBuild() {
+ EmbeddedDatabaseBuilder builder = EmbeddedDatabaseBuilder.relativeTo(getClass());
+ EmbeddedDatabase db = builder.schema("db-schema.sql").testData("db-test-data.sql").build();
+ JdbcTemplate template = new JdbcTemplate(db);
+ assertEquals("Keith", template.queryForObject("select NAME from T_TEST", String.class));
+ db.shutdown();
+ }
+
+ @Test
+ public void testBuildNoSuchSchema() {
+ try {
+ new EmbeddedDatabaseBuilder().schema("bogus.sql").build();
+ fail("Should have failed");
+ } catch (DataAccessException e) {
+
+ }
+ }
+
+ @Test
+ public void testBuildNoSuchTestdata() {
+ try {
+ new EmbeddedDatabaseBuilder().testData("bogus.sql").build();
+ fail("Should have failed");
+ } catch (DataAccessException e) {
+
+ }
+ }
+
+
+}
diff --git a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryTests.java b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryTests.java
new file mode 100644
index 0000000000..baff4ba25c
--- /dev/null
+++ b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryTests.java
@@ -0,0 +1,34 @@
+package org.springframework.jdbc.datasource.embedded;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.embedded.DatabasePopulator;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory;
+
+public class EmbeddedDatabaseFactoryTests {
+
+ private EmbeddedDatabaseFactory factory = new EmbeddedDatabaseFactory();
+
+ @Test
+ public void testGetDataSource() {
+ StubDatabasePopulator populator = new StubDatabasePopulator();
+ factory.setDatabasePopulator(populator);
+ EmbeddedDatabase db = factory.getDatabase();
+ assertTrue(populator.populateCalled);
+ db.shutdown();
+ }
+
+ private static class StubDatabasePopulator implements DatabasePopulator {
+
+ private boolean populateCalled;
+
+ public void populate(JdbcTemplate template) throws DataAccessException {
+ this.populateCalled = true;
+ }
+
+ }
+}
diff --git a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/db-schema.sql b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/db-schema.sql
new file mode 100644
index 0000000000..73d0feb406
--- /dev/null
+++ b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/db-schema.sql
@@ -0,0 +1,3 @@
+drop table T_TEST if exists;
+
+create table T_TEST (NAME varchar(50) not null);
\ No newline at end of file
diff --git a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/db-test-data.sql b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/db-test-data.sql
new file mode 100644
index 0000000000..51de08aac1
--- /dev/null
+++ b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/db-test-data.sql
@@ -0,0 +1 @@
+insert into T_TEST (NAME) values ('Keith');
\ No newline at end of file
diff --git a/org.springframework.jdbc/src/test/java/schema.sql b/org.springframework.jdbc/src/test/java/schema.sql
new file mode 100644
index 0000000000..73d0feb406
--- /dev/null
+++ b/org.springframework.jdbc/src/test/java/schema.sql
@@ -0,0 +1,3 @@
+drop table T_TEST if exists;
+
+create table T_TEST (NAME varchar(50) not null);
\ No newline at end of file
diff --git a/org.springframework.jdbc/src/test/java/test-data.sql b/org.springframework.jdbc/src/test/java/test-data.sql
new file mode 100644
index 0000000000..51de08aac1
--- /dev/null
+++ b/org.springframework.jdbc/src/test/java/test-data.sql
@@ -0,0 +1 @@
+insert into T_TEST (NAME) values ('Keith');
\ No newline at end of file