001/*
002 * Copyright 2002-2017 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.init;
018
019import java.sql.Connection;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.List;
023import javax.sql.DataSource;
024
025import org.springframework.core.io.Resource;
026import org.springframework.core.io.support.EncodedResource;
027import org.springframework.util.Assert;
028import org.springframework.util.StringUtils;
029
030/**
031 * Populates, initializes, or cleans up a database using SQL scripts defined in
032 * external resources.
033 *
034 * <ul>
035 * <li>Call {@link #addScript} to add a single SQL script location.
036 * <li>Call {@link #addScripts} to add multiple SQL script locations.
037 * <li>Consult the setter methods in this class for further configuration options.
038 * <li>Call {@link #populate} or {@link #execute} to initialize or clean up the
039 * database using the configured scripts.
040 * </ul>
041 *
042 * @author Keith Donald
043 * @author Dave Syer
044 * @author Juergen Hoeller
045 * @author Chris Beams
046 * @author Oliver Gierke
047 * @author Sam Brannen
048 * @author Chris Baldwin
049 * @since 3.0
050 * @see DatabasePopulatorUtils
051 * @see ScriptUtils
052 */
053public class ResourceDatabasePopulator implements DatabasePopulator {
054
055        List<Resource> scripts = new ArrayList<Resource>();
056
057        private String sqlScriptEncoding;
058
059        private String separator = ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
060
061        private String commentPrefix = ScriptUtils.DEFAULT_COMMENT_PREFIX;
062
063        private String blockCommentStartDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
064
065        private String blockCommentEndDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
066
067        private boolean continueOnError = false;
068
069        private boolean ignoreFailedDrops = false;
070
071
072        /**
073         * Construct a new {@code ResourceDatabasePopulator} with default settings.
074         * @since 4.0.3
075         */
076        public ResourceDatabasePopulator() {
077                /* no-op */
078        }
079
080        /**
081         * Construct a new {@code ResourceDatabasePopulator} with default settings
082         * for the supplied scripts.
083         * @param scripts the scripts to execute to initialize or clean up the database
084         * (never {@code null})
085         * @since 4.0.3
086         */
087        public ResourceDatabasePopulator(Resource... scripts) {
088                this();
089                setScripts(scripts);
090        }
091
092        /**
093         * Construct a new {@code ResourceDatabasePopulator} with the supplied values.
094         * @param continueOnError flag to indicate that all failures in SQL should be
095         * logged but not cause a failure
096         * @param ignoreFailedDrops flag to indicate that a failed SQL {@code DROP}
097         * statement can be ignored
098         * @param sqlScriptEncoding the encoding for the supplied SQL scripts; may
099         * be {@code null} or <em>empty</em> to indicate platform encoding
100         * @param scripts the scripts to execute to initialize or clean up the database
101         * (never {@code null})
102         * @since 4.0.3
103         */
104        public ResourceDatabasePopulator(boolean continueOnError, boolean ignoreFailedDrops,
105                        String sqlScriptEncoding, Resource... scripts) {
106
107                this(scripts);
108                this.continueOnError = continueOnError;
109                this.ignoreFailedDrops = ignoreFailedDrops;
110                setSqlScriptEncoding(sqlScriptEncoding);
111        }
112
113
114        /**
115         * Add a script to execute to initialize or clean up the database.
116         * @param script the path to an SQL script (never {@code null})
117         */
118        public void addScript(Resource script) {
119                Assert.notNull(script, "Script must not be null");
120                this.scripts.add(script);
121        }
122
123        /**
124         * Add multiple scripts to execute to initialize or clean up the database.
125         * @param scripts the scripts to execute (never {@code null})
126         */
127        public void addScripts(Resource... scripts) {
128                assertContentsOfScriptArray(scripts);
129                this.scripts.addAll(Arrays.asList(scripts));
130        }
131
132        /**
133         * Set the scripts to execute to initialize or clean up the database,
134         * replacing any previously added scripts.
135         * @param scripts the scripts to execute (never {@code null})
136         */
137        public void setScripts(Resource... scripts) {
138                assertContentsOfScriptArray(scripts);
139                // Ensure that the list is modifiable
140                this.scripts = new ArrayList<Resource>(Arrays.asList(scripts));
141        }
142
143        private void assertContentsOfScriptArray(Resource... scripts) {
144                Assert.notNull(scripts, "Scripts array must not be null");
145                Assert.noNullElements(scripts, "Scripts array must not contain null elements");
146        }
147
148        /**
149         * Specify the encoding for the configured SQL scripts, if different from the
150         * platform encoding.
151         * @param sqlScriptEncoding the encoding used in scripts; may be {@code null}
152         * or empty to indicate platform encoding
153         * @see #addScript(Resource)
154         */
155        public void setSqlScriptEncoding(String sqlScriptEncoding) {
156                this.sqlScriptEncoding = (StringUtils.hasText(sqlScriptEncoding) ? sqlScriptEncoding : null);
157        }
158
159        /**
160         * Specify the statement separator, if a custom one.
161         * <p>Defaults to {@code ";"} if not specified and falls back to {@code "\n"}
162         * as a last resort; may be set to {@link ScriptUtils#EOF_STATEMENT_SEPARATOR}
163         * to signal that each script contains a single statement without a separator.
164         * @param separator the script statement separator
165         */
166        public void setSeparator(String separator) {
167                this.separator = separator;
168        }
169
170        /**
171         * Set the prefix that identifies single-line comments within the SQL scripts.
172         * <p>Defaults to {@code "--"}.
173         * @param commentPrefix the prefix for single-line comments
174         */
175        public void setCommentPrefix(String commentPrefix) {
176                this.commentPrefix = commentPrefix;
177        }
178
179        /**
180         * Set the start delimiter that identifies block comments within the SQL
181         * scripts.
182         * <p>Defaults to {@code "/*"}.
183         * @param blockCommentStartDelimiter the start delimiter for block comments
184         * (never {@code null} or empty)
185         * @since 4.0.3
186         * @see #setBlockCommentEndDelimiter
187         */
188        public void setBlockCommentStartDelimiter(String blockCommentStartDelimiter) {
189                Assert.hasText(blockCommentStartDelimiter, "BlockCommentStartDelimiter must not be null or empty");
190                this.blockCommentStartDelimiter = blockCommentStartDelimiter;
191        }
192
193        /**
194         * Set the end delimiter that identifies block comments within the SQL
195         * scripts.
196         * <p>Defaults to <code>"*&#47;"</code>.
197         * @param blockCommentEndDelimiter the end delimiter for block comments
198         * (never {@code null} or empty)
199         * @since 4.0.3
200         * @see #setBlockCommentStartDelimiter
201         */
202        public void setBlockCommentEndDelimiter(String blockCommentEndDelimiter) {
203                Assert.hasText(blockCommentEndDelimiter, "BlockCommentEndDelimiter must not be null or empty");
204                this.blockCommentEndDelimiter = blockCommentEndDelimiter;
205        }
206
207        /**
208         * Flag to indicate that all failures in SQL should be logged but not cause a failure.
209         * <p>Defaults to {@code false}.
210         * @param continueOnError {@code true} if script execution should continue on error
211         */
212        public void setContinueOnError(boolean continueOnError) {
213                this.continueOnError = continueOnError;
214        }
215
216        /**
217         * Flag to indicate that a failed SQL {@code DROP} statement can be ignored.
218         * <p>This is useful for a non-embedded database whose SQL dialect does not
219         * support an {@code IF EXISTS} clause in a {@code DROP} statement.
220         * <p>The default is {@code false} so that if the populator runs accidentally, it will
221         * fail fast if a script starts with a {@code DROP} statement.
222         * @param ignoreFailedDrops {@code true} if failed drop statements should be ignored
223         */
224        public void setIgnoreFailedDrops(boolean ignoreFailedDrops) {
225                this.ignoreFailedDrops = ignoreFailedDrops;
226        }
227
228
229        /**
230         * {@inheritDoc}
231         * @see #execute(DataSource)
232         */
233        @Override
234        public void populate(Connection connection) throws ScriptException {
235                Assert.notNull(connection, "Connection must not be null");
236                for (Resource script : this.scripts) {
237                        EncodedResource encodedScript = new EncodedResource(script, this.sqlScriptEncoding);
238                        ScriptUtils.executeSqlScript(connection, encodedScript, this.continueOnError, this.ignoreFailedDrops,
239                                        this.commentPrefix, this.separator, this.blockCommentStartDelimiter, this.blockCommentEndDelimiter);
240                }
241        }
242
243        /**
244         * Execute this {@code ResourceDatabasePopulator} against the given
245         * {@link DataSource}.
246         * <p>Delegates to {@link DatabasePopulatorUtils#execute}.
247         * @param dataSource the {@code DataSource} to execute against (never {@code null})
248         * @throws ScriptException if an error occurs
249         * @since 4.1
250         * @see #populate(Connection)
251         */
252        public void execute(DataSource dataSource) throws ScriptException {
253                DatabasePopulatorUtils.execute(this, dataSource);
254        }
255
256}