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.jdbc;
018
019import java.util.HashMap;
020import java.util.Map;
021
022import javax.sql.DataSource;
023
024import org.springframework.beans.BeanUtils;
025import org.springframework.boot.context.properties.bind.Bindable;
026import org.springframework.boot.context.properties.bind.Binder;
027import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
028import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
029import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
030import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
031import org.springframework.util.ClassUtils;
032
033/**
034 * Convenience class for building a {@link DataSource} with common implementations and
035 * properties. If HikariCP, Tomcat or Commons DBCP are on the classpath one of them will
036 * be selected (in that order with Hikari first). In the interest of a uniform interface,
037 * and so that there can be a fallback to an embedded database if one can be detected on
038 * the classpath, only a small set of common configuration properties are supported. To
039 * inject additional properties into the result you can downcast it, or use
040 * {@code @ConfigurationProperties}.
041 *
042 * @param <T> type of DataSource produced by the builder
043 * @author Dave Syer
044 * @author Madhura Bhave
045 * @since 2.0.0
046 */
047public final class DataSourceBuilder<T extends DataSource> {
048
049        private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] {
050                        "com.zaxxer.hikari.HikariDataSource",
051                        "org.apache.tomcat.jdbc.pool.DataSource",
052                        "org.apache.commons.dbcp2.BasicDataSource" };
053
054        private Class<? extends DataSource> type;
055
056        private ClassLoader classLoader;
057
058        private Map<String, String> properties = new HashMap<>();
059
060        public static DataSourceBuilder<?> create() {
061                return new DataSourceBuilder<>(null);
062        }
063
064        public static DataSourceBuilder<?> create(ClassLoader classLoader) {
065                return new DataSourceBuilder<>(classLoader);
066        }
067
068        private DataSourceBuilder(ClassLoader classLoader) {
069                this.classLoader = classLoader;
070        }
071
072        @SuppressWarnings("unchecked")
073        public T build() {
074                Class<? extends DataSource> type = getType();
075                DataSource result = BeanUtils.instantiateClass(type);
076                maybeGetDriverClassName();
077                bind(result);
078                return (T) result;
079        }
080
081        private void maybeGetDriverClassName() {
082                if (!this.properties.containsKey("driverClassName")
083                                && this.properties.containsKey("url")) {
084                        String url = this.properties.get("url");
085                        String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
086                        this.properties.put("driverClassName", driverClass);
087                }
088        }
089
090        private void bind(DataSource result) {
091                ConfigurationPropertySource source = new MapConfigurationPropertySource(
092                                this.properties);
093                ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
094                aliases.addAliases("url", "jdbc-url");
095                aliases.addAliases("username", "user");
096                Binder binder = new Binder(source.withAliases(aliases));
097                binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
098        }
099
100        @SuppressWarnings("unchecked")
101        public <D extends DataSource> DataSourceBuilder<D> type(Class<D> type) {
102                this.type = type;
103                return (DataSourceBuilder<D>) this;
104        }
105
106        public DataSourceBuilder<T> url(String url) {
107                this.properties.put("url", url);
108                return this;
109        }
110
111        public DataSourceBuilder<T> driverClassName(String driverClassName) {
112                this.properties.put("driverClassName", driverClassName);
113                return this;
114        }
115
116        public DataSourceBuilder<T> username(String username) {
117                this.properties.put("username", username);
118                return this;
119        }
120
121        public DataSourceBuilder<T> password(String password) {
122                this.properties.put("password", password);
123                return this;
124        }
125
126        @SuppressWarnings("unchecked")
127        public static Class<? extends DataSource> findType(ClassLoader classLoader) {
128                for (String name : DATA_SOURCE_TYPE_NAMES) {
129                        try {
130                                return (Class<? extends DataSource>) ClassUtils.forName(name,
131                                                classLoader);
132                        }
133                        catch (Exception ex) {
134                                // Swallow and continue
135                        }
136                }
137                return null;
138        }
139
140        private Class<? extends DataSource> getType() {
141                Class<? extends DataSource> type = (this.type != null) ? this.type
142                                : findType(this.classLoader);
143                if (type != null) {
144                        return type;
145                }
146                throw new IllegalStateException("No supported DataSource type found");
147        }
148
149}