2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.persistence.jdbc.internal;
15 import java.lang.reflect.InvocationTargetException;
16 import java.util.Collections;
17 import java.util.Enumeration;
19 import java.util.Properties;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.persistence.jdbc.db.JdbcBaseDAO;
26 import org.openhab.persistence.jdbc.utils.MovingAverage;
27 import org.openhab.persistence.jdbc.utils.StringUtilsExt;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
34 * @author Helmut Lehmeyer - Initial contribution
36 public class JdbcConfiguration {
37 private final Logger logger = LoggerFactory.getLogger(JdbcConfiguration.class);
39 private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.([0-9.a-zA-Z]+)$");
40 private static final String DB_DAO_PACKAGE = "org.openhab.persistence.jdbc.db.Jdbc";
42 private Map<Object, Object> configuration;
44 private JdbcBaseDAO dBDAO = null;
45 private String dbName = null;
46 boolean dbConnected = false;
47 boolean driverAvailable = false;
49 private String serviceName;
50 private String name = "jdbc";
51 public final boolean valid;
53 // private String url;
54 // private String user;
55 // private String password;
56 private int numberDecimalcount = 3;
57 private boolean tableUseRealItemNames = false;
58 private String tableNamePrefix = "item";
59 private int tableIdDigitCount = 4;
60 private boolean rebuildTableNames = false;
62 private int errReconnectThreshold = 0;
64 public int timerCount = 0;
65 public int time1000Statements = 0;
66 public long timer1000 = 0;
67 public MovingAverage timeAverage50arr = new MovingAverage(50);
68 public MovingAverage timeAverage100arr = new MovingAverage(100);
69 public MovingAverage timeAverage200arr = new MovingAverage(200);
70 public boolean enableLogTime = false;
72 public JdbcConfiguration(Map<Object, Object> configuration) {
73 logger.debug("JDBC::JdbcConfiguration");
74 valid = updateConfig(configuration);
77 private boolean updateConfig(Map<Object, @Nullable Object> config) {
78 configuration = config;
80 logger.debug("JDBC::updateConfig: configuration size = {}", configuration.size());
82 String user = (String) configuration.get("user");
83 String password = (String) configuration.get("password");
86 String url = (String) configuration.get("url");
89 logger.error("Mandatory url parameter is missing in configuration!");
93 Properties parsedURL = StringUtilsExt.parseJdbcURL(url);
95 if (user == null || user.isBlank()) {
96 logger.debug("No jdbc:user parameter defined in jdbc.cfg");
98 if (password == null || password.isBlank()) {
99 logger.debug("No jdbc:password parameter defined in jdbc.cfg.");
104 "JDBC url is missing - please configure in jdbc.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'");
108 if ("false".equalsIgnoreCase(parsedURL.getProperty("parseValid"))) {
109 Enumeration<?> en = parsedURL.propertyNames();
111 for (Object key : Collections.list(en)) {
112 enstr += key + " = " + parsedURL.getProperty("" + key) + "\n";
115 "JDBC url is not well formatted: {}\nPlease configure in openhab.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'",
120 logger.debug("JDBC::updateConfig: user={}", user);
121 logger.debug("JDBC::updateConfig: password exists? {}", password != null && !password.isBlank());
122 logger.debug("JDBC::updateConfig: url={}", url);
124 // set database type and database type class
125 setDBDAOClass(parsedURL.getProperty("dbShortcut")); // derby, h2, hsqldb, mariadb, mysql, postgresql,
128 if (user != null && !user.isBlank()) {
129 dBDAO.databaseProps.setProperty("dataSource.user", user);
133 if (password != null && !password.isBlank()) {
134 dBDAO.databaseProps.setProperty("dataSource.password", password);
137 // set sql-types from external config
140 final Pattern isNumericPattern = Pattern.compile("\\d+(\\.\\d+)?");
141 String et = (String) configuration.get("reconnectCnt");
142 if (et != null && !et.isBlank() && isNumericPattern.matcher(et).matches()) {
143 errReconnectThreshold = Integer.parseInt(et);
144 logger.debug("JDBC::updateConfig: errReconnectThreshold={}", errReconnectThreshold);
147 String np = (String) configuration.get("tableNamePrefix");
148 if (np != null && !np.isBlank()) {
149 tableNamePrefix = np;
150 logger.debug("JDBC::updateConfig: tableNamePrefix={}", tableNamePrefix);
153 String dd = (String) configuration.get("numberDecimalcount");
154 if (dd != null && !dd.isBlank() && isNumericPattern.matcher(dd).matches()) {
155 numberDecimalcount = Integer.parseInt(dd);
156 logger.debug("JDBC::updateConfig: numberDecimalcount={}", numberDecimalcount);
159 String rn = (String) configuration.get("tableUseRealItemNames");
160 if (rn != null && !rn.isBlank()) {
161 tableUseRealItemNames = "true".equals(rn) ? Boolean.parseBoolean(rn) : false;
162 logger.debug("JDBC::updateConfig: tableUseRealItemNames={}", tableUseRealItemNames);
165 String td = (String) configuration.get("tableIdDigitCount");
166 if (td != null && !td.isBlank() && isNumericPattern.matcher(td).matches()) {
167 tableIdDigitCount = Integer.parseInt(td);
168 logger.debug("JDBC::updateConfig: tableIdDigitCount={}", tableIdDigitCount);
171 String rt = (String) configuration.get("rebuildTableNames");
172 if (rt != null && !rt.isBlank()) {
173 rebuildTableNames = Boolean.parseBoolean(rt);
174 logger.debug("JDBC::updateConfig: rebuildTableNames={}", rebuildTableNames);
178 String ac = (String) configuration.get("maximumPoolSize");
179 if (ac != null && !ac.isBlank()) {
180 dBDAO.databaseProps.setProperty("maximumPoolSize", ac);
184 String ic = (String) configuration.get("minimumIdle");
185 if (ic != null && !ic.isBlank()) {
186 dBDAO.databaseProps.setProperty("minimumIdle", ic);
190 String it = (String) configuration.get("idleTimeout");
191 if (it != null && !it.isBlank()) {
192 dBDAO.databaseProps.setProperty("idleTimeout", it);
195 String ent = (String) configuration.get("enableLogTime");
196 if (ent != null && !ent.isBlank()) {
197 enableLogTime = "true".equals(ent) ? Boolean.parseBoolean(ent) : false;
199 logger.debug("JDBC::updateConfig: enableLogTime {}", enableLogTime);
202 String fd = (String) configuration.get("driverClassName");
203 if (fd != null && !fd.isBlank()) {
204 dBDAO.databaseProps.setProperty("driverClassName", fd);
208 String ds = (String) configuration.get("dataSourceClassName");
209 if (ds != null && !ds.isBlank()) {
210 dBDAO.databaseProps.setProperty("dataSourceClassName", ds);
214 String dn = dBDAO.databaseProps.getProperty("driverClassName");
216 dn = dBDAO.databaseProps.getProperty("dataSourceClassName");
218 dBDAO.databaseProps.setProperty("jdbcUrl", url);
221 // test if JDBC driver bundle is available
224 logger.debug("JDBC::updateConfig: configuration complete. service={}", getName());
229 private void setDBDAOClass(String sn) {
230 serviceName = "none";
233 if (sn == null || sn.isBlank() || sn.length() < 2) {
235 "JDBC::updateConfig: Required database url like 'jdbc:<service>:<host>[:<port>;<attributes>]' - please configure the jdbc:url parameter in openhab.cfg");
239 logger.debug("JDBC::updateConfig: found serviceName = '{}'", serviceName);
241 // set class for database type
242 String ddp = DB_DAO_PACKAGE + serviceName.toUpperCase().charAt(0) + serviceName.toLowerCase().substring(1)
245 logger.debug("JDBC::updateConfig: Init Data Access Object Class: '{}'", ddp);
247 dBDAO = (JdbcBaseDAO) Class.forName(ddp).getConstructor().newInstance();
248 logger.debug("JDBC::updateConfig: dBDAO ClassName={}", dBDAO.getClass().getName());
249 } catch (IllegalAccessException | InstantiationException | InvocationTargetException
250 | NoSuchMethodException e) {
251 logger.error("JDBC::updateConfig: Exception: {}", e.getMessage());
252 } catch (ClassNotFoundException e) {
253 logger.warn("JDBC::updateConfig: no Configuration for serviceName '{}' found. ClassNotFoundException: {}",
254 serviceName, e.getMessage());
255 logger.debug("JDBC::updateConfig: using default Database Configuration: JdbcBaseDAO !!");
256 dBDAO = new JdbcBaseDAO();
257 logger.debug("JDBC::updateConfig: dBConfig done");
261 private void setSqlTypes() {
262 Set<Object> keys = configuration.keySet();
264 for (Object k : keys) {
265 String key = (String) k;
266 Matcher matcher = EXTRACT_CONFIG_PATTERN.matcher(key);
267 if (!matcher.matches()) {
272 if (!matcher.group(1).equals("sqltype")) {
275 String itemType = matcher.group(2);
276 if (!itemType.startsWith("table")) {
277 itemType = itemType.toUpperCase() + "ITEM";
279 String value = (String) configuration.get(key);
280 logger.debug("JDBC::updateConfig: set sqlTypes: itemType={} value={}", itemType, value);
281 dBDAO.sqlTypes.put(itemType, value);
285 private void testJDBCDriver(String driver) {
286 driverAvailable = true;
288 Class.forName(driver);
289 logger.debug("JDBC::updateConfig: load JDBC-driverClass was successful: '{}'", driver);
290 } catch (ClassNotFoundException e) {
291 driverAvailable = false;
293 "JDBC::updateConfig: could NOT load JDBC-driverClassName or JDBC-dataSourceClassName. ClassNotFoundException: '{}'",
296 + "\n\n\t!!!\n\tTo avoid this error, place an appropriate JDBC driver file for serviceName '{}' in addons directory.\n"
297 + "\tCopy missing JDBC-Driver-jar to your openHab/addons Folder.\n\t!!!\n" + "\tDOWNLOAD: \n";
298 if (serviceName.equals("derby")) {
299 warn += "\tDerby: version >= 10.11.1.1 from https://mvnrepository.com/artifact/org.apache.derby/derby\n";
300 } else if (serviceName.equals("h2")) {
301 warn += "\tH2: version >= 1.4.189 from https://mvnrepository.com/artifact/com.h2database/h2\n";
302 } else if (serviceName.equals("hsqldb")) {
303 warn += "\tHSQLDB: version >= 2.3.3 from https://mvnrepository.com/artifact/org.hsqldb/hsqldb\n";
304 } else if (serviceName.equals("mariadb")) {
305 warn += "\tMariaDB: version >= 1.2.0 from https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client\n";
306 } else if (serviceName.equals("mysql")) {
307 warn += "\tMySQL: version >= 5.1.36 from https://mvnrepository.com/artifact/mysql/mysql-connector-java\n";
308 } else if (serviceName.equals("postgresql")) {
309 warn += "\tPostgreSQL:version >= 9.4.1208 from https://mvnrepository.com/artifact/org.postgresql/postgresql\n";
310 } else if (serviceName.equals("sqlite")) {
311 warn += "\tSQLite: version >= 3.16.1 from https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc\n";
313 logger.warn(warn, serviceName);
317 public Properties getHikariConfiguration() {
318 return dBDAO.databaseProps;
321 public String getName() {
322 // return serviceName;
326 public String getServiceName() {
330 public String getTableNamePrefix() {
331 return tableNamePrefix;
334 public int getErrReconnectThreshold() {
335 return errReconnectThreshold;
338 public boolean getRebuildTableNames() {
339 return rebuildTableNames;
342 public int getNumberDecimalcount() {
343 return numberDecimalcount;
346 public boolean getTableUseRealItemNames() {
347 return tableUseRealItemNames;
350 public int getTableIdDigitCount() {
351 return tableIdDigitCount;
354 public JdbcBaseDAO getDBDAO() {
358 public String getDbName() {
362 public void setDbName(String dbName) {
363 this.dbName = dbName;
366 public boolean isDbConnected() {
370 public void setDbConnected(boolean dbConnected) {
371 logger.debug("JDBC::setDbConnected {}", dbConnected);
372 // Initializing step, after db is connected.
373 // Initialize sqlTypes, depending on DB version for example
374 dBDAO.initAfterFirstDbConnection();
375 // Running once again to prior external configured SqlTypes!
377 this.dbConnected = dbConnected;
380 public boolean isDriverAvailable() {
381 return driverAvailable;