001/* 002 * Copyright 2012-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 * http://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.boot.autoconfigure.jdbc; 018 019import java.nio.charset.Charset; 020import java.util.LinkedHashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.UUID; 024 025import javax.sql.DataSource; 026 027import org.springframework.beans.factory.BeanClassLoaderAware; 028import org.springframework.beans.factory.BeanCreationException; 029import org.springframework.beans.factory.InitializingBean; 030import org.springframework.boot.context.properties.ConfigurationProperties; 031import org.springframework.boot.jdbc.DataSourceBuilder; 032import org.springframework.boot.jdbc.DataSourceInitializationMode; 033import org.springframework.boot.jdbc.DatabaseDriver; 034import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; 035import org.springframework.util.Assert; 036import org.springframework.util.ClassUtils; 037import org.springframework.util.StringUtils; 038 039/** 040 * Base class for configuration of a data source. 041 * 042 * @author Dave Syer 043 * @author Maciej Walkowiak 044 * @author Stephane Nicoll 045 * @author Benedikt Ritter 046 * @author EddĂș MelĂ©ndez 047 * @since 1.1.0 048 */ 049@ConfigurationProperties(prefix = "spring.datasource") 050public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean { 051 052 private ClassLoader classLoader; 053 054 /** 055 * Name of the datasource. Default to "testdb" when using an embedded database. 056 */ 057 private String name; 058 059 /** 060 * Whether to generate a random datasource name. 061 */ 062 private boolean generateUniqueName; 063 064 /** 065 * Fully qualified name of the connection pool implementation to use. By default, it 066 * is auto-detected from the classpath. 067 */ 068 private Class<? extends DataSource> type; 069 070 /** 071 * Fully qualified name of the JDBC driver. Auto-detected based on the URL by default. 072 */ 073 private String driverClassName; 074 075 /** 076 * JDBC URL of the database. 077 */ 078 private String url; 079 080 /** 081 * Login username of the database. 082 */ 083 private String username; 084 085 /** 086 * Login password of the database. 087 */ 088 private String password; 089 090 /** 091 * JNDI location of the datasource. Class, url, username & password are ignored when 092 * set. 093 */ 094 private String jndiName; 095 096 /** 097 * Initialize the datasource with available DDL and DML scripts. 098 */ 099 private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED; 100 101 /** 102 * Platform to use in the DDL or DML scripts (such as schema-${platform}.sql or 103 * data-${platform}.sql). 104 */ 105 private String platform = "all"; 106 107 /** 108 * Schema (DDL) script resource references. 109 */ 110 private List<String> schema; 111 112 /** 113 * Username of the database to execute DDL scripts (if different). 114 */ 115 private String schemaUsername; 116 117 /** 118 * Password of the database to execute DDL scripts (if different). 119 */ 120 private String schemaPassword; 121 122 /** 123 * Data (DML) script resource references. 124 */ 125 private List<String> data; 126 127 /** 128 * Username of the database to execute DML scripts (if different). 129 */ 130 private String dataUsername; 131 132 /** 133 * Password of the database to execute DML scripts (if different). 134 */ 135 private String dataPassword; 136 137 /** 138 * Whether to stop if an error occurs while initializing the database. 139 */ 140 private boolean continueOnError = false; 141 142 /** 143 * Statement separator in SQL initialization scripts. 144 */ 145 private String separator = ";"; 146 147 /** 148 * SQL scripts encoding. 149 */ 150 private Charset sqlScriptEncoding; 151 152 private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE; 153 154 private Xa xa = new Xa(); 155 156 private String uniqueName; 157 158 @Override 159 public void setBeanClassLoader(ClassLoader classLoader) { 160 this.classLoader = classLoader; 161 } 162 163 @Override 164 public void afterPropertiesSet() throws Exception { 165 this.embeddedDatabaseConnection = EmbeddedDatabaseConnection 166 .get(this.classLoader); 167 } 168 169 /** 170 * Initialize a {@link DataSourceBuilder} with the state of this instance. 171 * @return a {@link DataSourceBuilder} initialized with the customizations defined on 172 * this instance 173 */ 174 public DataSourceBuilder<?> initializeDataSourceBuilder() { 175 return DataSourceBuilder.create(getClassLoader()).type(getType()) 176 .driverClassName(determineDriverClassName()).url(determineUrl()) 177 .username(determineUsername()).password(determinePassword()); 178 } 179 180 public String getName() { 181 return this.name; 182 } 183 184 public void setName(String name) { 185 this.name = name; 186 } 187 188 public boolean isGenerateUniqueName() { 189 return this.generateUniqueName; 190 } 191 192 public void setGenerateUniqueName(boolean generateUniqueName) { 193 this.generateUniqueName = generateUniqueName; 194 } 195 196 public Class<? extends DataSource> getType() { 197 return this.type; 198 } 199 200 public void setType(Class<? extends DataSource> type) { 201 this.type = type; 202 } 203 204 /** 205 * Return the configured driver or {@code null} if none was configured. 206 * @return the configured driver 207 * @see #determineDriverClassName() 208 */ 209 public String getDriverClassName() { 210 return this.driverClassName; 211 } 212 213 public void setDriverClassName(String driverClassName) { 214 this.driverClassName = driverClassName; 215 } 216 217 /** 218 * Determine the driver to use based on this configuration and the environment. 219 * @return the driver to use 220 * @since 1.4.0 221 */ 222 public String determineDriverClassName() { 223 if (StringUtils.hasText(this.driverClassName)) { 224 Assert.state(driverClassIsLoadable(), 225 () -> "Cannot load driver class: " + this.driverClassName); 226 return this.driverClassName; 227 } 228 String driverClassName = null; 229 if (StringUtils.hasText(this.url)) { 230 driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName(); 231 } 232 if (!StringUtils.hasText(driverClassName)) { 233 driverClassName = this.embeddedDatabaseConnection.getDriverClassName(); 234 } 235 if (!StringUtils.hasText(driverClassName)) { 236 throw new DataSourceBeanCreationException( 237 "Failed to determine a suitable driver class", this, 238 this.embeddedDatabaseConnection); 239 } 240 return driverClassName; 241 } 242 243 private boolean driverClassIsLoadable() { 244 try { 245 ClassUtils.forName(this.driverClassName, null); 246 return true; 247 } 248 catch (UnsupportedClassVersionError ex) { 249 // Driver library has been compiled with a later JDK, propagate error 250 throw ex; 251 } 252 catch (Throwable ex) { 253 return false; 254 } 255 } 256 257 /** 258 * Return the configured url or {@code null} if none was configured. 259 * @return the configured url 260 * @see #determineUrl() 261 */ 262 public String getUrl() { 263 return this.url; 264 } 265 266 public void setUrl(String url) { 267 this.url = url; 268 } 269 270 /** 271 * Determine the url to use based on this configuration and the environment. 272 * @return the url to use 273 * @since 1.4.0 274 */ 275 public String determineUrl() { 276 if (StringUtils.hasText(this.url)) { 277 return this.url; 278 } 279 String databaseName = determineDatabaseName(); 280 String url = (databaseName != null) 281 ? this.embeddedDatabaseConnection.getUrl(databaseName) : null; 282 if (!StringUtils.hasText(url)) { 283 throw new DataSourceBeanCreationException( 284 "Failed to determine suitable jdbc url", this, 285 this.embeddedDatabaseConnection); 286 } 287 return url; 288 } 289 290 /** 291 * Determine the name to used based on this configuration. 292 * @return the database name to use or {@code null} 293 * @since 2.0.0 294 */ 295 public String determineDatabaseName() { 296 if (this.generateUniqueName) { 297 if (this.uniqueName == null) { 298 this.uniqueName = UUID.randomUUID().toString(); 299 } 300 return this.uniqueName; 301 } 302 if (StringUtils.hasLength(this.name)) { 303 return this.name; 304 } 305 if (this.embeddedDatabaseConnection != EmbeddedDatabaseConnection.NONE) { 306 return "testdb"; 307 } 308 return null; 309 } 310 311 /** 312 * Return the configured username or {@code null} if none was configured. 313 * @return the configured username 314 * @see #determineUsername() 315 */ 316 public String getUsername() { 317 return this.username; 318 } 319 320 public void setUsername(String username) { 321 this.username = username; 322 } 323 324 /** 325 * Determine the username to use based on this configuration and the environment. 326 * @return the username to use 327 * @since 1.4.0 328 */ 329 public String determineUsername() { 330 if (StringUtils.hasText(this.username)) { 331 return this.username; 332 } 333 if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) { 334 return "sa"; 335 } 336 return null; 337 } 338 339 /** 340 * Return the configured password or {@code null} if none was configured. 341 * @return the configured password 342 * @see #determinePassword() 343 */ 344 public String getPassword() { 345 return this.password; 346 } 347 348 public void setPassword(String password) { 349 this.password = password; 350 } 351 352 /** 353 * Determine the password to use based on this configuration and the environment. 354 * @return the password to use 355 * @since 1.4.0 356 */ 357 public String determinePassword() { 358 if (StringUtils.hasText(this.password)) { 359 return this.password; 360 } 361 if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) { 362 return ""; 363 } 364 return null; 365 } 366 367 public String getJndiName() { 368 return this.jndiName; 369 } 370 371 /** 372 * Allows the DataSource to be managed by the container and obtained via JNDI. The 373 * {@code URL}, {@code driverClassName}, {@code username} and {@code password} fields 374 * will be ignored when using JNDI lookups. 375 * @param jndiName the JNDI name 376 */ 377 public void setJndiName(String jndiName) { 378 this.jndiName = jndiName; 379 } 380 381 public DataSourceInitializationMode getInitializationMode() { 382 return this.initializationMode; 383 } 384 385 public void setInitializationMode(DataSourceInitializationMode initializationMode) { 386 this.initializationMode = initializationMode; 387 } 388 389 public String getPlatform() { 390 return this.platform; 391 } 392 393 public void setPlatform(String platform) { 394 this.platform = platform; 395 } 396 397 public List<String> getSchema() { 398 return this.schema; 399 } 400 401 public void setSchema(List<String> schema) { 402 this.schema = schema; 403 } 404 405 public String getSchemaUsername() { 406 return this.schemaUsername; 407 } 408 409 public void setSchemaUsername(String schemaUsername) { 410 this.schemaUsername = schemaUsername; 411 } 412 413 public String getSchemaPassword() { 414 return this.schemaPassword; 415 } 416 417 public void setSchemaPassword(String schemaPassword) { 418 this.schemaPassword = schemaPassword; 419 } 420 421 public List<String> getData() { 422 return this.data; 423 } 424 425 public void setData(List<String> data) { 426 this.data = data; 427 } 428 429 public String getDataUsername() { 430 return this.dataUsername; 431 } 432 433 public void setDataUsername(String dataUsername) { 434 this.dataUsername = dataUsername; 435 } 436 437 public String getDataPassword() { 438 return this.dataPassword; 439 } 440 441 public void setDataPassword(String dataPassword) { 442 this.dataPassword = dataPassword; 443 } 444 445 public boolean isContinueOnError() { 446 return this.continueOnError; 447 } 448 449 public void setContinueOnError(boolean continueOnError) { 450 this.continueOnError = continueOnError; 451 } 452 453 public String getSeparator() { 454 return this.separator; 455 } 456 457 public void setSeparator(String separator) { 458 this.separator = separator; 459 } 460 461 public Charset getSqlScriptEncoding() { 462 return this.sqlScriptEncoding; 463 } 464 465 public void setSqlScriptEncoding(Charset sqlScriptEncoding) { 466 this.sqlScriptEncoding = sqlScriptEncoding; 467 } 468 469 public ClassLoader getClassLoader() { 470 return this.classLoader; 471 } 472 473 public Xa getXa() { 474 return this.xa; 475 } 476 477 public void setXa(Xa xa) { 478 this.xa = xa; 479 } 480 481 /** 482 * XA Specific datasource settings. 483 */ 484 public static class Xa { 485 486 /** 487 * XA datasource fully qualified name. 488 */ 489 private String dataSourceClassName; 490 491 /** 492 * Properties to pass to the XA data source. 493 */ 494 private Map<String, String> properties = new LinkedHashMap<>(); 495 496 public String getDataSourceClassName() { 497 return this.dataSourceClassName; 498 } 499 500 public void setDataSourceClassName(String dataSourceClassName) { 501 this.dataSourceClassName = dataSourceClassName; 502 } 503 504 public Map<String, String> getProperties() { 505 return this.properties; 506 } 507 508 public void setProperties(Map<String, String> properties) { 509 this.properties = properties; 510 } 511 512 } 513 514 static class DataSourceBeanCreationException extends BeanCreationException { 515 516 private final DataSourceProperties properties; 517 518 private final EmbeddedDatabaseConnection connection; 519 520 DataSourceBeanCreationException(String message, DataSourceProperties properties, 521 EmbeddedDatabaseConnection connection) { 522 super(message); 523 this.properties = properties; 524 this.connection = connection; 525 } 526 527 public DataSourceProperties getProperties() { 528 return this.properties; 529 } 530 531 public EmbeddedDatabaseConnection getConnection() { 532 return this.connection; 533 } 534 535 } 536 537}