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