/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.persistence.jdbc.internal;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.collections.iterators.IteratorEnumeration;
import org.apache.commons.lang.StringUtils;
import org.openhab.persistence.jdbc.db.JdbcBaseDAO;
import org.openhab.persistence.jdbc.utils.MovingAverage;
import org.openhab.persistence.jdbc.utils.StringUtilsExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Helmut Lehmeyer
* @since 1.8.0
*/
public class JdbcConfiguration {
private static final Logger logger = LoggerFactory.getLogger(JdbcConfiguration.class);
private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.([0-9.a-zA-Z]+)$");
private static final String DB_DAO_PACKAGE = "org.openhab.persistence.jdbc.db.Jdbc";
private Map<Object, Object> configuration;
private JdbcBaseDAO dBDAO = null;
private String dbName = null;
boolean dbConnected = false;
boolean driverAvailable = false;
private String serviceName;
private String name = "jdbc";
// private String url;
// private String user;
// private String password;
private int numberDecimalcount = 3;
private boolean tableUseRealItemNames = false;
private String tableNamePrefix = "item";
private int tableIdDigitCount = 4;
private boolean rebuildTableNames = false;
private int errReconnectThreshold = 0;
public int timerCount = 0;
public int time1000Statements = 0;
public long timer1000 = 0;
public MovingAverage timeAverage50arr = new MovingAverage(50);
public MovingAverage timeAverage100arr = new MovingAverage(100);
public MovingAverage timeAverage200arr = new MovingAverage(200);
public boolean enableLogTime = false;
public JdbcConfiguration(Map<Object, Object> configuration) {
logger.debug("JDBC::JdbcConfiguration");
updateConfig(configuration);
}
/**
* @{inheritDoc
*/
public void updateConfig(Map<Object, Object> config) {
configuration = config;
logger.debug("JDBC::updateConfig: configuration size = {}", configuration.size());
String user = (String) configuration.get("user");
String password = (String) configuration.get("password");
// mandatory url
String url = (String) configuration.get("url");
Properties parsedURL = StringUtilsExt.parseJdbcURL(url);
if (StringUtils.isBlank(user)) {
logger.debug("No jdbc:user parameter defined in openhab.cfg");
}
if (StringUtils.isBlank(password)) {
logger.debug("No jdbc:password parameter defined in openhab.cfg.");
}
if (StringUtils.isBlank(url)) {
logger.warn(
"JDBC url is missing - please configure in openhab.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'");
return;
}
if ("false".equalsIgnoreCase(parsedURL.getProperty("parseValid"))) {
Enumeration<?> en = parsedURL.propertyNames();
String enstr = "";
for (Object key : Collections.list(en)) {
enstr += key + " = " + parsedURL.getProperty("" + key) + "\n";
}
logger.warn(
"JDBC url is not well formatted: {}\nPlease configure in openhab.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'",
enstr);
return;
}
logger.debug("JDBC::updateConfig: user={}", user);
logger.debug("JDBC::updateConfig: password exists? {}", password != null & !StringUtils.isBlank(password));
logger.debug("JDBC::updateConfig: url={}", url);
// set database type and database type class
setDBDAOClass(parsedURL.getProperty("dbShortcut")); // derby, h2, hsqldb, mariadb, mysql, postgresql,
// sqlite
// set user
if (StringUtils.isNotBlank(user)) {
dBDAO.databaseProps.setProperty("dataSource.user", user);
}
// set password
if (StringUtils.isNotBlank(password)) {
dBDAO.databaseProps.setProperty("dataSource.password", password);
}
// set sql-types from external config
setSqlTypes();
String et = (String) configuration.get("reconnectCnt");
if (StringUtils.isNotBlank(et) && StringUtils.isNumeric(et)) {
errReconnectThreshold = Integer.parseInt(et);
logger.debug("JDBC::updateConfig: errReconnectThreshold={}", errReconnectThreshold);
}
String np = (String) configuration.get("tableNamePrefix");
if (StringUtils.isNotBlank(np)) {
tableNamePrefix = np;
logger.debug("JDBC::updateConfig: tableNamePrefix={}", tableNamePrefix);
}
String dd = (String) configuration.get("numberDecimalcount");
if (StringUtils.isNotBlank(dd) && StringUtils.isNumeric(dd)) {
numberDecimalcount = Integer.parseInt(dd);
logger.debug("JDBC::updateConfig: numberDecimalcount={}", numberDecimalcount);
}
String rn = (String) configuration.get("tableUseRealItemNames");
if (StringUtils.isNotBlank(rn)) {
tableUseRealItemNames = "true".equals(rn) ? Boolean.parseBoolean(rn) : false;
logger.debug("JDBC::updateConfig: tableUseRealItemNames={}", tableUseRealItemNames);
}
String td = (String) configuration.get("tableIdDigitCount");
if (StringUtils.isNotBlank(td) && StringUtils.isNumeric(td)) {
tableIdDigitCount = Integer.parseInt(td);
logger.debug("JDBC::updateConfig: tableIdDigitCount={}", tableIdDigitCount);
}
String rt = (String) configuration.get("rebuildTableNames");
if (StringUtils.isNotBlank(rt)) {
rebuildTableNames = Boolean.parseBoolean(rt);
logger.debug("JDBC::updateConfig: rebuildTableNames={}", rebuildTableNames);
}
// undocumented
String ac = (String) configuration.get("maximumPoolSize");
if (StringUtils.isNotBlank(ac)) {
dBDAO.databaseProps.setProperty("maximumPoolSize", ac);
}
// undocumented
String ic = (String) configuration.get("minimumIdle");
if (StringUtils.isNotBlank(ic)) {
dBDAO.databaseProps.setProperty("minimumIdle", ic);
}
// undocumented
String it = (String) configuration.get("idleTimeout");
if (StringUtils.isNotBlank(it)) {
dBDAO.databaseProps.setProperty("idleTimeout", it);
}
// undocumented
String ent = (String) configuration.get("enableLogTime");
if (StringUtils.isNotBlank(ent)) {
enableLogTime = "true".equals(ent) ? Boolean.parseBoolean(ent) : false;
}
logger.debug("JDBC::updateConfig: enableLogTime {}", enableLogTime);
// undocumented
String fd = (String) configuration.get("driverClassName");
if (StringUtils.isNotBlank(fd)) {
dBDAO.databaseProps.setProperty("driverClassName", fd);
}
// undocumented
String ds = (String) configuration.get("dataSourceClassName");
if (StringUtils.isNotBlank(ds)) {
dBDAO.databaseProps.setProperty("dataSourceClassName", ds);
}
// undocumented
String dn = dBDAO.databaseProps.getProperty("driverClassName");
if (dn == null) {
dn = dBDAO.databaseProps.getProperty("dataSourceClassName");
} else {
dBDAO.databaseProps.setProperty("jdbcUrl", url);
}
// test if JDBC driver bundle is available
testJDBCDriver(dn);
logger.debug("JDBC::updateConfig: configuration complete. service={}", getName());
}
private void setDBDAOClass(String sn) {
serviceName = "none";
// set database type
if (StringUtils.isBlank(sn) || sn.length() < 2) {
logger.error(
"JDBC::updateConfig: Required database url like 'jdbc:<service>:<host>[:<port>;<attributes>]' - please configure the jdbc:url parameter in openhab.cfg");
} else {
serviceName = sn;
}
logger.debug("JDBC::updateConfig: found serviceName = '{}'", serviceName);
// set class for database type
String ddp = DB_DAO_PACKAGE + serviceName.toUpperCase().charAt(0) + serviceName.toLowerCase().substring(1)
+ "DAO";
logger.debug("JDBC::updateConfig: Init Data Access Object Class: '{}'", ddp);
try {
dBDAO = (JdbcBaseDAO) Class.forName(ddp).newInstance();
logger.debug("JDBC::updateConfig: dBDAO ClassName={}", dBDAO.getClass().getName());
} catch (InstantiationException e) {
logger.error("JDBC::updateConfig: InstantiationException: {}", e.getMessage());
} catch (IllegalAccessException e) {
logger.error("JDBC::updateConfig: IllegalAccessException: {}", e.getMessage());
} catch (ClassNotFoundException e) {
logger.warn("JDBC::updateConfig: no Configuration for serviceName '{}' found. ClassNotFoundException: {}",
serviceName, e.getMessage());
logger.debug("JDBC::updateConfig: using default Database Configuration: JdbcBaseDAO !!");
dBDAO = new JdbcBaseDAO();
logger.debug("JDBC::updateConfig: dBConfig done");
}
}
private void setSqlTypes() {
@SuppressWarnings("unchecked")
Enumeration<String> keys = new IteratorEnumeration(configuration.keySet().iterator());
while (keys.hasMoreElements()) {
String key = keys.nextElement();
Matcher matcher = EXTRACT_CONFIG_PATTERN.matcher(key);
if (!matcher.matches()) {
continue;
}
matcher.reset();
matcher.find();
if (!matcher.group(1).equals("sqltype")) {
continue;
}
String itemType = matcher.group(2).toUpperCase() + "ITEM";
String value = (String) configuration.get(key);
logger.debug("JDBC::updateConfig: set sqlTypes: itemType={} value={}", itemType, value);
dBDAO.sqlTypes.put(itemType, value);
}
}
private void testJDBCDriver(String driver) {
driverAvailable = true;
try {
Class.forName(driver);
logger.debug("JDBC::updateConfig: load JDBC-driverClass was successful: '{}'", driver);
} catch (ClassNotFoundException e) {
driverAvailable = false;
logger.error(
"JDBC::updateConfig: could NOT load JDBC-driverClassName or JDBC-dataSourceClassName. ClassNotFoundException: '{}'",
e.getMessage());
String warn = ""
+ "\n\n\t!!!\n\tTo avoid this error, place an appropriate JDBC driver file for serviceName '{}' in addons directory.\n"
+ "\tCopy missing JDBC-Driver-jar to your OpenHab/addons Folder.\n\t!!!\n" + "\tDOWNLOAD: \n";
if (serviceName.equals("derby")) {
warn += "\tDerby: version >= 10.11.1.1 from http://mvnrepository.com/artifact/org.apache.derby/derby\n";
} else if (serviceName.equals("h2")) {
warn += "\tH2: version >= 1.4.189 from http://mvnrepository.com/artifact/com.h2database/h2\n";
} else if (serviceName.equals("hsqldb")) {
warn += "\tHSQLDB: version >= 2.3.3 from http://mvnrepository.com/artifact/org.hsqldb/hsqldb\n";
} else if (serviceName.equals("mariadb")) {
warn += "\tMariaDB: version >= 1.2.0 from http://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client\n";
} else if (serviceName.equals("mysql")) {
warn += "\tMySQL: version >= 5.1.36 from http://mvnrepository.com/artifact/mysql/mysql-connector-java\n";
} else if (serviceName.equals("postgresql")) {
warn += "\tPostgreSQL:version >= 9.4.1208 from http://mvnrepository.com/artifact/org.postgresql/postgresql\n";
} else if (serviceName.equals("sqlite")) {
warn += "\tSQLite: version >= 3.16.1 from http://mvnrepository.com/artifact/org.xerial/sqlite-jdbc\n";
}
logger.warn(warn, serviceName);
}
}
public Properties getHikariConfiguration() {
return dBDAO.databaseProps;
}
public String getName() {
// return serviceName;
return name;
}
public String getServiceName() {
return serviceName;
}
public String getTableNamePrefix() {
return tableNamePrefix;
}
public int getErrReconnectThreshold() {
return errReconnectThreshold;
}
public boolean getRebuildTableNames() {
return rebuildTableNames;
}
public int getNumberDecimalcount() {
return numberDecimalcount;
}
public boolean getTableUseRealItemNames() {
return tableUseRealItemNames;
}
public int getTableIdDigitCount() {
return tableIdDigitCount;
}
public JdbcBaseDAO getDBDAO() {
return dBDAO;
}
public String getDbName() {
return dbName;
}
public void setDbName(String dbName) {
this.dbName = dbName;
}
public boolean isDbConnected() {
return dbConnected;
}
public void setDbConnected(boolean dbConnected) {
logger.debug("JDBC::setDbConnected {}", dbConnected);
// Initializing step, after db is connected.
// Initialize sqlTypes, depending on DB version for example
dBDAO.initAfterFirstDbConnection();
// Running once again to prior external configured SqlTypes!
setSqlTypes();
this.dbConnected = dbConnected;
}
public boolean isDriverAvailable() {
return driverAvailable;
}
}