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}