package jef.database.datasource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import jef.common.log.LogUtil;
import jef.database.DbUtils;
import jef.tools.Assert;
import jef.tools.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 到数据库里去取的数据源的配置信息。以此进行数据源的查找。
*
* <p/>
* 如果利用一个配置库来维护其他各种数据库的连接信息,那么系统会到这个数据库中去寻找数据源
*
* 这个类同时实现了DataSourceInfoLookup和DataSourceLookup两个接口。在spring中配置的例子如下
*
* <pre>
* <code><bean class="jef.database.datasource.DbDataSourceLookup"
* p:configDataSource-ref="dataSource"
* p:configDbTable="DATASOURCE_CONFIG"
* p:whereCondition="enable='1'"
* p:columnOfId="DATABASE_NAME"
* p:columnOfUrl="JDBC_URL"
* p:columnOfUser="DB_USER"
* p:columnOfPassword="DB_PASSWORD"
* p:columnOfDriver=""
* p:datasourceIdOfconfigDB=""
* p:defaultDsName="" >
* <property name="passwordDecryptor">
* <!-- 自定义的数据库口令解密器 -->
* <bean class="org.googlecode.jef.spring.MyPasswordDecryptor" />
* </property>
* </bean>
* </code>
* </pre>
*
* @author jiyi
*
*/
public class DbDataSourceLookup implements DataSourceInfoLookup, DataSourceLookup {
private Logger log = LoggerFactory.getLogger(this.getClass());
// 配置库的信息(和配置信息二选一)
private String configDbUrl;
private String configDbUser;
private String configDbPassword;
// 配置库的连接(和配置信息二选一)
private DataSource configDataSource;
// 配置库中的其他信息
private String configDbTable;
private String whereCondition;// where条件
private String columnOfId;
private String columnOfUrl;
private String columnOfUser;
private String columnOfPassword;
private String columnOfDriver;
private boolean ignoreCase=true;
// 配置如果也作为数据源之一,那么需要设置ID
private String datasourceIdOfconfigDB;
//允许用户指定缺省数据源的名称
protected String defaultDsName;
// 解密器
private PasswordDecryptor passwordDecryptor = PasswordDecryptor.DUMMY;
// 缓存
private Map<String, DataSourceInfo> cache;
private int maxCache = 100;// 为了防止数据库里配置了即大量的数据源,首次查询只会返回限定的记录条数
public DataSourceInfo getDataSourceInfo(String dataSourceName) {
if (cache == null) {
initCache();
}
DataSourceInfo dsi = cache.get(ignoreCase?dataSourceName.toLowerCase():dataSourceName);
if (dsi == null) {
dsi = tryLoad(dataSourceName);
}
return dsi;
}
public DataSource getDataSource(String dataSourceName) {
return DataSources.getAsDataSource(getDataSourceInfo(dataSourceName));
}
private DataSourceInfo tryLoad(String dataSourceName) {
DataSource configDs = getConfigDS();
DataSourceInfo dsi = process(cache, configDs, dataSourceName);
return dsi;
}
private synchronized void initCache() {
Map<String, DataSourceInfo> result = new HashMap<String, DataSourceInfo>();
DataSource configDs = getConfigDS();
process(result, configDs, null);
// 将配置源也加入
if (StringUtils.isNotEmpty(datasourceIdOfconfigDB)) {
DataSourceInfo dsi = DataSources.wrapFor(configDs);
if (dsi != null) {
put(datasourceIdOfconfigDB.trim(), dsi,result);
} else {
log.warn("The Configuration datasource {} Can not be wrapped.",configDs.getClass());
}
}
this.cache=result;
}
private DataSourceInfo process(Map<String, DataSourceInfo> result, DataSource configDs, String key) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
DataSourceInfo dsi = null;
try {
conn = configDs.getConnection();
st = conn.createStatement();
String sql = getSql(key);
LogUtil.show("Executing:" + sql);
st.setMaxRows(maxCache);
rs = st.executeQuery(sql);
int count = 0;
while (rs.next()) {
count++;
String id = rs.getString(columnOfId);
String url = rs.getString(columnOfUrl);
String user = StringUtils.isEmpty(columnOfUser) ? null : rs.getString(columnOfUser);
String password = StringUtils.isEmpty(columnOfPassword) ? null : rs.getString(columnOfPassword);
String driver = StringUtils.isEmpty(columnOfDriver) ? null : rs.getString(columnOfDriver);
dsi = createDsi(url, user, password, driver);
if (StringUtils.isNotEmpty(id) && dsi != null) {
put(id, dsi,result);
}
}
LogUtil.show("got " + count + " datasource from database.");
} catch (SQLException e) {
throw new PersistenceException(e);
} finally {
DbUtils.close(rs);
DbUtils.close(st);
DbUtils.closeConnection(conn);
}
return dsi;
}
private void put(String id, DataSourceInfo dsi,Map<String,DataSourceInfo> map) {
if(ignoreCase)id=id.toLowerCase();
map.put(id, dsi);
log.info("Datasource {} was resoloved.",id, dsi);
}
private DataSourceInfo createDsi(String url, String user, String password, String driver) {
if (StringUtils.isEmpty(url))
return null;
DataSourceInfo dsi = new DataSourceInfoImpl();
dsi.setUrl(url);
dsi.setUser(user);
dsi.setPassword(passwordDecryptor.decrypt(password));
dsi.setDriverClass(driver);
return dsi;
}
private String getSql(String id) {
Assert.isNotEmpty(columnOfId);
Assert.isNotEmpty(columnOfUrl);
Assert.isNotEmpty(configDbTable);
StringBuilder sb = new StringBuilder("select ");
sb.ensureCapacity(128);
sb.append(columnOfId).append(',').append(columnOfUrl);
if (StringUtils.isNotEmpty(columnOfUser)) {
sb.append(',').append(columnOfUser);
}
if (StringUtils.isNotEmpty(columnOfPassword)) {
sb.append(',').append(columnOfPassword);
}
if (StringUtils.isNotEmpty(columnOfDriver)) {
sb.append(',').append(columnOfDriver);
}
sb.append(" from ").append(configDbTable);
if (StringUtils.isNotEmpty(whereCondition)) {
if (id == null) {
sb.append(" where ").append(whereCondition);
} else {
sb.append(" where (").append(whereCondition).append(") and ").append(columnOfId).append("='").append(id).append('\'');
}
} else {
if (id != null) {
sb.append(" where ").append(columnOfId).append("='").append(id).append('\'');
}
}
return sb.toString();
}
private DataSource getConfigDS() {
if (configDataSource != null) {
return configDataSource;
}
configDataSource = DbUtils.createSimpleDataSource(configDbUrl, configDbUser, configDbPassword);
return configDataSource;
}
public void setPasswordDecryptor(PasswordDecryptor passwordDecryptor) {
this.passwordDecryptor = passwordDecryptor;
}
/**
* 配置库的连接串<br/>
* 有两种方式可以指定配置库的连接信息,一种是直接指定DataSource对象
* {@link #setConfigDataSource(DataSource)}。 另一种是通过
* {@link #setConfigDbUrl(String)} {@link #setConfigDbPassword(String)}
* {@link #setConfigDbUser(String)}三个方法来提供配置库的信息。两种方法都可以,其中
* {@link #setConfigDataSource(DataSource)优先}
*
* @param configDbUrl
*/
public void setConfigDbUrl(String configDbUrl) {
this.configDbUrl = configDbUrl;
}
/**
* 配置库的用户名<br/>
* 有两种方式可以指定配置库的连接信息,一种是直接指定DataSource对象
* {@link #setConfigDataSource(DataSource)}。 另一种是通过
* {@link #setConfigDbUrl(String)} {@link #setConfigDbPassword(String)}
* {@link #setConfigDbUser(String)}三个方法来提供配置库的信息。两种方法都可以,其中
* {@link #setConfigDataSource(DataSource)优先}
*
* @param configDbUser
*/
public void setConfigDbUser(String configDbUser) {
this.configDbUser = configDbUser;
}
/**
* 配置库的密码(必须明文,不支持密文)<br>
* 有两种方式可以指定配置库的连接信息,一种是直接指定DataSource对象
* {@link #setConfigDataSource(DataSource)}。 另一种是通过
* {@link #setConfigDbUrl(String)} {@link #setConfigDbPassword(String)}
* {@link #setConfigDbUser(String)}三个方法来提供配置库的信息。两种方法都可以,其中
* {@link #setConfigDataSource(DataSource)优先}
*
* @param configDbPassword
*/
public void setConfigDbPassword(String configDbPassword) {
this.configDbPassword = configDbPassword;
}
/**
* 配置库的数据源 有两种方式可以指定配置库的连接信息,一种是直接指定DataSource对象,即本方法。 另一种是通过
* {@link #setConfigDbUrl(String)} {@link #setConfigDbPassword(String)}
* {@link #setConfigDbUser(String)}三个方法来提供配置库的信息。两种方法都可以,其中
* {@link #setConfigDataSource(DataSource)}优先
*
* @param configDataSource
*/
public void setConfigDataSource(DataSource configDataSource) {
this.configDataSource = configDataSource;
}
/**
* 查询数据源时的表名(视图名)
*
* @param configDbTable
*/
public void setConfigDbTable(String configDbTable) {
this.configDbTable = configDbTable;
}
/**
* 查询数据源时的where条件,可不设置(查全表) 配置时注意只要配条件,不需要写 ‘where’举例:
*
* <pre>
* enable=1 AND type='oracle'
* </pre>
*
* @param whereCondition
*/
public void setWhereCondition(String whereCondition) {
this.whereCondition = whereCondition;
}
/**
* 数据源唯一标志 列的名称
*
* @param columnOfId
*/
public void setColumnOfId(String columnOfId) {
this.columnOfId = columnOfId;
}
/**
* 数据源连接串 列的名称
*
* @param columnOfUrl
*/
public void setColumnOfUrl(String columnOfUrl) {
this.columnOfUrl = columnOfUrl;
}
/**
* 数据源用户名 列的名称
*
* @param columnOfUser
*/
public void setColumnOfUser(String columnOfUser) {
this.columnOfUser = columnOfUser;
}
/**
* 数据源password 列的名称。
*
* @param columnOfPassword
*/
public void setColumnOfPassword(String columnOfPassword) {
this.columnOfPassword = columnOfPassword;
}
/**
* 数据源DriverClassName列的名称。允许为null
*
* @param columnOfDriver
*/
public void setColumnOfDriver(String columnOfDriver) {
this.columnOfDriver = columnOfDriver;
}
/**
* 如果配置数据源的库本身也作为数据库之一,那么需要为其设置一个DataSource的ID
*
* @param datasourceIdOfconfigDB
*/
public void setDatasourceIdOfconfigDB(String datasourceIdOfconfigDB) {
this.datasourceIdOfconfigDB = StringUtils.trimToNull(datasourceIdOfconfigDB);
}
public String getDefaultDsName() {
return defaultDsName;
}
public void setDefaultDsName(String defaultBeanName) {
this.defaultDsName = StringUtils.trimToNull(defaultBeanName);
}
public String getDefaultKey() {
if (cache == null) {
initCache();
}
if (cache.size() == 1) {
return cache.keySet().iterator().next();
}
if(defaultDsName==null){
log.warn("You have not assign the default datasource name.");
defaultDsName=datasourceIdOfconfigDB;
}
return defaultDsName;
}
public Collection<String> getAvailableKeys() {
if (cache == null) {
initCache();
}
return Collections.unmodifiableCollection(cache.keySet());
}
public boolean isIgnoreCase() {
return ignoreCase;
}
public void setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
}
}