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}