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