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>"*/"</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}