001/*
002 *  Copyright (c) 2022-2025, 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.hasText(type)) {
053            dataSourceClassName = dataSourceAlias.getOrDefault(type, type);
054        } else {
055            dataSourceClassName = detectDataSourceClass();
056        }
057
058
059        if (StringUtil.noText(dataSourceClassName)) {
060            if (StringUtil.noText(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            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
069            Class<?> dataSourceClass = Class.forName(dataSourceClassName, false, contextClassLoader);
070            Object dataSourceObject = dataSourceClass.newInstance();
071            setDataSourceProperties(dataSourceObject);
072            return (DataSource) dataSourceObject;
073        } catch (Exception e) {
074            throw FlexExceptions.wrap(e, LocalizedFormats.DATASOURCE_CAN_NOT_INSTANCE, dataSourceClassName);
075        }
076    }
077
078    private void setDataSourceProperties(Object dataSourceObject) throws Exception {
079        Reflector reflector = new Reflector(dataSourceObject.getClass());
080        for (String attr : dataSourceProperties.keySet()) {
081            String value = dataSourceProperties.get(attr);
082            String camelAttr = attrToCamel(attr);
083            if ("url".equals(camelAttr) || "jdbcUrl".equals(camelAttr)) {
084                if (reflector.hasSetter("url")) {
085                    reflector.getSetInvoker("url").invoke(dataSourceObject, new Object[]{value});
086                } else if (reflector.hasSetter("jdbcUrl")) {
087                    reflector.getSetInvoker("jdbcUrl").invoke(dataSourceObject, new Object[]{value});
088                }
089            } else {
090                if (reflector.hasSetter(camelAttr)) {
091                    Invoker setInvoker = reflector.getSetInvoker(camelAttr);
092                    setInvoker.invoke(dataSourceObject, new Object[]{ConvertUtil.convert(value, setInvoker.getType())});
093                }
094            }
095        }
096    }
097
098
099    public static String attrToCamel(String string) {
100        int strLen = string.length();
101        StringBuilder sb = new StringBuilder(strLen);
102        for (int i = 0; i < strLen; i++) {
103            char c = string.charAt(i);
104            if (c == '-') {
105                if (++i < strLen) {
106                    sb.append(Character.toUpperCase(string.charAt(i)));
107                }
108            } else {
109                sb.append(c);
110            }
111        }
112        return sb.toString();
113    }
114
115
116    private String detectDataSourceClass() {
117        String[] detectClassNames = new String[]{
118            "com.alibaba.druid.pool.DruidDataSource",
119            "com.zaxxer.hikari.HikariDataSource",
120            "cn.beecp.BeeDataSource",
121            "org.apache.commons.dbcp2.BasicDataSource",
122        };
123
124        for (String detectClassName : detectClassNames) {
125            String result = doDetectDataSourceClass(detectClassName);
126            if (result != null) {
127                return result;
128            }
129        }
130
131        return null;
132    }
133
134
135    private String doDetectDataSourceClass(String className) {
136        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
137        try {
138            Class.forName(className, false, contextClassLoader);
139            return className;
140        } catch (ClassNotFoundException e) {
141            return null;
142        }
143    }
144
145}