001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jdbc.datasource.embedded;
018
019import javax.sql.DataSource;
020
021import org.springframework.core.io.DefaultResourceLoader;
022import org.springframework.core.io.ResourceLoader;
023import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
024import org.springframework.jdbc.datasource.init.ScriptUtils;
025import org.springframework.util.Assert;
026
027/**
028 * A builder that provides a convenient API for constructing an embedded database.
029 *
030 * <h3>Usage Example</h3>
031 * <pre class="code">
032 * EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
033 *     .generateUniqueName(true)
034 *     .setType(H2)
035 *     .setScriptEncoding("UTF-8")
036 *     .ignoreFailedDrops(true)
037 *     .addScript("schema.sql")
038 *     .addScripts("user_data.sql", "country_data.sql")
039 *     .build();
040 *
041 * // perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
042 *
043 * db.shutdown();
044 * </pre>
045 *
046 * @author Keith Donald
047 * @author Juergen Hoeller
048 * @author Dave Syer
049 * @author Sam Brannen
050 * @since 3.0
051 * @see org.springframework.jdbc.datasource.init.ScriptUtils
052 * @see org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
053 * @see org.springframework.jdbc.datasource.init.DatabasePopulatorUtils
054 */
055public class EmbeddedDatabaseBuilder {
056
057        private final EmbeddedDatabaseFactory databaseFactory;
058
059        private final ResourceDatabasePopulator databasePopulator;
060
061        private final ResourceLoader resourceLoader;
062
063
064        /**
065         * Create a new embedded database builder with a {@link DefaultResourceLoader}.
066         */
067        public EmbeddedDatabaseBuilder() {
068                this(new DefaultResourceLoader());
069        }
070
071        /**
072         * Create a new embedded database builder with the given {@link ResourceLoader}.
073         * @param resourceLoader the {@code ResourceLoader} to delegate to
074         */
075        public EmbeddedDatabaseBuilder(ResourceLoader resourceLoader) {
076                this.databaseFactory = new EmbeddedDatabaseFactory();
077                this.databasePopulator = new ResourceDatabasePopulator();
078                this.databaseFactory.setDatabasePopulator(this.databasePopulator);
079                this.resourceLoader = resourceLoader;
080        }
081
082        /**
083         * Specify whether a unique ID should be generated and used as the database name.
084         * <p>If the configuration for this builder is reused across multiple
085         * application contexts within a single JVM, this flag should be <em>enabled</em>
086         * (i.e., set to {@code true}) in order to ensure that each application context
087         * gets its own embedded database.
088         * <p>Enabling this flag overrides any explicit name set via {@link #setName}.
089         * @param flag {@code true} if a unique database name should be generated
090         * @return {@code this}, to facilitate method chaining
091         * @since 4.2
092         * @see #setName
093         */
094        public EmbeddedDatabaseBuilder generateUniqueName(boolean flag) {
095                this.databaseFactory.setGenerateUniqueDatabaseName(flag);
096                return this;
097        }
098
099        /**
100         * Set the name of the embedded database.
101         * <p>Defaults to {@link EmbeddedDatabaseFactory#DEFAULT_DATABASE_NAME} if
102         * not called.
103         * <p>Will be overridden if the {@code generateUniqueName} flag has been
104         * set to {@code true}.
105         * @param databaseName the name of the embedded database to build
106         * @return {@code this}, to facilitate method chaining
107         * @see #generateUniqueName
108         */
109        public EmbeddedDatabaseBuilder setName(String databaseName) {
110                this.databaseFactory.setDatabaseName(databaseName);
111                return this;
112        }
113
114        /**
115         * Set the type of embedded database.
116         * <p>Defaults to HSQL if not called.
117         * @param databaseType the type of embedded database to build
118         * @return {@code this}, to facilitate method chaining
119         */
120        public EmbeddedDatabaseBuilder setType(EmbeddedDatabaseType databaseType) {
121                this.databaseFactory.setDatabaseType(databaseType);
122                return this;
123        }
124
125        /**
126         * Set the factory to use to create the {@link DataSource} instance that
127         * connects to the embedded database.
128         * <p>Defaults to {@link SimpleDriverDataSourceFactory} but can be overridden,
129         * for example to introduce connection pooling.
130         * @return {@code this}, to facilitate method chaining
131         * @since 4.0.3
132         */
133        public EmbeddedDatabaseBuilder setDataSourceFactory(DataSourceFactory dataSourceFactory) {
134                Assert.notNull(dataSourceFactory, "DataSourceFactory is required");
135                this.databaseFactory.setDataSourceFactory(dataSourceFactory);
136                return this;
137        }
138
139        /**
140         * Add default SQL scripts to execute to populate the database.
141         * <p>The default scripts are {@code "schema.sql"} to create the database
142         * schema and {@code "data.sql"} to populate the database with data.
143         * @return {@code this}, to facilitate method chaining
144         */
145        public EmbeddedDatabaseBuilder addDefaultScripts() {
146                return addScripts("schema.sql", "data.sql");
147        }
148
149        /**
150         * Add an SQL script to execute to initialize or populate the database.
151         * @param script the script to execute
152         * @return {@code this}, to facilitate method chaining
153         */
154        public EmbeddedDatabaseBuilder addScript(String script) {
155                this.databasePopulator.addScript(this.resourceLoader.getResource(script));
156                return this;
157        }
158
159        /**
160         * Add multiple SQL scripts to execute to initialize or populate the database.
161         * @param scripts the scripts to execute
162         * @return {@code this}, to facilitate method chaining
163         * @since 4.0.3
164         */
165        public EmbeddedDatabaseBuilder addScripts(String... scripts) {
166                for (String script : scripts) {
167                        addScript(script);
168                }
169                return this;
170        }
171
172        /**
173         * Specify the character encoding used in all SQL scripts, if different from
174         * the platform encoding.
175         * @param scriptEncoding the encoding used in scripts
176         * @return {@code this}, to facilitate method chaining
177         * @since 4.0.3
178         */
179        public EmbeddedDatabaseBuilder setScriptEncoding(String scriptEncoding) {
180                this.databasePopulator.setSqlScriptEncoding(scriptEncoding);
181                return this;
182        }
183
184        /**
185         * Specify the statement separator used in all SQL scripts, if a custom one.
186         * <p>Defaults to {@code ";"} if not specified and falls back to {@code "\n"}
187         * as a last resort; may be set to {@link ScriptUtils#EOF_STATEMENT_SEPARATOR}
188         * to signal that each script contains a single statement without a separator.
189         * @param separator the statement separator
190         * @return {@code this}, to facilitate method chaining
191         * @since 4.0.3
192         */
193        public EmbeddedDatabaseBuilder setSeparator(String separator) {
194                this.databasePopulator.setSeparator(separator);
195                return this;
196        }
197
198        /**
199         * Specify the single-line comment prefix used in all SQL scripts.
200         * <p>Defaults to {@code "--"}.
201         * @param commentPrefix the prefix for single-line comments
202         * @return {@code this}, to facilitate method chaining
203         * @since 4.0.3
204         * @see #setCommentPrefixes(String...)
205         */
206        public EmbeddedDatabaseBuilder setCommentPrefix(String commentPrefix) {
207                this.databasePopulator.setCommentPrefix(commentPrefix);
208                return this;
209        }
210
211        /**
212         * Specify the prefixes that identify single-line comments within all SQL scripts.
213         * <p>Defaults to {@code ["--"]}.
214         * @param commentPrefixes the prefixes for single-line comments
215         * @return {@code this}, to facilitate method chaining
216         * @since 5.2
217         */
218        public EmbeddedDatabaseBuilder setCommentPrefixes(String... commentPrefixes) {
219                this.databasePopulator.setCommentPrefixes(commentPrefixes);
220                return this;
221        }
222
223        /**
224         * Specify the start delimiter for block comments in all SQL scripts.
225         * <p>Defaults to {@code "/*"}.
226         * @param blockCommentStartDelimiter the start delimiter for block comments
227         * @return {@code this}, to facilitate method chaining
228         * @since 4.0.3
229         * @see #setBlockCommentEndDelimiter
230         */
231        public EmbeddedDatabaseBuilder setBlockCommentStartDelimiter(String blockCommentStartDelimiter) {
232                this.databasePopulator.setBlockCommentStartDelimiter(blockCommentStartDelimiter);
233                return this;
234        }
235
236        /**
237         * Specify the end delimiter for block comments in all SQL scripts.
238         * <p>Defaults to <code>"*&#47;"</code>.
239         * @param blockCommentEndDelimiter the end delimiter for block comments
240         * @return {@code this}, to facilitate method chaining
241         * @since 4.0.3
242         * @see #setBlockCommentStartDelimiter
243         */
244        public EmbeddedDatabaseBuilder setBlockCommentEndDelimiter(String blockCommentEndDelimiter) {
245                this.databasePopulator.setBlockCommentEndDelimiter(blockCommentEndDelimiter);
246                return this;
247        }
248
249        /**
250         * Specify that all failures which occur while executing SQL scripts should
251         * be logged but should not cause a failure.
252         * <p>Defaults to {@code false}.
253         * @param flag {@code true} if script execution should continue on error
254         * @return {@code this}, to facilitate method chaining
255         * @since 4.0.3
256         */
257        public EmbeddedDatabaseBuilder continueOnError(boolean flag) {
258                this.databasePopulator.setContinueOnError(flag);
259                return this;
260        }
261
262        /**
263         * Specify that a failed SQL {@code DROP} statement within an executed
264         * script can be ignored.
265         * <p>This is useful for a database whose SQL dialect does not support an
266         * {@code IF EXISTS} clause in a {@code DROP} statement.
267         * <p>The default is {@code false} so that {@link #build building} will fail
268         * fast if a script starts with a {@code DROP} statement.
269         * @param flag {@code true} if failed drop statements should be ignored
270         * @return {@code this}, to facilitate method chaining
271         * @since 4.0.3
272         */
273        public EmbeddedDatabaseBuilder ignoreFailedDrops(boolean flag) {
274                this.databasePopulator.setIgnoreFailedDrops(flag);
275                return this;
276        }
277
278        /**
279         * Build the embedded database.
280         * @return the embedded database
281         */
282        public EmbeddedDatabase build() {
283                return this.databaseFactory.getDatabase();
284        }
285
286}