001/*
002 *  Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
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 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
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 */
016package com.mybatisflex.core.datasource;
017
018import com.mybatisflex.core.exception.FlexExceptions;
019import com.mybatisflex.core.util.ConvertUtil;
020import com.mybatisflex.core.util.StringUtil;
021import org.apache.ibatis.reflection.Reflector;
022import org.apache.ibatis.reflection.invoker.Invoker;
023
024import javax.sql.DataSource;
025import java.util.HashMap;
026import java.util.Map;
027
028public class DataSourceBuilder {
029
030    private static final Map<String, String> dataSourceAlias = new HashMap<>();
031
032    static {
033        dataSourceAlias.put("druid", "com.alibaba.druid.pool.DruidDataSource");
034        dataSourceAlias.put("hikari", "com.zaxxer.hikari.HikariDataSource");
035        dataSourceAlias.put("hikaricp", "com.zaxxer.hikari.HikariDataSource");
036        dataSourceAlias.put("bee", "cn.beecp.BeeDataSource");
037        dataSourceAlias.put("beecp", "cn.beecp.BeeDataSource");
038        dataSourceAlias.put("dbcp", "org.apache.commons.dbcp2.BasicDataSource");
039        dataSourceAlias.put("dbcp2", "org.apache.commons.dbcp2.BasicDataSource");
040    }
041
042    private final Map<String, String> dataSourceProperties;
043
044    public DataSourceBuilder(Map<String, String> dataSourceProperties) {
045        this.dataSourceProperties = dataSourceProperties;
046    }
047
048    public DataSource build() {
049        String dataSourceClassName = null;
050        String type = dataSourceProperties.get("type");
051        if (StringUtil.isNotBlank(type)) {
052            dataSourceClassName = dataSourceAlias.getOrDefault(type, type);
053        } else {
054            dataSourceClassName = detectDataSourceClass();
055        }
056
057
058        if (StringUtil.isBlank(dataSourceClassName)) {
059            if (StringUtil.isBlank(type)) {
060                throw FlexExceptions.wrap("The dataSource type can not be null or blank.");
061            } else {
062                throw FlexExceptions.wrap("Cannot find the dataSource type: " + type);
063            }
064        }
065
066        try {
067            Class<?> dataSourceClass = Class.forName(dataSourceClassName);
068            Object dataSourceObject = dataSourceClass.newInstance();
069            setDataSourceProperties(dataSourceObject);
070            return (DataSource) dataSourceObject;
071        } catch (Exception e) {
072            throw new RuntimeException("Cannot new instance dataSource by class: " + dataSourceClassName);
073        }
074    }
075
076    private void setDataSourceProperties(Object dataSourceObject) throws Exception {
077        Reflector reflector = new Reflector(dataSourceObject.getClass());
078        for (String attr : dataSourceProperties.keySet()) {
079            String value = dataSourceProperties.get(attr);
080            String camelAttr = attrToCamel(attr);
081            if ("url".equals(camelAttr) || "jdbcUrl".equals(camelAttr)) {
082                if (reflector.hasSetter("url")) {
083                    reflector.getSetInvoker("url").invoke(dataSourceObject, new Object[]{value});
084                } else if (reflector.hasSetter("jdbcUrl")) {
085                    reflector.getSetInvoker("jdbcUrl").invoke(dataSourceObject, new Object[]{value});
086                }
087            } else {
088                if (reflector.hasSetter(camelAttr)) {
089                    Invoker setInvoker = reflector.getSetInvoker(camelAttr);
090                    setInvoker.invoke(dataSourceObject, new Object[]{ConvertUtil.convert(value, setInvoker.getType())});
091                }
092            }
093        }
094    }
095
096
097    public static String attrToCamel(String string) {
098        int strLen = string.length();
099        StringBuilder sb = new StringBuilder(strLen);
100        for (int i = 0; i < strLen; i++) {
101            char c = string.charAt(i);
102            if (c == '-') {
103                if (++i < strLen) {
104                    sb.append(Character.toUpperCase(string.charAt(i)));
105                }
106            } else {
107                sb.append(c);
108            }
109        }
110        return sb.toString();
111    }
112
113
114    private String detectDataSourceClass() {
115        String[] detectClassNames = new String[]{
116            "com.alibaba.druid.pool.DruidDataSource",
117            "com.zaxxer.hikari.HikariDataSource",
118            "cn.beecp.BeeDataSource",
119            "org.apache.commons.dbcp2.BasicDataSource",
120        };
121
122        for (String detectClassName : detectClassNames) {
123            String result = doDetectDataSourceClass(detectClassName);
124            if (result != null) {
125                return result;
126            }
127        }
128
129        return null;
130    }
131
132
133    private String doDetectDataSourceClass(String className) {
134        try {
135            Class.forName(className);
136            return className;
137        } catch (ClassNotFoundException e) {
138            return null;
139        }
140    }
141
142}