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