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}