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