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 java.io.PrintWriter; 020import java.sql.Connection; 021import java.sql.SQLException; 022import java.util.UUID; 023import java.util.logging.Logger; 024 025import javax.sql.DataSource; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.jdbc.datasource.SimpleDriverDataSource; 031import org.springframework.jdbc.datasource.init.DatabasePopulator; 032import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; 033import org.springframework.lang.Nullable; 034import org.springframework.util.Assert; 035 036/** 037 * Factory for creating an {@link EmbeddedDatabase} instance. 038 * 039 * <p>Callers are guaranteed that the returned database has been fully 040 * initialized and populated. 041 * 042 * <p>The factory can be configured as follows: 043 * <ul> 044 * <li>Call {@link #generateUniqueDatabaseName} to set a unique, random name 045 * for the database. 046 * <li>Call {@link #setDatabaseName} to set an explicit name for the database. 047 * <li>Call {@link #setDatabaseType} to set the database type if you wish to 048 * use one of the supported types. 049 * <li>Call {@link #setDatabaseConfigurer} to configure support for a custom 050 * embedded database type. 051 * <li>Call {@link #setDatabasePopulator} to change the algorithm used to 052 * populate the database. 053 * <li>Call {@link #setDataSourceFactory} to change the type of 054 * {@link DataSource} used to connect to the database. 055 * </ul> 056 * 057 * <p>After configuring the factory, call {@link #getDatabase()} to obtain 058 * a reference to the {@link EmbeddedDatabase} instance. 059 * 060 * @author Keith Donald 061 * @author Juergen Hoeller 062 * @author Sam Brannen 063 * @since 3.0 064 */ 065public class EmbeddedDatabaseFactory { 066 067 /** 068 * Default name for an embedded database: {@value}. 069 */ 070 public static final String DEFAULT_DATABASE_NAME = "testdb"; 071 072 private static final Log logger = LogFactory.getLog(EmbeddedDatabaseFactory.class); 073 074 private boolean generateUniqueDatabaseName = false; 075 076 private String databaseName = DEFAULT_DATABASE_NAME; 077 078 private DataSourceFactory dataSourceFactory = new SimpleDriverDataSourceFactory(); 079 080 @Nullable 081 private EmbeddedDatabaseConfigurer databaseConfigurer; 082 083 @Nullable 084 private DatabasePopulator databasePopulator; 085 086 @Nullable 087 private DataSource dataSource; 088 089 090 /** 091 * Set the {@code generateUniqueDatabaseName} flag to enable or disable 092 * generation of a pseudo-random unique ID to be used as the database name. 093 * <p>Setting this flag to {@code true} overrides any explicit name set 094 * via {@link #setDatabaseName}. 095 * @since 4.2 096 * @see #setDatabaseName 097 */ 098 public void setGenerateUniqueDatabaseName(boolean generateUniqueDatabaseName) { 099 this.generateUniqueDatabaseName = generateUniqueDatabaseName; 100 } 101 102 /** 103 * Set the name of the database. 104 * <p>Defaults to {@value #DEFAULT_DATABASE_NAME}. 105 * <p>Will be overridden if the {@code generateUniqueDatabaseName} flag 106 * has been set to {@code true}. 107 * @param databaseName name of the embedded database 108 * @see #setGenerateUniqueDatabaseName 109 */ 110 public void setDatabaseName(String databaseName) { 111 Assert.hasText(databaseName, "Database name is required"); 112 this.databaseName = databaseName; 113 } 114 115 /** 116 * Set the factory to use to create the {@link DataSource} instance that 117 * connects to the embedded database. 118 * <p>Defaults to {@link SimpleDriverDataSourceFactory}. 119 */ 120 public void setDataSourceFactory(DataSourceFactory dataSourceFactory) { 121 Assert.notNull(dataSourceFactory, "DataSourceFactory is required"); 122 this.dataSourceFactory = dataSourceFactory; 123 } 124 125 /** 126 * Set the type of embedded database to use. 127 * <p>Call this when you wish to configure one of the pre-supported types. 128 * <p>Defaults to HSQL. 129 * @param type the database type 130 */ 131 public void setDatabaseType(EmbeddedDatabaseType type) { 132 this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(type); 133 } 134 135 /** 136 * Set the strategy that will be used to configure the embedded database instance. 137 * <p>Call this when you wish to use an embedded database type not already supported. 138 */ 139 public void setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) { 140 this.databaseConfigurer = configurer; 141 } 142 143 /** 144 * Set the strategy that will be used to initialize or populate the embedded 145 * database. 146 * <p>Defaults to {@code null}. 147 */ 148 public void setDatabasePopulator(DatabasePopulator populator) { 149 this.databasePopulator = populator; 150 } 151 152 /** 153 * Factory method that returns the {@linkplain EmbeddedDatabase embedded database} 154 * instance, which is also a {@link DataSource}. 155 */ 156 public EmbeddedDatabase getDatabase() { 157 if (this.dataSource == null) { 158 initDatabase(); 159 } 160 return new EmbeddedDataSourceProxy(this.dataSource); 161 } 162 163 164 /** 165 * Hook to initialize the embedded database. 166 * <p>If the {@code generateUniqueDatabaseName} flag has been set to {@code true}, 167 * the current value of the {@linkplain #setDatabaseName database name} will 168 * be overridden with an auto-generated name. 169 * <p>Subclasses may call this method to force initialization; however, 170 * this method should only be invoked once. 171 * <p>After calling this method, {@link #getDataSource()} returns the 172 * {@link DataSource} providing connectivity to the database. 173 */ 174 protected void initDatabase() { 175 if (this.generateUniqueDatabaseName) { 176 setDatabaseName(UUID.randomUUID().toString()); 177 } 178 179 // Create the embedded database first 180 if (this.databaseConfigurer == null) { 181 this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(EmbeddedDatabaseType.HSQL); 182 } 183 this.databaseConfigurer.configureConnectionProperties( 184 this.dataSourceFactory.getConnectionProperties(), this.databaseName); 185 this.dataSource = this.dataSourceFactory.getDataSource(); 186 187 if (logger.isInfoEnabled()) { 188 if (this.dataSource instanceof SimpleDriverDataSource) { 189 SimpleDriverDataSource simpleDriverDataSource = (SimpleDriverDataSource) this.dataSource; 190 logger.info(String.format("Starting embedded database: url='%s', username='%s'", 191 simpleDriverDataSource.getUrl(), simpleDriverDataSource.getUsername())); 192 } 193 else { 194 logger.info(String.format("Starting embedded database '%s'", this.databaseName)); 195 } 196 } 197 198 // Now populate the database 199 if (this.databasePopulator != null) { 200 try { 201 DatabasePopulatorUtils.execute(this.databasePopulator, this.dataSource); 202 } 203 catch (RuntimeException ex) { 204 // failed to populate, so leave it as not initialized 205 shutdownDatabase(); 206 throw ex; 207 } 208 } 209 } 210 211 /** 212 * Hook to shutdown the embedded database. Subclasses may call this method 213 * to force shutdown. 214 * <p>After calling, {@link #getDataSource()} returns {@code null}. 215 * <p>Does nothing if no embedded database has been initialized. 216 */ 217 protected void shutdownDatabase() { 218 if (this.dataSource != null) { 219 if (logger.isInfoEnabled()) { 220 if (this.dataSource instanceof SimpleDriverDataSource) { 221 logger.info(String.format("Shutting down embedded database: url='%s'", 222 ((SimpleDriverDataSource) this.dataSource).getUrl())); 223 } 224 else { 225 logger.info(String.format("Shutting down embedded database '%s'", this.databaseName)); 226 } 227 } 228 if (this.databaseConfigurer != null) { 229 this.databaseConfigurer.shutdown(this.dataSource, this.databaseName); 230 } 231 this.dataSource = null; 232 } 233 } 234 235 /** 236 * Hook that gets the {@link DataSource} that provides the connectivity to the 237 * embedded database. 238 * <p>Returns {@code null} if the {@code DataSource} has not been initialized 239 * or if the database has been shut down. Subclasses may call this method to 240 * access the {@code DataSource} instance directly. 241 */ 242 @Nullable 243 protected final DataSource getDataSource() { 244 return this.dataSource; 245 } 246 247 248 private class EmbeddedDataSourceProxy implements EmbeddedDatabase { 249 250 private final DataSource dataSource; 251 252 public EmbeddedDataSourceProxy(DataSource dataSource) { 253 this.dataSource = dataSource; 254 } 255 256 @Override 257 public Connection getConnection() throws SQLException { 258 return this.dataSource.getConnection(); 259 } 260 261 @Override 262 public Connection getConnection(String username, String password) throws SQLException { 263 return this.dataSource.getConnection(username, password); 264 } 265 266 @Override 267 public PrintWriter getLogWriter() throws SQLException { 268 return this.dataSource.getLogWriter(); 269 } 270 271 @Override 272 public void setLogWriter(PrintWriter out) throws SQLException { 273 this.dataSource.setLogWriter(out); 274 } 275 276 @Override 277 public int getLoginTimeout() throws SQLException { 278 return this.dataSource.getLoginTimeout(); 279 } 280 281 @Override 282 public void setLoginTimeout(int seconds) throws SQLException { 283 this.dataSource.setLoginTimeout(seconds); 284 } 285 286 @Override 287 public <T> T unwrap(Class<T> iface) throws SQLException { 288 return this.dataSource.unwrap(iface); 289 } 290 291 @Override 292 public boolean isWrapperFor(Class<?> iface) throws SQLException { 293 return this.dataSource.isWrapperFor(iface); 294 } 295 296 // getParentLogger() is required for JDBC 4.1 compatibility 297 @Override 298 public Logger getParentLogger() { 299 return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 300 } 301 302 @Override 303 public void shutdown() { 304 shutdownDatabase(); 305 } 306 } 307 308}