/*
* Seldon -- open source prediction engine
* =======================================
* Copyright 2011-2015 Seldon Technologies Ltd and Rummble Ltd (http://www.seldon.io/)
*
**********************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************************
*/
package io.seldon.dbcp;
import io.seldon.api.state.GlobalConfigHandler;
import io.seldon.api.state.GlobalConfigUpdateListener;
import io.seldon.db.jdo.JDOFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.PoolingDataSource;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
public class DbcpFactory implements DbcpPoolHandler,GlobalConfigUpdateListener {
private static Logger logger = Logger.getLogger( DbcpFactory.class.getName() );
private final GlobalConfigHandler globalConfigHandler;
private final Map<String,DataSource> dataSources = new ConcurrentHashMap<String,DataSource>();
List<DbcpInitialisedListener> listeners = new ArrayList<DbcpInitialisedListener>();
boolean initialised = false;
@Autowired
public DbcpFactory(GlobalConfigHandler globalConfigHandler)
{
this.globalConfigHandler = globalConfigHandler;
globalConfigHandler.addSubscriber("dbcp", this);
}
private void createDbcp(DbcpConfig conf)
{
if (!dataSources.containsKey(conf.name))
{
try
{
Class.forName(conf.driverClassName);
DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory(conf.jdbc,conf.user,conf.password);
PoolableConnectionFactory pcf = new PoolableConnectionFactory(cf,null);
pcf.setValidationQuery(conf.validationQuery);
//, pool, null, conf.validationQuery, false, true,abandondedConfig);
logger.info("Creating pool "+conf.toString());
// create a generic pool
GenericObjectPool<PoolableConnection> pool = new GenericObjectPool<PoolableConnection>(pcf);
pool.setMaxTotal(conf.maxTotal);
pool.setMaxIdle(conf.maxIdle);
pool.setMinIdle(conf.minIdle);
pool.setMaxWaitMillis(conf.maxWait);
pool.setTimeBetweenEvictionRunsMillis(conf.timeBetweenEvictionRunsMillis);
pool.setMinEvictableIdleTimeMillis(conf.minEvictableIdleTimeMillis);
pool.setTestWhileIdle(conf.testWhileIdle);
pool.setTestOnBorrow(conf.testOnBorrow);
AbandonedConfig abandonedConfig = new AbandonedConfig();
abandonedConfig.setRemoveAbandonedOnMaintenance(conf.removeAbanadoned);
abandonedConfig.setRemoveAbandonedTimeout(conf.removeAbandonedTimeout);
abandonedConfig.setLogAbandoned(conf.logAbandonded);
pool.setAbandonedConfig(abandonedConfig);
pcf.setPool(pool);
DataSource ds = new PoolingDataSource(pool);
dataSources.put(conf.name, ds);
} catch (ClassNotFoundException e) {
logger.error("Failed to create datasource for "+conf.name+ " with class "+conf.driverClassName);
}
}
else
{
logger.error("Pool "+conf.name+" already exists. Can't change existing datasource at present.");
}
}
private synchronized void updateInitialised()
{
if (!initialised)
{
this.initialised = true;
for (DbcpInitialisedListener l : listeners)
l.dbcpInitialised();
}
}
@Override
public synchronized void addInitialisedListener(DbcpInitialisedListener listener) {
listeners.add(listener);
if (initialised)
for (DbcpInitialisedListener l : listeners)
l.dbcpInitialised();
}
@Override
public DataSource get(String name)
{
return dataSources.get(name);
}
@Override
public void configUpdated(String configKey, String configValue) {
configValue = StringUtils.strip(configValue);
logger.info("KEY WAS " + configKey);
logger.info("Received new dbcp settings: " + configValue);
if (StringUtils.length(configValue) == 0) {
logger.warn("*WARNING* no dbcp is set!");
} else {
try {
logger.info("Processing configs "+configValue);
ObjectMapper mapper = new ObjectMapper();
DbcpConfigList configs = mapper.readValue(configValue, DbcpConfigList.class);
if (configs != null && configs.dbs != null)
{
for (DbcpConfig config : configs.dbs)
createDbcp(config);
}
updateInitialised();
logger.info("Successfully set dbcp.");
} catch (IOException e){
logger.error("Problem changing dbcp ", e);
}
}
if (dataSources.size() == 0)
{
logger.error("No DBCP settings. Seldon will not run without a database connection. Please add settings to /config/dbcp");
throw new DbcpUninitialisedException();
}
}
public static class DbcpConfigList {
public List<DbcpConfig> dbs;
}
public static class DbcpConfig {
public String name = JDOFactory.DEFAULT_DB_JNDI_NAME;
public String jdbc = "jdbc:mysql:replication://localhost:3306,localhost:3306/?characterEncoding=utf8&useServerPrepStmts=true&logger=com.mysql.jdbc.log.StandardLogger&roundRobinLoadBalance=true&transformedBitIsBoolean=true&rewriteBatchedStatements=true";
public String driverClassName = "com.mysql.jdbc.ReplicationDriver";
public String user = "user1";
public String password = "mypass";
public Integer maxTotal = 200;
public Integer maxIdle = 5;
public Integer minIdle = 0;
public Integer maxWait = 20000;
public Integer timeBetweenEvictionRunsMillis = 10000;
public Integer minEvictableIdleTimeMillis = 60000;
public Boolean testWhileIdle = true;
public Boolean testOnBorrow = true;
public String validationQuery = "/* ping */ SELECT 1";
public Boolean removeAbanadoned = true;
public Integer removeAbandonedTimeout = 60;
public Boolean logAbandonded = false;
@Override
public String toString() {
return "DbcpConfig [name=" + name + ", jdbc=" + jdbc
+ ", driverClassName=" + driverClassName + ", user=" + user
+ ", password=" + password + ", maxTotal=" + maxTotal
+ ", maxIdle=" + maxIdle + ", minIdle=" + minIdle
+ ", maxWait=" + maxWait
+ ", timeBetweenEvictionRunsMillis="
+ timeBetweenEvictionRunsMillis
+ ", minEvictableIdleTimeMillis="
+ minEvictableIdleTimeMillis + ", testWhileIdle="
+ testWhileIdle + ", testOnBorrow=" + testOnBorrow
+ ", validationQuery=" + validationQuery
+ ", removeAbanadoned=" + removeAbanadoned
+ ", removeAbandonedTimeout=" + removeAbandonedTimeout
+ ", logAbandonded=" + logAbandonded + "]";
}
}
}